handler.go 4.4 KB

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