response

package
v0.85.0-pre.1 Latest Latest
Warning

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

Go to latest
Published: Feb 15, 2026 License: BSD-3-Clause Imports: 8 Imported by: 0

README

kit/response

github.com/vormadev/vorma/kit/response

Helpers for writing HTTP responses directly (Response) or building/merging deferred responses (Proxy) before applying to a real http.ResponseWriter.

Import

import "github.com/vormadev/vorma/kit/response"

Choose The Right Type

  • Use Response in normal handlers that already own http.ResponseWriter.
  • Use Proxy when multiple tasks/goroutines independently produce response decisions and you need deterministic merge rules.

Response Quick Start

func handle(w http.ResponseWriter, r *http.Request) {
	res := response.New(w)

	if r.Method != http.MethodPost {
		res.MethodNotAllowed("POST only")
		return
	}

	res.JSON(map[string]any{"ok": true})
}

Proxy Quick Start

func handle(w http.ResponseWriter, r *http.Request) {
	a := response.NewProxy()
	a.SetStatus(http.StatusOK)
	a.AddHeader("X-Trace", "a")

	b := response.NewProxy()
	b.SetCookie(&http.Cookie{Name: "session", Value: "v2"})

	merged := response.MergeProxyResponses(a, b)
	merged.ApplyToResponseWriter(w, r)
}

Redirect Behavior

Two redirect modes are supported:

  • Server redirect: normal HTTP 3xx + Location.
  • Client redirect: sets X-Client-Redirect for JS/fetch clients that intentionally handle navigation themselves.

Opt in to client redirects by sending:

  • X-Accepts-Client-Redirect: true

Exported constants:

  • ClientRedirectHeader (X-Client-Redirect)
  • ClientAcceptsRedirectHeader (X-Accepts-Client-Redirect)

URL validation rules:

  • Empty URLs are rejected.
  • Relative URLs are allowed.
  • Absolute URLs must use http or https.

Redirect code rules:

  • If no code is provided, default is 303 See Other.
  • If a provided code is outside 300..399, it is normalized to 303.

Proxy Merge Rules (MergeProxyResponses)

When multiple proxies are merged:

  • Status: first error (>=400) wins; otherwise last success (2xx) wins.
  • Redirect: if merged status is not an error, first redirect wins.
  • Headers: header operations are replayed in order (Set replaces prior values for that key, Add appends).
  • Cookies: later cookies with the same name replace earlier ones.
  • Head elements: merged in input order.

Important Notes

  • Proxy is not thread-safe; do not share one proxy across goroutines.
  • Response.IsCommitted() tracks commits done through Response methods. If code writes directly via Response.Writer, commit tracking can diverge from actual writer state.
  • Response content helpers (JSON, Text, HTML, etc.) do not return write errors.
  • MergeProxyResponses ignores nil proxies; SetCookie(nil) is treated as a no-op.

API Reference

Types
  • type Response
  • type Proxy
Response field
  • Response.Writer http.ResponseWriter
Constructors and top-level functions
  • func New(w http.ResponseWriter) Response
  • func NewProxy() *Proxy
  • func MergeProxyResponses(proxies ...*Proxy) *Proxy
  • func GetClientRedirectURL(w http.ResponseWriter) string
Response methods
  • func (res *Response) IsCommitted() bool
  • func (res *Response) SetHeader(key, value string)
  • func (res *Response) AddHeader(key, value string)
  • func (res *Response) SetStatus(status int)
  • func (res *Response) Error(status int, reasons ...string)
  • func (res *Response) JSONBytes(bytes []byte)
  • func (res *Response) JSON(v any)
  • func (res *Response) OK()
  • func (res *Response) Text(text string)
  • func (res *Response) OKText()
  • func (res *Response) HTMLBytes(bytes []byte)
  • func (res *Response) HTML(html string)
  • func (res *Response) NotModified()
  • func (res *Response) NotFound()
  • func (res *Response) Unauthorized(reasons ...string)
  • func (res *Response) InternalServerError(reasons ...string)
  • func (res *Response) BadRequest(reasons ...string)
  • func (res *Response) TooManyRequests(reasons ...string)
  • func (res *Response) Forbidden(reasons ...string)
  • func (res *Response) MethodNotAllowed(reasons ...string)
  • func (res *Response) Redirect(r *http.Request, url string, code ...int) (usedClientRedirect bool, err error)
  • func (res *Response) ServerRedirect(r *http.Request, url string, code ...int)
  • func (res *Response) ClientRedirect(url string) error
Proxy methods
  • func (p *Proxy) SetStatus(status int, errorStatusText ...string)
  • func (p *Proxy) GetStatus() (int, string)
  • func (p *Proxy) SetHeader(key, value string)
  • func (p *Proxy) AddHeader(key, value string)
  • func (p *Proxy) GetHeader(key string) string
  • func (p *Proxy) GetHeaders(key string) []string
  • func (p *Proxy) SetCookie(cookie *http.Cookie)
  • func (p *Proxy) GetCookies() []*http.Cookie
  • func (p *Proxy) AddHeadEls(els *headels.HeadEls)
  • func (p *Proxy) GetHeadEls() *headels.HeadEls
  • func (p *Proxy) Redirect(r *http.Request, url string, code ...int) (bool, error)
  • func (p *Proxy) GetLocation() string
  • func (p *Proxy) IsError() bool
  • func (p *Proxy) IsRedirect() bool
  • func (p *Proxy) IsSuccess() bool
  • func (p *Proxy) ApplyToResponseWriter(w http.ResponseWriter, r *http.Request)

