forked from spaceweasel/mango
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontext.go
More file actions
225 lines (200 loc) · 6.52 KB
/
context.go
File metadata and controls
225 lines (200 loc) · 6.52 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
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
package mango
import (
"fmt"
"io"
"net/http"
"sort"
"strings"
)
// Response is an object used to facilitate building a response.
type Response struct {
context *Context
model interface{}
status int
}
// WithModel sets the Model that will be serialized for the response.
// The serialization mechanism will depend on the request Accept header
// and the encoding DefaultMediaType.
// This method returns the Response object and can be chained.
func (r *Response) WithModel(m interface{}) *Response {
r.context.model = m
r.context.responseReady = true
return r
}
// WithStatus sets the HTTP status code of the response.
// This method returns the Response object and can be chained.
func (r *Response) WithStatus(s int) *Response {
r.context.status = s
r.context.responseReady = true
return r
}
// WithHeader adds a header to the response.
// This method returns the Response object and can be chained.
func (r *Response) WithHeader(key, value string) *Response {
r.context.Writer.Header().Add(key, value)
return r
}
// WithContentType sets the Content-Type header of the response.
// This method returns the Response object and can be chained.
func (r *Response) WithContentType(ct string) *Response {
r.context.Writer.Header().Set("Content-Type", ct)
return r
}
// Context is the request context.
// Context encapsulates the underlying Req and Writer, but exposes
// them if required. It provides many helper methods which are
// designed to keep your handler code clean and free from boiler
// code.
type Context struct {
Request *http.Request
Writer http.ResponseWriter
status int
payload []byte
model interface{}
RouteParams map[string]string
encoderEngine EncoderEngine
Reader io.ReadCloser
Identity Identity
responseReady bool
}
// ContextHandlerFunc type is an adapter to allow the use of ordinary
// functions as HTTP handlers. It is similar to the standard library's
// http.HandlerFunc in that if f is a function with the appropriate
// signature, ContextHandlerFunc(f) is a Handler object that calls f.
type ContextHandlerFunc func(*Context)
// ServeHTTP calls f(c).
func (f ContextHandlerFunc) ServeHTTP(c *Context) {
f(c)
}
// Respond returns a new context based Response object.
func (c *Context) Respond() *Response {
return &Response{context: c}
}
// RespondWith is a generic method for producing a simple response.
// It takes a single parameter whose type will determine the action.
//
// Strings will be used for the response content.
// Integers will be used for the response status code.
// Any other type is deemed to be a model which will be serialized.
//
// The serialization mechanism for the model will depend on the
// request Accept header and the encoding DefaultMediaType.
// This method returns the Response object and can be chained.
func (c *Context) RespondWith(d interface{}) *Response {
response := &Response{context: c}
switch t := d.(type) {
case int:
c.status = t
case string:
c.payload = []byte(t)
default: //must be a model
c.model = d
}
c.responseReady = true
return response
}
// Authenticated returns true if a request user has been authenticated.
// Authentication should be performed in a pre-hook, assigning a valid
// Identity to the Context if authentication succeeds.
// This method simply examines whether the Context has a valid Identity.
func (c *Context) Authenticated() bool {
return c.Identity != nil
}
func (c *Context) urlSchemeHost() string {
if c.Request.TLS != nil {
return "https://" + c.Request.Host
}
return "http://" + c.Request.Host
}
// Error sends the specified message and HTTP status code as a response.
// Request handlers should cease execution after calling this method.
func (c *Context) Error(msg string, code int) {
http.Error(c.Writer, msg, code)
}
// Redirect sends a redirect response using the specified URL and HTTP
// status.
// Request handlers should cease execution after calling this method.
// TODO: Not yet implemented
func (c *Context) Redirect(urlStr string, code int) {
http.Redirect(c.Writer, c.Request, urlStr, code)
}
// // Render executes a template using the supplied data.
// // Request handlers should cease execution after calling this method.
// // TODO: Not yet implemented
// func (c *Context) Render(tmpl string, data interface{}) {
// panic("not yet implemented")
// }
//
// func (c *Context) sendResponse() {
// fmt.Fprintf(c.Writer, "")
// }
func (c *Context) contentDecoder() (Decoder, error) {
ct := c.Request.Header.Get("Content-Type")
return c.encoderEngine.GetDecoder(c.Request.Body, ct)
}
func (c *Context) acceptableMediaTypes() []string {
hdr := c.Request.Header.Get("Accept")
hdr = strings.Replace(hdr, " ", "", -1)
types := strings.Split(hdr, ",")
mt := make(mediaTypes, len(types))
for i, t := range types {
m, err := newMediaType(t)
if err != nil {
continue
}
mt[i] = *m
}
sort.Sort(mt)
r := []string{}
for _, t := range mt {
r = append(r, t.String())
}
return r
}
// GetEncoder returns an Encoder suitable for serializing data in a response.
// The Encoder is selected based on the request Accept header (or default media
// type if no Accept header supplied).
// If successful, the an encoder and content-type are returned and a nil error.
// Success is determined by a nil error.
// The returned encoder will have been pre-injected with an io.Writer, so the
// Encode method can be called directly, passing the data to be encoded as the
// only parameter.
func (c *Context) GetEncoder() (Encoder, string, error) {
mts := c.acceptableMediaTypes()
var err error
var mt string
for _, mt = range mts {
if mt == "*/*" {
mt = c.encoderEngine.DefaultMediaType()
}
var encoder Encoder
encoder, err = c.encoderEngine.GetEncoder(c.Writer, mt)
if err == nil {
return encoder, mt, nil
}
}
return nil, mt, err
}
// Bind populates the supplied model with data from the request.
// This is performed in stages. initially, any requestbody content is
// deserialized.
//
// TODO: Following is not yet implemented:
//
// Route parameters are used next to populate any unset members.
// Finally, query parameters are used to populate any remaining unset members.
//
// This method is under review - currently Binding only uses deserialized
// request body content.
func (c *Context) Bind(m interface{}) error {
decoder, err := c.contentDecoder()
if err != nil {
return fmt.Errorf("unable to bind: %v", err)
}
err = decoder.Decode(m)
if err != nil {
return fmt.Errorf("unable to bind: %v", err)
}
// TODO: now update any missing empty properties from url path/query params
return nil
}