-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontext.go
326 lines (275 loc) · 9.04 KB
/
context.go
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
package looli
import (
"html/template"
"math"
"net"
"net/http"
"net/url"
"strings"
)
var defaultStatusCode = http.StatusOK
// Context construct Request and ResponseWriter, provide useful methods
type Context struct {
http.ResponseWriter
// current handler that processing request
current int8
// Short for http.Request
Request *http.Request
// middleware handlers
handlers []HandlerFunc
// Param is URL parameter, a map[string]string.
Params Params
// Short for Request.URL.Path
Path string
// Short for Request.Method
Method string
// templete is use to render HTML
template *template.Template
engine *Engine
// statusCode that write to response
statusCode int
// Error when processing request
Err *Error
}
type JSON map[string]interface{}
const abortIndex int8 = math.MaxInt8 / 2
func NewContext(p *RouterPrefix, rw http.ResponseWriter, req *http.Request) *Context {
return &Context{
ResponseWriter: rw,
Request: req,
current: -1,
Path: req.URL.Path,
Method: req.Method,
template: p.engine.Template,
engine: p.engine,
statusCode: defaultStatusCode,
}
}
// Next should be used only inside middleware. It executes the pending handlers in the chain
// inside the calling handler
func (c *Context) Next() {
c.current++
length := int8(len(c.handlers))
for ; c.current < length; c.current++ {
c.handlers[c.current](c)
}
}
// Abort prevents pending handlers from being called. Note that this will not stop the current handler.
// if you want to stop current handler you should return, after call abort, call Abort to ensure the
// remaining handlers for this request are not called.
func (c *Context) Abort() {
c.current = abortIndex
}
// AbortWithStatus prevents pending handlers from being called and set statuscode. Note that this will not
// stop the current handler. if you want to stop current handler you should return, after call abort, call
// Abort to ensure the remaining handlers for this request are not called.
func (c *Context) AbortWithStatus(code int) {
c.statusCode = code
c.Status(code)
c.Abort()
}
// IsAborted returns true if the current context was aborted.
func (c *Context) IsAborted() bool {
return c.current >= abortIndex
}
// Param return the parameters by name in the request path
func (c *Context) Param(name string) string {
return c.Params[name]
}
// Query returns the keyed url query value if it exists, othewise it returns an empty string `("")`.
// It is shortcut for `c.Request.URL.Query().Get(key)` GET /path?&name=cssivision&age=23
// c.Query("name") == "cssivision"
// c.Query("age") == "23"
// c.Query("sex") == ""
func (c *Context) Query(key string) string {
req := c.Request
query := req.URL.Query()
if values, ok := query[key]; ok && len(values) > 0 {
return values[0]
}
return ""
}
// Query returns the keyed url query value if it exists, othewise it returns spectfic defaultValue.
// It is shortcut for `c.Request.URL.Query().Get(key)` GET /path?&name=cssivision&age=23
// c.DefaultQuery("name", "balabala") == "cssivision"
// c.Query("age", "24") == "23"
// c.Query("sex", "male") == "male"
func (c *Context) DefaultQuery(key, defaultValue string) string {
val := c.Query(key)
if val == "" {
return defaultValue
}
return val
}
// PostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns an empty string.
func (c *Context) PostForm(key string) string {
req := c.Request
req.ParseForm()
req.ParseMultipartForm(32 << 20)
val := ""
if values := req.PostForm[key]; len(values) > 0 {
val = values[0]
}
if req.MultipartForm != nil && req.MultipartForm.File != nil {
if values := req.MultipartForm.Value[key]; len(values) > 0 {
val = values[0]
}
}
return val
}
// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns the specified defaultValue string.
func (c *Context) DefaultPostForm(key, defaultValue string) string {
val := c.PostForm(key)
if val == "" {
return defaultValue
}
return val
}
// ClientIP implements a best effort algorithm to return the real client IP, it parses
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
func (c *Context) ClientIP() string {
if c.engine.ForwardedByClientIP {
clientIP := strings.TrimSpace(c.Header("X-Real-Ip"))
if len(clientIP) > 0 {
return clientIP
}
clientIP = c.Header("X-Forwarded-For")
if index := strings.IndexByte(clientIP, ','); index >= 0 {
clientIP = clientIP[0:index]
}
clientIP = strings.TrimSpace(clientIP)
if len(clientIP) > 0 {
return clientIP
}
}
if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil {
return ip
}
return ""
}
// Bind checks the Content-Type to select a binding engine automatically,
// Depending the "Content-Type" header different bindings are used:
// "application/json" --> JSON
// "application/xml" --> XML
// otherwise --> returns an error
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer.
// Like ParseBody() but this method also writes a 400 error if the json is not valid.
func (c *Context) Bind(data BindingStruct) error {
binding := bindDefault(c.Request.Method, c.ContentType())
if err := binding.Bind(c.Request, data); err != nil {
return err
}
return data.Validate()
}
// WriteHeader sends an HTTP response header with status code.
// If WriteHeader is not called explicitly, the first call to Write
// will trigger an implicit WriteHeader(http.StatusOK).
// Thus explicit calls to WriteHeader are mainly used to
// send error codes.
func (c *Context) Status(code int) {
c.statusCode = code
c.ResponseWriter.WriteHeader(code)
}
// Redirect replies to the request with a redirect to url, which may be a path relative to the request path.
func (c *Context) Redirect(location string) {
http.Redirect(c.ResponseWriter, c.Request, location, http.StatusFound)
}
// ServeFile replies to the request with the contents of the named file or directory.
// If the provided file or directory name is a relative path, it is interpreted
// relative to the current directory and may ascend to parent directories. If
// the provided name is constructed from user input, it should be sanitized
// before calling ServeFile. As a precaution, ServeFile will reject requests
// where r.URL.Path contains a ".." path element.
// As a special case, ServeFile redirects any request where r.URL.Path ends in
// "/index.html" to the same path, without the final "index.html". To avoid
// such redirects either modify the path or use ServeContent.
func (c *Context) ServeFile(filepath string) {
http.ServeFile(c.ResponseWriter, c.Request, filepath)
}
// Get gets the first value associated with the given key. It is case insensitive
func (c *Context) Header(key string) string {
return c.Request.Header.Get(key)
}
// Set sets the header entries associated with key to the single element value.
// It replaces any existing values associated with key.
func (c *Context) SetHeader(key, value string) {
if value == "" {
c.ResponseWriter.Header().Del(key)
} else {
c.ResponseWriter.Header().Set(key, value)
}
}
// Cookie get cookie from request header by name, if err != nil, return "", err
func (c *Context) Cookie(name string) (string, error) {
cookie, err := c.Request.Cookie(name)
if err != nil {
return "", err
}
val, _ := url.QueryUnescape(cookie.Value)
return val, nil
}
// SetCookie use http.SetCookie to set set-cookie header
func (c *Context) SetCookie(cookie *http.Cookie) {
http.SetCookie(c.ResponseWriter, cookie)
}
// ContentType return content-type from header
func (c *Context) ContentType() string {
if values, _ := c.Request.Header["Content-Type"]; len(values) > 0 {
return values[0]
}
return ""
}
func (c *Context) Error(err error) {
var parsedError *Error
switch err.(type) {
case *Error:
parsedError = err.(*Error)
default:
parsedError = &Error{
Err: err,
}
}
c.Err = parsedError
}
// String write format string to response
func (c *Context) String(format string, values ...interface{}) {
if err := renderString(c.ResponseWriter, format, values...); err != nil {
panic(err)
}
}
// JSON write obj to response
func (c *Context) JSON(data interface{}) {
if err := renderJSON(c.ResponseWriter, data); err != nil {
panic(err)
}
}
// SetResult set response code and msg
func (c *Context) SetResult(code int, msg string) {
data := map[string]interface{}{
"code": code,
"msg": msg,
}
if err := renderJSON(c.ResponseWriter, data); err != nil {
panic(err)
}
}
// SetBody return json body
func (c *Context) SetBody(data interface{}) {
rsp := map[string]interface{}{
"code": 0,
"msg": "ok",
"data": data,
}
if err := renderJSON(c.ResponseWriter, rsp); err != nil {
panic(err)
}
}
// HTML rendder html resp
func (c *Context) HTML(name string, data interface{}) {
if err := renderHTML(c.ResponseWriter, c.template, name, data); err != nil {
panic(err)
}
}