mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
166 lines
5.3 KiB
Go
166 lines
5.3 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/kataras/iris/v12/cache/cfg"
|
|
"github.com/kataras/iris/v12/cache/client/rule"
|
|
"github.com/kataras/iris/v12/cache/uri"
|
|
"github.com/kataras/iris/v12/context"
|
|
)
|
|
|
|
// ClientHandler is the client-side handler
|
|
// for each of the cached route paths's response
|
|
// register one client handler per route.
|
|
//
|
|
// it's just calls a remote cache service server/handler, which may lives on other, external machine.
|
|
type ClientHandler struct {
|
|
// bodyHandler the original route's handler
|
|
bodyHandler context.Handler
|
|
|
|
// Rule optional validators for pre cache and post cache actions
|
|
//
|
|
// See more at ruleset.go
|
|
rule rule.Rule
|
|
|
|
life time.Duration
|
|
|
|
remoteHandlerURL string
|
|
}
|
|
|
|
// NewClientHandler returns a new remote client handler
|
|
// which asks the remote handler the cached entry's response
|
|
// with a GET request, or add a response with POST request
|
|
// these all are done automatically, users can use this
|
|
// handler as they use the local.go/NewHandler
|
|
//
|
|
// the ClientHandler is useful when user
|
|
// wants to apply horizontal scaling to the app and
|
|
// has a central http server which handles
|
|
func NewClientHandler(bodyHandler context.Handler, life time.Duration, remote string) *ClientHandler {
|
|
return &ClientHandler{
|
|
bodyHandler: bodyHandler,
|
|
rule: DefaultRuleSet,
|
|
life: life,
|
|
remoteHandlerURL: remote,
|
|
}
|
|
}
|
|
|
|
// Rule sets the ruleset for this handler,
|
|
// see internal/net/http/ruleset.go for more information.
|
|
//
|
|
// returns itself.
|
|
func (h *ClientHandler) Rule(r rule.Rule) *ClientHandler {
|
|
if r == nil {
|
|
// if nothing passed then use the allow-everything rule
|
|
r = rule.Satisfied()
|
|
}
|
|
h.rule = r
|
|
|
|
return h
|
|
}
|
|
|
|
// AddRule adds a rule in the chain, the default rules are executed first.
|
|
//
|
|
// returns itself.
|
|
func (h *ClientHandler) AddRule(r rule.Rule) *ClientHandler {
|
|
if r == nil {
|
|
return h
|
|
}
|
|
|
|
h.rule = rule.Chained(h.rule, r)
|
|
return h
|
|
}
|
|
|
|
// Client is used inside the global Request function
|
|
// this client is an exported to give you a freedom of change its Transport, Timeout and so on(in case of ssl)
|
|
var Client = &http.Client{Timeout: cfg.RequestCacheTimeout}
|
|
|
|
// ServeHTTP , or remote cache client whatever you like, it's the client-side function of the ServeHTTP
|
|
// sends a request to the server-side remote cache Service and sends the cached response to the frontend client
|
|
// it is used only when you achieved something like horizontal scaling (separate machines)
|
|
// look ../remote/remote.ServeHTTP for more
|
|
//
|
|
// if cache din't find then it sends a POST request and save the bodyHandler's body to the remote cache.
|
|
//
|
|
// It takes 3 parameters
|
|
// the first is the remote address (it's the address you started your http server which handled by the Service.ServeHTTP)
|
|
// the second is the handler (or the mux) you want to cache
|
|
// and the third is the, optionally, cache expiration,
|
|
// which is used to set cache duration of this specific cache entry to the remote cache service
|
|
// if <=minimumAllowedCacheDuration then the server will try to parse from "cache-control" header
|
|
//
|
|
// client-side function
|
|
func (h *ClientHandler) ServeHTTP(ctx *context.Context) {
|
|
// check for deniers, if at least one of them return true
|
|
// for this specific request, then skip the whole cache
|
|
if !h.rule.Claim(ctx) {
|
|
h.bodyHandler(ctx)
|
|
return
|
|
}
|
|
|
|
uri := &uri.URIBuilder{}
|
|
uri.ServerAddr(h.remoteHandlerURL).ClientURI(ctx.Request().URL.RequestURI()).ClientMethod(ctx.Request().Method)
|
|
|
|
// set the full url here because below we have other issues, probably net/http bugs
|
|
request, err := http.NewRequest(http.MethodGet, uri.String(), nil)
|
|
if err != nil {
|
|
//// println("error when requesting to the remote service: " + err.Error())
|
|
// somehing very bad happens, just execute the user's handler and return
|
|
h.bodyHandler(ctx)
|
|
return
|
|
}
|
|
|
|
// println("GET Do to the remote cache service with the url: " + request.URL.String())
|
|
response, err := Client.Do(request)
|
|
|
|
if err != nil || response.StatusCode == cfg.FailStatus {
|
|
|
|
// if not found on cache, then execute the handler and save the cache to the remote server
|
|
recorder := ctx.Recorder()
|
|
h.bodyHandler(ctx)
|
|
|
|
// check if it's a valid response, if it's not then just return.
|
|
if !h.rule.Valid(ctx) {
|
|
return
|
|
}
|
|
// save to the remote cache
|
|
// we re-create the request for any case
|
|
body := recorder.Body()[0:]
|
|
if len(body) == 0 {
|
|
//// println("Request: len body is zero, do nothing")
|
|
return
|
|
}
|
|
uri.StatusCode(recorder.StatusCode())
|
|
uri.Lifetime(h.life)
|
|
uri.ContentType(recorder.Header().Get(cfg.ContentTypeHeader))
|
|
|
|
request, err = http.NewRequest(http.MethodPost, uri.String(), bytes.NewBuffer(body)) // yes new buffer every time
|
|
|
|
// println("POST Do to the remote cache service with the url: " + request.URL.String())
|
|
if err != nil {
|
|
//// println("Request: error on method Post of request to the remote: " + err.Error())
|
|
return
|
|
}
|
|
// go Client.Do(request)
|
|
resp, err := Client.Do(request)
|
|
if err != nil {
|
|
return
|
|
}
|
|
resp.Body.Close()
|
|
} else {
|
|
// get the status code , content type and the write the response body
|
|
ctx.ContentType(response.Header.Get(cfg.ContentTypeHeader))
|
|
ctx.StatusCode(response.StatusCode)
|
|
responseBody, err := io.ReadAll(response.Body)
|
|
response.Body.Close()
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, _ = ctx.Write(responseBody)
|
|
}
|
|
}
|