github.com/goldeneggg/goa@v1.3.1/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 return d.Decode(v) 130 } 131 132 // Register sets a specific decoder to be used for the specified content types. If a decoder is 133 // already registered, it is overwritten. 134 func (decoder *HTTPDecoder) Register(f DecoderFunc, contentTypes ...string) { 135 p := newDecodePool(f) 136 137 for _, contentType := range contentTypes { 138 mediaType, _, err := mime.ParseMediaType(contentType) 139 if err != nil { 140 mediaType = contentType 141 } 142 decoder.pools[mediaType] = p 143 } 144 } 145 146 // newDecodePool checks to see if the DecoderFunc returns reusable decoders and if so, creates a 147 // pool. 148 func newDecodePool(f DecoderFunc) *decoderPool { 149 // get a new decoder and type assert to see if it can be reset 150 d := f(nil) 151 rd, ok := d.(ResettableDecoder) 152 153 p := &decoderPool{fn: f} 154 155 // if the decoder can be reset, create a pool and put the typed decoder in 156 if ok { 157 p.pool = &sync.Pool{ 158 New: func() interface{} { return f(nil) }, 159 } 160 p.pool.Put(rd) 161 } 162 163 return p 164 } 165 166 // Get returns an already reset Decoder from the pool or creates a new one if necessary. 167 func (p *decoderPool) Get(r io.Reader) Decoder { 168 if p.pool == nil { 169 return p.fn(r) 170 } 171 172 d := p.pool.Get().(ResettableDecoder) 173 d.Reset(r) 174 return d 175 } 176 177 // Put returns a Decoder into the pool if possible. 178 func (p *decoderPool) Put(d Decoder) { 179 if p.pool == nil { 180 return 181 } 182 p.pool.Put(d) 183 } 184 185 // Encode uses the registered encoders and given content type to marshal and write the given value 186 // using the given writer. 187 func (encoder *HTTPEncoder) Encode(v interface{}, resp io.Writer, accept string) error { 188 now := time.Now() 189 if accept == "" { 190 accept = "*/*" 191 } 192 var contentType string 193 for _, t := range encoder.contentTypes { 194 if accept == "*/*" || accept == t { 195 contentType = accept 196 break 197 } 198 } 199 defer MeasureSince([]string{"goa", "encode", contentType}, now) 200 p := encoder.pools[contentType] 201 if p == nil && contentType != "*/*" { 202 p = encoder.pools["*/*"] 203 } 204 if p == nil { 205 return fmt.Errorf("No encoder registered for %s and no default encoder", contentType) 206 } 207 208 // the encoderPool will handle whether or not a pool is actually in use 209 e := p.Get(resp) 210 if err := e.Encode(v); err != nil { 211 return err 212 } 213 p.Put(e) 214 215 return nil 216 } 217 218 // Register sets a specific encoder to be used for the specified content types. If an encoder is 219 // already registered, it is overwritten. 220 func (encoder *HTTPEncoder) Register(f EncoderFunc, contentTypes ...string) { 221 p := newEncodePool(f) 222 for _, contentType := range contentTypes { 223 mediaType, _, err := mime.ParseMediaType(contentType) 224 if err != nil { 225 mediaType = contentType 226 } 227 encoder.pools[mediaType] = p 228 } 229 230 // Rebuild a unique index of registered content encoders to be used in EncodeResponse 231 encoder.contentTypes = make([]string, 0, len(encoder.pools)) 232 for contentType := range encoder.pools { 233 encoder.contentTypes = append(encoder.contentTypes, contentType) 234 } 235 } 236 237 // newEncodePool checks to see if the EncoderFactory returns reusable encoders and if so, creates 238 // a pool. 239 func newEncodePool(f EncoderFunc) *encoderPool { 240 // get a new encoder and type assert to see if it can be reset 241 e := f(nil) 242 re, ok := e.(ResettableEncoder) 243 244 p := &encoderPool{fn: f} 245 246 // if the encoder can be reset, create a pool and put the typed encoder in 247 if ok { 248 p.pool = &sync.Pool{ 249 New: func() interface{} { return f(nil) }, 250 } 251 p.pool.Put(re) 252 } 253 254 return p 255 } 256 257 // Get returns an already reset Encoder from the pool or creates a new one if necessary. 258 func (p *encoderPool) Get(w io.Writer) Encoder { 259 if p.pool == nil { 260 return p.fn(w) 261 } 262 263 e := p.pool.Get().(ResettableEncoder) 264 e.Reset(w) 265 return e 266 } 267 268 // Put returns a Decoder into the pool if possible. 269 func (p *encoderPool) Put(e Encoder) { 270 if p.pool == nil { 271 return 272 } 273 p.pool.Put(e) 274 }