Generated HTTP Server and Client Code

Learn about the HTTP code generated by Goa, including server and client implementations, routing, and error handling.

The HTTP code generation creates a complete client and server implementation that handles all of the transport-level concerns, including request routing, data serialization, error handling, and more. This section covers the key components of the generated HTTP code.

HTTP Server Implementation

Goa generates a complete HTTP server implementations in gen/http/<name of service>/server/server.go that expose the service methods using HTTP paths and verbs described in the design. The server implementation handles all the networking details, including request routing, response encoding, and error handling:

// New instantiates HTTP handlers for all the calc service endpoints using the
// provided encoder and decoder. The handlers are mounted on the given mux
// using the HTTP verb and path defined in the design. errhandler is called
// whenever a response fails to be encoded. formatter is used to format errors
// returned by the service methods prior to encoding. Both errhandler and
// formatter are optional and can be nil.
func New(
    e *calc.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(ctx context.Context, err error) goahttp.Statuser,
) *Server

Instantiating the HTTP server requires providing the service endpoints, a muxer to route requests to the correct handler, and encoder and decoder functions to marshal and unmarshal data. The server also supports custom error handling and formatting (both of which are optional). See HTTP Services for additional information on HTTP encoding, error handling and formatting.

Goa also generates a helper function that can be used to mount the HTTP server on the muxer:

// Mount configures the mux to serve the calc endpoints.
func Mount(mux goahttp.Muxer, h *Server) {
    MountAddHandler(mux, h.Add)
    MountMultiplyHandler(mux, h.Multiply)
}

This automatically configures the muxer to route requests to the correct handler based on the HTTP verb and path defined in the design.

The Server struct also exposes fields that can be used to modify individual handlers or apply middleware to specific endpoints:

// Server lists the calc service endpoint HTTP handlers.
type Server struct {
    Mounts   []*MountPoint // Method names and associated HTTP verb and path
    Add      http.Handler  // HTTP handler for "add" service method
    Multiply http.Handler  // HTTP handler for "multiply" service method
}

The Mounts field contains metadata on all the mounted handlers useful for introspection while the Add and Multiply fields expose the individual handlers for each service method.

The MountPoint struct contains the method name, HTTP verb, and path for each handler. This metadata is useful for introspection and debugging.

Finally the Server struct also exposes a Use method that can be used to apply HTTP middleware to all the service methods:

// Use applies the given middleware to all the "calc" service HTTP handlers.
func (s *Server) Use(m func(http.Handler) http.Handler) {
    s.Add = m(s.Add)
    s.Multiply = m(s.Multiply)
}

For example, you can apply a logging middleware to all handlers:

// LoggingMiddleware is an example middleware that logs each request.
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Received request: %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

// Apply the middleware to the server
server.Use(LoggingMiddleware)

HTTP Paths

Goa generates functions that can return the HTTP paths for each service method given any required parameters. For example, the Add method has the following function generated:

// AddCalcPath returns the URL path to the calc service add HTTP endpoint.
func AddCalcPath(a int, b int) string {
	return fmt.Sprintf("/add/%v/%v", a, b)
}

Such functions can be used to generate URLs for the service methods.

Putting It All Together

The service interface and endpoint layers are the core building blocks of your Goa-generated service. Your main package will use these layers to create the transport-specific server and client implementations, allowing you to run your service and interact with it using HTTP:

package main

import (
    "net/http"

    goahttp "goa.design/goa/v3/http"

    "github.com/<your username>/calc"
    gencalc "github.com/<your username>/calc/gen/calc"
    genhttp "github.com/<your username>/calc/gen/http/calc/server"
)

func main() {
    svc := calc.New()                      // Your service implementation
    endpoints := gencalc.NewEndpoints(svc) // Create the service endpoints
    mux := goahttp.NewMuxer()              // Create the HTTP request multiplexer
    server := genhttp.New(                 // Create the HTTP server
        endpoints,
        mux,
        goahttp.RequestDecoder,
        goahttp.ResponseEncoder,
        nil, nil)              
    genhttp.Mount(mux, server)             // Mount the server
    http.ListenAndServe(":8080", mux)      // Start the HTTP server
}

HTTP Client Implementation

Goa also generates a complete HTTP client implementation that can be used to interact with the service. The client code is generated in the client package gen/http/<name of service>/client/client.go and provides methods that create Goa endpoints for each of the service methods. These endpoints can in turned be wrapped into transport-agnostic client implementations using the generated endpoint client code in gen/<name of service>/client.go:

The generated NewClient function in the HTTP client package creates an object that can be used to create transport-agnostic client endpoints:

// NewClient instantiates HTTP clients for all the calc service servers.
func NewClient(
	scheme string,
	host string,
	doer goahttp.Doer,
	enc func(*http.Request) goahttp.Encoder,
	dec func(*http.Response) goahttp.Decoder,
	restoreBody bool,
) *Client

The function requires the service scheme (http or https), host (e.g. example.com), and an HTTP client to make requests. The standard Go HTTP client satisfies the goahttp.Doer interface. NewClient also requires encoder and decoder functions to marshal and unmarshal data. The restoreBody flag indicates whether the response body should be restored in the underlying Go io.Reader object after decoding.

HTTP Client

The instantiated Client struct exposes fields for each of the service endpoints making it possible to override the HTTP client for specific endpoints:

// Client lists the calc service endpoint HTTP clients.
type Client struct {
	// AddDoer is the HTTP client used to make requests to the add
	// endpoint.
	AddDoer goahttp.Doer

	// MultiplyDoer is the HTTP client used to make requests to the multiply
	// endpoint.
	MultiplyDoer goahttp.Doer

	// RestoreResponseBody controls whether the response bodies are reset after
	// decoding so they can be read again.
	RestoreResponseBody bool

    // Private fields...
}

The struct also exposes methods that build transport-agnostic endpoints:

// Multiply returns an endpoint that makes HTTP requests to the calc service
// multiply server.
func (c *Client) Multiply() goa.Endpoint

Putting It All Together

As described in the ./4-client.md section, Goa generates transport-agnostic clients for each service. These clients are initialized with the appropriate endpoints and can be used to make requests to the service.

Here’s an example of how to create and use the HTTP client for the calc service:

package main

import (
    "context"
    "log"
    "net/http"

    goahttp "goa.design/goa/v3/http"
    
    gencalc "github.com/<your username>/calc/gen/calc"
    genclient "github.com/<your username>/calc/gen/http/calc/client"
)

func main() {
    // Create the HTTP client
    httpClient := genclient.NewClient(
        "http",                    // Scheme
        "localhost:8080",          // Host
        http.DefaultClient,        // HTTP client
        goahttp.RequestEncoder,    // Request encoder
        goahttp.ResponseDecoder,   // Response decoder
        false,                     // Don't restore response body
    )

    // Create the endpoint client
    client := gencalc.NewClient(
        httpClient.Add(),          // Add endpoint
        httpClient.Multiply(),     // Multiply endpoint
    )

    // Call the service methods
    result, err := client.Add(context.Background(), &gencalc.AddPayload{A: 1, B: 2})
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("1 + 2 = %d", result)
}

This example shows how to create an HTTP client, wrap it in an endpoint client, and make requests to the service. The HTTP client handles all the transport-level concerns while the endpoint client provides a clean interface for making service calls.

Conclusion

The generated HTTP client provides a convenient way to interact with the service using HTTP. By creating a client instance and calling the service methods, you can easily integrate the service into your application.