httpe

package
v0.0.42 Latest Latest
Warning

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

Go to latest
Published: Jan 2, 2021 License: Apache-2.0 Imports: 3 Imported by: 2

Documentation

Overview

Package httpe provides a model of HTTP handler that returns errors.

The standard Go library package net/http provides a Handler interface that requires the handler write errors to the provided ResponseWriter. This is different to the usual Go way of handling errors that has functions returning errors, and it makes normal http.Handlers a bit cumbersome and repetitive in the error handling cases.

This package provides a HandlerE interface with a ServeHTTPe method that has the same signature as http.Handler.ServeHTTP except it also returns an error. A separate error handler can be bound to the HandlerE using NewHandler() and turn the HandlerE into an http.Handler.

As well as making handler code a little simpler, separating the ErrWriter allows for common error handling amongst disparate handlers.

The default ErrWriter writes errors that wrap an httpe.StatusError by writing the status code of that error and if the status code is a client error, writes the error formatted as a string. If it is not a client error, it writes just the text for that status code. Errors that do not wrap an httpe.StatusError are treated as httpe.ErrInternalServerError.

Option arguments to NewHandler() allow a custom ErrWriter to be provided.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrBadRequest                   = StatusError(http.StatusBadRequest)
	ErrUnauthorized                 = StatusError(http.StatusUnauthorized)
	ErrPaymentRequired              = StatusError(http.StatusPaymentRequired)
	ErrForbidden                    = StatusError(http.StatusForbidden)
	ErrNotFound                     = StatusError(http.StatusNotFound)
	ErrMethodNotAllowed             = StatusError(http.StatusMethodNotAllowed)
	ErrNotAcceptable                = StatusError(http.StatusNotAcceptable)
	ErrProxyAuthRequired            = StatusError(http.StatusProxyAuthRequired)
	ErrRequestTimeout               = StatusError(http.StatusRequestTimeout)
	ErrConflict                     = StatusError(http.StatusConflict)
	ErrGone                         = StatusError(http.StatusGone)
	ErrLengthRequired               = StatusError(http.StatusLengthRequired)
	ErrPreconditionFailed           = StatusError(http.StatusPreconditionFailed)
	ErrRequestEntityTooLarge        = StatusError(http.StatusRequestEntityTooLarge)
	ErrRequestURITooLong            = StatusError(http.StatusRequestURITooLong)
	ErrUnsupportedMediaType         = StatusError(http.StatusUnsupportedMediaType)
	ErrRequestedRangeNotSatisfiable = StatusError(http.StatusRequestedRangeNotSatisfiable)
	ErrExpectationFailed            = StatusError(http.StatusExpectationFailed)
	ErrTeapot                       = StatusError(http.StatusTeapot)
	ErrMisdirectedRequest           = StatusError(http.StatusMisdirectedRequest)
	ErrUnprocessableEntity          = StatusError(http.StatusUnprocessableEntity)
	ErrLocked                       = StatusError(http.StatusLocked)
	ErrFailedDependency             = StatusError(http.StatusFailedDependency)
	ErrTooEarly                     = StatusError(http.StatusTooEarly)
	ErrUpgradeRequired              = StatusError(http.StatusUpgradeRequired)
	ErrPreconditionRequired         = StatusError(http.StatusPreconditionRequired)
	ErrTooManyRequests              = StatusError(http.StatusTooManyRequests)
	ErrRequestHeaderFieldsTooLarge  = StatusError(http.StatusRequestHeaderFieldsTooLarge)
	ErrUnavailableForLegalReasons   = StatusError(http.StatusUnavailableForLegalReasons)

	ErrInternalServerError           = StatusError(http.StatusInternalServerError)
	ErrNotImplemented                = StatusError(http.StatusNotImplemented)
	ErrBadGateway                    = StatusError(http.StatusBadGateway)
	ErrServiceUnavailable            = StatusError(http.StatusServiceUnavailable)
	ErrGatewayTimeout                = StatusError(http.StatusGatewayTimeout)
	ErrHTTPVersionNotSupported       = StatusError(http.StatusHTTPVersionNotSupported)
	ErrVariantAlsoNegotiates         = StatusError(http.StatusVariantAlsoNegotiates)
	ErrInsufficientStorage           = StatusError(http.StatusInsufficientStorage)
	ErrLoopDetected                  = StatusError(http.StatusLoopDetected)
	ErrNotExtended                   = StatusError(http.StatusNotExtended)
	ErrNetworkAuthenticationRequired = StatusError(http.StatusNetworkAuthenticationRequired)
)

HTTP status codes as registered with IANA. See: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml

