thttp

package
v0.0.0-...-406b1e7 Latest Latest
Warning

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

Go to latest
Published: Jun 22, 2023 License: Apache-2.0 Imports: 23 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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

View Source
var ErrMissingAuthToken = errors.New("missing authentication token")

ErrMissingAuthToken is an error return by BearerToken if there is no Authorization HTTP header

View Source
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

func BearerToken(header http.Header) (string, error)

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

func JSONResult(logger *zap.Logger, writer http.ResponseWriter, res any, code int)

JSONResult writes HTTP error code and JSON

func Log

func Log(next http.Handler) http.Handler

Log is a middleware that logs before and after handling of each request. Does not include logging of request and response bodies.

func LogBodies

func LogBodies(next http.Handler) http.Handler

LogBodies is a middleware that logs request and response bodies.

Only has an effect when debug logging is enabled.

func Origin

func Origin(r *http.Request) (string, error)

Origin returns the origin of HTTP request.

func Recover

func Recover(next http.Handler) http.Handler

Recover is a middleware that catches and logs panics from HTTP handlers

func ShouldGzip

func ShouldGzip(r *http.Request) bool

ShouldGzip returns if gzip-compression is asked for in HTTP request

func StandardMiddleware

func StandardMiddleware(next http.Handler) http.Handler

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

func Test(handler http.Handler, r *http.Request) *http.Response

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

func TestCtx(ctx context.Context, handler http.Handler, r *http.Request) *http.Response

TestCtx is similar to Test, except that the given context is injected into the request

func WithRequestsLogging

func WithRequestsLogging(client *http.Client) *http.Client

WithRequestsLogging returns an http client with logging

func Wrap

func Wrap(handler http.Handler, mw ...func(http.Handler) http.Handler) http.Handler

Wrap installs a number of middleware on HTTP handler. The first middleware listed will be the first one to see the request.

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

func (HandlerTransport) RoundTrip

func (ht HandlerTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip is an implementation of http.RoundTripper

type LoggingTransport

type LoggingTransport struct {
	Transport       http.RoundTripper
	SkipRequestBody bool
}

LoggingTransport is HTTP transport with logging

func (*LoggingTransport) RoundTrip

func (t *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip is an implementation of RoundTripper.

RoundTripper is an interface representing the ability to execute a single HTTP transaction, obtaining the Response for a given Request.

A RoundTripper must be safe for concurrent use by multiple goroutines.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server wraps an HTTP server

func NewServer

func NewServer(listener net.Listener, handler http.Handler) *Server

NewServer creates a Server

func (*Server) ListenAddr

func (s *Server) ListenAddr() net.Addr

ListenAddr returns the local address of the server's listener

func (*Server) Run

func (s *Server) Run(ctx context.Context) error

Run serves requests until the context is closed, then performs graceful shutdown for up to gracefulShutdownTimeout

Jump to

Keyboard shortcuts

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