httputil

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 24, 2026 License: Apache-2.0 Imports: 19 Imported by: 1

Documentation

Overview

Package httputil simplifies the decoding of HTTP requests (REST API) into Go structs for easier consumption. It implements decoding based on the OpenAPI 3.1 specification.

In general, it is better to use code generation from the API specification,

Key Features:

  • Decodes path parameters, query parameters, request headers (not yet implemented), and request body.
  • Supports different query parameter styles: form, space-delimited, pipe-delimited, and deep (nested) objects.
  • Allows customization of field names, required parameters, and decoding behavior through struct tags.
  • Handles different body content types (JSON, XML) based on the Accept header or a specified field tag.

When using Go standard packages, the code might look something like:

func handler(w http.ResponseWriter, r *http.Request) {
	var (
		err error
		req struct {
			ID     int     // path value
			Expand *string // query param
		}
	)

	if req.ID, err = strconv.Atoi(r.PathValue("id")); err != nil {
		// handle error
		return
	}

	if expand := r.URL.Query().Get("expand"); expand != "" {
		req.Expand = &expand
	}
}

The request package allows to bind data using field tags. Effectively, reducing the boilerplate code significantly.

func handler(w http.ResponseWriter, r *http.Request) {
	var req struct {
		ID     int     `path:"id"`      // path value
		Expand *string `query:"expand"` // query param
	}

	err := Decode(r, &req)
	if err != nil {
		// handle error
		return
	}
}

Package pagination provides support for pagination requests and responses.

Index

Constants

View Source
const (
	QueryStyleForm           = "form"           // imploded "?id=3,4,5" or exploded "?id=3&id=4&id=5"
	QueryStyleSpaceDelimited = "spaceDelimited" // imploded "?id=3%204%205" or exploded "?id=3&id=4&id=5"
	QueryStylePipeDelimited  = "pipeDelimited"  // imploded "?id=3|4|5" or exploded "?id=3&id=4&=5"
	QueryStyleDeepObject     = "deepObject"     // exploded "?id[role]=admin&id[firstName]=Alex"
)

List of supported serialization styles.

Variables

View Source
var (
	// DefaultPageSize specifies the default page size
	DefaultPageSize = 100
	// MaxPageSize specifies the maximum page size
	MaxPageSize = 1000
	// PageVar specifies the query parameter name for page number
	PageVar = "page"
	// PageSizeVar specifies the query parameter name for page size
	PageSizeVar = "per_page"
)

Functions

func Decode added in v0.4.0

func Decode(r *http.Request, i any) error

Decode decodes an HTTP request into a Go struct according to OpenAPI 3 specification.

func QueryParam

func QueryParam[T ParamTypes, K FromConstraint](from K, param string, validators ...validator.ValidatorFunc[T]) (T, error)

func QueryParamOrDefault

func QueryParamOrDefault[T ParamTypes, K FromConstraint](from K, param string, defValue T, validators ...validator.ValidatorFunc[T]) T

func Values added in v0.7.0

func Values(v any) (url.Values, error)

Values returns the url.Values encoding of v.

Values expects to be passed a struct, and traverses it recursively using the following encoding rules.

Each exported struct field is encoded as a URL parameter unless

  • the field's tag is "-", or
  • the field is empty and its tag specifies the "omitempty" option

The empty values are false, 0, any nil pointer or interface value, any array slice, map, or string of length zero, and any type (such as time.Time) that returns true for IsZero().

The URL parameter name defaults to the struct field name but can be specified in the struct field's tag value. The "query" key in the struct field's tag value is the key name, followed by an optional comma and options. For example:

// Field is ignored by this package.
Field int `url:"-"`

// Field appears as URL parameter "myName".
Field int `url:"myName"`

// Field appears as URL parameter "myName" and the field is omitted if
// its value is empty
Field int `url:"myName,omitempty"`

// Field appears as URL parameter "Field" (the default), but the field
// is skipped if empty.  Note the leading comma.
Field int `url:",omitempty"`

For encoding individual field values, the following type-dependent rules apply:

Boolean values default to encoding as the strings "true" or "false". Including the "int" option signals that the field should be encoded as the strings "1" or "0".

