You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

212 lines
5.3KB

  1. package client
  2. import (
  3. "strings"
  4. "sync"
  5. "time"
  6. "github.com/kataras/iris/v12/cache/client/rule"
  7. "github.com/kataras/iris/v12/cache/entry"
  8. "github.com/kataras/iris/v12/context"
  9. )
  10. func init() {
  11. context.SetHandlerName("iris/cache/client.(*Handler).ServeHTTP-fm", "iris.cache")
  12. }
  13. // Handler the local cache service handler contains
  14. // the original response, the memory cache entry and
  15. // the validator for each of the incoming requests and post responses
  16. type Handler struct {
  17. // Rule optional validators for pre cache and post cache actions
  18. //
  19. // See more at ruleset.go
  20. rule rule.Rule
  21. // when expires.
  22. expiration time.Duration
  23. // entries the memory cache stored responses.
  24. entries map[string]*entry.Entry
  25. mu sync.RWMutex
  26. }
  27. // NewHandler returns a new cached handler for the "bodyHandler"
  28. // which expires every "expiration".
  29. func NewHandler(expiration time.Duration) *Handler {
  30. return &Handler{
  31. rule: DefaultRuleSet,
  32. expiration: expiration,
  33. entries: make(map[string]*entry.Entry),
  34. }
  35. }
  36. // Rule sets the ruleset for this handler.
  37. //
  38. // returns itself.
  39. func (h *Handler) Rule(r rule.Rule) *Handler {
  40. if r == nil {
  41. // if nothing passed then use the allow-everything rule
  42. r = rule.Satisfied()
  43. }
  44. h.rule = r
  45. return h
  46. }
  47. // AddRule adds a rule in the chain, the default rules are executed first.
  48. //
  49. // returns itself.
  50. func (h *Handler) AddRule(r rule.Rule) *Handler {
  51. if r == nil {
  52. return h
  53. }
  54. h.rule = rule.Chained(h.rule, r)
  55. return h
  56. }
  57. var emptyHandler = func(ctx *context.Context) {
  58. ctx.StopWithText(500, "cache: empty body handler")
  59. }
  60. func parseLifeChanger(ctx *context.Context) entry.LifeChanger {
  61. return func() time.Duration {
  62. return time.Duration(ctx.MaxAge()) * time.Second
  63. }
  64. }
  65. const entryKeyContextKey = "iris.cache.server.entry.key"
  66. // SetKey sets a custom entry key for cached pages.
  67. // See root package-level `WithKey` instead.
  68. func SetKey(ctx *context.Context, key string) {
  69. ctx.Values().Set(entryKeyContextKey, key)
  70. }
  71. // GetKey returns the entry key for the current page.
  72. func GetKey(ctx *context.Context) string {
  73. return ctx.Values().GetString(entryKeyContextKey)
  74. }
  75. func getOrSetKey(ctx *context.Context) string {
  76. if key := GetKey(ctx); key != "" {
  77. return key
  78. }
  79. // Note: by-default the rules(ruleset pkg)
  80. // explicitly ignores the cache handler
  81. // execution on authenticated requests
  82. // and immediately runs the next handler:
  83. // if !h.rule.Claim(ctx) ...see `Handler` method.
  84. // So the below two lines are useless,
  85. // however we add it for cases
  86. // that the end-developer messedup with the rules
  87. // and by accident allow authenticated cached results.
  88. username, password, _ := ctx.Request().BasicAuth()
  89. authPart := username + strings.Repeat("*", len(password))
  90. key := ctx.Method() + authPart
  91. u := ctx.Request().URL
  92. if !u.IsAbs() {
  93. key += ctx.Scheme() + ctx.Host()
  94. }
  95. key += u.String()
  96. SetKey(ctx, key)
  97. return key
  98. }
  99. func (h *Handler) ServeHTTP(ctx *context.Context) {
  100. // check for pre-cache validators, if at least one of them return false
  101. // for this specific request, then skip the whole cache
  102. bodyHandler := ctx.NextHandler()
  103. if bodyHandler == nil {
  104. emptyHandler(ctx)
  105. return
  106. }
  107. // skip prepares the context to move to the next handler if the "nextHandler" has a ctx.Next() inside it,
  108. // even if it's not executed because it's cached.
  109. ctx.Skip()
  110. if !h.rule.Claim(ctx) {
  111. bodyHandler(ctx)
  112. return
  113. }
  114. var (
  115. response *entry.Response
  116. valid = false
  117. // unique per subdomains and paths with different url query.
  118. key = getOrSetKey(ctx)
  119. )
  120. h.mu.RLock()
  121. e, found := h.entries[key]
  122. h.mu.RUnlock()
  123. if found {
  124. // the entry is here, .Response will give us
  125. // if it's expired or no
  126. response, valid = e.Response()
  127. } else {
  128. // create the entry now.
  129. // fmt.Printf("create new cache entry\n")
  130. // fmt.Printf("key: %s\n", key)
  131. e = entry.NewEntry(h.expiration)
  132. h.mu.Lock()
  133. h.entries[key] = e
  134. h.mu.Unlock()
  135. }
  136. if !valid {
  137. // if it's expired, then execute the original handler
  138. // with our custom response recorder response writer
  139. // because the net/http doesn't give us
  140. // a builtin way to get the status code & body
  141. recorder := ctx.Recorder()
  142. bodyHandler(ctx)
  143. // now that we have recordered the response,
  144. // we are ready to check if that specific response is valid to be stored.
  145. // check if it's a valid response, if it's not then just return.
  146. if !h.rule.Valid(ctx) {
  147. return
  148. }
  149. // no need to copy the body, its already done inside
  150. body := recorder.Body()
  151. if len(body) == 0 {
  152. // if no body then just exit.
  153. return
  154. }
  155. // check for an expiration time if the
  156. // given expiration was not valid then check for GetMaxAge &
  157. // update the response & release the recorder
  158. e.Reset(
  159. recorder.StatusCode(),
  160. recorder.Header(),
  161. body,
  162. parseLifeChanger(ctx),
  163. )
  164. // fmt.Printf("reset cache entry\n")
  165. // fmt.Printf("key: %s\n", key)
  166. // fmt.Printf("content type: %s\n", recorder.Header().Get(cfg.ContentTypeHeader))
  167. // fmt.Printf("body len: %d\n", len(body))
  168. return
  169. }
  170. // if it's valid then just write the cached results
  171. entry.CopyHeaders(ctx.ResponseWriter().Header(), response.Headers())
  172. ctx.SetLastModified(e.LastModified)
  173. ctx.StatusCode(response.StatusCode())
  174. ctx.Write(response.Body())
  175. // fmt.Printf("key: %s\n", key)
  176. // fmt.Printf("write content type: %s\n", response.Headers()["ContentType"])
  177. // fmt.Printf("write body len: %d\n", len(response.Body()))
  178. }