Help us shape the future of Go! Take the 2026 Go Developer Survey

httpserver

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package httpserver builds an HTTP server out of a route list, a middleware chain and an error handler.

A server is assembled with functional options and started with Run, which returns the function that shuts it down:

server := httpserver.New(
	httpserver.WithPort("8080"),
	httpserver.WithRoutes(httpserver.NewGet("/orders/{id}", handleOrder)),
	httpserver.WithMiddlewares(httpserver.RequestID, httpserver.Compress()),
)

shutdown := server.Run()
defer shutdown(context.Background())

Run does not block. It hands the listener error to the channel returned by ShutdownListener, so a caller that wants to react to a failed bind reads from there.

Handlers return an error instead of writing the failure themselves:

func handleOrder(w http.ResponseWriter, r *http.Request) error { ... }

A non-nil return goes to the ErrorHandler installed with WithErrorHandler. The default writes 500 and nothing else, so production servers should supply their own.

Routing is chi underneath. GetParam reads path parameters, and the router is instrumented with Datadog tracing regardless of the WithAPM option - that option only controls whether the tracer itself is started.

Every route is wrapped in request logging that emits an entry and two Datadog metrics (request rate and response time) tagged with method, path and status. The logger it uses comes from the context, so the Logger middleware has to run before the metrics are worth reading.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetParam

func GetParam(r *http.Request, key string) string

GetParam reads a path parameter, such as the "id" in "/orders/{id}".

It reads from the chi route context, which only exists once the router has matched the request. In a test that calls a handler directly, populate it with httpservertest.SetURLParam.

func Middlewares

func Middlewares(main http.Handler, middlewares ...Middleware) http.Handler

Middlewares wraps main in the given middlewares so that the first one listed is the outermost, and therefore runs first.

func NewLogRequest

func NewLogRequest(route, method string, main http.Handler) http.Handler

NewLogRequest wraps main so each request emits a log entry with its duration plus the request-rate and response-time metrics, tagged with method, route and status. The server applies it to every route; call it directly only when composing a handler by hand.

func RequestID

func RequestID(next http.Handler) http.Handler

RequestID copies an inbound X-Request-ID header into the log fields.

It only propagates an id the client supplied; it does not generate one. Use TraceID for an identifier that always exists.

Types

type ErrorHandler

type ErrorHandler func(ctx context.Context, w http.ResponseWriter, err error)

ErrorHandler turns an error returned by a Handler into a response. Install one with WithErrorHandler; the default writes 500 and logs nothing.

type Handler

type Handler func(w http.ResponseWriter, req *http.Request) error

Handler is an http.HandlerFunc that may return an error instead of writing the failure response itself. A non-nil return is passed to the ErrorHandler.

type Middleware

type Middleware func(handler http.Handler) http.Handler

Middleware wraps an http.Handler.

func AllowAll

func AllowAll() Middleware

AllowAll answers CORS preflight requests permissively, accepting every origin, method and header. There is no configurable variant, so this is unlikely to be what a public deployment wants.

func Compress

func Compress() Middleware

Compress enables gzip compression of responses at level 5.

func ContentTypeJSON

func ContentTypeJSON() Middleware

ContentTypeJSON sets the Content-Type response header to application/json.

func Logger

func Logger(logger log.Logger) Middleware

Logger puts logger in the request context so handlers and the request log pick it up with log.FromContext. Without it, each lookup falls back to a logger built with the package defaults.

func TraceID

func TraceID(idGenerator gen.StringGenerator) Middleware

TraceID generates an identifier per request and adds it to the log fields, so every entry from one request can be correlated.

type Option

type Option func(s settings) settings

func WithAPM

func WithAPM(enabled bool) Option

WithAPM controls whether Run starts the Datadog tracer.

It does not control instrumentation: the chi router is traced either way, so disabling this only means no tracer is running to collect the spans.

func WithErrorHandler

func WithErrorHandler(handler ErrorHandler) Option

WithErrorHandler installs the handler invoked when a Handler returns an error. Without it, errors become a bare 500 and are not logged.

Example

A handler reports failure by returning an error; the ErrorHandler decides what the client sees. The default writes 500 and nothing else, so anything that needs a body or a log has to install its own.

