-
Notifications
You must be signed in to change notification settings - Fork 36
/
context.go
217 lines (201 loc) · 5.95 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
package jas
import (
"compress/gzip"
"encoding/json"
"errors"
"io"
"net/http"
"strings"
)
type Response struct {
Data interface{} `json:"data"`
Error interface{} `json:"error"`
}
//Context contains all the information for a single request.
//it hides the http.ResponseWriter because directly writing to http.ResponseWriter will make Context unable to work correctly.
//it embeds *http.Request and Finder, so you can call methods and access fields in Finder and *http.Request directly.
type Context struct {
Finder
*http.Request
ResponseHeader http.Header
Callback string //jsonp callback
Status int
Error AppError
Data interface{} //The data to be written after the resource method has returned.
UserId int64
Id int64
Extra interface{} //Store extra data generated/used by hook functions, e.g. 'BeforeServe'.
writer io.Writer
responseWriter http.ResponseWriter
clientClosed bool
written int
config *Config
pathSegments []string
gaps []string
}
var NoJsonBody = errors.New("jas.Context: no json body")
//Write and flush the data.
//It can be used for http streaming or to write a portion of large amount of data.
//If the type of the data is not []byte, it will be marshaled to json format.
func (ctx *Context) FlushData(data interface{}) (written int, err error) {
var dataBytes []byte
switch data.(type) {
case []byte:
dataBytes = data.([]byte)
default:
dataBytes, err = json.Marshal(data)
if err != nil {
return
}
}
if ctx.config.FlushDelimiter != nil {
dataBytes = append(dataBytes, ctx.config.FlushDelimiter...)
}
if ctx.written == 0 && ctx.Status != 200 {
ctx.responseWriter.WriteHeader(ctx.Status)
}
written, err = ctx.writer.Write(dataBytes)
if err != nil {
return
}
ctx.written += written
if gzipWriter, ok := ctx.writer.(*gzip.Writer); ok {
err = gzipWriter.Flush()
if err != nil {
return
}
}
ctx.responseWriter.(http.Flusher).Flush()
return
}
//Add response header Set-Cookie.
func (ctx *Context) SetCookie(cookie *http.Cookie) {
ctx.ResponseHeader.Add("Set-Cookie", cookie.String())
}
//override *http.Request AddCookie method to add response header's cookie.
//Same as SetCookie.
func (ctx *Context) AddCookie(cookie *http.Cookie) {
ctx.ResponseHeader.Add("Set-Cookie", cookie.String())
}
func (ctx *Context) deferredResponse() {
if x := recover(); x != nil {
var appErr AppError
if handled, ok := x.(AppError); ok {
appErr = handled
} else {
appErr = NewInternalError(x)
}
ctx.Error = appErr
}
var resp Response
resp.Data = ctx.Data
if ctx.Error != nil {
ctx.Status = ctx.Error.Status()
resp.Error = ctx.Error.Message()
}
var written int
if ctx.config.HijackWrite != nil {
ctx.responseWriter.WriteHeader(ctx.Status)
written = ctx.config.HijackWrite(ctx.writer, ctx)
} else {
jsonBytes, _ := json.Marshal(resp)
if ctx.Callback != "" { // handle JSONP
if ctx.written == 0 {
ctx.ResponseHeader.Set("Content-Type", "application/javascript; charset=utf-8")
ctx.responseWriter.WriteHeader(ctx.Status)
}
a, _ := ctx.writer.Write([]byte(ctx.Callback + "("))
b, _ := ctx.writer.Write(jsonBytes)
c, _ := ctx.writer.Write([]byte(");"))
written = a + b + c
} else {
if ctx.written == 0 {
ctx.responseWriter.WriteHeader(ctx.Status)
written, _ = ctx.writer.Write(jsonBytes)
} else if resp.Data != nil || resp.Error != nil {
written, _ = ctx.writer.Write(jsonBytes)
}
}
}
if ctx.Error != nil {
ctx.written += written
ctx.writer = nil
ctx.Error.Log(ctx)
if ctx.config.OnAppError != nil {
go ctx.config.OnAppError(ctx.Error, ctx)
}
}
}
//Typically used in for loop condition.along with Flush.
func (ctx *Context) ClientClosed() bool {
if ctx.clientClosed {
return true
}
select {
case <-ctx.responseWriter.(http.CloseNotifier).CloseNotify():
ctx.clientClosed = true
default:
}
return ctx.clientClosed
}
//the segment index starts at the resource segment
func (ctx *Context) PathSegment(index int) string {
if len(ctx.pathSegments) <= index {
return ""
}
return ctx.pathSegments[index]
}
//If the gap has multiple segments, the key should be
//the segment defined in resource Gap method.
//e.g. for gap ":domain/:language", use key ":domain"
//to get the first gap segment, use key ":language" to get the second gap segment.
//The first gap segment can also be gotten by empty string key "" for convenience.
func (ctx *Context) GapSegment(key string) string {
for i := 0; i < len(ctx.gaps); i++ {
if key == "" {
return ctx.pathSegments[i+1]
}
if key == ctx.gaps[i] {
return ctx.pathSegments[i+1]
}
}
return ""
}
//It is an convenient method to validate and get the user id.
func (ctx *Context) RequireUserId() int64 {
if ctx.UserId <= 0 {
requerstError := NewRequestError("Unauthorized")
requerstError.StatusCode = UnauthorizedStatusCode
panic(requerstError)
}
return ctx.UserId
}
//Unmarshal the request body into the interface.
//It only works when you set Config option `DisableAutoUnmarshal` to true.
func (ctx *Context) Unmarshal(in interface{}) error {
if !ctx.config.DisableAutoUnmarshal {
panic("Should only call it when 'DisableAutoUnmarshal' is set to true")
}
if ctx.ContentLength > 0 && strings.Contains(ctx.Header.Get("Content-Type"), "application/json") {
decoder := json.NewDecoder(ctx.Body)
decoder.UseNumber()
return decoder.Decode(in)
}
return NoJsonBody
}
//If set Config option `DisableAutoUnmarshal` to true, you should call this method first before you can get body parameters in Finder methods..
func (ctx *Context) UnmarshalInFinder() {
if ctx.value == nil && ctx.ContentLength > 0 && strings.Contains(ctx.Header.Get("Content-Type"), "application/json") {
var in interface{}
decoder := json.NewDecoder(ctx.Body)
decoder.UseNumber()
ctx.err = decoder.Decode(&in)
ctx.value = in
}
}
type ContextWriter struct {
Ctx *Context
}
func (cw ContextWriter) Write(p []byte) (n int, err error) {
return cw.Ctx.FlushData(p)
}