123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- package client
- import (
- "sync"
- "time"
- "github.com/kataras/iris/cache/client/rule"
- "github.com/kataras/iris/cache/entry"
- "github.com/kataras/iris/context"
- )
- // Handler the local cache service handler contains
- // the original response, the memory cache entry and
- // the validator for each of the incoming requests and post responses
- type Handler struct {
- // Rule optional validators for pre cache and post cache actions
- //
- // See more at ruleset.go
- rule rule.Rule
- // when expires.
- expiration time.Duration
- // entries the memory cache stored responses.
- entries map[string]*entry.Entry
- mu sync.RWMutex
- }
- // NewHandler returns a new cached handler for the "bodyHandler"
- // which expires every "expiration".
- func NewHandler(expiration time.Duration) *Handler {
- return &Handler{
- rule: DefaultRuleSet,
- expiration: expiration,
- entries: make(map[string]*entry.Entry, 0),
- }
- }
- // Rule sets the ruleset for this handler.
- //
- // returns itself.
- func (h *Handler) Rule(r rule.Rule) *Handler {
- if r == nil {
- // if nothing passed then use the allow-everything rule
- r = rule.Satisfied()
- }
- h.rule = r
- return h
- }
- // AddRule adds a rule in the chain, the default rules are executed first.
- //
- // returns itself.
- func (h *Handler) AddRule(r rule.Rule) *Handler {
- if r == nil {
- return h
- }
- h.rule = rule.Chained(h.rule, r)
- return h
- }
- var emptyHandler = func(ctx context.Context) {
- ctx.StatusCode(500)
- ctx.WriteString("cache: empty body handler")
- ctx.StopExecution()
- }
- func parseLifeChanger(ctx context.Context) entry.LifeChanger {
- return func() time.Duration {
- return time.Duration(ctx.MaxAge()) * time.Second
- }
- }
- ///TODO: debug this and re-run the parallel tests on larger scale,
- // because I think we have a bug here when `core/router#StaticWeb` is used after this middleware.
- func (h *Handler) ServeHTTP(ctx context.Context) {
- // check for pre-cache validators, if at least one of them return false
- // for this specific request, then skip the whole cache
- bodyHandler := ctx.NextHandler()
- if bodyHandler == nil {
- emptyHandler(ctx)
- return
- }
- // skip prepares the context to move to the next handler if the "nextHandler" has a ctx.Next() inside it,
- // even if it's not executed because it's cached.
- ctx.Skip()
- if !h.rule.Claim(ctx) {
- bodyHandler(ctx)
- return
- }
- scheme := "http"
- if ctx.Request().TLS != nil {
- scheme = "https"
- }
- var (
- response *entry.Response
- valid = false
- // unique per subdomains and paths with different url query.
- key = scheme + ctx.Host() + ctx.Request().URL.RequestURI()
- )
- h.mu.RLock()
- e, found := h.entries[key]
- h.mu.RUnlock()
- if found {
- // the entry is here, .Response will give us
- // if it's expired or no
- response, valid = e.Response()
- } else {
- // create the entry now.
- // fmt.Printf("create new cache entry\n")
- // fmt.Printf("key: %s\n", key)
- e = entry.NewEntry(h.expiration)
- h.mu.Lock()
- h.entries[key] = e
- h.mu.Unlock()
- }
- if !valid {
- // if it's expired, then execute the original handler
- // with our custom response recorder response writer
- // because the net/http doesn't give us
- // a built'n way to get the status code & body
- recorder := ctx.Recorder()
- bodyHandler(ctx)
- // now that we have recordered the response,
- // we are ready to check if that specific response is valid to be stored.
- // check if it's a valid response, if it's not then just return.
- if !h.rule.Valid(ctx) {
- return
- }
- // no need to copy the body, its already done inside
- body := recorder.Body()
- if len(body) == 0 {
- // if no body then just exit.
- return
- }
- // check for an expiration time if the
- // given expiration was not valid then check for GetMaxAge &
- // update the response & release the recorder
- e.Reset(
- recorder.StatusCode(),
- recorder.Header(),
- body,
- parseLifeChanger(ctx),
- )
- // fmt.Printf("reset cache entry\n")
- // fmt.Printf("key: %s\n", key)
- // fmt.Printf("content type: %s\n", recorder.Header().Get(cfg.ContentTypeHeader))
- // fmt.Printf("body len: %d\n", len(body))
- return
- }
- // if it's valid then just write the cached results
- entry.CopyHeaders(ctx.ResponseWriter().Header(), response.Headers())
- ctx.SetLastModified(e.LastModified)
- ctx.StatusCode(response.StatusCode())
- ctx.Write(response.Body())
- // fmt.Printf("key: %s\n", key)
- // fmt.Printf("write content type: %s\n", response.Headers()["ContentType"])
- // fmt.Printf("write body len: %d\n", len(response.Body()))
- }
|