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.

173 lines
5.3KB

  1. package client
  2. import (
  3. "bytes"
  4. "io/ioutil"
  5. "net/http"
  6. "time"
  7. "github.com/kataras/iris/v12/cache/cfg"
  8. "github.com/kataras/iris/v12/cache/client/rule"
  9. "github.com/kataras/iris/v12/cache/uri"
  10. "github.com/kataras/iris/v12/context"
  11. )
  12. // ClientHandler is the client-side handler
  13. // for each of the cached route paths's response
  14. // register one client handler per route.
  15. //
  16. // it's just calls a remote cache service server/handler,
  17. // which lives on other, external machine.
  18. //
  19. type ClientHandler struct {
  20. // bodyHandler the original route's handler
  21. bodyHandler context.Handler
  22. // Rule optional validators for pre cache and post cache actions
  23. //
  24. // See more at ruleset.go
  25. rule rule.Rule
  26. life time.Duration
  27. remoteHandlerURL string
  28. }
  29. // NewClientHandler returns a new remote client handler
  30. // which asks the remote handler the cached entry's response
  31. // with a GET request, or add a response with POST request
  32. // these all are done automatically, users can use this
  33. // handler as they use the local.go/NewHandler
  34. //
  35. // the ClientHandler is useful when user
  36. // wants to apply horizontal scaling to the app and
  37. // has a central http server which handles
  38. func NewClientHandler(bodyHandler context.Handler, life time.Duration, remote string) *ClientHandler {
  39. return &ClientHandler{
  40. bodyHandler: bodyHandler,
  41. rule: DefaultRuleSet,
  42. life: life,
  43. remoteHandlerURL: remote,
  44. }
  45. }
  46. // Rule sets the ruleset for this handler,
  47. // see internal/net/http/ruleset.go for more information.
  48. //
  49. // returns itself.
  50. func (h *ClientHandler) Rule(r rule.Rule) *ClientHandler {
  51. if r == nil {
  52. // if nothing passed then use the allow-everything rule
  53. r = rule.Satisfied()
  54. }
  55. h.rule = r
  56. return h
  57. }
  58. // AddRule adds a rule in the chain, the default rules are executed first.
  59. //
  60. // returns itself.
  61. func (h *ClientHandler) AddRule(r rule.Rule) *ClientHandler {
  62. if r == nil {
  63. return h
  64. }
  65. h.rule = rule.Chained(h.rule, r)
  66. return h
  67. }
  68. // Client is used inside the global Request function
  69. // this client is an exported to give you a freedom of change its Transport, Timeout and so on(in case of ssl)
  70. var Client = &http.Client{Timeout: cfg.RequestCacheTimeout}
  71. const (
  72. methodGet = "GET"
  73. methodPost = "POST"
  74. )
  75. // ServeHTTP , or remote cache client whatever you like, it's the client-side function of the ServeHTTP
  76. // sends a request to the server-side remote cache Service and sends the cached response to the frontend client
  77. // it is used only when you achieved something like horizontal scaling (separate machines)
  78. // look ../remote/remote.ServeHTTP for more
  79. //
  80. // if cache din't find then it sends a POST request and save the bodyHandler's body to the remote cache.
  81. //
  82. // It takes 3 parameters
  83. // the first is the remote address (it's the address you started your http server which handled by the Service.ServeHTTP)
  84. // the second is the handler (or the mux) you want to cache
  85. // and the third is the, optionally, cache expiration,
  86. // which is used to set cache duration of this specific cache entry to the remote cache service
  87. // if <=minimumAllowedCacheDuration then the server will try to parse from "cache-control" header
  88. //
  89. // client-side function
  90. func (h *ClientHandler) ServeHTTP(ctx *context.Context) {
  91. // check for deniers, if at least one of them return true
  92. // for this specific request, then skip the whole cache
  93. if !h.rule.Claim(ctx) {
  94. h.bodyHandler(ctx)
  95. return
  96. }
  97. uri := &uri.URIBuilder{}
  98. uri.ServerAddr(h.remoteHandlerURL).ClientURI(ctx.Request().URL.RequestURI()).ClientMethod(ctx.Request().Method)
  99. // set the full url here because below we have other issues, probably net/http bugs
  100. request, err := http.NewRequest(methodGet, uri.String(), nil)
  101. if err != nil {
  102. //// println("error when requesting to the remote service: " + err.Error())
  103. // somehing very bad happens, just execute the user's handler and return
  104. h.bodyHandler(ctx)
  105. return
  106. }
  107. // println("GET Do to the remote cache service with the url: " + request.URL.String())
  108. response, err := Client.Do(request)
  109. if err != nil || response.StatusCode == cfg.FailStatus {
  110. // if not found on cache, then execute the handler and save the cache to the remote server
  111. recorder := ctx.Recorder()
  112. h.bodyHandler(ctx)
  113. // check if it's a valid response, if it's not then just return.
  114. if !h.rule.Valid(ctx) {
  115. return
  116. }
  117. // save to the remote cache
  118. // we re-create the request for any case
  119. body := recorder.Body()[0:]
  120. if len(body) == 0 {
  121. //// println("Request: len body is zero, do nothing")
  122. return
  123. }
  124. uri.StatusCode(recorder.StatusCode())
  125. uri.Lifetime(h.life)
  126. uri.ContentType(recorder.Header().Get(cfg.ContentTypeHeader))
  127. request, err = http.NewRequest(methodPost, uri.String(), bytes.NewBuffer(body)) // yes new buffer every time
  128. // println("POST Do to the remote cache service with the url: " + request.URL.String())
  129. if err != nil {
  130. //// println("Request: error on method Post of request to the remote: " + err.Error())
  131. return
  132. }
  133. // go Client.Do(request)
  134. resp, err := Client.Do(request)
  135. if err != nil {
  136. return
  137. }
  138. resp.Body.Close()
  139. } else {
  140. // get the status code , content type and the write the response body
  141. ctx.ContentType(response.Header.Get(cfg.ContentTypeHeader))
  142. ctx.StatusCode(response.StatusCode)
  143. responseBody, err := ioutil.ReadAll(response.Body)
  144. response.Body.Close()
  145. if err != nil {
  146. return
  147. }
  148. _, _ = ctx.Write(responseBody)
  149. }
  150. }