Documentation
¶
Overview ¶
Package httpfun provides utilities for creating HTTP handlers from wrapped functions.
Overview ¶
The httpfun package enables you to convert any wrapped function into an HTTP handler. It handles request argument parsing, function execution, result formatting, and error handling.
Basic Usage ¶
Convert a function to an HTTP handler:
func Calculate(ctx context.Context, operation string, a, b int) (int, error) {
switch operation {
case "add":
return a + b, nil
case "multiply":
return a * b, nil
default:
return 0, fmt.Errorf("unknown operation: %s", operation)
}
}
func main() {
wrapper := function.MustReflectWrapper("Calculate", Calculate)
handler := httpfun.Handler(
httpfun.RequestQueryArgs, // Parse args from query params
wrapper,
httpfun.RespondJSON, // Respond with JSON
)
http.Handle("/calculate", handler)
http.ListenAndServe(":8080", nil)
}
Usage:
GET /calculate?operation=add&a=5&b=3
Response: 8
POST /calculate with JSON: {"operation":"multiply","a":5,"b":3}
Response: 15
Request Argument Parsing ¶
Extract function arguments from various HTTP request sources:
// From query parameters
httpfun.RequestQueryArgs
httpfun.RequestQueryArg("argName")
// From JSON body
httpfun.RequestBodyJSONFieldsAsArgs
// From headers
httpfun.RequestHeaderArg("Authorization")
httpfun.RequestHeadersAsArgs(map[string]string{
"Authorization": "token",
"User-Agent": "userAgent",
})
// From form data
httpfun.RequestMultipartFormArgs
// From environment variables
httpfun.RequestArgFromEnvVar("API_KEY", "apiKey")
// Constant values
httpfun.ConstRequestArg("version", "1.0")
// Merge multiple sources (later sources override earlier ones)
httpfun.MergeRequestArgs(
httpfun.ConstRequestArg("defaultValue", "abc"),
httpfun.RequestQueryArgs,
httpfun.RequestBodyJSONFieldsAsArgs,
)
Response Writers ¶
Format function results as HTTP responses:
// JSON response (default)
httpfun.RespondJSON
// JSON with named fields
httpfun.RespondJSONObject("result", "error")
// XML response
httpfun.RespondXML
// Plain text
httpfun.RespondPlaintext
// HTML
httpfun.RespondHTML
// Binary data with specific content type
httpfun.RespondBinary("application/pdf")
// Auto-detect content type
httpfun.RespondDetectContentType
// Static responses
httpfun.RespondStaticHTML("<html>...</html>")
httpfun.RespondStaticJSON(`{"status":"ok"}`)
// Redirects
httpfun.RespondRedirect("/success")
httpfun.RespondRedirectFunc(func(r *http.Request) (string, error) {
return "/user/" + getUserID(r), nil
})
Custom Response Writers ¶
Create custom response formatting:
customWriter := httpfun.ResultsWriterFunc(func(
results []any,
err error,
w http.ResponseWriter,
r *http.Request,
) error {
if err != nil {
return err // Let error handler deal with it
}
// Custom formatting logic
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "Success: %v", results[0])
return nil
})
Error Handling ¶
Customize error responses:
httpfun.HandleError = func(err error, w http.ResponseWriter, r *http.Request) {
log.Printf("Error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
Or provide custom error handlers per endpoint:
handler := httpfun.Handler(
httpfun.RequestQueryArgs,
wrapper,
httpfun.RespondJSON,
customErrorHandler, // Handles errors for this endpoint only
)
Configuration ¶
Global configuration options:
// Pretty-print JSON responses (default: true)
httpfun.PrettyPrint = true
httpfun.PrettyPrintIndent = " "
// Catch panics in handlers (default: true)
httpfun.CatchHandlerPanics = true
// Custom error handler (default uses go-httpx/httperr)
httpfun.HandleError = func(err error, w http.ResponseWriter, r *http.Request) {
// Custom error handling
}
Advanced Examples ¶
## REST API with Multiple Endpoints
type UserService struct{}
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) { ... }
func (s *UserService) CreateUser(ctx context.Context, name, email string) (*User, error) { ... }
func (s *UserService) DeleteUser(ctx context.Context, id string) error { ... }
func main() {
svc := &UserService{}
http.Handle("/user", httpfun.Handler(
httpfun.MergeRequestArgs(
httpfun.ConstRequestArg("id", ""),
httpfun.RequestQueryArgs,
),
function.MustReflectWrapper("GetUser", svc.GetUser),
httpfun.RespondJSON,
))
http.Handle("/user/create", httpfun.Handler(
httpfun.RequestBodyJSONFieldsAsArgs,
function.MustReflectWrapper("CreateUser", svc.CreateUser),
httpfun.RespondJSON,
))
http.ListenAndServe(":8080", nil)
}
## File Upload Handler
func ProcessFile(ctx context.Context, fileData string) (string, error) {
// Process the file
return "processed", nil
}
handler := httpfun.Handler(
httpfun.RequestBodyAsArg("fileData"),
function.MustReflectWrapper("ProcessFile", ProcessFile),
httpfun.RespondPlaintext,
)
## Binary Response Handler
func GeneratePDF(ctx context.Context, title string) ([]byte, error) {
// Generate PDF
return pdfBytes, nil
}
handler := httpfun.Handler(
httpfun.RequestQueryArgs,
function.MustReflectWrapper("GeneratePDF", GeneratePDF),
httpfun.RespondBinary("application/pdf"),
)
Best Practices ¶
- Always include context.Context as the first parameter for cancellation support
- Return errors as the last result for proper error handling
- Use MergeRequestArgs to combine multiple argument sources
- Configure error handling before starting the server
- Enable panic recovery (CatchHandlerPanics) in production
- Use appropriate content types for your responses
- Validate inputs in your functions, not in request parsers
- Log errors in your HandleError function for debugging
Performance Considerations ¶
- Request argument parsing happens on every request
- JSON marshaling can be expensive for large responses
- Consider disabling PrettyPrint in production for performance
- Use binary responses for large files instead of base64 encoding
- Cache wrapped functions instead of creating them per request
Thread Safety ¶
The package-level configuration variables (PrettyPrint, HandleError, etc.) are not thread-safe. Set them once during initialization before handling requests. Handler functions are safe for concurrent use.
Index ¶
- Variables
- func DetectContentType(data []byte) string
- func Handler(getArgs RequestArgsFunc, function function.CallWithNamedStringsWrapper, ...) http.HandlerFunc
- func HandlerNoWrapper(function func(context.Context) ([]byte, error), resultsWriter ResultsWriter, ...) http.HandlerFunc
- func RequestBodyJSONFieldsAsArgs(request *http.Request) (map[string]string, error)
- func RequestMultipartFormArgs(request *http.Request) (map[string]string, error)
- func RequestQueryArgs(request *http.Request) (map[string]string, error)
- type RequestArgsFunc
- func ConstRequestArg(name, value string) RequestArgsFunc
- func ConstRequestArgs(args map[string]string) RequestArgsFunc
- func MergeRequestArgs(getters ...RequestArgsFunc) RequestArgsFunc
- func RequestArgFromEnvVar(envVar, argName string) RequestArgsFunc
- func RequestBodyAsArg(argName string) RequestArgsFunc
- func RequestHeaderArg(headerKeyArgName string) RequestArgsFunc
- func RequestHeaderAsArg(headerKey, argName string) RequestArgsFunc
- func RequestHeadersAsArgs(headerToArg map[string]string) RequestArgsFunc
- func RequestQueryArg(queryKeyArgName string) RequestArgsFunc
- func RequestQueryAsArg(queryKey, argName string) RequestArgsFunc
- type RespondRedirect
- type RespondRedirectFunc
- type RespondStaticHTML
- type RespondStaticJSON
- type RespondStaticPlaintext
- type RespondStaticXML
- type ResultsWriter
- type ResultsWriterFunc
Constants ¶
This section is empty.
Variables ¶
var ( // PrettyPrint controls whether JSON and XML responses are formatted with indentation. // Set to false in production for smaller responses and better performance. // Default: true PrettyPrint = true // PrettyPrintIndent is the indentation string used when PrettyPrint is true. // Default: " " (two spaces) PrettyPrintIndent = " " // CatchHandlerPanics controls whether panics in HTTP handlers are recovered. // When true, panics are caught and converted to HTTP errors via HandleError. // When false, panics will crash the server (useful for debugging). // Default: true // // WARNING: This is a global setting. Set it once during initialization. // Not thread-safe to modify after handlers start running. CatchHandlerPanics = true // HandleError is called when an error occurs during request handling. // It writes the error to the HTTP response. // // The default implementation uses github.com/ungerik/go-httpx/httperr.DefaultHandler, // which provides sensible error responses with proper HTTP status codes. // // Customize this to implement your own error handling strategy: // // httpfun.HandleError = func(err error, w http.ResponseWriter, r *http.Request) { // log.Printf("HTTP error: %v", err) // http.Error(w, "Internal Server Error", http.StatusInternalServerError) // } // // WARNING: This is a global setting. Set it once during initialization. // Not thread-safe to modify after handlers start running. HandleError = func(err error, response http.ResponseWriter, request *http.Request) { if err != nil { httperr.DefaultHandler.HandleError(err, response, request) } } )
var RespondNothing respondNothing
Functions ¶
func DetectContentType ¶
DetectContentType tries to detect the MIME content-type of data, or returns "application/octet-stream" if none could be identified.
func Handler ¶
func Handler(getArgs RequestArgsFunc, function function.CallWithNamedStringsWrapper, resultsWriter ResultsWriter, errHandlers ...httperr.Handler) http.HandlerFunc
Handler creates an http.HandlerFunc from a wrapped function.
Parameters:
- getArgs: Extracts function arguments from the HTTP request (can be nil for no args)
- function: The wrapped function to execute
- resultsWriter: Formats and writes results to the response (can be nil for no response)
- errHandlers: Optional custom error handlers (uses global HandleError if empty)
The handler:
- Recovers from panics if CatchHandlerPanics is true
- Parses arguments from the request using getArgs
- Executes the wrapped function with request.Context()
- Writes results using resultsWriter
- Handles errors via errHandlers or global HandleError
Example:
func Calculate(ctx context.Context, a, b int) (int, error) {
return a + b, nil
}
handler := httpfun.Handler(
httpfun.RequestQueryArgs,
function.MustReflectWrapper("Calculate", Calculate),
httpfun.RespondJSON,
)
http.Handle("/calculate", handler)
Usage: GET /calculate?a=5&b=3 returns: 8
func HandlerNoWrapper ¶
func HandlerNoWrapper(function func(context.Context) ([]byte, error), resultsWriter ResultsWriter, errHandlers ...httperr.Handler) http.HandlerFunc
HandlerNoWrapper creates an http.HandlerFunc from a simple function without using wrappers. The function must have the signature: func(context.Context) ([]byte, error)
This is useful for simple handlers that don't need argument parsing, or when you want to handle the request directly.
Parameters:
- function: Function returning response bytes
- resultsWriter: Formats and writes the bytes to the response
- errHandlers: Optional custom error handlers
Example:
func GetStatus(ctx context.Context) ([]byte, error) {
return []byte(`{"status":"ok"}`), nil
}
http.Handle("/status", httpfun.HandlerNoWrapper(
GetStatus,
httpfun.RespondJSON,
))
func RequestBodyJSONFieldsAsArgs ¶
RequestBodyJSONFieldsAsArgs returns a RequestArgsFunc that parses the body of the request as JSON object with the object field names as argument names and the field values as argument values.
func RequestMultipartFormArgs ¶
RequestMultipartFormArgs returns the multipart form values of the request as string map. If a form field has multiple values, they are joined with ";".
Types ¶
type RequestArgsFunc ¶
RequestArgsFunc is a function type that returns a map of argument names and values for a given HTTP request.
func ConstRequestArg ¶
func ConstRequestArg(name, value string) RequestArgsFunc
ConstRequestArg returns a RequestArgsFunc that returns a constant value for an argument name.
func ConstRequestArgs ¶
func ConstRequestArgs(args map[string]string) RequestArgsFunc
ConstRequestArgs returns a RequestArgsFunc that returns a constant map of argument names and values.
func MergeRequestArgs ¶
func MergeRequestArgs(getters ...RequestArgsFunc) RequestArgsFunc
MergeRequestArgs returns a RequestArgsFunc that merges the arguments of the given getters. Later getters overwrite earlier ones.
func RequestArgFromEnvVar ¶
func RequestArgFromEnvVar(envVar, argName string) RequestArgsFunc
RequestArgFromEnvVar returns a RequestArgsFunc that returns the value of the environment variable envVar as the value of the argument argName.
An error is returned if the environment variable is not set.
func RequestBodyAsArg ¶
func RequestBodyAsArg(argName string) RequestArgsFunc
RequestBodyAsArg returns a RequestArgsFunc that returns the body of the request as the value of the argument argName.
func RequestHeaderArg ¶
func RequestHeaderArg(headerKeyArgName string) RequestArgsFunc
RequestHeaderArg returns a RequestArgsFunc that returns the value of the header headerKeyArgName as the value of the argument headerKeyArgName.
func RequestHeaderAsArg ¶
func RequestHeaderAsArg(headerKey, argName string) RequestArgsFunc
RequestHeaderAsArg returns a RequestArgsFunc that returns the value of the header headerKey as the value of the argument argName.
func RequestHeadersAsArgs ¶
func RequestHeadersAsArgs(headerToArg map[string]string) RequestArgsFunc
RequestHeadersAsArgs returns a RequestArgsFunc that returns the values of the request headers as argument values. The keys of headerToArg are the header keys and the values are the argument names.
func RequestQueryArg ¶
func RequestQueryArg(queryKeyArgName string) RequestArgsFunc
RequestQueryArg returns a RequestArgsFunc that returns the value of the query param queryKeyArgName as the value of the argument queryKeyArgName.
func RequestQueryAsArg ¶
func RequestQueryAsArg(queryKey, argName string) RequestArgsFunc
RequestQueryAsArg returns a RequestArgsFunc that returns the value of the query param queryKey as the value of the argument argName.
type RespondRedirect ¶
type RespondRedirect string
RespondRedirect implements HTTPResultsWriter and http.Handler with for a redirect URL string. The redirect will be done with HTTP status code 302: Found.
func (RespondRedirect) ServeHTTP ¶
func (re RespondRedirect) ServeHTTP(response http.ResponseWriter, request *http.Request)
func (RespondRedirect) WriteResults ¶
func (re RespondRedirect) WriteResults(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error
type RespondRedirectFunc ¶
RespondRedirectFunc implements HTTPResultsWriter and http.Handler with a function that returns the redirect URL. The redirect will be done with HTTP status code 302: Found.
func (RespondRedirectFunc) ServeHTTP ¶
func (f RespondRedirectFunc) ServeHTTP(response http.ResponseWriter, request *http.Request)
func (RespondRedirectFunc) WriteResults ¶
func (f RespondRedirectFunc) WriteResults(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error
type RespondStaticHTML ¶
type RespondStaticHTML string
func (RespondStaticHTML) ServeHTTP ¶
func (html RespondStaticHTML) ServeHTTP(response http.ResponseWriter, _ *http.Request)
func (RespondStaticHTML) WriteResults ¶
func (html RespondStaticHTML) WriteResults(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error
type RespondStaticJSON ¶
type RespondStaticJSON string
func (RespondStaticJSON) ServeHTTP ¶
func (json RespondStaticJSON) ServeHTTP(response http.ResponseWriter, _ *http.Request)
func (RespondStaticJSON) WriteResults ¶
func (json RespondStaticJSON) WriteResults(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error
type RespondStaticPlaintext ¶
type RespondStaticPlaintext string
func (RespondStaticPlaintext) ServeHTTP ¶
func (text RespondStaticPlaintext) ServeHTTP(response http.ResponseWriter, _ *http.Request)
func (RespondStaticPlaintext) WriteResults ¶
func (text RespondStaticPlaintext) WriteResults(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error
type RespondStaticXML ¶
type RespondStaticXML string
func (RespondStaticXML) ServeHTTP ¶
func (xml RespondStaticXML) ServeHTTP(response http.ResponseWriter, _ *http.Request)
func (RespondStaticXML) WriteResults ¶
func (xml RespondStaticXML) WriteResults(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error
type ResultsWriter ¶
type ResultsWriter interface {
// WriteResults writes the results and optionally the resultErr to the response.
// If the method does not handle the resultErr then it should return it
// so it can be handled by the next writer in the chain.
WriteResults(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error
}
ResultsWriter implementations write the results of a function call to an HTTP response.
func RespondContentType ¶
func RespondContentType(contentType string) ResultsWriter
type ResultsWriterFunc ¶
type ResultsWriterFunc func(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error
var RespondDetectContentType ResultsWriterFunc = func(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error { if resultErr != nil || request.Context().Err() != nil { return resultErr } if len(results) != 1 { return fmt.Errorf("RespondDetectContentType needs 1 result, got %d", len(results)) } data, ok := results[0].([]byte) if !ok { return fmt.Errorf("RespondDetectContentType needs []byte result, got %T", results[0]) } response.Header().Add("Content-Type", DetectContentType(data)) _, err := response.Write(data) return err }
var RespondHTML ResultsWriterFunc = func(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error { if resultErr != nil || request.Context().Err() != nil { return resultErr } var buf bytes.Buffer for _, result := range results { if b, ok := result.([]byte); ok { buf.Write(b) } else { fmt.Fprint(&buf, result) } } response.Header().Add("Content-Type", contenttype.HTML) _, err := response.Write(buf.Bytes()) return err }
var RespondJSON ResultsWriterFunc = func(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error { if resultErr != nil || request.Context().Err() != nil { return resultErr } if len(results) == 0 { return nil } var r any if len(results) == 1 { r = results[0] } else { r = results } j, err := encodeJSON(r) if err != nil { return err } response.Header().Set("Content-Type", contenttype.JSON) _, err = response.Write(j) return err }
RespondJSON writes the results of a function call as a JSON to the response. If the function returns one non-error result then it is marshalled as is. If the function returns multiple results then they are marshalled as a JSON array. If the function returns an resultErr then it is returned by this method, so it can be handled by the next writer in the chain. Any resultErr is not handled and will be returned by this method.
var RespondPlaintext ResultsWriterFunc = func(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error { if resultErr != nil || request.Context().Err() != nil { return resultErr } var buf bytes.Buffer fmt.Fprint(&buf, results...) response.Header().Add("Content-Type", contenttype.PlainText) _, err := response.Write(buf.Bytes()) return err }
RespondPlaintext writes the results of a function call as a plaintext to the response using fmt.Fprint to format the results. Spaces are added between results when neither is a string. Any resultErr is not handled and will be returned by this method.
var RespondXML ResultsWriterFunc = func(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error { if resultErr != nil || request.Context().Err() != nil { return resultErr } var buf []byte for i, result := range results { if i > 0 { buf = append(buf, '\n') } b, err := encodeXML(result) if err != nil { return err } buf = append(buf, b...) } response.Header().Set("Content-Type", contenttype.XML) _, err := response.Write(buf) return err }
func RespondBinary ¶
func RespondBinary(contentType string) ResultsWriterFunc
RespondBinary responds with contentType using the binary data from results of type []byte, string, or io.Reader. Any resultErr is not handled and will be returned by this method.
func RespondJSONObject ¶
func RespondJSONObject(resultKeys ...string) ResultsWriterFunc
RespondJSONObject writes the results of a function call as a JSON object to the response. The resultKeys are the keys of the JSON object, naming the function results in order.
If the last result is an error and the resultKeys don't have a key for it then the error is returned unhandled if not nil. If there is a result key for the error then the error is marshalled as JSON string and not returned.
An error is returned if the number of results does not match the number of resultKeys or number of resultKeys minus one if the last result is an error.
func (ResultsWriterFunc) WriteResults ¶
func (f ResultsWriterFunc) WriteResults(results []any, resultErr error, response http.ResponseWriter, request *http.Request) error