Encoding
Overview
goa supports a flexible encoding and decoding strategy that makes it possible to associate arbitrary encoders and decoders with given response and request content types. By default all goa services can decode and encode JSON, XML and gob.
Decoding
The goa decoder looks at the incoming request Content-Type
header and matches it with a decoder.
By default application/json
is mapped to the JSON decoder, application/xml
to the XML decoder
and application/gob
to the gob decoder. The JSON decoder is also used when the Content-Type
header is missing or does not match one of the known values. If decoding fails goa writes an error
response using status code 400 using the
ErrInvalidEncoding
error to write the body (see Error Handling for more information on
how errors translate to HTTP responses).
Encoding
The goa encoder looks at the Accept
header of the incoming request and implements a simple content
negotiation algorithm to match it with the encoders available to the service. Similarly to the
decoder it supports JSON, XML and gob by default. It also defaults to JSON if there is no Accept
header or if its value does not match one of the known content types.
Using Custom Decoders
There are many reasons why your service may need to use different decoders. For example you may want
to switch from the stdlib JSON package to a custom package that may provide a performance
improvement for your use case. You may also need to support different serialization formats such as
msgpack
.
goa defines the interface that a decoder must implement to be mounted on a goa service. The interface is:
// A Decoder unmarshals an io.Reader into an interface.
Decoder interface {
Decode(v interface{}) error
}
A decoder may also implement the resettable decoder interface which adds a Reset
method:
// 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)
}
A decoder that implements ResettableDecoder
makes it possible for goa to maintain a pool of
reusable instances thereby significantly reducing the overhead of creating decoders for each
request.
goa also defines the function signature for a decoder constructor that it uses to create instances of the decoder:
// DecoderFunc instantiates a decoder that decodes data read from the given io reader.
DecoderFunc func(r io.Reader) Decoder
Packages that implement custom decoders must expose a NewDecoder
function (by default, see below)
that implements DecoderFunc
. The code generated by goa automatically calls that function to create
new decoder to decode incoming request payloads.
Setting Up Custom Decoders
Using a custom decoder is a simple two step process:
-
The design of the service must use the Consumes DSL function to list the content types and corresponding decoder packages and optionally function.
-
The package implementing the
DecoderFunc
function describes above must be imported by the service.
goagen app
takes care of generating the code that configures the service decoders (in the
controllers.go
file if you’re curious).
The Consumes
DSL allows overriding the name of the package function used to create decoders via
the Function
DSL:
Consumes("application/json", func() {
Package("github.com/myjsondecoder")
Function("CustomNewDecoder")
})
Setting the Default Decoder
The default decoder is the decoder used when the incoming request Content-Type
header is missing
or does not match any of the content types defined in the Consumes
DSL. It is the first decoder to
be listed in the Consumes
DSL. If no decoder is listed then the generated code defaults to the
stdlib JSON decoder.
Built-in Custom Decoders
goa comes with a few custom decoders (or to be more exact adapters to external decoders) that cover
binc
, cbor
, msgpack
and ugorji’s JSON
decoder. Taking msgpack
as an example, the design would look like:
var _ = API("My API", func() {
// ...
Consumes("application/msgpack", func() {
Package("github.com/goadesign/goa/encoding/msgpack")
})
// ...
})
And that’s all there is to do. The goa msgpack package exposes the NewDecoder function and the generated code takes care of configuring the goa decoder.
Using Custom Encoders
The mechanism for using custom encoders is very similar to using custom decoders. An encoder must
implement the Encoder
interface:
// An Encoder marshals from an interface into an io.Writer.
Encoder interface {
Encode(v interface{}) error
}
It may also optionally implement the ResettableEncoder
interface thereby enabling the use of sync
pools:
// 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)
}
The encoder package must implement a NewEncoder
function with the following signature:
// EncoderFunc instantiates an encoder that encodes data into the given writer.
EncoderFunc func(w io.Writer) Encoder
Setting Up Custom Encoders
The DSL function for specifying custom encoders is
Produces,
it supports the same syntax as Consumes
:
var _ = API("My API", func() {
// ...
Produces("application/msgpack", func() {
Package("github.com/goadesign/goa/encoding/msgpack")
})
// ...
})
As with decoders the first encoder listed in the DSL becomes the default encoder used when the
incoming request Accept
header is missing or does not match any of the content types listed in the
design. If no encoder is listed in the design then the generated code defaults to the stdlib JSON
encoder.