github.com/vicanso/pike@v1.0.1-0.20210630235453-9099e041f6ec/server/cache.go (about)

     1  // MIT License
     2  
     3  // Copyright (c) 2020 Tree Xie
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in all
    13  // copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    21  // SOFTWARE.
    22  
    23  package server
    24  
    25  import (
    26  	"net/http"
    27  
    28  	"github.com/vicanso/elton"
    29  	"github.com/vicanso/pike/cache"
    30  )
    31  
    32  const (
    33  	// spaceByte 空格
    34  	spaceByte = byte(' ')
    35  )
    36  
    37  // requestIsPass check request is passed
    38  func requestIsPass(req *http.Request) bool {
    39  	// 非GET HEAD 的请求均直接pass
    40  	return req.Method != http.MethodGet &&
    41  		req.Method != http.MethodHead
    42  }
    43  
    44  // getKey get key of request
    45  func getKey(req *http.Request) []byte {
    46  	methodLen := len(req.Method)
    47  	hostLen := len(req.Host)
    48  	uri := req.RequestURI
    49  	// 正常RequestURI均不为空,但是如果直接创建一个request对象,
    50  	// 则有可能为空
    51  	if len(uri) == 0 {
    52  		uri = req.URL.String()
    53  	}
    54  	uriLen := len(uri)
    55  	buffer := make([]byte, methodLen+hostLen+uriLen+2)
    56  	len := 0
    57  
    58  	copy(buffer[len:], req.Method)
    59  	len += methodLen
    60  
    61  	buffer[len] = spaceByte
    62  	len++
    63  
    64  	copy(buffer[len:], req.Host)
    65  	len += hostLen
    66  
    67  	buffer[len] = spaceByte
    68  	len++
    69  
    70  	copy(buffer[len:], uri)
    71  	return buffer
    72  }
    73  
    74  // NewCache new a cache middleware
    75  func NewCache(s *server) elton.Handler {
    76  	return func(c *elton.Context) (err error) {
    77  		// 不可缓存请求,直接pass至upstream
    78  		if requestIsPass(c.Request) {
    79  			setCacheStatus(c, cache.StatusPassed)
    80  			return c.Next()
    81  		}
    82  		disp := cache.GetDispatcher(s.GetCache())
    83  		if disp == nil {
    84  			err = ErrCacheDispatcherNotFound
    85  			return
    86  		}
    87  
    88  		key := getKey(c.Request)
    89  		httpCache := disp.GetHTTPCache(key)
    90  		cacheStatus, httpResp := httpCache.Get()
    91  
    92  		cacheable := false
    93  		// 对于fetching类的请求,如果最终是不可缓存的,则设置hit for pass
    94  		// 保证只要不是panic,fetching的请求非可缓存的都为hit for pass
    95  		if cacheStatus == cache.StatusFetching {
    96  			defer func() {
    97  				if !cacheable {
    98  					httpCache.HitForPass(disp.GetHitForPass())
    99  				}
   100  			}()
   101  		}
   102  
   103  		setCacheStatus(c, cacheStatus)
   104  		// 缓存中读取的可缓存数据,不需要next
   105  		if cacheStatus == cache.StatusHit {
   106  			// 设置缓存数据
   107  			setHTTPResp(c, httpResp)
   108  			// 设置缓存数据的age
   109  			setHTTPRespAge(c, httpCache.Age())
   110  			return nil
   111  		}
   112  
   113  		err = c.Next()
   114  		if err != nil {
   115  			return err
   116  		}
   117  
   118  		// TODO 如果是hit for pass,但此次返回的缓存有效期不为0,
   119  		// 有可能因为上一次接口出错,导致了hit for pass,此次成功则可缓存,
   120  		// 后续再确认是否需要在此情况下将缓存更新
   121  		if cacheStatus == cache.StatusFetching {
   122  			// 获取缓存有效期
   123  			if maxAge := getHTTPCacheMaxAge(c); maxAge > 0 {
   124  				// 只有有响应数据可缓存时才设置为cacheable
   125  				if httpResp = getHTTPResp(c); httpResp != nil {
   126  					cacheable = true
   127  					httpCache.Cacheable(httpResp, maxAge)
   128  				}
   129  			}
   130  		}
   131  		return nil
   132  	}
   133  }