View Source
var (
	// Get is a HandlerE that returns a ErrMethodNotAllowed if the request
	// method is not GET. Use with Chain or New/Must.
	Get = newMethodChecker(http.MethodGet)

	// Head is a HandlerE that returns a ErrMethodNotAllowed if the request
	// method is not HEAD. Use with Chain or New/Must.
	Head = newMethodChecker(http.MethodHead)

	// Post is a HandlerE that returns a ErrMethodNotAllowed if the request
	// method is not POST. Use with Chain or New/Must.
	Post = newMethodChecker(http.MethodPost)

	// Put is a HandlerE that returns a ErrMethodNotAllowed if the request
	// method is not PUT. Use with Chain or New/Must.
	Put = newMethodChecker(http.MethodPut)

	// Patch is a HandlerE that returns a ErrMethodNotAllowed if the
	// request method is not PATCH. Use with Chain or New/Must.
	Patch = newMethodChecker(http.MethodPatch)

	// Delete is a HandlerE that returns a ErrMethodNotAllowed if the
	// request method is not DELETE. Use with Chain or New/Must.
	Delete = newMethodChecker(http.MethodDelete)

	// Connect is a HandlerE that returns a ErrMethodNotAllowed if the
	// request method is not CONNECT. Use with Chain or New/Must.
	Connect = newMethodChecker(http.MethodConnect)

	// Options is a HandlerE that returns a ErrMethodNotAllowed if the
	// request method is not OPTIONS. Use with Chain or New/Must.
	Options = newMethodChecker(http.MethodOptions)

	// Trace is a HandlerE that returns a ErrMethodNotAllowed if the
	// request method is not TRACE. Use with Chain or New/Must.
	Trace = newMethodChecker(http.MethodTrace)
)

Functions

func Must added in v0.0.29

func Must(args ...interface{}) http.Handler

Must passes all its args to New() and panics if New() returns an error. If it does not, the handler result of New() is returned.

func New added in v0.0.29

func New(args ...interface{}) (http.Handler, error)

New returns an http.Handler that calls in sequence all the args that are a type of handler, stopping if any return an error. If any of the args is an ErrWriter or a function that has the signature of an ErrWriterFunc, it will be called to handle the error if there was one.

The types that are recognised as handlers in the arg list are any type that implements HandlerE (including HandlerFuncE), a function that matches the signature of a HandlerFuncE, an http.Handler, or a function that matches the signature of an http.HandlerFunc. Args of the latter two are adapted to always return a nil error.

If an argument does not match any of the preceding types or more than one ErrWriter is passed, an error is returned.

Example
package main

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

	"foxygo.at/s/httpe"
)

func main() {
	// Create a handler by chaining a number of other handlers and an
	// error writer to handle any errors from those handlers.
	handler, _ := httpe.New(corsAllowAll, httpe.Get, &api{}, errWriter)

	w := httptest.NewRecorder()
	r := httptest.NewRequest("GET", "/hello", nil)
	handler.ServeHTTP(w, r)
	fmt.Printf("%d %s\n", w.Code, w.Body.String()) // 200 world

	w = httptest.NewRecorder()
	r = httptest.NewRequest("POST", "/hello", nil)
	handler.ServeHTTP(w, r)
	fmt.Printf("%d %s\n", w.Code, w.Body.String()) // 405 🐈 METHOD NOT ALLOWED!!!1!

	w = httptest.NewRecorder()
	r = httptest.NewRequest("GET", "/goodbye", nil)
	handler.ServeHTTP(w, r)
	fmt.Printf("%d %s\n", w.Code, w.Body.String()) // 500 🐈 NO GOODBYES!!!1!

}

// A traditional http.Handler compatible function.
func corsAllowAll(w http.ResponseWriter, _ *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
}

type api struct{}

// Implements httpe.HandlerE.
func (a *api) ServeHTTPe(w http.ResponseWriter, r *http.Request) error {
	switch r.URL.Path {
	case "/hello":
		fmt.Fprintf(w, "world")
	case "/goodbye":
		return errors.New("no goodbyes")
	}
	return nil
}

// Matches httpe.ErrWriterFunc.
func errWriter(w http.ResponseWriter, err error) {
	var se httpe.StatusError
	if errors.As(err, &se) {
		w.WriteHeader(se.Code())
	} else {
		w.WriteHeader(http.StatusInternalServerError)
	}
	fmt.Fprintf(w, "🐈 "+strings.ToUpper(err.Error())+"!!!1!")
}
Output:

200 world
405 🐈 METHOD NOT ALLOWED!!!1!
500 🐈 NO GOODBYES!!!1!

func NewHandler

func NewHandler(h HandlerE, opts ...option) http.Handler

NewHandler returns an http.Handler that calls h.ServeHTTPe and handles the error returned, if any, with an ErrWriter to write the error to the ResponseWriter. The default ErrWriter is httpe.WriteSafeErr but can be overridden with an option passed to NewHandler.

func NewHandlerFunc

func NewHandlerFunc(h HandlerFuncE, opts ...option) http.HandlerFunc

NewHandlerFunc returns an http.HandlerFunc that calls h() and handles the error returned, if any, with an ErrWriter to write the error to the ResponseWriter. The default ErrWriter is httpe.WriteSafeErr but can be overridden with an option passed to NewHandlerFunc.

func WithErrWriter added in v0.0.28

func WithErrWriter(ew ErrWriter) option

