github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/encoding.go (about) 1 package goa 2 3 import ( 4 "encoding/gob" 5 "encoding/json" 6 "encoding/xml" 7 "fmt" 8 "io" 9 "mime" 10 "sync" 11 "time" 12 ) 13 14 type ( 15 // DecoderFunc instantiates a decoder that decodes data read from the given io reader. 16 DecoderFunc func(r io.Reader) Decoder 17 18 // A Decoder unmarshals an io.Reader into an interface. 19 Decoder interface { 20 Decode(v interface{}) error 21 } 22 23 // ResettableDecoder is used to determine whether or not a Decoder can be reset and thus 24 // safely reused in a sync.Pool. 25 ResettableDecoder interface { 26 Decoder 27 Reset(r io.Reader) 28 } 29 30 // decoderPool smartly determines whether to instantiate a new Decoder or reuse one from a 31 // sync.Pool. 32 decoderPool struct { 33 fn DecoderFunc 34 pool *sync.Pool 35 } 36 37 // EncoderFunc instantiates an encoder that encodes data into the given writer. 38 EncoderFunc func(w io.Writer) Encoder 39 40 // An Encoder marshals from an interface into an io.Writer. 41 Encoder interface { 42 Encode(v interface{}) error 43 } 44 45 // The ResettableEncoder is used to determine whether or not a Encoder can be reset and 46 // thus safely reused in a sync.Pool. 47 ResettableEncoder interface { 48 Encoder 49 Reset(w io.Writer) 50 } 51 52 // encoderPool smartly determines whether to instantiate a new Encoder or reuse one from a 53 // sync.Pool. 54 encoderPool struct { 55 fn EncoderFunc 56 pool *sync.Pool 57 } 58 59 // HTTPDecoder is a Decoder that decodes HTTP request or response bodies given a set of 60 // known Content-Type to decoder mapping. 61 HTTPDecoder struct { 62 pools map[string]*decoderPool // Registered decoders 63 } 64 65 // HTTPEncoder is a Encoder that encodes HTTP request or response bodies given a set of 66 // known Content-Type to encoder mapping. 67 HTTPEncoder struct { 68 pools map[string]*encoderPool // Registered encoders 69 contentTypes []string // List of content types for type negotiation 70 } 71 ) 72 73 // NewJSONEncoder is an adapter for the encoding package JSON encoder. 74 func NewJSONEncoder(w io.Writer) Encoder { return json.NewEncoder(w) } 75 76 // NewJSONDecoder is an adapter for the encoding package JSON decoder. 77 func NewJSONDecoder(r io.Reader) Decoder { return json.NewDecoder(r) } 78 79 // NewXMLEncoder is an adapter for the encoding package XML encoder. 80 func NewXMLEncoder(w io.Writer) Encoder { return xml.NewEncoder(w) } 81 82 // NewXMLDecoder is an adapter for the encoding package XML decoder. 83 func NewXMLDecoder(r io.Reader) Decoder { return xml.NewDecoder(r) } 84 85 // NewGobEncoder is an adapter for the encoding package gob encoder. 86 func NewGobEncoder(w io.Writer) Encoder { return gob.NewEncoder(w) } 87 88 // NewGobDecoder is an adapter for the encoding package gob decoder. 89 func NewGobDecoder(r io.Reader) Decoder { return gob.NewDecoder(r) } 90 91 // NewHTTPEncoder creates an encoder that maps HTTP content types to low level encoders. 92 func NewHTTPEncoder() *HTTPEncoder { 93 return &HTTPEncoder{ 94 pools: make(map[string]*encoderPool), 95 } 96 } 97 98 // NewHTTPDecoder creates a decoder that maps HTTP content types to low level decoders. 99 func NewHTTPDecoder() *HTTPDecoder { 100 return &HTTPDecoder{ 101 pools: make(map[string]*decoderPool), 102 } 103 } 104 105 // Decode uses registered Decoders to unmarshal a body based on the contentType. 106 func (decoder *HTTPDecoder) Decode(v interface{}, body io.Reader, contentType string) error { 107 now := time.Now() 108 defer MeasureSince([]string{"goa", "decode", contentType}, now) 109 var p *decoderPool 110 if contentType == "" { 111 // Default to JSON 112 contentType = "application/json" 113 } else { 114 if mediaType, _, err := mime.ParseMediaType(contentType); err == nil { 115 contentType = mediaType 116 } 117 } 118 p = decoder.pools[contentType] 119 if p == nil { 120 p = decoder.pools["*/*"] 121 } 122 if p == nil { 123 return nil 124 } 125 126 // the decoderPool will handle whether or not a pool is actually in use 127 d := p.Get(body) 128 defer p.Put(d) 129 if err := d.Decode(v); err != nil { 130 return err 131 } 132 133 return nil 134 } 135 136 // Register sets a specific decoder to be used for the specified content types. If a decoder is 137 // already registered, it is overwritten. 138 func (decoder *HTTPDecoder) Register(f DecoderFunc, contentTypes ...string) { 139 p := newDecodePool(f) 140 141 for _, contentType := range contentTypes { 142 mediaType, _, err := mime.ParseMediaType(contentType) 143 if err != nil { 144 mediaType = contentType 145 } 146 decoder.pools[mediaType] = p 147 } 148 } 149 150 // newDecodePool checks to see if the DecoderFunc returns reusable decoders and if so, creates a 151 // pool. 152 func newDecodePool(f DecoderFunc) *decoderPool { 153 // get a new decoder and type assert to see if it can be reset 154 d := f(nil) 155 rd, ok := d.(ResettableDecoder) 156 157 p := &decoderPool{fn: f} 158 159 // if the decoder can be reset, create a pool and put the typed decoder in 160 if ok { 161 p.pool = &sync.Pool{ 162 New: func() interface{} { return f(nil) }, 163 } 164 p.pool.Put(rd) 165 } 166 167 return p 168 } 169 170 // Get returns an already reset Decoder from the pool or creates a new one if necessary. 171 func (p *decoderPool) Get(r io.Reader) Decoder { 172 if p.pool == nil { 173 return p.fn(r) 174 } 175 176 d := p.pool.Get().(ResettableDecoder) 177 d.Reset(r) 178 return d 179 } 180 181 // Put returns a Decoder into the pool if possible. 182 func (p *decoderPool) Put(d Decoder) { 183 if p.pool == nil { 184 return 185 } 186 p.pool.Put(d) 187 } 188 189 // Encode uses the registered encoders and given content type to marshal and write the given value 190 // using the given writer. 191 func (encoder *HTTPEncoder) Encode(v interface{}, resp io.Writer, accept string) error { 192 now := time.Now() 193 if accept == "" { 194 accept = "*/*" 195 } 196 var contentType string 197 for _, t := range encoder.contentTypes { 198 if accept == "*/*" || accept == t { 199 contentType = accept 200 break 201 } 202 } 203 defer MeasureSince([]string{"goa", "encode", contentType}, now) 204 p := encoder.pools[contentType] 205 if p == nil && contentType != "*/*" { 206 p = encoder.pools["*/*"] 207 } 208 if p == nil { 209 return fmt.Errorf("No encoder registered for %s and no default encoder", contentType) 210 } 211 212 // the encoderPool will handle whether or not a pool is actually in use 213 e := p.Get(resp) 214 if err := e.Encode(v); err != nil { 215 return err 216 } 217 p.Put(e) 218 219 return nil 220 } 221 222 // Register sets a specific encoder to be used for the specified content types. If an encoder is 223 // already registered, it is overwritten. 224 func (encoder *HTTPEncoder) Register(f EncoderFunc, contentTypes ...string) { 225 p := newEncodePool(f) 226 for _, contentType := range contentTypes { 227 mediaType, _, err := mime.ParseMediaType(contentType) 228 if err != nil { 229 mediaType = contentType 230 } 231 encoder.pools[mediaType] = p 232 } 233 234 // Rebuild a unique index of registered content encoders to be used in EncodeResponse 235 encoder.contentTypes = make([]string, 0, len(encoder.pools)) 236 for contentType := range encoder.pools { 237 encoder.contentTypes = append(encoder.contentTypes, contentType) 238 } 239 } 240 241 // newEncodePool checks to see if the EncoderFactory returns reusable encoders and if so, creates 242 // a pool. 243 func newEncodePool(f EncoderFunc) *encoderPool { 244 // get a new encoder and type assert to see if it can be reset 245 e := f(nil) 246 re, ok := e.(ResettableEncoder) 247 248 p := &encoderPool{fn: f} 249 250 // if the encoder can be reset, create a pool and put the typed encoder in 251 if ok { 252 p.pool = &sync.Pool{ 253 New: func() interface{} { return f(nil) }, 254 } 255 p.pool.Put(re) 256 } 257 258 return p 259 } 260 261 // Get returns an already reset Encoder from the pool or creates a new one if necessary. 262 func (p *encoderPool) Get(w io.Writer) Encoder { 263 if p.pool == nil { 264 return p.fn(w) 265 } 266 267 e := p.pool.Get().(ResettableEncoder) 268 e.Reset(w) 269 return e 270 } 271 272 // Put returns a Decoder into the pool if possible. 273 func (p *encoderPool) Put(e Encoder) { 274 if p.pool == nil { 275 return 276 } 277 p.pool.Put(e) 278 }