Function-valued header rewrite: Rule.SetFunc #40

Closed
opened 2026-10-26 21:37:28 +00:00 by mikluko · 0 comments
Owner

headers.Rule.Set is a fixed http.Header, built once when the middleware is constructed. That fits a static API key and nothing that changes per request or over time:

  • a short-lived token that has to be refreshed before it expires (an OAuth access token, a cloud STS credential)
  • a credential chosen by the request itself, such as a token scoped to the account named in the path (/repos/{owner}/...)
  • a value fetched from a secret store, where the fetch can fail

Proposal: a function-valued counterpart to Set.

type Rule struct {
	Host   string
	Set    http.Header
	Delete []string

	// SetFunc returns headers to set on rq, replacing each named header as Set does.
	// It runs after Delete and Set. An error answers the request with 502 Bad Gateway,
	// and the request is not forwarded.
	SetFunc func(rq *http.Request) (http.Header, error)
}

Points worth settling:

  • Fail closed. When SetFunc errors, the request must not go upstream without the header the rule was meant to set. Forwarding it bare would send an unauthenticated request at best, and the client's own header at worst if Delete did not strip it. 502 plus a log record via the log middleware helpers.
  • Context. rq.Context() carries cancellation and the trace span, so a slow fetch stops when the client goes away.
  • Read-only request. SetFunc receives the request to decide by method, path or other headers; it returns headers instead of mutating rq, so ordering within the middleware stays defined by Rewrite.
  • Caching is the caller's. The middleware calls SetFunc per matching request; a function wrapping a remote fetch keeps its own cache.

Example:

headers.Rewrite(headers.Rule{
	Host:   "api.example.com",
	Delete: []string{"Authorization"},
	SetFunc: func(rq *http.Request) (http.Header, error) {
		tok, err := tokens.Get(rq.Context(), ownerFromPath(rq.URL.Path))
		if err != nil {
			return nil, err
		}
		return http.Header{"Authorization": {"Bearer " + tok}}, nil
	},
})

The CLI flags stay static; this is a library addition only.

`headers.Rule.Set` is a fixed `http.Header`, built once when the middleware is constructed. That fits a static API key and nothing that changes per request or over time: - a short-lived token that has to be refreshed before it expires (an OAuth access token, a cloud STS credential) - a credential chosen by the request itself, such as a token scoped to the account named in the path (`/repos/{owner}/...`) - a value fetched from a secret store, where the fetch can fail Proposal: a function-valued counterpart to `Set`. ```go type Rule struct { Host string Set http.Header Delete []string // SetFunc returns headers to set on rq, replacing each named header as Set does. // It runs after Delete and Set. An error answers the request with 502 Bad Gateway, // and the request is not forwarded. SetFunc func(rq *http.Request) (http.Header, error) } ``` Points worth settling: - **Fail closed.** When `SetFunc` errors, the request must not go upstream without the header the rule was meant to set. Forwarding it bare would send an unauthenticated request at best, and the client's own header at worst if `Delete` did not strip it. 502 plus a log record via the `log` middleware helpers. - **Context.** `rq.Context()` carries cancellation and the trace span, so a slow fetch stops when the client goes away. - **Read-only request.** `SetFunc` receives the request to decide by method, path or other headers; it returns headers instead of mutating `rq`, so ordering within the middleware stays defined by `Rewrite`. - **Caching is the caller's.** The middleware calls `SetFunc` per matching request; a function wrapping a remote fetch keeps its own cache. Example: ```go headers.Rewrite(headers.Rule{ Host: "api.example.com", Delete: []string{"Authorization"}, SetFunc: func(rq *http.Request) (http.Header, error) { tok, err := tokens.Get(rq.Context(), ownerFromPath(rq.URL.Path)) if err != nil { return nil, err } return http.Header{"Authorization": {"Bearer " + tok}}, nil }, }) ``` The CLI flags stay static; this is a library addition only.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
mikluko/multiproxy#40
No description provided.