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 ¶
- func GetParam(r *http.Request, key string) string
- func Middlewares(main http.Handler, middlewares ...Middleware) http.Handler
- func NewLogRequest(route, method string, main http.Handler) http.Handler
- func RequestID(next http.Handler) http.Handler
- type ErrorHandler
- type Handler
- type Middleware
- type Option
- func WithAPM(enabled bool) Option
- func WithErrorHandler(handler ErrorHandler) Option
- func WithHTTPServerReadTimeout(duration time.Duration) Option
- func WithMiddlewares(middlewares ...Middleware) Option
- func WithName(name string) Option
- func WithPort(port string) Option
- func WithRoutes(routes ...Route) Option
- type Route
- func NewDelete(path string, handler Handler, middlewares ...Middleware) Route
- func NewGet(path string, handler Handler, middlewares ...Middleware) Route
- func NewPost(path string, handler Handler, middlewares ...Middleware) Route
- func NewPut(path string, handler Handler, middlewares ...Middleware) Route
- type Server
- type Shutdown
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GetParam ¶
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 ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
WithName sets the service name reported to Datadog. Defaults to "go-devkit-httpserver".
func WithRoutes ¶
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.
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 ¶
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
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. |