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 ¶
- Variables
- func Must(args ...interface{}) http.Handler
- func New(args ...interface{}) (http.Handler, error)
- func NewHandler(h HandlerE, opts ...option) http.Handler
- func NewHandlerFunc(h HandlerFuncE, opts ...option) http.HandlerFunc
- func WithErrWriter(ew ErrWriter) option
- func WithErrWriterFunc(f ErrWriterFunc) option
- func WriteSafeErr(w http.ResponseWriter, err error)
- type ErrWriter
- type ErrWriterFunc
- type HandlerE
- type HandlerFuncE
- type StatusError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrBadRequest = StatusError(http.StatusBadRequest) 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) ErrInternalServerError = StatusError(http.StatusInternalServerError) ErrNotImplemented = StatusError(http.StatusNotImplemented) ErrBadGateway = StatusError(http.StatusBadGateway) 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
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
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
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 ¶
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.
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.