time.Time values default to encoding as RFC3339 timestamps. Including the "unix" option signals that the field should be encoded as a Unix time (see time.Unix()). The "unixmilli" and "unixnano" options will encode the number of milliseconds and nanoseconds, respectively, since January 1, 1970 (see time.UnixNano()). Including the "layout" struct tag (separate from the "query" tag) will use the value of the "layout" tag as a layout passed to time.Format. For example:

// Encode a time.Time as YYYY-MM-DD
Field time.Time `layout:"2006-01-02"`

Slice and Array values default to encoding as multiple URL values of the same name. Including the "comma" option signals that the field should be encoded as a single comma-delimited value. Including the "space" option similarly encodes the value as a single space-delimited string. Including the "semicolon" option will encode the value as a semicolon-delimited string. Including the "brackets" option signals that the multiple URL values should have "[]" appended to the value name. "numbered" will append a number to the end of each incidence of the value name, example: name0=value0&name1=value1, etc. Including the "del" struct tag (separate from the "query" tag) will use the value of the "del" tag as the delimiter. For example:

// Encode a slice of bools as ints ("1" for true, "0" for false),
// separated by exclamation points "!".
Field []bool `url:",int" del:"!"`

Anonymous struct fields are usually encoded as if their inner exported fields were fields in the outer struct, subject to the standard Go visibility rules. An anonymous struct field with a name given in its URL tag is treated as having that name, rather than being anonymous.

Non-nil pointer values are encoded as the value pointed to.

Nested structs have their fields processed recursively and are encoded including parent fields in value names for scoping. For example,

"user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO"

All other values are encoded using their default string representation.

Multiple fields that encode to the same URL parameter name will be included as multiple URL values of the same name.

Types

type Chain

type Chain struct {
	// contains filtered or unexported fields
}

Chain acts as a list of http.Handler constructors. Chain is effectively immutable: once created, it will always hold the same set of constructors in the same order.

func NewChain

func NewChain(constructors ...Constructor) Chain

NewChain creates a new chain, memorizing the given list of middleware constructors. New serves no other function, constructors are only called upon a call to Then().

func (Chain) Append

func (c Chain) Append(constructors ...Constructor) Chain

Append extends a chain, adding the specified constructors as the last ones in the request flow.

Append returns a new chain, leaving the original one untouched.

stdChain := httputil.NewChain(m1, m2)
extChain := stdChain.Append(m3, m4)
// requests in stdChain go m1 -> m2
// requests in extChain go m1 -> m2 -> m3 -> m4

func (Chain) Extend

func (c Chain) Extend(chain Chain) Chain

Extend extends a chain by adding the specified chain as the last one in the request flow.

Extend returns a new chain, leaving the original one untouched.

stdChain := httputil.NewChain(m1, m2)
ext1Chain := httputil.NewChain(m3, m4)
ext2Chain := stdChain.Extend(ext1Chain)
// requests in stdChain go  m1 -> m2
// requests in ext1Chain go m3 -> m4
// requests in ext2Chain go m1 -> m2 -> m3 -> m4

Another example:

 aHtmlAfterNosurf := httputil.NewChain(m2)
	aHtml := httputil.NewChain(m1, func(h http.Handler) http.Handler {
		csrf := nosurf.New(h)
		csrf.SetFailureHandler(aHtmlAfterNosurf.ThenFunc(csrfFail))
		return csrf
	}).Extend(aHtmlAfterNosurf)
		// requests to aHtml hitting nosurfs success handler go m1 -> nosurf -> m2 -> target-handler
		// requests to aHtml hitting nosurfs failure handler go m1 -> nosurf -> m2 -> csrfFail

func (Chain) Then

func (c Chain) Then(h http.Handler) http.Handler

Then chains the middleware and returns the final http.Handler.

New(m1, m2, m3).Then(h)

is equivalent to:

m1(m2(m3(h)))

When the request comes in, it will be passed to m1, then m2, then m3 and finally, the given handler (assuming every middleware calls the following one).

A chain can be safely reused by calling Then() several times.

stdStack := httputil.NewChain(ratelimitHandler, csrfHandler)
indexPipe = stdStack.Then(indexHandler)
authPipe = stdStack.Then(authHandler)

