github.com/aacfactory/fns@v1.2.85/transports/middlewares/cachecontrol/make.go (about)

     1  /*
     2   * Copyright 2023 Wang Min Xiang
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   * 	http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   */
    17  
    18  package cachecontrol
    19  
    20  import (
    21  	"bytes"
    22  	"github.com/aacfactory/fns/commons/bytex"
    23  	"github.com/aacfactory/fns/context"
    24  	"github.com/aacfactory/fns/services"
    25  	"github.com/aacfactory/fns/transports"
    26  	"github.com/valyala/bytebufferpool"
    27  	"strconv"
    28  )
    29  
    30  var (
    31  	pragma          = []byte("Pragma")
    32  	noCache         = []byte("no-cache")
    33  	noStore         = []byte("no-store")
    34  	zeroMaxAge      = []byte("max-age=0")
    35  	noTransform     = []byte("no-transform")
    36  	public          = []byte("public")
    37  	private         = []byte("private")
    38  	maxAge          = []byte("max-age")
    39  	mustRevalidate  = []byte("must-revalidate")
    40  	proxyRevalidate = []byte("proxy-revalidate")
    41  	comma           = []byte(", ")
    42  	equal           = []byte("=")
    43  )
    44  
    45  type MakeOptions struct {
    46  	mustRevalidate  bool
    47  	proxyRevalidate bool
    48  	public          bool
    49  	maxAge          int
    50  }
    51  
    52  type MakeOption func(option *MakeOptions)
    53  
    54  func MustRevalidate() MakeOption {
    55  	return func(option *MakeOptions) {
    56  		option.mustRevalidate = true
    57  	}
    58  }
    59  
    60  func ProxyRevalidate() MakeOption {
    61  	return func(option *MakeOptions) {
    62  		option.proxyRevalidate = true
    63  	}
    64  }
    65  
    66  func Private() MakeOption {
    67  	return func(option *MakeOptions) {
    68  		option.public = false
    69  	}
    70  }
    71  
    72  func Public() MakeOption {
    73  	return func(option *MakeOptions) {
    74  		option.public = true
    75  	}
    76  }
    77  
    78  func MaxAge(age int) MakeOption {
    79  	return func(option *MakeOptions) {
    80  		if age < 0 {
    81  			age = 0
    82  		}
    83  		option.maxAge = age
    84  	}
    85  }
    86  
    87  func Make(ctx context.Context, options ...MakeOption) {
    88  	// check enable
    89  	enableCV := ctx.LocalValue(contextEnableKey)
    90  	if enableCV == nil {
    91  		return
    92  	}
    93  	if enable, _ := enableCV.(bool); !enable {
    94  		return
    95  	}
    96  	// check internal
    97  	sr, hasSR := services.TryLoadRequest(ctx)
    98  	if !hasSR {
    99  		return
   100  	}
   101  	if sr.Header().Internal() {
   102  		return
   103  	}
   104  	// check transport request path
   105  	tr, hasTR := transports.TryLoadRequest(ctx)
   106  	if !hasTR {
   107  		return
   108  	}
   109  	path := tr.Path()
   110  	s, f := sr.Fn()
   111  	sf := append([]byte{'/'}, s...)
   112  	sf = append(sf, '/')
   113  	sf = append(sf, f...)
   114  	if !bytes.Equal(path, sf) {
   115  		return
   116  	}
   117  
   118  	header := tr.Header()
   119  	// pragma
   120  	ph := header.Get(pragma)
   121  	if len(ph) > 0 && bytes.Equal(ph, noCache) {
   122  		return
   123  	}
   124  	//
   125  	noTransformEnabled := false
   126  	// cache control
   127  	cch := header.Get(transports.CacheControlHeaderName)
   128  	if len(cch) > 0 {
   129  		// no-cache, no-store, max-age=0
   130  		if bytes.Contains(cch, noCache) || bytes.Contains(cch, noStore) || bytes.Contains(cch, zeroMaxAge) {
   131  			return
   132  		}
   133  		if bytes.Contains(cch, noTransform) {
   134  			noTransformEnabled = true
   135  		}
   136  	}
   137  	// write response header
   138  	responseHeader, hasResponseHeader := transports.TryLoadResponseHeader(ctx)
   139  	if !hasResponseHeader {
   140  		return
   141  	}
   142  	opt := MakeOptions{}
   143  	for _, option := range options {
   144  		option(&opt)
   145  	}
   146  	ccr := bytebufferpool.Get()
   147  	if opt.public {
   148  		_, _ = ccr.Write(comma)
   149  		_, _ = ccr.Write(public)
   150  		if opt.proxyRevalidate {
   151  			_, _ = ccr.Write(comma)
   152  			_, _ = ccr.Write(proxyRevalidate)
   153  		}
   154  		if opt.mustRevalidate {
   155  			_, _ = ccr.Write(comma)
   156  			_, _ = ccr.Write(mustRevalidate)
   157  		}
   158  		if noTransformEnabled {
   159  			_, _ = ccr.Write(comma)
   160  			_, _ = ccr.Write(noTransform)
   161  		}
   162  	} else {
   163  		_, _ = ccr.Write(comma)
   164  		_, _ = ccr.Write(private)
   165  		if opt.mustRevalidate {
   166  			_, _ = ccr.Write(comma)
   167  			_, _ = ccr.Write(mustRevalidate)
   168  		}
   169  	}
   170  	if opt.maxAge > 0 {
   171  		_, _ = ccr.Write(comma)
   172  		_, _ = ccr.Write(maxAge)
   173  		_, _ = ccr.Write(equal)
   174  		_, _ = ccr.Write(bytex.FromString(strconv.Itoa(opt.maxAge)))
   175  	}
   176  	h := ccr.Bytes()
   177  	if len(h) > 0 {
   178  		h = h[2:]
   179  	}
   180  	responseHeader.Set(transports.CacheControlHeaderName, h)
   181  	bytebufferpool.Put(ccr)
   182  	return
   183  }