Documentation

Index

Constants

View Source
const (
	ClientRedirectHeader        = "X-Client-Redirect"
	ClientAcceptsRedirectHeader = "X-Accepts-Client-Redirect"
)

Variables

This section is empty.

Functions

func GetClientRedirectURL

func GetClientRedirectURL(w http.ResponseWriter) string

Types

type Proxy

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

For usage in JSON API handlers that may run in parallel or do not have direct access to the http.ResponseWriter. Proxy instances are not meant to be shared. Rather, they should exist inside a single function/handler scope, and afterwards should be used by a parent scope to actually de-duplicate, determine priority, and write to the real http.ResponseWriter.

Concurrency model: Proxy instances are NOT thread-safe and must not be shared across goroutines. The intended usage pattern is:

  • Create one Proxy per goroutine/task
  • Each goroutine writes only to its own Proxy
  • After all goroutines complete, merge proxies on a single goroutine using MergeProxyResponses
  • Apply the merged result to the ResponseWriter

Do not instantiate directly. Use NewProxy().

func MergeProxyResponses

func MergeProxyResponses(proxies ...*Proxy) *Proxy

Consumers should deduplicate head els after calling MergeProxyResponses by using headels.ToHeadEls(proxy.GetHeadEls())

func NewProxy

func NewProxy() *Proxy

func (*Proxy) AddHeadEls added in v0.83.0

func (p *Proxy) AddHeadEls(els *headels.HeadEls)

func (*Proxy) AddHeader

func (p *Proxy) AddHeader(key, value string)

func (*Proxy) ApplyToResponseWriter

func (p *Proxy) ApplyToResponseWriter(w http.ResponseWriter, r *http.Request)

func (*Proxy) GetCookies

func (p *Proxy) GetCookies() []*http.Cookie

func (*Proxy) GetHeadEls added in v0.83.0

func (p *Proxy) GetHeadEls() *headels.HeadEls

func (*Proxy) GetHeader

func (p *Proxy) GetHeader(key string) string

func (*Proxy) GetHeaders

func (p *Proxy) GetHeaders(key string) []string

func (*Proxy) GetLocation

func (p *Proxy) GetLocation() string

func (*Proxy) GetStatus

func (p *Proxy) GetStatus() (int, string)

func (*Proxy) IsError

func (p *Proxy) IsError() bool

func (*Proxy) IsRedirect

func (p *Proxy) IsRedirect() bool

func (*Proxy) IsSuccess

func (p *Proxy) IsSuccess() bool

func (*Proxy) Redirect

func (p *Proxy) Redirect(r *http.Request, url string, code ...int) (bool, error)

Redirect sets a redirect on the proxy. If the request accepts client redirects, a client redirect is set; otherwise, a server redirect is set. Returns whether a client redirect was used and any error from URL validation.

func (*Proxy) SetCookie

func (p *Proxy) SetCookie(cookie *http.Cookie)

func (*Proxy) SetHeader

func (p *Proxy) SetHeader(key, value string)

func (*Proxy) SetStatus

func (p *Proxy) SetStatus(status int, errorStatusText ...string)

type Response

type Response struct {
	Writer http.ResponseWriter
	// contains filtered or unexported fields
}

func New

func (*Response) AddHeader

func (res *Response) AddHeader(key, value string)

func (*Response) BadRequest

func (res *Response) BadRequest(reasons ...string)

func (*Response) ClientRedirect

func (res *Response) ClientRedirect(url string) error

ClientRedirect sets a client-side redirect header and status 200. Returns an error if the response is already committed.

func (*Response) Error

func (res *Response) Error(status int, reasons ...string)

func (*Response) Forbidden

func (res *Response) Forbidden(reasons ...string)

func (*Response) HTML

func (res *Response) HTML(html string)

func (*Response) HTMLBytes

func (res *Response) HTMLBytes(bytes []byte)

func (*Response) InternalServerError

func (res *Response) InternalServerError(reasons ...string)

func (*Response) IsCommitted

func (res *Response) IsCommitted() bool

func (*Response) JSON

func (res *Response) JSON(v any)

Pass in non-encoded JSON-marshallable data

func (*Response) JSONBytes

func (res *Response) JSONBytes(bytes []byte)

Pass in pre-encoded JSON bytes

func (*Response) MethodNotAllowed

func (res *Response) MethodNotAllowed(reasons ...string)

func (*Response) NotFound

func (res *Response) NotFound()

func (*Response) NotModified

func (res *Response) NotModified()

func (*Response) OK

func (res *Response) OK()

Returns a 200 JSON response of {"ok":true}

func (*Response) OKText

func (res *Response) OKText()

Returns a 200 text response of "OK"

func (*Response) Redirect

func (res *Response) Redirect(r *http.Request, url string, code ...int) (usedClientRedirect bool, err error)

func (*Response) ServerRedirect

func (res *Response) ServerRedirect(r *http.Request, url string, code ...int)

func (*Response) SetHeader

func (res *Response) SetHeader(key, value string)

func (*Response) SetStatus

func (res *Response) SetStatus(status int)

func (*Response) Text

func (res *Response) Text(text string)

func (*Response) TooManyRequests

func (res *Response) TooManyRequests(reasons ...string)

func (*Response) Unauthorized

func (res *Response) Unauthorized(reasons ...string)

Jump to

Keyboard shortcuts

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