handler.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. package client
  2. import (
  3. "net/http"
  4. "strings"
  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. maxAgeFunc MaxAgeFunc
  23. // entries the memory cache stored responses.
  24. entryPool *entry.Pool
  25. entryStore entry.Store
  26. }
  27. type MaxAgeFunc func(*context.Context) time.Duration
  28. // NewHandler returns a new Server-side cached handler for the "bodyHandler"
  29. // which expires every "expiration".
  30. func NewHandler(maxAgeFunc MaxAgeFunc) *Handler {
  31. return &Handler{
  32. rule: DefaultRuleSet,
  33. maxAgeFunc: maxAgeFunc,
  34. entryPool: entry.NewPool(),
  35. entryStore: entry.NewMemStore(),
  36. }
  37. }
  38. // Rule sets the ruleset for this handler.
  39. //
  40. // returns itself.
  41. func (h *Handler) Rule(r rule.Rule) *Handler {
  42. if r == nil {
  43. // if nothing passed then use the allow-everything rule
  44. r = rule.Satisfied()
  45. }
  46. h.rule = r
  47. return h
  48. }
  49. // AddRule adds a rule in the chain, the default rules are executed first.
  50. //
  51. // returns itself.
  52. func (h *Handler) AddRule(r rule.Rule) *Handler {
  53. if r == nil {
  54. return h
  55. }
  56. h.rule = rule.Chained(h.rule, r)
  57. return h
  58. }
  59. // Store sets a custom store for this handler.
  60. func (h *Handler) Store(store entry.Store) *Handler {
  61. h.entryStore = store
  62. return h
  63. }
  64. // MaxAge customizes the expiration duration for this handler.
  65. func (h *Handler) MaxAge(fn MaxAgeFunc) *Handler {
  66. h.maxAgeFunc = fn
  67. return h
  68. }
  69. var emptyHandler = func(ctx *context.Context) {
  70. ctx.StopWithText(500, "cache: empty body handler")
  71. }
  72. const entryKeyContextKey = "iris.cache.server.entry.key"
  73. // SetKey sets a custom entry key for cached pages.
  74. // See root package-level `WithKey` instead.
  75. func SetKey(ctx *context.Context, key string) {
  76. ctx.Values().Set(entryKeyContextKey, key)
  77. }
  78. // GetKey returns the entry key for the current page.
  79. func GetKey(ctx *context.Context) string {
  80. return ctx.Values().GetString(entryKeyContextKey)
  81. }
  82. func getOrSetKey(ctx *context.Context) string {
  83. if key := GetKey(ctx); key != "" {
  84. return key
  85. }
  86. // Note: by-default the rules(ruleset pkg)
  87. // explicitly ignores the cache handler
  88. // execution on authenticated requests
  89. // and immediately runs the next handler:
  90. // if !h.rule.Claim(ctx) ...see `Handler` method.
  91. // So the below two lines are useless,
  92. // however we add it for cases
  93. // that the end-developer messedup with the rules
  94. // and by accident allow authenticated cached results.
  95. username, password, _ := ctx.Request().BasicAuth()
  96. authPart := username + strings.Repeat("*", len(password))
  97. key := ctx.Method() + authPart
  98. u := ctx.Request().URL
  99. if !u.IsAbs() {
  100. key += ctx.Scheme() + ctx.Host()
  101. }
  102. key += u.String()
  103. SetKey(ctx, key)
  104. return key
  105. }
  106. func (h *Handler) ServeHTTP(ctx *context.Context) {
  107. // check for pre-cache validators, if at least one of them return false
  108. // for this specific request, then skip the whole cache
  109. bodyHandler := ctx.NextHandler()
  110. if bodyHandler == nil {
  111. emptyHandler(ctx)
  112. return
  113. }
  114. // skip prepares the context to move to the next handler if the "nextHandler" has a ctx.Next() inside it,
  115. // even if it's not executed because it's cached.
  116. ctx.Skip()
  117. if !h.rule.Claim(ctx) {
  118. bodyHandler(ctx)
  119. return
  120. }
  121. key := getOrSetKey(ctx) // unique per subdomains and paths with different url query.
  122. e := h.entryStore.Get(key)
  123. if e == nil {
  124. // if it's expired, then execute the original handler
  125. // with our custom response recorder response writer
  126. // because the net/http doesn't give us
  127. // a builtin way to get the status code & body
  128. recorder := ctx.Recorder()
  129. bodyHandler(ctx)
  130. // now that we have recordered the response,
  131. // we are ready to check if that specific response is valid to be stored.
  132. // check if it's a valid response, if it's not then just return.
  133. if !h.rule.Valid(ctx) {
  134. return
  135. }
  136. // no need to copy the body, its already done inside
  137. body := recorder.Body()
  138. if len(body) == 0 {
  139. // if no body then just exit.
  140. return
  141. }
  142. // fmt.Printf("reset cache entry\n")
  143. // fmt.Printf("key: %s\n", key)
  144. // fmt.Printf("content type: %s\n", recorder.Header().Get(cfg.ContentTypeHeader))
  145. // fmt.Printf("body len: %d\n", len(body))
  146. r := entry.NewResponse(recorder.StatusCode(), recorder.Header(), body)
  147. e = h.entryPool.Acquire(h.maxAgeFunc(ctx), r, func() {
  148. h.entryStore.Delete(key)
  149. })
  150. h.entryStore.Set(key, e)
  151. return
  152. }
  153. // if it's valid then just write the cached results
  154. r := e.Response()
  155. // if !ok {
  156. // // it shouldn't be happen because if it's not valid (= expired)
  157. // // then it shouldn't be found on the store, we return as it is, the body was written.
  158. // return
  159. // }
  160. copyHeaders(ctx.ResponseWriter().Header(), r.Headers())
  161. ctx.SetLastModified(e.LastModified)
  162. ctx.StatusCode(r.StatusCode())
  163. ctx.Write(r.Body())
  164. // fmt.Printf("key: %s\n", key)
  165. // fmt.Printf("write content type: %s\n", response.Headers()["ContentType"])
  166. // fmt.Printf("write body len: %d\n", len(response.Body()))
  167. }
  168. func copyHeaders(dst, src http.Header) {
  169. // Clone returns a copy of h or nil if h is nil.
  170. if src == nil {
  171. return
  172. }
  173. // Find total number of values.
  174. nv := 0
  175. for _, vv := range src {
  176. nv += len(vv)
  177. }
  178. sv := make([]string, nv) // shared backing array for headers' values
  179. for k, vv := range src {
  180. if vv == nil {
  181. // Preserve nil values. ReverseProxy distinguishes
  182. // between nil and zero-length header values.
  183. dst[k] = nil
  184. continue
  185. }
  186. n := copy(sv, vv)
  187. dst[k] = sv[:n:n]
  188. sv = sv[n:]
  189. }
  190. }