A body cap bounds how many input bytes a request can make your application
read. Choose a ceiling for the endpoint before decoding: a small invoice JSON
request should not inherit the space allowance needed by an upload route.
The bodylimit middleware applies that ceiling before handlers and other
body-reading middleware.
Set a Ceiling
With a fresh router named routes, import
arkoren.dev/core/http/middleware/bodylimit and allow at most 64 KiB:
routes.Use(bodylimit.Middleware(func(limit *bodylimit.Builder) {
limit.Limit(64 << 10)
}))
Register routes afterward. Without a configured limit, the middleware uses 10 MiB, matching the request body helpers’ default. Zero and negative middleware limits also select 10 MiB; they do not disable the cap or require an empty body.
The limit bounds input bytes, not decoded memory, temporary-file usage from later processing, or application work. Use validation and processing limits where decoded input can expand or trigger expensive operations.
Handle Rejections
If Content-Length exceeds the ceiling, the middleware returns
request.ErrBodyTooLarge (413) before calling the next layer. Otherwise it
wraps the body with http.MaxBytesReader: unknown-length or understated bodies
fail only when downstream code reads far enough. The middleware does not
pre-read or verify an unread remainder.
The request helpers convert a native limit-read error
to the same 413 problem. If your code reads r.Body directly, handle
*http.MaxBytesError yourself. In a raw-reader fragment using io, errors,
net/http, and arkoren.dev/core/http/request:
data, err := io.ReadAll(r.Body)
var tooLarge *http.MaxBytesError
if errors.As(err, &tooLarge) {
return request.ErrBodyTooLarge
}
Other read failures remain errors to handle before using data. Returning the
raw MaxBytesError alone does not give it the helper’s HTTP problem identity.
Return parsing and size errors before committing a response; after output
starts, neither a 413 nor a malformed-input problem can replace it.
A nil body is empty input, including with request helpers: raw reads return no data, while JSON and XML return their malformed-body problems. The declared-length check still runs first, even for a nil body.
Middleware puts the cap in context and replaces the body only on a shallow request copy. It does not replace the caller’s context/body fields, read the body, or close it. The underlying non-nil stream remains shared: downstream reads consume it, and closing the wrapper closes that stream.
Scope Larger Inputs
An outer ceiling cannot be raised by another bodylimit middleware,
request.MaxSize, or request.Unlimited. Put larger routes outside the
smaller scope from the start. On a fresh router without the global cap above,
with existing createInvoice and uploadAttachment handlers:
routes.Group("/invoices", func(invoices *router.Router) {
invoices.Use(bodylimit.Middleware(func(limit *bodylimit.Builder) {
limit.Limit(64 << 10)
}))
invoices.Post("/", createInvoice)
})
The ordinary endpoint now has its own cap. Add the upload as a separate scope, not a child of that group:
routes.With(bodylimit.Middleware(func(limit *bodylimit.Builder) {
limit.Limit(12 << 20)
})).Post("/attachments", uploadAttachment)
That route allows 12 MiB, subject to any additional parser limit. Apply the middleware before anything that reads the body. A webhook receiver also has its own body policy; any surrounding middleware remains a ceiling above it.
Tighten One Read
Inside a cap, a handler can ask its decoder to accept less. With an existing
CreateInvoice input struct:
input, err := request.JSON[CreateInvoice](r, request.MaxSize(16<<10))
MaxSize(0) permits only empty input, unlike middleware Limit(0).
A negative per-call size selects the 10 MiB default. Options apply in order;
a later size option replaces an earlier per-call setting, but no option
removes the upstream reader’s cap.
Unlimited() removes the helper’s own cap and ignores a stored context limit,
not a ceiling already installed on the body. Use it only when memory use is
bounded another way; uncapped raw reads let a hostile client exhaust memory.
For configuration and per-call options, see the
bodylimit and
request API references.