Skip to content

Request Helpers

The contract/request package provides convenience functions for extracting data from *http.Request. These are thin wrappers over Go’s standard library that reduce boilerplate and provide consistent patterns with default value support.

Extract path parameters set by the router’s {name} patterns:

import "github.com/studiolambda/cosmos/contract/request"
func getUser(w http.ResponseWriter, r *http.Request) error {
id := request.Param(r, "id")
// ...
}

Use ParamOr to provide a default value when the parameter is missing or empty:

page := request.ParamOr(r, "page", "1")

Parse path parameters directly as integers:

id, err := request.ParamInt(r, "id")
if err != nil {
return err // returns error if not a valid integer
}
// With a default value
page := request.ParamIntOr(r, "page", 1)

Read URL query string values:

search := request.Query(r, "q") // returns "" if not present
limit := request.QueryOr(r, "limit", "20") // returns "20" if not present

Check if a query parameter exists (even if empty):

if request.HasQuery(r, "verbose") {
// ?verbose or ?verbose= was in the URL
}

HasQuery distinguishes between a missing parameter and one present with an empty value. QueryOr only returns the default when the parameter does not exist at all — if it exists with an empty value, the empty string is returned.

Parse query parameters directly as integers:

limit, err := request.QueryInt(r, "limit")
offset := request.QueryIntOr(r, "offset", 0)

Read request headers:

contentType := request.Header(r, "Content-Type")
token := request.HeaderOr(r, "Authorization", "none")

Check if a header is present and non-empty:

if request.HasHeader(r, "X-Request-ID") {
// header exists with a non-empty value
}

Get all values for a multi-value header:

acceptValues := request.HeaderValues(r, "Accept")

Read cookies from the request:

cookie := request.Cookie(r, "session_id") // returns *http.Cookie or nil
value := request.CookieValue(r, "session_id") // returns the value string or ""

With a default value:

theme := request.CookieValueOr(r, "theme", "dark")

Read the raw request body:

bytes, err := request.Bytes(r)
text, err := request.String(r)

Decode JSON or XML into a typed value using generics:

type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
func createUser(w http.ResponseWriter, r *http.Request) error {
body, err := request.JSON[CreateUserRequest](r)
if err != nil {
return err
}
// body is of type CreateUserRequest
// ...
}

XML works the same way:

type Order struct {
XMLName xml.Name `xml:"order"`
ID string `xml:"id,attr"`
Total float64 `xml:"total"`
}
order, err := request.XML[Order](r)

Both JSON and XML use streaming decoders for memory efficiency. The request body is consumed on first read and cannot be read again.

To prevent out-of-memory attacks from oversized request bodies, use the size-limited variants:

// JSON with 1MB limit
body, err := request.LimitedJSON[CreateUserRequest](r, 1<<20)
// JSON with default 10MB limit (pass -1)
body, err := request.LimitedJSON[CreateUserRequest](r, -1)
// Strict JSON that rejects unknown fields
body, err := request.StrictJSON[CreateUserRequest](r)
// Strict JSON with size limit
body, err := request.StrictLimitedJSON[CreateUserRequest](r, 1<<20)
// Size-limited raw bytes and strings
data, err := request.LimitedBytes(r, 1<<20)
text, err := request.LimitedString(r, 1<<20)
// Size-limited XML
order, err := request.LimitedXML[Order](r, 1<<20)

The unlimited JSON, XML, Bytes, and String functions still exist but should be avoided in production — prefer their Limited variants.

Access the session from the request context (requires the session middleware):

session, ok := request.Session(r)
if !ok {
// no session middleware configured
}
value, exists := session.Get("user_id")

Use MustSession when you know the session middleware is present — it panics with a Problem Details error if it isn’t:

session := request.MustSession(r)
session.Put("user_id", "123")

For applications using multiple session stores with different context keys:

session, ok := request.SessionKeyed(r, myCustomKey)
session := request.MustSessionKeyed(r, myCustomKey)

Access the lifecycle hooks from the request context:

hooks := request.Hooks(r)
hooks.AfterResponse(func(err error) {
// cleanup logic
})

This panics if the hooks context is not present. In the framework, hooks are always available because the handler’s ServeHTTP method injects them automatically.

For middleware or code that may run outside the framework’s handler lifecycle, use the non-panicking variant:

hooks, ok := request.TryHooks(r)
if ok {
hooks.AfterResponse(func(err error) {
// cleanup logic
})
}