client.go 5.3 KB

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