Documentation
¶
Overview ¶
Package httpserver provides typed request binding and response construction on top of the standard library http.ServeMux.
Request flow ¶
Create a server with NewServer, optionally derive routers with Router.Group, and register routes with Router.Handle.
RequestParser turns a typed request handler into a Handler:
type GetUserRequest struct {
ID string `url:"id" validate:"required"`
Verbose bool `query:"verbose" default:"false"`
}
router.Handle("GET /users/{id}", RequestParser(
func(ctx *Context, request GetUserRequest) {
ctx.NewResponse(http.StatusOK).JsonBody(map[string]any{
"id": request.ID,
"verbose": request.Verbose,
})
},
))
MiddlewareParser provides the same typed request binding for Middleware.
For each request, defaults are applied first, request values are bound next, and validation runs last. The typed handler or middleware runs only when all steps succeed.
Route patterns use standard http.ServeMux syntax. URL tags bind wildcards from the matched pattern.
Request tags ¶
Request fields are bound with tags of the form `source:"name"`:
type Request struct {
ID string `url:"id"`
Search string `query:"q"`
Token string `header:"Authorization"`
}
The supported sources are:
header HTTP headers cookie cookies query URL query parameters url ServeMux wildcards form application/x-www-form-urlencoded fields json JSON object fields multipart multipart body parts (stream; see below) body raw request body (stream; see below)
Named values are converted to the destination field type. Conversion failures are request errors and prevent the typed handler or middleware from running.
An empty tag binds the complete source instead of one named value:
header:"" -> http.Header cookie:"" -> KeyValues query:"" -> KeyValues url:"" -> KeyValue form:"" -> KeyValues
`json:""` is slightly different: it decodes the complete JSON value directly into the field.
For a source, use either named fields or one whole-source field; do not mix both forms.
Multipart and raw bodies are exposed as streams:
multipart:"" -> *multipart.Reader body:"" -> io.ReadCloser body:"type/subtype ..." -> io.ReadCloser for the listed media types
The framework applies no size cap or read timeout to these streams; the handler owns any size or time budget (e.g. via http.MaxBytesReader, the request context, or a self-imposed deadline). Form and JSON bindings are bounded by maxBodyLength (1 MiB) and maxReadBodyDuration (5s).
`default:"value"` supplies a value before request binding.
`validate:"rule"` validates the completed request after all binding has finished.
Binding ¶
A request starts at its zero value. Values are applied in this order:
default -> header -> cookie -> query -> URL -> body -> validation
Later sources may overwrite values supplied by earlier sources.
Body binding is considered for POST, PUT, PATCH, and DELETE requests. The body binder is selected from form, JSON, multipart, or raw body according to the request Content-Type.
Form and JSON bodies are buffered and decoded before the typed handler runs. Multipart and raw body tags instead expose the live request stream and should be consumed during the handler or middleware that receives them. The framework applies no size or time cap to these streams; the handler owns any budget.
Responses and middleware ¶
Handlers construct responses with Context.NewResponse and Response.
A response is written only after the complete middleware and handler chain returns. Middleware can therefore inspect or replace the downstream response after calling next:
func(ctx *Context, next func()) {
// Before the downstream chain.
next()
// After the downstream chain.
}
Middleware may short-circuit a request by returning without calling next.
If the chain completes without creating a response, Router.Handle returns 500 Internal Server Error. Servers created by NewServer also recover panics at the HTTP boundary and return 500 when no final response has been committed.
Index ¶
- type Context
- type Handler
- type KeyValue
- type KeyValues
- type Middleware
- type MiddlewareHandler
- type RequestHandler
- type Response
- func (r Response) Body() any
- func (r Response) BytesBody(body []byte)
- func (r Response) Cookie(cookie http.Cookie)
- func (r Response) Header() http.Header
- func (r Response) JsonBody(body any)
- func (r Response) MarshalZerologObject(e *zerolog.Event)
- func (r Response) OctetsBody(body []byte)
- func (r Response) PlainTextBody(body string)
- func (r Response) Status() int
- func (r Response) StreamBody(body func(io.Writer) error)
- func (r Response) StringBody(body string)
- type Router
- type ServerConfig
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Context ¶
type Context struct {
// contains filtered or unexported fields
}
Context is the per-request state passed to Handler and Middleware. It implements context.Context by delegating to the underlying HTTP request and owns the response state assembled by the handler chain.
Context values are created by Router.Handle. The zero value is invalid, and a Context must not be copied after first use.
func (*Context) Done ¶ added in v0.0.34
func (c *Context) Done() <-chan struct{}
Done delegates to the HTTP request context.
func (*Context) NewResponse ¶ added in v0.0.34
NewResponse starts a new response with status and returns its handle. It clears the previous body and all response headers. The response is not written until the Router.Handle middleware and handler chain returns.
NewResponse panics unless status is between 200 and 599.
func (*Context) Response ¶
Response returns a handle to the current response without changing it. Its status is zero until Context.NewResponse is called.
type Handler ¶ added in v0.0.34
type Handler = func(ctx *Context)
Handler handles one HTTP request through a Context. A handler normally creates or replaces the response with Context.NewResponse. If the complete middleware and handler chain returns without creating a response, Router.Handle sends 500 Internal Server Error.
func RequestParser ¶
func RequestParser[Request any](handler RequestHandler[Request]) Handler
RequestParser converts a typed RequestHandler into a Handler.
Request must be a non-pointer struct. Its default values and request-binding tag layout are checked when RequestParser is called; an invalid request definition panics. For each HTTP request, RequestParser creates a fresh Request value, applies defaults, binds request data, validates the result, and then calls handler.
Binding or validation failures configure an empty HTTP error response and do not call handler. RequestParser does not write the response itself; the enclosing Router.Handle writes it after the middleware and handler chain returns. Panics from handler propagate to the server boundary, where servers created by NewServer recover them.
type KeyValue ¶
KeyValue contains all named ServeMux path wildcard values for an empty `url:""` request tag.
type KeyValues ¶
KeyValues contains all values for an empty `cookie:""`, `query:""`, or `form:""` request tag.
type Middleware ¶
type Middleware = func(ctx *Context, next func())
Middleware wraps a Handler in a chain. Call next to continue to the next middleware or the route handler. Returning without calling next short-circuits the chain. Code after next runs on the way out and may inspect or replace the downstream response through Context.Response or Context.NewResponse.
func MiddlewareParser ¶ added in v0.0.34
func MiddlewareParser[Request any](handler MiddlewareHandler[Request]) Middleware
MiddlewareParser converts a typed MiddlewareHandler into Middleware. Request defaults, binding, and validation follow the same rules as RequestParser.
Parser middleware shares the same Context with downstream middleware and the route handler. Request bodies are not buffered or rewound, so a body consumed by one parser cannot be parsed again downstream.
A binding or validation failure configures an error response and stops the chain. The response is written later by Router.Handle.
type MiddlewareHandler ¶ added in v0.0.34
MiddlewareHandler handles a parsed request around the next middleware or route handler. Call next to continue the chain. Returning without calling next short-circuits the chain; code after next may inspect or replace the downstream response.
type RequestHandler ¶
RequestHandler handles a request after defaults, request binding, and validation have completed. The handler normally creates its response through Context.NewResponse.
type Response ¶
type Response struct {
// contains filtered or unexported fields
}
Response is a handle to response state owned by a Context. Copies share the same state. The zero value is invalid.
func (Response) Body ¶ added in v0.0.34
Body returns the configured body value, or nil if no body is set.
func (Response) Header ¶
Header returns the live response header map. A later Context.NewResponse call clears it.
func (Response) JsonBody ¶
JsonBody stores body for JSON marshaling when the response is written. Successful marshaling sets Content-Type to "application/json; charset=utf-8". A marshal failure writes 500 Internal Server Error with an empty body.
func (Response) MarshalZerologObject ¶
MarshalZerologObject implements zerolog.LogObjectMarshaler for the configured status, headers, and body.
func (Response) OctetsBody ¶
OctetsBody sets body with Content-Type "application/octet-stream".
func (Response) PlainTextBody ¶
PlainTextBody sets body with Content-Type "text/plain; charset=utf-8".
func (Response) Status ¶
Status returns the configured HTTP status, or zero before Context.NewResponse is called.
func (Response) StreamBody ¶
StreamBody sets a body writer without setting Content-Type. The HTTP status is committed before body runs, so an error returned by body can be logged but cannot change the response status.
func (Response) StringBody ¶
StringBody sets a raw string body without setting Content-Type.
type Router ¶ added in v0.0.34
type Router struct {
// contains filtered or unexported fields
}
Router registers Handler values on a shared http.ServeMux with an ordered middleware chain. Routers returned by Router.Group share the same ServeMux but keep independent middleware slices.
The zero value is invalid. Create a Router with NewServer or derive one from an existing Router with Router.Group.
func NewServer ¶
func NewServer(config *ServerConfig) Router
NewServer creates a Router backed by a new http.ServeMux and registers its HTTP server with the ctrl lifecycle. The server wraps all requests with request/response logging and panic recovery.
The server listens on ":<config.Port>" when the lifecycle starts and shuts down during cleanup. config must already have defaults applied and be validated before NewServer is called, and it should not be modified afterward. If serving fails unexpectedly, config.ShutdownOnError controls whether the application lifecycle is canceled.
func (Router) Group ¶ added in v0.0.34
func (r Router) Group(middlewares ...Middleware) Router
Group returns a Router that shares r's routes and logger and appends middlewares to r's middleware chain. Group does not mutate r and does not retain the caller's middleware slice.
func (Router) Handle ¶ added in v0.0.34
Handle registers handler for pattern using http.ServeMux pattern syntax. Requests run r's middleware in order followed by handler. Middleware may stop the chain by returning without calling next.
The response is written after the complete chain returns, so middleware may inspect or replace a downstream response after next returns. If the chain returns without creating a response, Handle writes 500 Internal Server Error. Registration errors and pattern conflicts follow http.ServeMux behavior.
type ServerConfig ¶
type ServerConfig struct {
// Port is the TCP port to listen on all interfaces.
Port uint16 `cfg:"port" validate:"required" default:"8080"`
// ReadHeaderTimeout limits time spent reading request headers, in seconds.
ReadHeaderTimeout int `cfg:"read_header_timeout" validate:"min=1,max=60" default:"5"`
// IdleTimeout limits idle keep-alive time, in seconds.
IdleTimeout int `cfg:"idle_timeout" validate:"min=1,max=3600" default:"60"`
// MaxHeaderBytes limits request header size in bytes.
MaxHeaderBytes int `cfg:"max_header_bytes" validate:"min=0,max=65536" default:"4096"`
// ShutdownOnError cancels the application when serving fails unexpectedly.
ShutdownOnError bool `cfg:"shutdown_on_error" default:"true"`
}
ServerConfig configures the http.Server registered by NewServer. Timeout values are in seconds. NewServer does not apply defaults or validate the configuration tags.