package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/diegodesousas/go-devkit/pkg/httpserver"
	"github.com/pkg/errors"
)

func main() {
	errNotFound := errors.New("order not found")

	server := httpserver.New(
		httpserver.WithErrorHandler(func(ctx context.Context, w http.ResponseWriter, err error) {
			if errors.Is(err, errNotFound) {
				w.WriteHeader(http.StatusNotFound)
				_, _ = fmt.Fprint(w, `{"error":"not found"}`)
				return
			}
			w.WriteHeader(http.StatusInternalServerError)
		}),
		httpserver.WithRoutes(
			httpserver.NewGet("/orders/{id}", func(w http.ResponseWriter, r *http.Request) error {
				return errors.WithStack(errNotFound)
			}),
		),
	)

	rec := httptest.NewRecorder()
	server.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/orders/42", nil))

	fmt.Println(rec.Code)
	fmt.Println(rec.Body.String())

}
Output:
404
{"error":"not found"}

func WithHTTPServerReadTimeout

func WithHTTPServerReadTimeout(duration time.Duration) Option

WithHTTPServerReadTimeout sets http.Server.ReadTimeout.

It is the only server timeout exposed today; WriteTimeout, IdleTimeout and ReadHeaderTimeout stay at zero, meaning no limit.

func WithMiddlewares

func WithMiddlewares(middlewares ...Middleware) Option

WithMiddlewares appends middlewares that run for every route, in the order given. It accumulates across calls.

func WithName

func WithName(name string) Option

WithName sets the service name reported to Datadog. Defaults to "go-devkit-httpserver".

func WithPort

func WithPort(port string) Option

WithPort sets the TCP port to listen on, as a string. Defaults to "80".

func WithRoutes

func WithRoutes(routes ...Route) Option

WithRoutes appends routes. It accumulates, so several calls add up.

type Route

type Route struct {
	Path        string
	Method      string
	Handler     Handler
	Middlewares []Middleware
	Timeout     time.Duration
}

Route binds a handler to a method and path, with optional per-route middlewares.

Build one with NewGet, NewPost, NewPut or NewDelete. Timeout is honoured by the server but is not set by those constructors, so a route that needs one has to be built as a literal.

func NewDelete

func NewDelete(path string, handler Handler, middlewares ...Middleware) Route

NewDelete builds a DELETE route.

func NewGet

func NewGet(path string, handler Handler, middlewares ...Middleware) Route

NewGet builds a GET route.

func NewPost

func NewPost(path string, handler Handler, middlewares ...Middleware) Route

NewPost builds a POST route.

func NewPut

func NewPut(path string, handler Handler, middlewares ...Middleware) Route

NewPut builds a PUT route.

type Server

type Server interface {
	Run() Shutdown
	ShutdownListener() chan error
	ServeHTTP(http.ResponseWriter, *http.Request)
}

Server is the assembled HTTP server.

It also implements http.Handler, which is what makes it usable with httptest.NewServer in tests.

func New

func New(configs ...Option) Server

New assembles a Server from the given options.

It builds the router, installs the global middlewares in the order supplied and wraps every route in request logging and metrics. It does not bind the port - Run does.

Example

A Server is also an http.Handler, so a whole configuration can be exercised with httptest instead of binding a port.

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/diegodesousas/go-devkit/pkg/httpserver"
)

func main() {
	server := httpserver.New(
		httpserver.WithPort("8080"),
		httpserver.WithRoutes(
			httpserver.NewGet("/orders/{id}", func(w http.ResponseWriter, r *http.Request) error {
				_, err := fmt.Fprintf(w, "order %s", httpserver.GetParam(r, "id"))
				return err
			}),
		),
	)

	rec := httptest.NewRecorder()
	server.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/orders/42", nil))

	fmt.Println(rec.Code)
	fmt.Println(rec.Body.String())

}
Output:
200
order 42

type Shutdown

type Shutdown func(ctx context.Context) error

Shutdown stops the server, waiting for in-flight requests until ctx is done.

Directories

Path Synopsis
Package httpservertest holds helpers for testing handlers written against pkg/httpserver.
Package httpservertest holds helpers for testing handlers written against pkg/httpserver.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL