mid

package module
v1.7.1 Latest Latest
Warning

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

Go to latest
Published: Jun 28, 2022 License: MIT Imports: 16 Imported by: 5

README

Mid - Assorted middleware for HTTP services.

Go Reference Go Report Card Tests Coverage Status

This is mid, a collection of useful middleware for HTTP services.

Err

The Err function turns a func(http.ResponseWriter, *http.Request) error into an http.Handler, allowing handler functions to return errors in a more natural fashion. These errors result in the handler producing an HTTP 500 status code, but CodeErr and Responder allow you to control this behavior (see below). A nil return produces a 200, or a 204 (“no content”) if no bytes were written to the response object.

Usage:

func main() {
  http.Handle("/foo", Err(fooHandler))
  http.ListenAndServe(":8080", nil)
}

func fooHandler(w http.ResponseWriter, req *http.Request) error {
  if x := req.FormValue("x"); x != "secret password" {
    return CodeErr{C: http.StatusUnauthorized}
  }
  fmt.Fprintf(w, "You know the secret password")
  return nil
}

JSON

The JSON function turns a func(context.Context, X) (Y, error) into an http.Handler, where X is the type of a parameter into which the HTTP request body is automatically JSON-unmarshaled, and Y is the type of a result that is automatically JSON-marshaled into the HTTP response. Any error is handled as with Err (see above). All parts of the func signature are optional: the context.Context parameter, the X parameter, the Y result, and the error result.

Usage:

func main() {
  // Parses the request body as a JSON-encoded array of strings,
  // then sorts, re-encodes, and returns that array.
  http.Handle("/bar", JSON(barHandler))

  http.ListenAndServe(":8080", nil)
}

func barHandler(inp []string) []string {
  sort.Strings(inp)
  return inp
}

CodeErr and Responder

CodeErr is an error type suitable for returning from Err- and JSON-wrapped handlers that can control the HTTP status code that gets returned. It contains an HTTP status code field and a nested error.

Responder is an interface (implemented by CodeErr) that allows an error type to control how Err- and JSON-wrapped handlers respond to the pending request.

ResponseWrapper

ResponseWrapper is an http.ResponseWriter that wraps a nested http.ResponseWriter and also records the status code and number of bytes sent in the response.

Trace

The Trace function wraps an http.Handler and decorates the context.Context in its *http.Request with any “trace ID” string found in the request header.

Log

The Log function wraps an http.Handler with a function that writes a simple log line on the way into and out of the handler. The log line includes any “trace ID” found in the request’s context.Context.

Documentation

Overview

Package mid contains assorted middleware for use in HTTP services.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrCSRF = errors.New("CSRF check failed")

ErrCSRF is the error produced when an invalid CSRF token is presented to CSRFCheck.

View Source
var ErrNoSession = errors.New("no session")

ErrNoSession is the error produced by SessionStore.Get when no matching session is found.

Functions

func CSRFCheck added in v1.5.0

func CSRFCheck(s Session, inp string) error

CSRFCheck checks a CSRF token against a session for validity. TODO: check s is active and unexpired?

func CSRFToken added in v1.5.0

func CSRFToken(s Session) (string, error)

CSRFToken generates a new token containing a random nonce hashed with this session's CSRF key. It can be used to protect against CSRF attacks. Resources served by the application (e.g. HTML pages) should include a CSRF token. State-changing requests to the application that rely on a Session for authentication should require the caller to supply a valid CSRF token. Validity can be checked with CSRFCheck. For more on this topic see https://en.wikipedia.org/wiki/Cross-site_request_forgery.

func Err

Err wraps an error-returning function as an http.Handler. If the returned error is a Responder (such as a CodeErr), its Respond method is used to respond to the request. Otherwise, if a status code has not already been set, an error return will set it to http.StatusInternalServerError, and the absence of an error will set it to http.StatusOK, or http.StatusNoContent if nothing has been written to the ResponseWriter.

func Errf

