Documentation
¶
Overview ¶
Package httpx provides additional [http.Handler]s and utility functions
Index ¶
Examples ¶
Constants ¶
const ( ErrInternalServerError = StatusCode(http.StatusInternalServerError) ErrBadRequest = StatusCode(http.StatusBadRequest) ErrNotFound = StatusCode(http.StatusNotFound) ErrForbidden = StatusCode(http.StatusForbidden) ErrMethodNotAllowed = StatusCode(http.StatusMethodNotAllowed) )
Common Errors accepted by most httpx functions.
These are guaranteed to implement both [error] and http.Handler. See also [StatusCodeError].
const ( ContentTypeText = "text/plain; charset=utf-8" ContentTypeHTML = "text/html; charset=utf-8" ContentTypeJSON = "application/json; charset=utf-8" )
Content Types for standard content offered by several functions.
Variables ¶
This section is empty.
Functions ¶
func RenderErrorPage ¶
RenderErrorPage renders a debug error page instead of the fallback response res. The error page is intended to replace error pages for debugging and should not be used in production.
It will keep the original status code of res, but will replace the content type and body. It will render as 'text/html'.
Example ¶
Render an error page in response to a panic
package main import ( "fmt" "net/http" "net/http/httptest" _ "embed" "github.com/tkw1536/pkglib/httpx" ) func main() { // response for errors res := httpx.Response{StatusCode: http.StatusNotFound, Body: []byte("not found")} // a handler with an error page handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // do some operation, here it always fails for testing. err := fmt.Errorf("for debugging: %w", httpx.ErrNotFound) if err != nil { // render the error page httpx.RenderErrorPage(err, res, w, r) return } // ... do some normal processing here ... panic("normal rendering, never reached") }) // run the request { req, err := http.NewRequest(http.MethodGet, "/", nil) if err != nil { panic(err) } rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) result := rr.Result() fmt.Printf("Got status: %d\n", result.StatusCode) fmt.Printf("Got content-type: %s\n", result.Header.Get("Content-Type")) } }
Output: Got status: 404 Got content-type: text/html; charset=utf-8
Example (Panic) ¶
Render an error page in response to a panic
package main import ( "fmt" "net/http" "net/http/httptest" _ "embed" "github.com/tkw1536/pkglib/httpx" "github.com/tkw1536/pkglib/recovery" ) func main() { // response for errors res := httpx.Response{StatusCode: http.StatusInternalServerError, Body: []byte("something went wrong")} // a handler with an error page handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { // recover any errors if err := recovery.Recover(recover()); err != nil { // render the error page // which will replace it with a text/html page httpx.RenderErrorPage(err, res, w, r) } }() // ... do actual code ... panic("error for testing") }) // run the request { req, err := http.NewRequest(http.MethodGet, "/", nil) if err != nil { panic(err) } rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) result := rr.Result() fmt.Printf("Got status: %d\n", result.StatusCode) fmt.Printf("Got content-type: %s\n", result.Header.Get("Content-Type")) } }
Output: Got status: 500 Got content-type: text/html; charset=utf-8
Types ¶
type ErrInterceptor ¶
type ErrInterceptor struct { // Errors determines which error classes are intercepted. // // Errors are compared using [errors.Is] is undetermined order. // This means that if an error that [errors.Is] for multiple keys, // the returned response may any of the values. Errors map[error]Response // Fallback is the response for errors that are not of any of the above error classes. Fallback Response // RenderError indicates that instead of intercepting an error regularly // a human-readable error page with the appropriate error code should be displayed. // See [ErrorPage] for documentation on the error page handler. // // This option should only be used during development, as it exposes potentially security-critical data. RenderError bool // OnFallback is called when an unknown error is intercepted. OnFallback func(*http.Request, error) }
ErrInterceptor can handle errors for http responses and render appropriate error responses.
Example ¶
package main import ( "fmt" "io" "net/http" "net/http/httptest" "github.com/tkw1536/pkglib/httpx" ) func main() { // create an error interceptor interceptor := httpx.ErrInterceptor{ // handle [ErrNotFound] with a not found response Errors: map[error]httpx.Response{ // error not found (and wraps thereof) return that status code httpx.ErrNotFound: { StatusCode: http.StatusNotFound, Body: []byte("Not Found"), }, // forbidden (isn't actually used in this example) httpx.ErrForbidden: { StatusCode: http.StatusForbidden, Body: []byte("Forbidden"), }, }, // fallback to a generic not found error Fallback: httpx.Response{ StatusCode: http.StatusServiceUnavailable, Body: []byte("fallback error"), }, } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // ... do some work ... // in prod this would be an error returned from some operation result := map[string]error{ "/": nil, // no error "/notfound": httpx.ErrNotFound, "/wrapped": fmt.Errorf("wrapped: %w", httpx.ErrNotFound), }[r.URL.Path] // intercept an error if interceptor.Intercept(w, r, result) { return } _, _ = w.Write([]byte("Normal response")) }) // a function to make a request to a specific method makeRequest := func(path string) { req, err := http.NewRequest(http.MethodGet, path, nil) if err != nil { panic(err) } rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) rrr := rr.Result() result, _ := io.ReadAll(rrr.Body) fmt.Printf("%q returned code %d with %s %q\n", path, rrr.StatusCode, rrr.Header.Get("Content-Type"), string(result)) } makeRequest("/") makeRequest("/notfound") makeRequest("/wrapped") }
Output: "/" returned code 200 with text/plain; charset=utf-8 "Normal response" "/notfound" returned code 404 with text/plain; charset=utf-8 "Not Found" "/wrapped" returned code 404 with text/plain; charset=utf-8 "Not Found"
var ( TextInterceptor ErrInterceptor JSONInterceptor ErrInterceptor HTMLInterceptor ErrInterceptor )
Common interceptors for specific content types.
These handle all common http status codes by sending their response with a common error code. See the Error constants of this package for supported errors.
func (ErrInterceptor) Intercept ¶
func (ei ErrInterceptor) Intercept(w http.ResponseWriter, r *http.Request, err error) (intercepted bool)
Intercept intercepts the given error, and writes the response to the struct. A response is written to w if and only error is not nil. The return value indicates if error was nil and a response was written.
A typical use of an Intercept should be as follows:
// get interceptor from somewhere var ei ErrInterceptor // perform an operation, intercept the error or bail out result, err := SomeOperation() if ei.Intercept(w, r, err) { return }
// ... write result to the response ...
The precise behavior of Intercept is documented inside ErrInterceptor itself.
type ErrorLogger ¶
ErrorLogger is a function that can log an error occurred during some http handling process. A nil logger performs no logging.
type Response ¶
type Response struct { ContentType string // defaults to [ContentTypeTextPlain] Body []byte // immutable body to be sent to the client Modtime time.Time StatusCode int // defaults to a 2XX status code }
Response represents a static http Response. It implements http.Handler.
Example ¶
Using a response with a plain http status
package main import ( "fmt" "io" "net/http" "net/http/httptest" "github.com/tkw1536/pkglib/httpx" ) func main() { response := httpx.Response{ StatusCode: http.StatusOK, ContentType: httpx.ContentTypeHTML, Body: []byte("<!DOCTYPE html>Hello world"), } req, err := http.NewRequest(http.MethodGet, "/", nil) if err != nil { panic(err) } rr := httptest.NewRecorder() response.ServeHTTP(rr, req) result := rr.Result() body, _ := io.ReadAll(result.Body) fmt.Printf("Got status: %d\n", result.StatusCode) fmt.Printf("Got content-type: %s\n", result.Header.Get("Content-Type")) fmt.Printf("Got body: %s", string(body)) }
Output: Got status: 200 Got content-type: text/html; charset=utf-8 Got body: <!DOCTYPE html>Hello world
Example (Defaults) ¶
It is possible to omit everything, and defaults will be set correctly.
package main import ( "fmt" "io" "net/http" "net/http/httptest" "github.com/tkw1536/pkglib/httpx" ) func main() { response := httpx.Response{ Body: []byte("Hello world"), } req, err := http.NewRequest(http.MethodGet, "/", nil) if err != nil { panic(err) } rr := httptest.NewRecorder() response.ServeHTTP(rr, req) result := rr.Result() body, _ := io.ReadAll(result.Body) fmt.Printf("Got status: %d\n", result.StatusCode) fmt.Printf("Got content-type: %s\n", result.Header.Get("Content-Type")) fmt.Printf("Got body: %s", string(body)) }
Output: Got status: 200 Got content-type: text/plain; charset=utf-8 Got body: Hello world
func (Response) Now ¶
Now returns a copy of the response with the Modtime field set to the current time in UTC.
Example ¶
Using now one can set the time when the response was modified. This means that appropriate 'if-modified-since' headers are respected
package main import ( "fmt" "net/http" "net/http/httptest" "time" "github.com/tkw1536/pkglib/httpx" ) func main() { response := httpx.Response{ Body: []byte("Hello world"), }.Now() req, err := http.NewRequest(http.MethodGet, "/", nil) if err != nil { panic(err) } req.Header.Set("If-Modified-Since", response.Modtime.Add(time.Second).Format(http.TimeFormat)) // set an if-modified-since rr := httptest.NewRecorder() response.ServeHTTP(rr, req) result := rr.Result() fmt.Printf("Got status: %d\n", result.StatusCode) }
Output: Got status: 304
type StatusCode ¶
type StatusCode int
StatusCode represents an error based on a http status code. The integer is the http status code.
StatusCode implements both [error] and http.Handler. When used as a handler, it sets the appropriate status code, and returns a simple text response.
Example ¶
package main import ( "fmt" "io" "net/http" "net/http/httptest" "github.com/tkw1536/pkglib/httpx" ) func main() { // we can create an error based on the status code handler := httpx.StatusCode(http.StatusNotFound) // which automatically generates error messages fmt.Printf("String: %s\n", handler.String()) fmt.Printf("GoString: %s\n", handler.GoString()) fmt.Printf("Error: %s\n", handler.Error()) // it also implements a static http.Handler { req, err := http.NewRequest(http.MethodGet, "/", nil) if err != nil { panic(err) } rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) rrr := rr.Result() body, _ := io.ReadAll(rrr.Body) fmt.Printf("ServeHTTP() status: %d\n", rrr.StatusCode) fmt.Printf("ServeHTTP() content-type: %s\n", rrr.Header.Get("Content-Type")) fmt.Printf("ServeHTTP() body: %s\n", body) } }
Output: String: Not Found GoString: httpx.StatusCode(404/* Not Found */) Error: httpx: Not Found ServeHTTP() status: 404 ServeHTTP() content-type: text/plain; charset=utf-8 ServeHTTP() body: Not Found
func (StatusCode) Error ¶
func (code StatusCode) Error() string
Error implements the built-in [error] interface.
func (StatusCode) GoString ¶
func (code StatusCode) GoString() string
GoString returns a go source code representation of string.
func (StatusCode) ServeHTTP ¶
func (code StatusCode) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler.
func (StatusCode) String ¶
func (code StatusCode) String() string
String returns the status text belonging to this error.
Directories
¶
Path | Synopsis |
---|---|
_examples
|
|
Package content provides handlers for common http server content.
|
Package content provides handlers for common http server content. |
Package form provides a form abstraction for http
|
Package form provides a form abstraction for http |
Package mux provides Mux
|
Package mux provides Mux |
Package wrap provides wrappers for [http.Handler]s.
|
Package wrap provides wrappers for [http.Handler]s. |