Documentation
¶
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type RequestDeadline ¶ added in v0.18.0
RequestDeadline arms a deadline over the ENTIRE request — response headers AND the body stream — by deriving a context.WithTimeout from the request context and serving the downstream handler with it. When the deadline elapses the request context is cancelled; a transport that honors the request context (parapet's upstream transports do) then aborts the in-flight call, including a backend that has already written headers but stalls mid-body.
How it differs from Timeout ¶
- Timeout is a write-header deadline: it fires only until the upstream writes response headers, then stops mattering (its timeoutRW is irrelevant once headers are written). A backend that sends headers and then stalls mid-body is NOT bounded by Timeout. On expiry Timeout writes its own 504 Gateway Timeout response.
- RequestDeadline is a TOTAL request deadline (headers + body). It is a bare context wrapper: no SSE detection, no streaming heuristics, no response buffering. It does NOT write a custom timeout response of its own — the context deadline simply propagates and the downstream handler / transport surfaces the resulting error (typically a 502/504 from pkg/upstream once the context-cancelled call fails).
Motivation ¶
pkg/upstream's LeastConnLoadBalancer MaxConcurrent bulkhead holds a slot until the response body is closed. A backend that writes headers then stalls mid-body keeps its slot indefinitely — no http.Transport timeout covers that stall (ResponseHeaderTimeout bounds only time-to-headers; IdleConnTimeout reaps only idle pooled connections). After MaxConcurrent such stalls the target sheds all traffic permanently: the cap becomes a latch, not a limiter. Capping TOTAL request time via a request-scoped context deadline the transport honors is the in-tree mitigation, and that is exactly what RequestDeadline provides — which Timeout cannot, since it disarms once upstream headers are written.
WARNING: do not blanket-deploy ¶
A total request deadline will KILL legitimate long-lived responses — Server-Sent Events, streaming responses, WebSocket-style upgrades, and large file downloads — because those intentionally keep the body open far longer than any sane header-to-completion bound. Do NOT apply RequestDeadline globally. Apply it PER-ROUTE (via pkg/location or pkg/block) only to endpoints whose total time genuinely should be bounded, and exclude streaming/download routes.
A Timeout (this field) of <= 0 makes ServeHandler a pass-through no-op, matching Timeout.
Example ¶
RequestDeadline bounds the TOTAL request time (headers AND body) via the request context, so it also aborts a backend that writes headers then stalls mid-body — unlike timeout.New/Timeout, which disarms once headers are written.
Apply it PER-ROUTE, never globally: a blanket total deadline would kill legitimate long-lived responses (SSE, streaming, large downloads). Here only the /api prefix is bounded; streaming routes elsewhere are left untouched.
package main
import (
"time"
"github.com/moonrhythm/parapet"
"github.com/moonrhythm/parapet/pkg/location"
"github.com/moonrhythm/parapet/pkg/timeout"
)
func main() {
api := location.Prefix("/api")
api.Use(timeout.NewRequestDeadline(30 * time.Second))
// api.Use(upstream.SingleHost(...)) — the bounded handler.
s := parapet.New()
s.Use(api)
}
Output:
func NewRequestDeadline ¶ added in v0.18.0
func NewRequestDeadline(d time.Duration) *RequestDeadline
NewRequestDeadline creates a RequestDeadline middleware that bounds the TOTAL request time to d.
func (RequestDeadline) ServeHandler ¶ added in v0.18.0
func (m RequestDeadline) ServeHandler(h http.Handler) http.Handler
ServeHandler implements middleware interface
type Timeout ¶ added in v0.18.0
Timeout sets a write header timeout. It fires only until the upstream writes response headers; a backend that writes headers then stalls mid-body is NOT bounded by it — use RequestDeadline for a total-request deadline.
Example ¶
Replace the default 504 response with a custom one by setting TimeoutHandler.
package main
import (
"net/http"
"time"
"github.com/moonrhythm/parapet"
"github.com/moonrhythm/parapet/pkg/timeout"
)
func main() {
m := timeout.New(5 * time.Second)
m.TimeoutHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Retry-After", "10")
http.Error(w, "upstream is taking too long, try again shortly", http.StatusGatewayTimeout)
})
s := parapet.New()
s.Use(m)
}
Output:
func New ¶
New creates timeout middleware
Example ¶
Bound how long the downstream handler may take to start responding: if it hasn't written a header within the deadline, the request's context is cancelled and a 504 Gateway Timeout is sent.
package main
import (
"time"
"github.com/moonrhythm/parapet"
"github.com/moonrhythm/parapet/pkg/timeout"
)
func main() {
s := parapet.New()
s.Use(timeout.New(30 * time.Second))
// s.Use(upstream.SingleHost(...)) — the handler the deadline applies to.
}
Output: