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 }