github.com/vicanso/pike@v1.0.1-0.20210630235453-9099e041f6ec/cache/http_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  // 针对同一个请求,在状态未知时,控制只允许一个请求转发至后续流程
    24  // 在获取状态之后,支持hit for pass 与 hit 两种处理,其中hit for pass表示该请求不可缓存,
    25  // 直接转发至后端程序,而hit则返回当前缓存的响应数据
    26  
    27  package cache
    28  
    29  import (
    30  	"bytes"
    31  	"sync"
    32  	"time"
    33  
    34  	"github.com/vicanso/pike/compress"
    35  	"github.com/vicanso/pike/log"
    36  	"github.com/vicanso/pike/store"
    37  	"go.uber.org/zap"
    38  )
    39  
    40  type Status int
    41  
    42  const (
    43  	// StatusUnknown unknown status
    44  	StatusUnknown Status = iota
    45  	// StatusFetching fetching status
    46  	StatusFetching
    47  	// StatusHitForPass hit-for-pass status
    48  	StatusHitForPass
    49  	// StatusHit hit cache status
    50  	StatusHit
    51  	// StatusPassed pass status
    52  	StatusPassed
    53  )
    54  
    55  // defaultHitForPassSeconds default hit for pass: 300 seconds
    56  const defaultHitForPassSeconds = 300
    57  
    58  type (
    59  	// httpCache http cache (only for same request method+host+uri)
    60  	httpCache struct {
    61  		// key the key of store data
    62  		key []byte
    63  		// store the store to save http cache
    64  		store store.Store
    65  
    66  		mu        *sync.RWMutex
    67  		status    Status
    68  		chanList  []chan struct{}
    69  		response  *HTTPResponse
    70  		createdAt int64
    71  		expiredAt int64
    72  	}
    73  )
    74  
    75  func nowUnix() int64 {
    76  	return time.Now().Unix()
    77  }
    78  
    79  func (i Status) String() string {
    80  	switch i {
    81  	case StatusFetching:
    82  		return "fetching"
    83  	case StatusHitForPass:
    84  		return "hitForPass"
    85  	case StatusHit:
    86  		return "hit"
    87  	case StatusPassed:
    88  		return "passed"
    89  	default:
    90  		return "unknown"
    91  	}
    92  }
    93  
    94  // NewHTTPCache new a http cache
    95  func NewHTTPCache() *httpCache {
    96  	return &httpCache{
    97  		mu: &sync.RWMutex{},
    98  	}
    99  }
   100  
   101  // NewHTTPStoreCache new a http store cache
   102  func NewHTTPStoreCache(key []byte, store store.Store) *httpCache {
   103  	hc := NewHTTPCache()
   104  	hc.key = key
   105  	hc.store = store
   106  	return hc
   107  }
   108  
   109  // Get get http cache
   110  func (hc *httpCache) Get() (status Status, response *HTTPResponse) {
   111  	hc.mu.Lock()
   112  	status, done, response := hc.get()
   113  	hc.mu.Unlock()
   114  	// 如果done不为空,表示需要等待确认当前请求状态
   115  	if done != nil {
   116  		// TODO 后续再考虑是否需要添加timeout(proxy部分有超时,因此暂时可不添加)
   117  		<-done
   118  		// 完成后重新获取当前状态与响应
   119  		// 此时状态只可能是hit for pass 或者 hit
   120  		// 而此两种状态的数据缓存均不会立即失效,因此可以从hc中获取
   121  		status = hc.status
   122  		response = hc.response
   123  	}
   124  	return
   125  }
   126  
   127  // Bytes httpcache to bytes
   128  func (hc *httpCache) Bytes() (data []byte, err error) {
   129  	statusBuf := uint32ToBytes(int(hc.status))
   130  
   131  	var respBuf []byte
   132  	// 如果有响应数据,则转换
   133  	if hc.response != nil {
   134  		respBuf, err = hc.response.Bytes()
   135  		if err != nil {
   136  			return
   137  		}
   138  	}
   139  	respSizeBuf := uint32ToBytes(len(respBuf))
   140  
   141  	createdAtBuf := uint64ToBytes(hc.createdAt)
   142  	expiredAtBuf := uint64ToBytes(hc.expiredAt)
   143  
   144  	return bytes.Join([][]byte{
   145  		statusBuf,
   146  		respSizeBuf,
   147  		respBuf,
   148  		createdAtBuf,
   149  		expiredAtBuf,
   150  	}, []byte("")), nil
   151  }
   152  
   153  // FromBytes restore httpcache from bytes
   154  func (hc *httpCache) FromBytes(data []byte) (err error) {
   155  	buffer := bytes.NewBuffer(data)
   156  	status, err := readUint32ToInt(buffer)
   157  	if err != nil {
   158  		return
   159  	}
   160  	hc.status = Status(status)
   161  
   162  	respSize, err := readUint32ToInt(buffer)
   163  	if err != nil {
   164  		return
   165  	}
   166  	respBuf := buffer.Next(respSize)
   167  	resp := &HTTPResponse{}
   168  	err = resp.FromBytes(respBuf)
   169  	if err != nil {
   170  		return
   171  	}
   172  	hc.response = resp
   173  
   174  	hc.createdAt, err = readUint64ToInt64(buffer)
   175  	if err != nil {
   176  		return
   177  	}
   178  
   179  	hc.expiredAt, err = readUint64ToInt64(buffer)
   180  	if err != nil {
   181  		return
   182  	}
   183  
   184  	return
   185  }
   186  
   187  // initFromStore init cache from store
   188  func (hc *httpCache) initFromStore() (err error) {
   189  	if hc.store == nil || len(hc.key) == 0 {
   190  		return
   191  	}
   192  	data, err := hc.store.Get(hc.key)
   193  	if err != nil {
   194  		return
   195  	}
   196  	return hc.FromBytes(data)
   197  }
   198  
   199  // saveToStore save cache to store
   200  func (hc *httpCache) saveToStore() (err error) {
   201  	if hc.store == nil || len(hc.key) == 0 {
   202  		return
   203  	}
   204  	data, err := hc.Bytes()
   205  	if err != nil {
   206  		return
   207  	}
   208  	ttl := time.Duration(hc.expiredAt-nowUnix()) * time.Second
   209  	return hc.store.Set(hc.key, data, ttl)
   210  }
   211  
   212  func (hc *httpCache) get() (status Status, done chan struct{}, data *HTTPResponse) {
   213  	now := nowUnix()
   214  	// 如果首次创建并且设置store
   215  	if hc.status == StatusUnknown {
   216  		// 如果从缓存中读取失败,暂忽略出错信息
   217  		err := hc.initFromStore()
   218  		// 如果是无数据,则不输出日志
   219  		if err != nil && err != store.ErrNotFound {
   220  			log.Default().Error("init from store fail",
   221  				zap.Error(err),
   222  			)
   223  		}
   224  	}
   225  
   226  	// 如果缓存已过期,设置为StatusUnknown
   227  	if hc.expiredAt != 0 && hc.expiredAt < now {
   228  		hc.status = StatusUnknown
   229  		// 将有效期重置(若不重置则导致hs.status每次都被重置为Unknown)
   230  		hc.expiredAt = 0
   231  	}
   232  
   233  	// 仅有同类请求为fetching,才会需要等待
   234  	// 如果是fetching,则相同的请求需要等待完成
   235  	// 通过chan返回完成
   236  	if hc.status == StatusFetching {
   237  		done = make(chan struct{})
   238  		hc.chanList = append(hc.chanList, done)
   239  	}
   240  
   241  	if hc.status == StatusUnknown {
   242  		hc.status = StatusFetching
   243  		hc.chanList = make([]chan struct{}, 0, 5)
   244  	}
   245  
   246  	status = hc.status
   247  	// 为什么需要返回status与data
   248  	// 因为有可能在函数调用完成后,刚好缓存过期了,如果此时不返回status与data
   249  	// 当其它goroutine获取锁之后,有可能刚好重置数据
   250  	if status == StatusHit {
   251  		data = hc.response
   252  	}
   253  	return
   254  }
   255  
   256  // HitForPass set the http cache hit for pass
   257  func (hc *httpCache) HitForPass(ttl int) {
   258  	hc.mu.Lock()
   259  	defer hc.mu.Unlock()
   260  	if ttl <= 0 {
   261  		ttl = defaultHitForPassSeconds
   262  	}
   263  	hc.expiredAt = nowUnix() + int64(ttl)
   264  	hc.status = StatusHitForPass
   265  	list := hc.chanList
   266  	hc.chanList = nil
   267  	for _, ch := range list {
   268  		ch <- struct{}{}
   269  	}
   270  	err := hc.saveToStore()
   271  	if err != nil {
   272  		log.Default().Error("save cache to store fail",
   273  			zap.String("category", "hitForPass"),
   274  			zap.String("key", string(hc.key)),
   275  			zap.Error(err),
   276  		)
   277  	}
   278  }
   279  
   280  // Cacheable set http cache cacheable and compress it
   281  func (hc *httpCache) Cacheable(resp *HTTPResponse, ttl int) {
   282  	hc.mu.Lock()
   283  	defer hc.mu.Unlock()
   284  	// 如果是可缓存数据,则选择默认的best compression
   285  	resp.CompressSrv = compress.BestCompression
   286  	_ = resp.Compress()
   287  	hc.createdAt = nowUnix()
   288  	hc.expiredAt = hc.createdAt + int64(ttl)
   289  	hc.status = StatusHit
   290  	hc.response = resp
   291  	list := hc.chanList
   292  	hc.chanList = nil
   293  	for _, ch := range list {
   294  		ch <- struct{}{}
   295  	}
   296  	err := hc.saveToStore()
   297  	if err != nil {
   298  		log.Default().Error("save cache to store fail",
   299  			zap.String("category", "cacheable"),
   300  			zap.String("key", string(hc.key)),
   301  			zap.Error(err),
   302  		)
   303  	}
   304  }
   305  
   306  // Age get http cache's age
   307  func (hc *httpCache) Age() int {
   308  	hc.mu.RLock()
   309  	defer hc.mu.RUnlock()
   310  	return int(nowUnix() - hc.createdAt)
   311  }
   312  
   313  // GetStatus get http cache status
   314  func (hc *httpCache) GetStatus() Status {
   315  	hc.mu.RLock()
   316  	defer hc.mu.RUnlock()
   317  	return hc.status
   318  }
   319  
   320  // IsExpired the cache is expired
   321  func (hc *httpCache) IsExpired() bool {
   322  	hc.mu.RLock()
   323  	defer hc.mu.RUnlock()
   324  	if hc.expiredAt == 0 {
   325  		return false
   326  	}
   327  	return hc.expiredAt < nowUnix()
   328  }