mirror of
https://github.com/eolinker/apinto
synced 2025-10-04 16:32:44 +08:00
248 lines
5.0 KiB
Go
248 lines
5.0 KiB
Go
package cache_strategy
|
|
|
|
import (
|
|
"github.com/eolinker/apinto/drivers/strategy/cache-strategy/cache"
|
|
"github.com/eolinker/apinto/resources"
|
|
"github.com/eolinker/eosc/eocontext"
|
|
http_service "github.com/eolinker/eosc/eocontext/http-context"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
actuatorSet ActuatorSet
|
|
)
|
|
|
|
func init() {
|
|
actuator := newtActuator()
|
|
actuatorSet = actuator
|
|
}
|
|
|
|
type ActuatorSet interface {
|
|
Strategy(ctx eocontext.EoContext, next eocontext.IChain, cache resources.ICache) error
|
|
Set(string, *CacheValidTimeHandler)
|
|
Del(id string)
|
|
}
|
|
|
|
type tActuator struct {
|
|
lock sync.RWMutex
|
|
all map[string]*CacheValidTimeHandler
|
|
handlers []*CacheValidTimeHandler
|
|
}
|
|
|
|
func (a *tActuator) Destroy() {
|
|
|
|
}
|
|
|
|
func (a *tActuator) Set(id string, val *CacheValidTimeHandler) {
|
|
// 调用来源有锁
|
|
a.all[id] = val
|
|
a.rebuild()
|
|
|
|
}
|
|
|
|
func (a *tActuator) Del(id string) {
|
|
// 调用来源有锁
|
|
delete(a.all, id)
|
|
a.rebuild()
|
|
}
|
|
|
|
func (a *tActuator) rebuild() {
|
|
|
|
handlers := make([]*CacheValidTimeHandler, 0, len(a.all))
|
|
for _, h := range a.all {
|
|
if !h.stop {
|
|
handlers = append(handlers, h)
|
|
}
|
|
}
|
|
sort.Sort(handlerListSort(handlers))
|
|
a.lock.Lock()
|
|
defer a.lock.Unlock()
|
|
a.handlers = handlers
|
|
}
|
|
func newtActuator() *tActuator {
|
|
return &tActuator{
|
|
all: make(map[string]*CacheValidTimeHandler),
|
|
}
|
|
}
|
|
|
|
func (a *tActuator) Strategy(ctx eocontext.EoContext, next eocontext.IChain, iCache resources.ICache) error {
|
|
|
|
httpCtx, err := http_service.Assert(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if httpCtx.Request().Method() != http.MethodGet {
|
|
if next != nil {
|
|
return next.DoChain(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
a.lock.RLock()
|
|
handlers := a.handlers
|
|
a.lock.RUnlock()
|
|
|
|
for _, handler := range handlers {
|
|
if handler.filter.Check(httpCtx) {
|
|
|
|
uri := httpCtx.Request().URI().RequestURI()
|
|
responseData := cache.GetResponseData(iCache, uri)
|
|
|
|
if responseData != nil {
|
|
httpCtx.SetCompleteHandler(responseData)
|
|
} else {
|
|
httpCtx.SetCompleteHandler(NewCacheGetCompleteHandler(httpCtx.GetComplete(), handler.validTime, uri, iCache))
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if next != nil {
|
|
return next.DoChain(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type CacheCompleteHandler struct {
|
|
orgHandler eocontext.CompleteHandler
|
|
validTime int
|
|
uri string
|
|
cache resources.ICache
|
|
}
|
|
|
|
func NewCacheGetCompleteHandler(orgHandler eocontext.CompleteHandler, validTime int, uri string, cache resources.ICache) *CacheCompleteHandler {
|
|
return &CacheCompleteHandler{
|
|
orgHandler: orgHandler,
|
|
validTime: validTime,
|
|
uri: uri,
|
|
cache: cache,
|
|
}
|
|
}
|
|
|
|
func (c *CacheCompleteHandler) Complete(ctx eocontext.EoContext) error {
|
|
|
|
if c.orgHandler != nil {
|
|
if err := c.orgHandler.Complete(ctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
httpCtx, err := http_service.Assert(ctx)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
//从cache-control中判断是否需要缓存
|
|
if parseHttpContext(httpCtx).IsCache() {
|
|
responseData := &cache.ResponseData{
|
|
Header: httpCtx.Response().Headers(),
|
|
Body: httpCtx.Response().GetBody(),
|
|
ValidTime: c.validTime,
|
|
Now: time.Now(),
|
|
}
|
|
cache.SetResponseData(c.cache, httpCtx.Request().URI().RequestURI(), responseData, c.validTime)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type handlerListSort []*CacheValidTimeHandler
|
|
|
|
func (hs handlerListSort) Len() int {
|
|
return len(hs)
|
|
}
|
|
|
|
func (hs handlerListSort) Less(i, j int) bool {
|
|
|
|
return hs[i].priority < hs[j].priority
|
|
}
|
|
|
|
func (hs handlerListSort) Swap(i, j int) {
|
|
hs[i], hs[j] = hs[j], hs[i]
|
|
}
|
|
|
|
type httpContext struct {
|
|
cacheControl map[string]string
|
|
reqHeader http.Header
|
|
resHeader http.Header
|
|
}
|
|
|
|
func (c httpContext) NoCache() bool {
|
|
if _, ok := c.cacheControl["no-cache"]; ok {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c httpContext) IsCache() bool {
|
|
if maxAgeStr, ok := c.cacheControl["max-age"]; ok {
|
|
maxAge, _ := strconv.Atoi(maxAgeStr)
|
|
if maxAge == 0 {
|
|
return false
|
|
}
|
|
}
|
|
|
|
if c.NoCache() {
|
|
return false
|
|
}
|
|
|
|
if !c.IsPublic() {
|
|
return false
|
|
}
|
|
|
|
if _, ok := c.cacheControl["no-store"]; ok {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (c httpContext) IsPublic() bool {
|
|
if _, ok := c.reqHeader["Authorization"]; ok {
|
|
if _, pOk := c.cacheControl["public"]; pOk {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
//只要不是私有的 都算公有
|
|
if _, ok := c.cacheControl["private"]; ok {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func parseHttpContext(httpCtx http_service.IHttpContext) httpContext {
|
|
hc := httpContext{
|
|
cacheControl: make(map[string]string),
|
|
}
|
|
|
|
hc.resHeader = httpCtx.Response().Headers()
|
|
hc.reqHeader = httpCtx.Request().Header().Headers()
|
|
|
|
cacheControlHeader := httpCtx.Response().GetHeader("Cache-Control")
|
|
for _, part := range strings.Split(cacheControlHeader, ",") {
|
|
part = strings.Trim(part, " ")
|
|
if part == "" {
|
|
continue
|
|
}
|
|
if strings.ContainsRune(part, '=') {
|
|
keyVal := strings.Split(part, "=")
|
|
hc.cacheControl[strings.Trim(keyVal[0], " ")] = strings.Trim(keyVal[1], ",")
|
|
|
|
} else {
|
|
hc.cacheControl[part] = ""
|
|
}
|
|
}
|
|
return hc
|
|
}
|
|
|
|
func DoStrategy(ctx eocontext.EoContext, next eocontext.IChain, cache resources.ICache) error {
|
|
return actuatorSet.Strategy(ctx, next, cache)
|
|
}
|