Documentation ¶
Overview ¶
Package thttp contains HTTP server and client utilities.
HTTP Server ¶
Most use cases for http.Server are covered by thttp.Server with the following advantages:
* Instead of pre-context-era start-and-stop paradigm, thttp.Server is controlled with a context passed to its Run method. This fits much better into hierarchies of internal components that need to be started and shut down as a whole. Plays especially nice with parallel.Run.
* The server code ensures that every incoming request has a context inherited from the context passed to Run, thus supporting the global expectation that every context contains a logger.
* The somewhat tricky graceful shutdown sequence is taken care of by thttp.Server.
Note that only a single handler is passed to thttp.NewServer as its second argument. Most use cases will need path-based routing. The standard solution is to use github.com/gorilla/mux as in the example below.
Example ¶
thttp.Server fits best into components that themselves are context-controlled. The following example demonstrates the use of the parallel library to run both an HTTP server and a Limestone instance. When the parent context is closed, RunDataServer will return after graceful shutdown of both sub-components.
func RunDataServer(ctx context.Context, addr string) error { return parallel.Run(ctx, func(ctx context.Context, spawn parallel.SpawnFn) error { db := limestone.New(...) spawn("limestone", parallel.Fail, db.Run) // Creating the listener early allows incoming connections to be queued // while Limestone is catching up listener, err := tnet.Listen(addr) if err != nil { return errors.Wrap(err, "failed to run data server") } err := db.WaitReady(ctx) if err != nil { return errors.Wrap(err, "failed to run data server") } router := mux.NewRouter() router.HandleFunc("/data/{id}", getHandler(db)).Methods(http.MethodGet) router.HandleFunc("/data/{id}", putHandler(db)).Methods(http.MethodPut) server := thttp.NewServer(listener, thttp.Wrap(router, thttp.StandardMiddleware, thttp.LogBodies)) spawn("http", parallel.Fail, server.Run) return nil }) }
Middleware ¶
A middleware is a function that takes an http.Handler and returns an http.Handler, usually wrapping the handler with code that runs before, after or even instead of the one being wrapped.
One can use a middleware to wrap a handler manually:
handler = thttp.CORS(handler)
A middleware can also be applied to the mux router or a sub-router:
router.Use(thttp.CORS)
To apply a handler to all requests handled by the server, which is the most common use case, it's convenient to use thttp.Wrap function. This function takes any number of middleware, which are applied in order so that the first one listed is the first to see the incoming request.
server := thttp.NewServer(listener, thttp.Wrap(handler, thttp.StandardMiddleware, thttp.LogBodies))
It is recommended to include at least thttp.StandardMiddleware, and put it first. This single middleware is equivalent to listing thttp.Log, thttp.Recover and thttp.CORS, in this order. It does not include LogBodies because its use is less universal.
Request context ¶
In an HTTP handler, r.Context() returns the request context. It is a descendant of the context passed into the Run method of thttp.Server, and contains all the values stored there. However, during shutdown it will stay open for somewhat longer than the parent context to allow current running requests to complete.
Logging guidelines ¶
For all logging in HTTP handlers, use the logger embedded in the request context:
logger := tlog.Get(r.Context())
This logger contains the following structured fields:
* httpServer: the local listening address (to distinguish between messages from several HTTP servers)
* remoteAddr: the IP address and port of the remote client
If the thttp.Log middleware is installed, which it should be (usually via thttp.StandardMiddleware), this logger will also contain the following fields:
* requestID: a unique ID generated for the request to tie the log messages together
* method, host, url: self-explanatory
Please avoid redundant logging. In particular:
* Don't log any of the information above explicitly.
* Don't include generic unconditional log messages at the beginning and end of your handler unless they contain informative fields. The thttp.Log middleware already logs before and after handling of each request.
* If you need to log all request and response bodies (at Debug level), use the thttp.LogBodies middleware.
* In case of an internal error, don't log it explicitly. Just panic, and the thttp.Recover middleware will log the complete error with the panic stack. The client will receive a generic error 500 (unless the headers have already been sent), without the details of the error being exposed. Afterwards, the server will be terminated gracefully.
Index ¶
- Variables
- func BearerToken(header http.Header) (string, error)
- func CaptureStatus(w http.ResponseWriter, status *int) http.ResponseWriter
- func GzipEnabledFileHandler(filename string) func(w http.ResponseWriter, r *http.Request)
- func JSONResult(logger *zap.Logger, writer http.ResponseWriter, res any, code int)
- func Log(next http.Handler) http.Handler
- func LogBodies(next http.Handler) http.Handler
- func Origin(r *http.Request) (string, error)
- func Recover(next http.Handler) http.Handler
- func ShouldGzip(r *http.Request) bool
- func StandardMiddleware(next http.Handler) http.Handler
- func Test(handler http.Handler, r *http.Request) *http.Response
- func TestCtx(ctx context.Context, handler http.Handler, r *http.Request) *http.Response
- func WithRequestsLogging(client *http.Client) *http.Client
- func Wrap(handler http.Handler, mw ...func(http.Handler) http.Handler) http.Handler
- type ErrMalformedAuthHeader
- type HandlerTransport
- type LoggingTransport
- type Server
Constants ¶
This section is empty.
Variables ¶
var CORS = handlers.CORS( handlers.AllowedMethods(allowedMethods), handlers.AllowedHeaders(allowedHeaders), handlers.ExposedHeaders(exposedHeaders), handlers.AllowedOrigins([]string{"*"}), )
CORS is a middleware that allows cross-origin requests
var ErrMissingAuthToken = errors.New("missing authentication token")
ErrMissingAuthToken is an error return by BearerToken if there is no Authorization HTTP header
var RetryingDNSClient = &http.Client{ Transport: &http.Transport{ DialContext: retryingDialer, }, }
RetryingDNSClient is a http.Client that retries in case of DNS "not found" errors
Functions ¶
func BearerToken ¶
BearerToken returns a bearer token, or an error if it is not found
func CaptureStatus ¶
func CaptureStatus(w http.ResponseWriter, status *int) http.ResponseWriter
CaptureStatus wraps a http.ResponseWriter to capture the response status code. The status code will be written into *status.
The returned ResponseWriter works the same way as the original one, including the http.Hijacker functionality, if available.
func GzipEnabledFileHandler ¶
func GzipEnabledFileHandler(filename string) func(w http.ResponseWriter, r *http.Request)
GzipEnabledFileHandler returns HTTP handler that serves the file. If the HTTP request asks for gzip Content-Encoding, the handler serves the file gzipped.
func JSONResult ¶
JSONResult writes HTTP error code and JSON
func Log ¶
Log is a middleware that logs before and after handling of each request. Does not include logging of request and response bodies.
func LogBodies ¶
LogBodies is a middleware that logs request and response bodies.
Only has an effect when debug logging is enabled.
func ShouldGzip ¶
ShouldGzip returns if gzip-compression is asked for in HTTP request
func StandardMiddleware ¶
StandardMiddleware is a composition of typically used middleware, in the recommended order:
1. Log (log before and after the request) 2. Recover (catch and log panic, then shut down the server) 3. CORS (allow cross-origin requests)
func Test ¶
Test processes an http.Request (usually obtained from httptest.NewRequest) with the given handler as if it was received on the network. Only useful in tests.
Does not require a running HTTP server to be running.
func TestCtx ¶
TestCtx is similar to Test, except that the given context is injected into the request
func WithRequestsLogging ¶
WithRequestsLogging returns an http client with logging
Types ¶
type ErrMalformedAuthHeader ¶
type ErrMalformedAuthHeader struct {
// contains filtered or unexported fields
}
ErrMalformedAuthHeader is an error returned by BearerToken if Authorization HTTP header is not in form "Bearer token"
func (ErrMalformedAuthHeader) Error ¶
func (e ErrMalformedAuthHeader) Error() string
type HandlerTransport ¶
type HandlerTransport struct { Context context.Context //nolint:containedctx // this struct has a context because net/http is pre-context Handler http.Handler }
HandlerTransport is a http.Transport that shortcuts requests to a given http.Handler locally
type LoggingTransport ¶
type LoggingTransport struct { Transport http.RoundTripper SkipRequestBody bool }
LoggingTransport is HTTP transport with logging