HTTP Encoding
Overview
Goa supports a flexible encoding and decoding strategy that makes it possible to associate arbitrary encoders and decoders with given HTTP response and request content types. An encoder is a struct that implements the Encoder interface while a decoder implements the Decoder interface.
The generated server constructors accept an encoder and a decoder constructor functions as argument making it possible to provide arbitrary implementations. Goa comes with default encoders and decoders that support JSON, XML and gob. Here is the signature of the server constructor generated by Goa for the basic example:
// New instantiates HTTP handlers for all the calc service endpoints.
func New(
e *divider.Endpoints,
mux goahttp.Muxer,
decoder func(*http.Request) goahttp.Decoder,
encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
errhandler func(context.Context, http.ResponseWriter, error),
formatter func(context.Context, err error) goahttp.Statuser
) *Server
The argument decoder
is a function that accepts a request and returns a decoder. Goa invokes this
function for each request making it possible to provide different decoders for different HTTP
requests. The argument encoder
is a function that accepts a context and a HTTP response writer and
returns an encoder.
Default Encoder and Decoder Constructors
The Goa package provides constructors for a default HTTP encoder and decoder that can encode and decode JSON, XML and gob. Here is how the default example generator leverages these constructors in the calc example:
var (
dec = goahttp.RequestDecoder
enc = goahttp.ResponseEncoder
)
//...
var (
calcServer *calcsvr.Server
)
{
// ...
calcServer = calcsvr.New(calcEndpoints, mux, dec, enc, eh)
}
Decoding
The default request decoder looks at the incoming request Content-Type
and matches it
with a decoder. The value 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
calls the error handler registered when creating the HTTP server (see
Error Handling for more information on how errors translate to HTTP
responses).
Writing a Custom Decoder
As indicated in the Overview writing a decoder consists of writing an implementation
for the Decoder
interface and for the decoder constructor function with the following signature:
func(r *http.Request) (goahttp.Decoder, error)
where goahttp
is an alias for the package with path goa.design/goa/v3/http
. The constructor
function has access to the request object and can thus inspect its state to infer the best possible
decoder. The function is given to the generated server constructor as shown in the overview.
Encoding
The default response encoder implements a simple content negotiation algorithm leveraging the value
of the Accept
header of the incoming request or - when missing - of the Content-Type
header.
The algorithm sanitizes the value of the header and compares it with the mime types for JSON, XML
and gob to infer the proper encoder. The algorithm defaults to JSON if there is no Accept
or
Content-Type
header or if the values do not match one of the known content types.
Writing a Custom Encoder
There are many reasons why your service may need to use different encoders. 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
. As indicated in the overview an encoder must implement the Encoder
interface and can
be provided to the generated code via a constructor function with the following signature:
func(ctx context.Context, w http.ResponseWriter) (goahttp.Encoder, error)
The context given when Goa invokes the constructor function contains both the request Content-Type
and Accept
header values under the
ContentTypeKey and AcceptTypeKey
respectively. This makes it possible for the encoder constructor to implement a form of content type
negotiation that looks at the values of these headers and return an encoder that is best suited for
the client.
Setting a Default Content Type
The Response design DSL makes it possible to
specify a content type using ContentType. When
set the value overrides any content type specified in the request headers. Note that this does NOT
override any value specified in the Accept
header. This provides a way to control the content type
used by the response encoder constructor when the Accept
header is missing.