func Errf(w http.ResponseWriter, code int, format string, args ...interface{})

Errf is a convenience wrapper for http.Error. It calls http.Error(w, fmt.Sprintf(format, args...), code). It also logs that message with log.Print. If code is 0, it defaults to http.StatusInternalServerError. If format is "", Errf uses http.StatusText instead.

func IsNoSession added in v1.5.0

func IsNoSession(err error) bool

IsNoSession tests whether the given error is either ErrNoSession or http.ErrNoCookie.

func JSON

func JSON(f interface{}) http.Handler

JSON produces an http.Handler by JSON encoding and decoding of a given function's input and output.

The signature of the function is:

func(context.Context, inType) (outType, error)

where inType is any type that can be decoded from JSON and outType is any type that can be encoded to JSON. These may alternatively be pointers to such types.

Every part of the signature is optional (both arguments and both return values).

Passing the wrong type of object to this function produces a panic.

When the function is called:

  • If a context argument is present, it is supplied from the Context() method of the pending *http.Request. That context is further adorned with the pending *http.Request and the pending http.ResponseWriter, which can be retrieved with the Request and ResponseWriter functions.

  • If an inType argument is present, the request is checked to ensure that the method is POST and the Content-Type is application/json; then the request body is unmarshaled into the inType argument. Note that the JSON decoder uses the UseNumber setting; see https://golang.org/pkg/encoding/json/#Decoder.UseNumber.

  • If an outType result is present, it is JSON marshaled and written to the pending ResponseWriter with an HTTP status of 200 (ok). If no outType is present, the default HTTP status is 204 (no content).

  • If an error result is present, it is handled as in Err.

Some of the code in this function is (liberally) adapted from github.com/chain/chain.

Example
type (
	inType struct {
		Referer   bool `json:"referer"`
		UserAgent bool `json:"user_agent"`
	}
	outType struct {
		Referer   string `json:"referer"`
		UserAgent string `json:"user_agent"`
	}
)

handler := func(ctx context.Context, in inType) (outType, error) {
	var (
		req = Request(ctx)
		out outType
	)

	if in.Referer {
		out.Referer = req.Referer()
	}
	if in.UserAgent {
		out.UserAgent = req.UserAgent()
	}

	return out, nil
}

s := httptest.NewServer(JSON(handler))
defer s.Close()

inp := `{"user_agent":true}`

req, err := http.NewRequest("POST", s.URL, strings.NewReader(inp))
if err != nil {
	log.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "ExampleJSON-agent/1.0")

var c http.Client
resp, err := c.Do(req)
if err != nil {
	log.Fatal(err)
}
defer resp.Body.Close()

var out outType
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Server got user agent %s\n", out.UserAgent)
Output:

Server got user agent ExampleJSON-agent/1.0

func Log

func Log(next http.Handler) http.Handler

Log adds logging on entry to and exit from an http.Handler.

If the request is decorated with a trace ID (see Trace), it is included in the generated log lines.

func Request

func Request(ctx context.Context) *http.Request

Request returns the pending *http.Request object when called on the context passed to a JSON handler.

func RespondJSON added in v1.7.0

func RespondJSON(w http.ResponseWriter, obj interface{}) error

RespondJSON responds to an http request with a JSON-encoded object.

func ResponseWriter

func ResponseWriter(ctx context.Context) http.ResponseWriter

ResponseWriter returns the pending http.ResponseWriter object when called on the context passed to a JSON handler.

func SessionHandler added in v1.5.0

func SessionHandler(store SessionStore, cookieName string, next http.Handler) http.Handler

SessionHandler is an http.Handler middleware wrapper. It checks the incoming request for a session in the given store. If one is found, the request's context is decorated with the session. It can be retrieved by the next handler with ContextSession. If an active, unexpired session is not found, a 403 Forbidden error is returned.

func Trace

func Trace(next http.Handler) http.Handler

