forked from kataras/iris
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclient.go
More file actions
169 lines (146 loc) · 5.23 KB
/
Copy pathclient.go
File metadata and controls
169 lines (146 loc) · 5.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package client
import (
"bytes"
"io/ioutil"
"net/http"
"time"
"github.com/kataras/iris/cache/cfg"
"github.com/kataras/iris/cache/client/rule"
"github.com/kataras/iris/cache/uri"
"github.com/kataras/iris/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 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}
const (
methodGet = "GET"
methodPost = "POST"
)
// 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(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(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)
Client.Do(request)
} 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 := ioutil.ReadAll(response.Body)
response.Body.Close()
if err != nil {
return
}
ctx.Write(responseBody)
}
}