-
Notifications
You must be signed in to change notification settings - Fork 10
/
context.go
343 lines (304 loc) · 8.93 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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
// Copyright 2014 Codehack http://codehack.com
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package relax
import (
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"context"
)
// HandlerFunc is simply a version of http.HandlerFunc that uses Context.
// All filters must return and accept this type.
type HandlerFunc func(*Context)
// Context has information about the request and filters. It implements
// http.ResponseWriter.
type Context struct {
context.Context
// ResponseWriter is the response object passed from ``net/http``.
http.ResponseWriter
wroteHeader bool
status int
bytes int
// Request points to the http.Request information for this request.
Request *http.Request
// PathValues contains the values matched in PSEs by the router. It is a
// name=values map (map[string][]string).
// Examples:
//
// ctx.PathValues.Get("username") // returns the first value for "username"
// ctx.PathValues.Get("_2") // values are also accessible by index
// ctx.PathValues["colors"] // if more than one color value.
//
// See also: Router, url.Values
PathValues url.Values
// Encode is the media encoding function requested by the client.
// To see the media type use:
//
// ctx.Get("content.encoding")
//
// See also: Encoder.Encode
Encode func(io.Writer, interface{}) error
// Decode is the decoding function when this request was made. It expects an
// object that implements io.Reader, usually Request.Body. Then it will decode
// the data and try to save it into a variable interface.
// To see the media type use:
//
// ctx.Get("content.decoding")
//
// See also: Encoder.Decode
Decode func(io.Reader, interface{}) error
}
// contextPool allows us to reuse some Context objects to conserve resources.
var contextPool = sync.Pool{
New: func() interface{} { return new(Context) },
}
// newContext returns a new Context object.
// This function will alter Request.URL, adding scheme and host:port as provided by the client.
func newContext(parent context.Context, w http.ResponseWriter, r *http.Request) *Context {
ctx := contextPool.Get().(*Context)
ctx.Context = parent
ctx.ResponseWriter = w
ctx.Request = r
return ctx
}
// free frees a Context object back to the usage pool for later, to conserve
// system resources.
func (ctx *Context) free() {
ctx.ResponseWriter = nil
ctx.wroteHeader = false
ctx.status = 0
ctx.bytes = 0
ctx.PathValues = nil
ctx.Decode = nil
ctx.Encode = nil
contextPool.Put(ctx)
}
// Clone returns a shallow cloned context using 'w', an http.ResponseWriter object.
// If 'w' is nil, the ResponseWriter value can be assigned after cloning.
func (ctx *Context) Clone(w http.ResponseWriter) *Context {
clone := contextPool.Get().(*Context)
clone.Context = ctx.Context
clone.ResponseWriter = w
clone.Request = ctx.Request
clone.PathValues = ctx.PathValues
clone.bytes = ctx.bytes
clone.Decode = ctx.Decode
clone.Encode = ctx.Encode
return clone
}
// Set stores the value of key in the Context k/v tree.
func (ctx *Context) Set(key string, value interface{}) {
ctx.Context = context.WithValue(ctx.Context, key, value)
}
// Get retrieves the value of key from Context storage. The value is returned
// as an interface so it must be converted to an actual type. If the type implements
// fmt.Stringer then it may be used by functions that expect a string.
func (ctx *Context) Get(key string) interface{} {
return ctx.Context.Value(key)
}
// Header implements ResponseWriter.Header
func (ctx *Context) Header() http.Header {
return ctx.ResponseWriter.Header()
}
// Write implements ResponseWriter.Write
func (ctx *Context) Write(b []byte) (int, error) {
n, err := ctx.ResponseWriter.Write(b)
ctx.bytes += n
return n, err
}
// WriteHeader will force a status code header, if one hasn't been set.
// If no call to WriteHeader is done within this context, it defaults to
// http.StatusOK (200), which is sent by net/http.
func (ctx *Context) WriteHeader(code int) {
if ctx.wroteHeader {
return
}
ctx.wroteHeader = true
ctx.status = code
ctx.ResponseWriter.WriteHeader(code)
}
// Status returns the current known HTTP status code, or http.StatusOK if unknown.
func (ctx *Context) Status() int {
if !ctx.wroteHeader {
return http.StatusOK
}
return ctx.status
}
// Bytes returns the number of bytes written in the response.
func (ctx *Context) Bytes() int {
return ctx.bytes
}
/*
Respond writes a response back to the client. A complete RESTful response
should be contained within a structure.
'v' is the object value to be encoded. 'code' is an optional HTTP status code.
If at any point the response fails (due to encoding or system issues), an
error is returned but not written back to the client.
type Message struct {
Status int `json:"status"`
Text string `json:"text"`
}
ctx.Respond(&Message{Status: 201, Text: "Ticket created"}, http.StatusCreated)
See also: Context.Encode, WriteHeader
*/
func (ctx *Context) Respond(v interface{}, code ...int) error {
if code != nil {
ctx.WriteHeader(code[0])
}
err := ctx.Encode(ctx.ResponseWriter, v)
if err != nil {
// encoding failed, most likely we tried to encode something that hasn't
// been made marshable yet.
panic(err)
}
return err
}
/*
Error sends an error response, with appropriate encoding. It basically calls
Respond using a status code and wrapping the message in a StatusError object.
'code' is the HTTP status code of the error. 'message' is the actual error message
or reason. 'details' are additional details about this error (optional).
type RouteDetails struct {
Method string `json:"method"`
Path string `json:"path"`
}
ctx.Error(http.StatusNotImplemented, "That route is not implemented", &RouteDetails{"PATCH", "/v1/tickets/{id}"})
See also: Respond, StatusError
*/
func (ctx *Context) Error(code int, message string, details ...interface{}) {
response := &StatusError{code, message, nil}
if details != nil {
response.Details = details[0]
}
ctx.Respond(response, code)
}
/*
Format implements the fmt.Formatter interface, based on Apache HTTP's
CustomLog directive. This allows a Context object to have Sprintf verbs for
its values. See: https://httpd.apache.org/docs/2.4/mod/mod_log_config.html#formats
Verb Description
---- ---------------------------------------------------
%% Percent sign
%a Client remote address
%b Size of response in bytes, excluding headers. Or '-' if zero.
%#a Proxy client address, or unknown.
%h Remote hostname. Will perform lookup.
%l Remote ident, will write '-' (only for Apache log support).
%m Request method
%q Request query string.
%r Request line.
%#r Request line without protocol.
%s Response status code.
%#s Response status code and text.
%t Request time, as string.
%u Remote user, if any.
%v Request host name.
%A User agent.
%B Size of response in bytes, excluding headers.
%D Time lapsed to serve request, in seconds.
%H Request protocol.
%I Bytes received.
%L Request ID.
%P Server port used.
%R Referer.
%U Request path.
Example:
// Print request line and remote address.
// Index [1] needed to reuse ctx argument.
fmt.Printf("\"%r\" %[1]a", ctx)
// Output:
// "GET /v1/" 192.168.1.10
*/
func (ctx *Context) Format(f fmt.State, c rune) {
var str string
p, pok := f.Precision()
if !pok {
p = -1
}
switch c {
case 'a':
if f.Flag('#') {
str = GetRealIP(ctx.Request)
break
}
str = ctx.Request.RemoteAddr
case 'b':
if ctx.Bytes() == 0 {
f.Write([]byte{45})
return
}
fallthrough
case 'B':
str = strconv.Itoa(ctx.Bytes())
case 'h':
t := strings.Split(ctx.Request.RemoteAddr, ":")
str = t[0]
case 'l':
f.Write([]byte{45})
return
case 'm':
str = ctx.Request.Method
case 'q':
str = ctx.Request.URL.RawQuery
case 'r':
str = ctx.Request.Method + " " + ctx.Request.URL.RequestURI()
if f.Flag('#') {
break
}
str += " " + ctx.Request.Proto
case 's':
str = strconv.Itoa(ctx.Status())
if f.Flag('#') {
str += " " + http.StatusText(ctx.Status())
}
case 't':
t := ctx.Get("request.start_time").(time.Time)
str = t.Format("[02/Jan/2006:15:04:05 -0700]")
case 'u':
// XXX: i dont think net/http sets User
if ctx.Request.URL.User == nil {
f.Write([]byte{45})
return
}
str = ctx.Request.URL.User.Username()
case 'v':
str = ctx.Request.Host
case 'A':
str = ctx.Request.UserAgent()
case 'D':
when := ctx.Get("request.start_time").(time.Time)
if when.IsZero() {
f.Write([]byte("%!(BADTIME)"))
return
}
pok = false
str = strconv.FormatFloat(time.Since(when).Seconds(), 'f', p, 32)
case 'H':
str = ctx.Request.Proto
case 'I':
str = fmt.Sprintf("%d", ctx.Request.ContentLength)
case 'L':
str = ctx.Get("request.id").(string)
case 'P':
s := strings.Split(ctx.Request.Host, ":")
if len(s) > 1 {
str = s[1]
break
}
str = "80"
case 'R':
str = ctx.Request.Referer()
case 'U':
str = ctx.Request.URL.Path
}
if pok {
str = str[:p]
}
f.Write([]byte(str))
}