Note that constructors are called on every call to Then() and thus several instances of the same middleware will be created when a chain is reused in this way. For proper middleware, this should cause no problems.

Then() treats nil as http.DefaultServeMux.

func (Chain) ThenFunc

func (c Chain) ThenFunc(fn http.HandlerFunc) http.Handler

ThenFunc works identically to Then, but takes a HandlerFunc instead of a Handler.

The following two statements are equivalent:

c.Then(http.HandlerFunc(fn))
c.ThenFunc(fn)

ThenFunc provides all the guarantees of Then.

type Client

type Client struct {
	// contains filtered or unexported fields
}

func NewClient

func NewClient(uri string, options ...ClientOption) *Client

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, rawurl string, options ...RequestOption) error

helper function for making an http DELETE request.

func (*Client) Get

func (c *Client) Get(ctx context.Context, rawurl string, out any, options ...RequestOption) error

helper function for making an http GET request.

func (*Client) Patch

func (c *Client) Patch(ctx context.Context, rawurl string, in, out any, options ...RequestOption) error

helper function for making an http PATCH request.

func (*Client) Post

func (c *Client) Post(ctx context.Context, rawurl string, in, out any, options ...RequestOption) error

helper function for making an http POST request.

func (*Client) SetClient

func (c *Client) SetClient(client *http.Client)

SetClient sets the default http client. This can be used in conjunction with golang.org/x/oauth2 to authenticate requests to the server.

func (*Client) SetDebug

func (c *Client) SetDebug(debug bool)

SetDebug sets the debug flag. When the debug flag is true, the http.Resposne body to stdout which can be helpful when debugging.

type ClientOption

type ClientOption interface {
	Apply(r *Client)
}

type ClientOptionFunc

type ClientOptionFunc func(c *Client)

func WithHTTPTimeout

func WithHTTPTimeout(timeout time.Duration) ClientOptionFunc

WithHTTPTimeout sets timeout

func (ClientOptionFunc) Apply

func (f ClientOptionFunc) Apply(c *Client)

type Constructor

type Constructor func(http.Handler) http.Handler

A constructor for a piece of middleware.

type Decoder added in v0.7.0

type Decoder struct {
	// contains filtered or unexported fields
}

Decoder decodes (binds) net/http.Request data into Go struct.

func NewDecoder added in v0.7.0

func NewDecoder(opts ...Opt) Decoder

NewDecoder returns a new decoder to decode net/http.Request data into Go struct.

By default:

  • the decoder reads path value using https://pkg.go.dev/net/http#Request.PathValue. Override with [request.PathValue] option.
  • the decoder uses exploded query parameters. Override with [request.QueryImplode] or [request.QueryExplode] option.
  • the decoder uses [request.QueryStyleForm] query parameter style. Override with [request.QueryStyle] option.

func (Decoder) Decode added in v0.7.0

func (d Decoder) Decode(r *http.Request, i any) error

Decode decodes an HTTP request into Go struct.

Decoding of query params conforms to the Query Serialization spec.

// default - ?id=1&id=2&id=3
var req struct {
	ID []int // case insensitive match of field name and query parameter
}

// comma delimited - ?id=1,2,3
var req struct {
	ID []int  `query:"id,form"`     // implicitly implode
	IDs []int `query:"id,implode` // form by default
}

