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(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 writes an error response using status code 400 and the ErrInvalidEncoding error to write the body (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.