Trace decorates a request's context with a trace ID. The ID for the request is obtained from the X-Trace-Id field in the request header. If that field does not exist or is empty, Idempotency-Key and X-Idempotency-Key are tried. Failing those, a randomly generated ID is used.

The trace ID can be retrieved from a context so decorated using TraceID. Any trace ID present will be included in log lines generated by Log.

func TraceID

func TraceID(ctx context.Context) string

TraceID returns the trace ID decorating the given context, if any. See Trace.

Types

type CodeErr

type CodeErr struct {
	// C is an HTTP status code.
	C int

	// Err is an optional wrapped error.
	Err error
}

CodeErr is an error that can be returned from the function wrapped by Err to control the HTTP status code returned from the pending request.

func (CodeErr) As

func (c CodeErr) As(target interface{}) bool

As implements the interface for errors.As.

func (CodeErr) Error

func (c CodeErr) Error() string

Error implements the error interface.

func (CodeErr) Respond

func (c CodeErr) Respond(w http.ResponseWriter)

Respond implements Responder.

func (CodeErr) Unwrap

func (c CodeErr) Unwrap() error

Unwrap implements the interface for errors.Unwrap.

type Responder

type Responder interface {
	Respond(http.ResponseWriter)
}

Responder is an interface for objects that know how to respond to an HTTP request. It is useful in the case of errors that want to set custom error strings and/or status codes (e.g. via http.Error).

type ResponseWrapper added in v1.3.0

type ResponseWrapper struct {
	// W is the wrapped ResponseWriter to which method calls are delegated.
	W http.ResponseWriter

	// N is the number of bytes that have been written with calls to Write.
	N int

	// Code is the status code that has been written with WriteHeader,
	// or zero if no call to WriteHeader has yet been made.
	// If Write is called before any call to WriteHeader,
	// then this is set to http.StatusOK (200).
	Code int
}

ResponseWrapper implements http.ResponseWriter, delegating calls to a wrapped http.ResponseWriter object. It also records the status code and the number of response bytes that have been written.

func (*ResponseWrapper) Header added in v1.3.0

func (ww *ResponseWrapper) Header() http.Header

Header implements http.ResponseWriter.Header.

func (*ResponseWrapper) Result added in v1.4.0

func (ww *ResponseWrapper) Result() int

Result returns the value of the Code field, if it has been set. Otherwise it returns http.StatusOK or http.StatusNoContent depending on whether any bytes have been written.

func (*ResponseWrapper) Write added in v1.3.0

func (ww *ResponseWrapper) Write(b []byte) (int, error)

Write implements http.ResponseWriter.Write.

func (*ResponseWrapper) WriteHeader added in v1.3.0

func (ww *ResponseWrapper) WriteHeader(code int)

WriteHeader implements http.ResponseWriter.WriteHeader.

type Session added in v1.5.0

type Session interface {
	// CSRFKey is a persistent random bytestring that can be used for CSRF protection.
	CSRFKey() [sha256.Size]byte

	// Active is true when the session is created and false after it is canceled (via SessionStore.Cancel).
	Active() bool

	// Exp is the expiration time of the session.
	Exp() time.Time
}

Session is the type of a session stored in a SessionStore.

func ContextSession added in v1.5.0

func ContextSession(ctx context.Context) Session

ContextSession returns the Session associated with a context (by SessionHandler), if there is one. If there isn't, this returns nil.

func GetSession added in v1.5.0

func GetSession(ctx context.Context, store SessionStore, cookieName string, req *http.Request) (Session, error)

GetSession checks for a session cookie in a given HTTP request and gets the corresponding session from the store.

type SessionStore added in v1.5.0

type SessionStore interface {
	// Get gets the session with the given key.
	// If no such session is found, it returns ErrNoSession.
	Get(context.Context, string) (Session, error)

	// Cancel cancels the session with the given unique key.
	// If the session does not exist, or is already canceled or expired,
	// this function silently succeeds.
	Cancel(context.Context, string) error
}

SessionStore is persistent storage for session objects.

Jump to

Keyboard shortcuts

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