// pipe delimited - ?id=1|2|3
var req struct {
	ID []int `query:"id,pipeDelimited" // implicitly implode
}

// space delimited - ?id=1%202%203
var req struct {
	ID []int `query:"id,spaceDelimited"` // implicitly imploded
}

// set different name - ?id=1,2,3
var req struct {
	FilterClientIDs []int `query:"id,form"` // implicitly imploded
}

Use encoding.TextUnmarshaler to implement custom decoding.

Decoding of request headers is NOT yet implemented.

Decoding of request body is simple - it uses either json or xml unmarshaller:

type Entity struct {
	ID int
}

// If no field tag value specified, "Accept" request header is used to determine decoding. Uses json by default.
var req struct {
	Entity `body:"entity"`
}

// Always use JSON umarshalling, ignore "Accept" request header:
var req struct {
	Entity `body:"entity,json"`
}

// Always use XML unmarshalling, ignore "Accept" request header:
var req struct {
	Entity `body:"entity,xml"`
}

type Encoder added in v0.7.0

type Encoder interface {
	EncodeValues(key string, v *url.Values) error
}

Encoder is an interface implemented by any type that wishes to encode itself into URL values in a non-standard way.

type ErrorResponse

type ErrorResponse struct {
	Status  int
	Payload string
}

func (ErrorResponse) Error

func (r ErrorResponse) Error() string

type FromConstraint

type FromConstraint interface {
	*http.Request | *url.URL | url.Values
}

type Opt added in v0.7.0

type Opt interface {
	// contains filtered or unexported methods
}

Opt allows to override default [request.Decoder] options.

func PathValue added in v0.7.0

func PathValue(pathValue func(r *http.Request, name string) string) Opt

PathValue allows to override default path parameter getter in [request.NewDecoder].

func QueryExplode added in v0.7.0

func QueryExplode() Opt

QueryExplode sets each value in a separate query parameter (e.g "?id=1&id=2"). The query delimiter is ignored.

func QueryImplode added in v0.7.0

func QueryImplode() Opt

QueryImplode sets all values in a single query parameter and all values are separated by a delimiter (e.g "?id=1,2").

func QueryStyle added in v0.7.0

func QueryStyle(style string) Opt

QueryStyle allows to override default query parameter style:

  • [request.QueryStyleForm]
  • [request.QueryStyleSpaceDelimited]
  • [request.QueryStylePipeDelimited]
  • [request.QueryStyleDeepObject]

type Pages

type Pages[T any] struct {
	Page       int `json:"page"`
	PerPage    int `json:"per_page"`
	PageCount  int `json:"page_count"`
	TotalCount int `json:"total_count"`
	Items      []T `json:"items"`
}

Pages represents a paginated list of data items.

func NewPages

func NewPages[T any](page, perPage, total int) *Pages[T]

NewPages creates a new Pages instance. The page parameter is 1-based and refers to the current page index/number. The perPage parameter refers to the number of items on each page. And the total parameter specifies the total number of data items. If total is less than 0, it means total is unknown.

func NewPagesWithItems added in v0.3.1

func NewPagesWithItems[T any](page, perPage, total int, items []T) *Pages[T]

func PagesFromReqAndData added in v0.3.1

func PagesFromReqAndData[T any](req *http.Request, fn func(limit, offset int) ([]T, int64, error)) (*Pages[T], error)

PagesFromRequest creates a Pages object using the query parameters found in the given HTTP request. count stands for the total number of items. Use -1 if this is unknown.

func PagesFromRequest

func PagesFromRequest[T any](req *http.Request, count int) *Pages[T]

PagesFromRequest creates a Pages object using the query parameters found in the given HTTP request. count stands for the total number of items. Use -1 if this is unknown.

func (*Pages[T]) BuildLinkHeader

func (p *Pages[T]) BuildLinkHeader(baseURL string, defaultPerPage int) string

BuildLinkHeader returns an HTTP header containing the links about the pagination.

func (p *Pages[T]) BuildLinks(baseURL string, defaultPerPage int) [4]string

BuildLinks returns the first, prev, next, and last links corresponding to the pagination. A link could be an empty string if it is not needed. For example, if the pagination is at the first page, then both first and prev links will be empty.

func (*Pages[T]) Limit

func (p *Pages[T]) Limit() int

Limit returns the LIMIT value that can be used in a SQL statement.

func (*Pages[T]) Offset

func (p *Pages[T]) Offset() int

Offset returns the OFFSET value that can be used in a SQL statement.

type ParamTypes

type ParamTypes interface {
	~string | *string | ~int | ~*int | ~int64 | ~*int64 | ~bool | ~*bool | ~float64 | time.Time |
		~[]string | ~[]int | ~[]int64 | ~[]bool | ~[]float64 | ~[]time.Time
}

type RequestOption

type RequestOption interface {
	Apply(r *http.Request)
}

type RequestOptionFunc

type RequestOptionFunc func(r *http.Request)

func WithAcceptVersion

func WithAcceptVersion(value string) RequestOptionFunc

func WithAuthHeader

func WithAuthHeader(value string) RequestOptionFunc

func (RequestOptionFunc) Apply

func (f RequestOptionFunc) Apply(r *http.Request)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL