response

package
v0.85.0-pre.4 Latest Latest
Warning

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

Go to latest
Published: Mar 10, 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 ClientRedirectURL(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) Status() (int, string)
  • func (p *Proxy) SetHeader(key, value string)
  • func (p *Proxy) AddHeader(key, value string)
  • func (p *Proxy) Header(key string) string
  • func (p *Proxy) Headers(key string) []string
  • func (p *Proxy) SetCookie(cookie *http.Cookie)
  • func (p *Proxy) Cookies() []*http.Cookie
  • func (p *Proxy) AddHeadEls(els *headels.HeadEls)
  • func (p *Proxy) HeadEls() *headels.HeadEls
  • func (p *Proxy) Redirect(r *http.Request, url string, code ...int) (bool, error)
  • func (p *Proxy) Location() 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

Overview

Package response provides thin, explicit HTTP response helpers.

The `Response` type wraps `http.ResponseWriter` and centralizes common response patterns (JSON, text, HTML, redirects, status/error helpers) while still exposing straightforward HTTP semantics.

Package response intentionally avoids hidden policy and keeps behavior predictable so higher-level runtime layers can compose it safely.

Index

Constants

View Source
const (
	// ClientRedirectHeader carries a client-consumed redirect target URL.
	ClientRedirectHeader = "X-Client-Redirect"
	// ClientAcceptsRedirectHeader opts-in to client redirect responses.
	ClientAcceptsRedirectHeader = "X-Accepts-Client-Redirect"
)

Variables

This section is empty.

Functions

func GetClientRedirectURL

func GetClientRedirectURL(w http.ResponseWriter) string

GetClientRedirectURL reads the client redirect target header from w.

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.HeadEls())

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) Cookies

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

func (*Proxy) HeadEls

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

func (*Proxy) HeadElsIfPresent

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

HeadElsIfPresent returns the proxy head elements only when already present.

func (*Proxy) Header

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

func (*Proxy) Headers

func (p *Proxy) Headers(key string) []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) Location

func (p *Proxy) Location() string

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)

func (*Proxy) Status

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

type Response

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

Response is a small convenience wrapper for writing HTTP responses.

func New

New creates a Response helper around w.

func (*Response) AddHeader

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

AddHeader appends a response header value without committing the response.

func (*Response) BadRequest

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

BadRequest writes a 400 response.

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)

Error writes an HTTP error with optional custom reason text.

func (*Response) Forbidden

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

Forbidden writes a 403 response.

func (*Response) HTML

func (res *Response) HTML(html string)

HTML writes an HTML string to the response body.

func (*Response) HTMLBytes

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

HTMLBytes writes HTML bytes to the response body.

func (*Response) InternalServerError

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

InternalServerError writes a 500 response.

func (*Response) IsCommitted

func (res *Response) IsCommitted() bool

IsCommitted reports whether this helper has already written a status or body.

func (*Response) JSON

func (res *Response) JSON(v any)

JSON marshals v as JSON and writes it to the response body.

func (*Response) JSONBytes

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

JSONBytes writes pre-encoded JSON bytes.

func (*Response) MethodNotAllowed

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

MethodNotAllowed writes a 405 response.

func (*Response) NotFound

func (res *Response) NotFound()

NotFound writes a 404 status.

func (*Response) NotModified

func (res *Response) NotModified()

NotModified writes a 304 status.

func (*Response) OK

func (res *Response) OK()

OK writes a 200 response with {"ok":true}.

func (*Response) OKText

func (res *Response) OKText()

OKText writes a 200 response with "OK".

func (*Response) Redirect

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

Redirect chooses client redirect headers or server redirects based on request headers.

func (*Response) ServerRedirect

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

ServerRedirect writes an HTTP redirect response.

func (*Response) SetHeader

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

SetHeader sets a response header without committing the response.

func (*Response) SetStatus

func (res *Response) SetStatus(status int)

SetStatus writes the status code and marks the response committed.

func (*Response) Text

func (res *Response) Text(text string)

Text writes plain text to the response body.

func (*Response) TooManyRequests

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

TooManyRequests writes a 429 response.

func (*Response) Unauthorized

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

Unauthorized writes a 401 response.

Jump to

Keyboard shortcuts

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