forked from goadesign/goa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathencoding.go
344 lines (286 loc) · 8.64 KB
/
encoding.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
344
package goa
import (
"encoding/gob"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"mime"
"net/http"
"sync"
"time"
"golang.org/x/net/context"
)
type (
// A DecoderFactory generates custom decoders
DecoderFactory interface {
NewDecoder(r io.Reader) Decoder
}
// A Decoder unmarshals an io.Reader into an interface
Decoder interface {
Decode(v interface{}) error
}
// The ResettableDecoder is used to determine whether or not a Decoder can be reset and
// thus safely reused in a sync.Pool
ResettableDecoder interface {
Decoder
Reset(r io.Reader)
}
// decoderPool smartly determines whether to instantiate a new Decoder or reuse
// one from a sync.Pool
decoderPool struct {
factory DecoderFactory
pool *sync.Pool
}
// A EncoderFactory generates custom encoders
EncoderFactory interface {
NewEncoder(w io.Writer) Encoder
}
// An Encoder marshals from an interface into an io.Writer
Encoder interface {
Encode(v interface{}) error
}
// The ResettableEncoder is used to determine whether or not a Encoder can be reset and
// thus safely reused in a sync.Pool
ResettableEncoder interface {
Encoder
Reset(w io.Writer)
}
// encoderPool smartly determines whether to instantiate a new Encoder or reuse
// one from a sync.Pool
encoderPool struct {
factory EncoderFactory
pool *sync.Pool
}
// jsonFactory uses encoding/json to act as an DecoderFactory and EncoderFactory
jsonFactory struct{}
// xmlFactory uses encoding/xml to act as an DecoderFactory and EncoderFactory
xmlFactory struct{}
// gobFactory uses encoding/gob to act as an DecoderFactory and EncoderFactory
gobFactory struct{}
)
// DecodeRequest retrives the request body and `Content-Type` header and uses Decode
// to unmarshal into the provided `interface{}`
func (ver *ServiceVersion) DecodeRequest(req *http.Request, v interface{}) error {
body, contentType := req.Body, req.Header.Get("Content-Type")
defer body.Close()
if err := ver.Decode(v, body, contentType); err != nil {
return fmt.Errorf("failed to decode request body with content type %#v: %s", contentType, err)
}
return nil
}
// Decode uses registered Decoders to unmarshal a body based on the contentType
func (ver *ServiceVersion) Decode(v interface{}, body io.Reader, contentType string) error {
now := time.Now()
defer MeasureSince([]string{"goa", "decode", contentType}, now)
var p *decoderPool
if contentType == "" {
// Default to JSON
contentType = "application/json"
} else {
if mediaType, _, err := mime.ParseMediaType(contentType); err == nil {
contentType = mediaType
}
}
p = ver.decoderPools[contentType]
if p == nil {
p = ver.decoderPools["*/*"]
}
if p == nil {
return nil
}
// the decoderPool will handle whether or not a pool is actually in use
decoder := p.Get(body)
defer p.Put(decoder)
if err := decoder.Decode(v); err != nil {
return err
}
return nil
}
// SetDecoder sets a specific decoder to be used for the specified content types. If
// a decoder is already registered, it will be overwritten.
func (ver *ServiceVersion) SetDecoder(f DecoderFactory, makeDefault bool, contentTypes ...string) {
p := newDecodePool(f)
for _, contentType := range contentTypes {
mediaType, _, err := mime.ParseMediaType(contentType)
if err != nil {
mediaType = contentType
}
ver.decoderPools[mediaType] = p
}
if makeDefault {
ver.decoderPools["*/*"] = p
}
}
// newDecodePool checks to see if the DecoderFactory returns reusable decoders
// and if so, creates a pool
func newDecodePool(f DecoderFactory) *decoderPool {
// get a new decoder and type assert to see if it can be reset
decoder := f.NewDecoder(nil)
rd, ok := decoder.(ResettableDecoder)
p := &decoderPool{
factory: f,
}
// if the decoder can be reset, create a pool and put the typed decoder in
if ok {
p.pool = &sync.Pool{
New: func() interface{} { return f.NewDecoder(nil) },
}
p.pool.Put(rd)
}
return p
}
// Get returns an already reset Decoder from the pool if possible
// or creates a new one if necessary
func (p *decoderPool) Get(r io.Reader) Decoder {
if p.pool == nil {
return p.factory.NewDecoder(r)
}
decoder := p.pool.Get().(ResettableDecoder)
decoder.Reset(r)
return decoder
}
// Put returns a Decoder into the pool if possible
func (p *decoderPool) Put(d Decoder) {
if p.pool == nil {
return
}
p.pool.Put(d)
}
// EncodeResponse uses registered Encoders to marshal the response body based on the request
// `Accept` header and writes it to the http.ResponseWriter
func (ver *ServiceVersion) EncodeResponse(ctx context.Context, v interface{}) error {
now := time.Now()
accept := Request(ctx).Header.Get("Accept")
if accept == "" {
accept = "*/*"
}
var contentType string
for _, t := range ver.encodableContentTypes {
if accept == "*/*" || accept == t {
contentType = accept
break
}
}
defer MeasureSince([]string{"goa", "encode", contentType}, now)
p := ver.encoderPools[contentType]
if p == nil && contentType != "*/*" {
p = ver.encoderPools["*/*"]
}
if p == nil {
return fmt.Errorf("No encoder registered for %s and no default encoder", contentType)
}
// the encoderPool will handle whether or not a pool is actually in use
encoder := p.Get(Response(ctx))
if err := encoder.Encode(v); err != nil {
return err
}
p.Put(encoder)
return nil
}
// SetEncoder sets a specific encoder to be used for the specified content types. If
// an encoder is already registered, it will be overwritten.
func (ver *ServiceVersion) SetEncoder(f EncoderFactory, makeDefault bool, contentTypes ...string) {
p := newEncodePool(f)
for _, contentType := range contentTypes {
mediaType, _, err := mime.ParseMediaType(contentType)
if err != nil {
mediaType = contentType
}
ver.encoderPools[mediaType] = p
}
if makeDefault {
ver.encoderPools["*/*"] = p
}
// Rebuild a unique index of registered content encoders to be used in EncodeResponse
ver.encodableContentTypes = make([]string, 0, len(ver.encoderPools))
for contentType := range ver.encoderPools {
ver.encodableContentTypes = append(ver.encodableContentTypes, contentType)
}
}
// newEncodePool checks to see if the EncoderFactory returns reusable encoders
// and if so, creates a pool
func newEncodePool(f EncoderFactory) *encoderPool {
// get a new encoder and type assert to see if it can be reset
encoder := f.NewEncoder(nil)
re, ok := encoder.(ResettableEncoder)
p := &encoderPool{
factory: f,
}
// if the encoder can be reset, create a pool and put the typed encoder in
if ok {
p.pool = &sync.Pool{
New: func() interface{} { return f.NewEncoder(nil) },
}
p.pool.Put(re)
}
return p
}
// Get returns an already reset Encoder from the pool if possible
// or creates a new one if necessary
func (p *encoderPool) Get(w io.Writer) Encoder {
if p.pool == nil {
return p.factory.NewEncoder(w)
}
encoder := p.pool.Get().(ResettableEncoder)
encoder.Reset(w)
return encoder
}
// Put returns a Decoder into the pool if possible
func (p *encoderPool) Put(e Encoder) {
if p.pool == nil {
return
}
p.pool.Put(e)
}
// encoding/json default encoder/decoder
// JSONDecoderFactory returns a struct that can generate new json.Decoders
func JSONDecoderFactory() DecoderFactory {
return &jsonFactory{}
}
// NewDecoder returns a new json.Decoder
func (f *jsonFactory) NewDecoder(r io.Reader) Decoder {
return json.NewDecoder(r)
}
// JSONEncoderFactory returns a struct that can generate new json.Encoders
func JSONEncoderFactory() EncoderFactory {
return &jsonFactory{}
}
// NewEncoder returns a new json.Encoder
func (f *jsonFactory) NewEncoder(w io.Writer) Encoder {
return json.NewEncoder(w)
}
// encoding/xml default encoder/decoder
// XMLDecoderFactory returns a struct that can generate new xml.Decoders
func XMLDecoderFactory() DecoderFactory {
return &xmlFactory{}
}
// NewDecoder returns a new xml.Decoder
func (f *xmlFactory) NewDecoder(r io.Reader) Decoder {
return xml.NewDecoder(r)
}
// XMLEncoderFactory returns a struct that can generate new xml.Encoders
func XMLEncoderFactory() EncoderFactory {
return &xmlFactory{}
}
// NewEncoder returns a new xml.Encoder
func (f *xmlFactory) NewEncoder(w io.Writer) Encoder {
return xml.NewEncoder(w)
}
// encoding/gob default encoder/decoder
// GobDecoderFactory returns a struct that can generate new gob.Decoders
func GobDecoderFactory() DecoderFactory {
return &gobFactory{}
}
// NewDecoder returns a new gob.Decoder
func (f *gobFactory) NewDecoder(r io.Reader) Decoder {
return gob.NewDecoder(r)
}
// GobEncoderFactory returns a struct that can generate new gob.Encoders
func GobEncoderFactory() EncoderFactory {
return &gobFactory{}
}
// NewEncoder returns a new gob.Encoder
func (f *gobFactory) NewEncoder(w io.Writer) Encoder {
return gob.NewEncoder(w)
}