This repository has been archived by the owner on Sep 10, 2019. It is now read-only.
forked from phonkee/goexpose
-
Notifications
You must be signed in to change notification settings - Fork 1
/
response.go
288 lines (237 loc) · 5.02 KB
/
response.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
package goexpose
/*
Response module
small helper to make writing json responses easier with along with logging of requests.
Usage:
in all examples r is *http.Request and w is http.ResponseWriter
NewResponse(http.StatusNotFound).Write(w, r)
writes following json response along with log message:
{
"status": 404,
"message": "Not found"
}
Following example adds also time to response log, and also result.
Result will be marshalled json
t := time.Now()
NewResponse(http.StatusOK).Response(map[string]interface{}{}).Write(w, r, t)
yields to
{
"status": 200,
"message": "OK",
"result": {}
}
In case we want to add another key in top level structure we can do following
NewResponse(http.StatusOK).AddValue("size", 1).Write(w, r)
yields to:
{
"status": 200,
"message": "OK",
"size": 1
}
We have also shorthand method for error
NewResponse(http.StatusInternalServerError).Error("error").Write(w, r)
yields to:
{
"status": 500,
"message": "Internal Server Error",
"error": "error"
}
For responses that don't need status/message in json response, you need to call
StripStatusData method. This is useful for embedded Responses inside Responses.
So e.g.:
NewResponse(http.StatusOK).StripStatusData()
will yield to:
{}
*/
import (
"encoding/json"
"fmt"
"net/http"
"time"
"encoding/base64"
"github.com/golang/glog"
)
/*
Creates new json response
*/
func NewResponse(status int) (response *Response) {
return NewResponseWithContentType(status, "application/json")
}
func NewResponseWithContentType(status int, contentType string) (response *Response) {
response = &Response{
data: map[string]interface{}{},
}
response.Status(status)
response.contentType = contentType
return
}
/*
Response
*/
type Response struct {
status int
pretty bool
contentType string
// json data
data map[string]interface{}
// raw data
raw *[]byte
}
/*
Set status
*/
func (r *Response) Status(status int) *Response {
r.status = status
return r.AddValue("status", status).AddValue("message", http.StatusText(r.status))
}
/*
GetStatus returns status
*/
func (r *Response) GetStatus() int {
return r.status
}
/*
Sets pretty printing of json
*/
func (r *Response) Pretty(pretty bool) *Response {
r.pretty = pretty
return r
}
/*
Result method adds result, it's just a shorthand to AddValue("result", result)
*/
func (r *Response) Result(result interface{}) *Response {
return r.AddValue("result", result)
}
/*
Set raw response
param can be following:
nil => clear raw
raw is fmt.Stringer or string => convert to []byte
[]byte leave as it is
otherwise try to marshal to json []byte
*/
func (r *Response) Raw(raw interface{}) *Response {
if raw == nil {
r.raw = nil
return r
}
switch t := raw.(type) {
case fmt.Stringer:
resp := []byte(t.String())
r.raw = &resp
case string:
resp := []byte(t)
r.raw = &resp
case []byte:
r.raw = &t
default:
var (
body []byte
err error
)
if body, err = json.Marshal(raw); err == nil {
r.raw = &body
} else {
r.Error(err)
}
}
return r
}
/*
Error method adds error, it's just a shorthand to AddValue("error", err)
@TODO: store just string from error
*/
func (r *Response) Error(err interface{}) *Response {
return r.AddValue("error", err)
}
/*
Adds value
*/
func (r *Response) AddValue(key string, value interface{}) *Response {
r.data[key] = value
return r
}
/*
Removes value
*/
func (r *Response) DelValue(key string) *Response {
delete(r.data, key)
return r
}
/*
Whether response has value
*/
func (r *Response) HasValue(key string) bool {
_, ok := r.data[key]
return ok
}
/*
Writes response to response writer and logs request
*/
func (r *Response) Write(w http.ResponseWriter, req *http.Request, start ...time.Time) (err error) {
var (
body []byte
)
// add headers
w.Header().Add("Content-Type", r.contentType)
w.WriteHeader(r.status)
// write body
if r.raw != nil {
w.Write(*r.raw)
} else {
if body, err = json.Marshal(r); err != nil {
return
}
w.Write(body)
}
var (
format string
args []interface{}
)
// log request
if len(start) > 0 {
format = "%s %s %d %v"
args = []interface{}{req.Method, req.URL.Path, r.status, time.Now().Sub(start[0])}
} else {
format = "%s %s %d"
args = []interface{}{req.Method, req.URL.Path, r.status}
}
glog.V(1).Infof(format, args...)
return
}
/*
Marshaler interface
support json marshalling
*/
func (r *Response) MarshalJSON() (result []byte, err error) {
if r.raw != nil {
buf := make([]byte, base64.StdEncoding.EncodedLen(len(*r.raw)))
base64.StdEncoding.Encode(buf, *r.raw)
return buf, nil
}
if r.pretty {
result, err = json.MarshalIndent(r.data, "", " ")
} else {
result, err = json.Marshal(r.data)
}
return
}
/*
Strips status/message from data
*/
func (r *Response) StripStatusData() *Response {
delete(r.data, "status")
if message, ok := r.data["message"]; ok {
if message == http.StatusText(r.status) {
delete(r.data, "status")
}
}
return r
}
/*
Updates stripped status data
*/
func (r *Response) UpdateStatusData() *Response {
return r.Status(r.status)
}