WithErrWriter returns an option to use the given ErrWriter as the ErrWriter for a HandlerE.

func WithErrWriterFunc added in v0.0.28

func WithErrWriterFunc(f ErrWriterFunc) option

WithErrWriterFunc returns an option to use the given ErrWriterFunc as the ErrWriter for a HandlerE.

func WriteSafeErr added in v0.0.25

func WriteSafeErr(w http.ResponseWriter, err error)

WriteSafeErr writes err as an HTTP error to the http.ResponseWriter.

If err wraps a StatusError, WriteSafeErr writes the Code of that error as the HTTP status code. Otherwise writes 500 as the code (internal server error).

WriteSafeErr writes the HTTP response body as the error described as a string if the error wraps a StatusError and the Code for that error is a client error (code 400 to 499). Otherwise, only the text for the status code is written. Errors that do not wrap a StatusError are treated as ErrInternalServerError. This prevents leaking internal details from a server error into the response to the client, but allows adding information to a client error to inform the client of details of that error.

Types

type ErrWriter

type ErrWriter interface {
	WriteErr(http.ResponseWriter, error)
}

ErrWriter translates an error into the appropriate http response StatusCode and Body and writes it.

type ErrWriterFunc

type ErrWriterFunc func(http.ResponseWriter, error)

The ErrWriterFunc type is an adapter to allow the use of ordinary functions as ErrWriter. If f is a function with the appropriate signature, ErrWriterFunc(f) is a ErrWriter that calls f.

func (ErrWriterFunc) WriteErr

func (ew ErrWriterFunc) WriteErr(w http.ResponseWriter, err error)

WriteErr translates an error into the appropriate http response StatusCode and Body and writes it.

type HandlerE

type HandlerE interface {
	ServeHTTPe(http.ResponseWriter, *http.Request) error
}

HandlerE works like an HTTP.Handler with the addition of an error return value. It is intended to be used with ErrWriter which handles the error and writes an appropriate http response StatusCode and Body to the ResponseWriter.

func Chain

func Chain(he ...HandlerE) HandlerE

Chain returns a HandlerE that executes each of the HandlerFuncE parameters sequentially, stopping at the first one that returns an error and returning that error. It returns nil if none of the handlers return an error.

type HandlerFuncE

type HandlerFuncE func(http.ResponseWriter, *http.Request) error

The HandlerFuncE type is an adapter to allow the use of ordinary functions as HandlerE. If f is a function with the appropriate signature, HandlerFuncE(f) is a HandlerE that calls f.

Example
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strings"

	"foxygo.at/s/httpe"
)

type User struct {
	Name string
	Age  int
}

var (
	errInput     = errors.New("input error")
	errDuplicate = errors.New("duplicate")
	storage      = map[string]User{}
)

func main() {
	// Create a http.HandlerFunc from our httpe.HandlerFuncE function and
	// an httpe.ErrWriterFunc function.
	handler := httpe.NewHandlerFunc(handle, httpe.WithErrWriterFunc(writeErr))

	// In this example, we call the handler directly using httptest.
	// Normally you would start a http server.
	// http.ListenAndServe(":9090", handler)
	w := httptest.NewRecorder()
	r := httptest.NewRequest("POST", "/user", strings.NewReader(`{"Name": "truncated...`))
	handler(w, r)
	fmt.Printf("%d %s", w.Code, w.Body.String())
}

func handle(w http.ResponseWriter, r *http.Request) error {
	user := User{}
	body, _ := ioutil.ReadAll(r.Body)
	if err := json.Unmarshal(body, &user); err != nil {
		return errInput
	}
	if user.Name == "" || user.Age < 0 {
		return errInput
	}
	if _, ok := storage[user.Name]; ok {
		return errDuplicate
	}
	storage[user.Name] = user
	return nil
}

func writeErr(w http.ResponseWriter, err error) {
	switch {
	case errors.Is(err, errInput):
		http.Error(w, err.Error(), http.StatusBadRequest)
	case errors.Is(err, errDuplicate):
		http.Error(w, "duplicate user", http.StatusForbidden)
	default:
		http.Error(w, "something went wrong", http.StatusInternalServerError)
	}
}
Output:

400 input error

func (HandlerFuncE) ServeHTTPe

func (f HandlerFuncE) ServeHTTPe(w http.ResponseWriter, r *http.Request) error

ServeHTTPe calls f(w, r) and returns its error.

type StatusError added in v0.0.25

type StatusError int

StatusError wraps an http.StatusCode of value 4xx or 5xx. It is used in combination with various sentinel values each representing a http status code for convenience with HandlerE and HandlerFuncE.

func (StatusError) Code added in v0.0.25

func (err StatusError) Code() int

Code returns a status int representing an http.Status* value.

func (StatusError) Error added in v0.0.25

func (err StatusError) Error() string

Error returns the error message and implements the error interface.

func (StatusError) IsClientError added in v0.0.25

func (err StatusError) IsClientError() bool

IsClientError returns true if the error is in range 400 to 499.

Jump to

Keyboard shortcuts

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