handler.go 4.3 KB

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