This project is considered experimental Build Status codecov GoDoc

Martin's HTTP package: It's not a "framework", but just a collection of functions for building HTTP services.

Honestly, I'm not sure how useful this will be for other people at this stage, but it's pretty useful for me.

Much of this was extracted from GoatCounter.

zhttp.Wrap() allows returning errors from HTTP endpoints:

http.HandleFunc("/bar", zhttp.Wrap(func(w http.ResponseWriter, r *http.Request) error {
    d, err := getData()
    if err != nil {
        return err

    return zhttp.Text(w, "Hello, %s", d)

It's just more convenient than `http.Error(...)` followed by a `return`. The
`ErrFunc()` will be used to report returned errors (you can override it if you
need to).

Return helpers (with the `http.ResponseWriter` elided in the function

    Stream(fp io.Reader)                     Stream any data.
    Bytes(b []byte)                          Send []byte
    String(s string)                         Send string
    Text(s string)                           Send string with Content-Type text/plain
    JSON(i interface{})                      Send JSON
    Template(name string, data interface{})  Render a template (see below)
    MovedPermanently(url string)             301 Moved Permanently
    SeeOther(url string)                     303 See Other


Templates can be rendered with the `ztpl` package; you have to call
`ztpl.Init()` first with the path to load templates from, and an optional map
for templates compiled in the binary.

You can use `ztpl.Reload()` to reload the templates from disk on changes, which
is useful for development. e.g. with

ztpl.Init("tpl", pack.Templates)

go func() {
    err := reload.Do(zlog.Module("main").Debugf, reload.Dir("./tpl", ztpl.Reload))
    if err != nil {
        panic(errors.Errorf("reload.Do: %v", err))

Few other tidbits:

  • User auth can be added with zhttpAuth(), zhttp.SetAuthCookie(), and zhttp.Filter().

  • zhttp.Decode()scans forms, JSON body, or URL query parameters in to a struct. It's just a convencience wrapper around formam.

  • zhttp.NewStatic() will create a static file host.

  • zhttp.HostRoute() routes request to chi routers based on the Host header.

Expand ▾ Collapse ▴




const (
	ContentUnsupported uint8 = iota

const (
	LevelInfo  = "i"
	LevelError = "e"

Level constants.

const (
	ServeTLS      = uint8(0b0001)
	ServeRedirect = uint8(0b0010)

const (
	// Don't set any header.
	CacheNoHeader = 0

	// Set to "no-cache" to tell browsers to always validate a cache (with e.g.
	// If-Match or If-None-Match). It does NOT tell browsers to never store a
	// cache; use Cache NoStore for that.
	CacheNoCache = -1

	// Set to "no-store, no-cache" to tell browsers to never store a local copy
	// (the no-cache is there to be sure previously stored copies from before
	// this header are revalidated).
	CacheNoStore = -2

Constants for the NewStatic() cache parameter.


var (
	CookieSecure   = false
	CookieSameSite = http.SameSiteLaxMode

Flags to add to all cookies (login and flash).

var DefaultHeaders = http.Header{
	"Strict-Transport-Security": []string{"max-age=2592000"},
	"X-Frame-Options":           []string{"deny"},
	"X-Content-Type-Options":    []string{"nosniff"},

DefaultHeaders will be set by default.

var ErrPage = DefaultErrPage

var LogUnknownFields bool

LogUnknownFields tells Decode() to issue an error log on unknown fields.

var Static404 = func(w http.ResponseWriter, r *http.Request) {
	http.Error(w, fmt.Sprintf("packed file not found: %q", r.RequestURI), 404)


func Auth

func Auth(load loadFunc) func(http.Handler) http.Handler

Auth adds user auth to an endpoint.

func Bytes

func Bytes(w http.ResponseWriter, b []byte) error

Bytes sends the bytes as-is to the client.

func ClearAuthCookie

func ClearAuthCookie(w http.ResponseWriter, domain string)

ClearAuthCookie sends an empty auth cookie with an expiry in the past for the given domain, clearing it in the client.

Make sure the domain matches with what was sent before *exactly*, or the browser will set a second cookie.

func Decode

func Decode(r *http.Request, dst interface{}) (uint8, error)

Decode request parameters from a form, JSON body, or query parameters.

Returns one of the Content* constants, which is useful if you want to alternate the responses.

func DefaultErrPage

func DefaultErrPage(w http.ResponseWriter, r *http.Request, code int, reported error)

func File

func File(w http.ResponseWriter, path string) error

File outputs a file on the disk.

This does NO PATH NORMALISATION! People can enter "../../../../etc/passwd". Make sure you sanitize your paths if they're from untrusted input.

func Filter

func Filter(f filterFunc) func(http.Handler) http.Handler

Filter access to a resource.

If the returning error is a and has a redirect code, then the error value is used as a redirection.

func Flash

func Flash(w http.ResponseWriter, msg string, v ...interface{})

Flash sets a new flash message at the LevelInfo, overwriting any previous messages (if any).

func FlashError

func FlashError(w http.ResponseWriter, msg string, v ...interface{})

FlashError sets a new flash message at the LevelError, overwriting any previous messages (if any).

func HandlerCSP

func HandlerCSP() func(w http.ResponseWriter, r *http.Request)

HandlerCSP handles CSP errors.

func HandlerJSErr

func HandlerJSErr() func(w http.ResponseWriter, r *http.Request)

HandlerJSErr logs JavaScript errors from window.onerror.

func HandlerRedirectHTTP

func HandlerRedirectHTTP(port string) http.HandlerFunc

HandlerRedirectHTTP redirects all HTTP requests to HTTPS.

func HandlerRobots

func HandlerRobots(rules [][]string) func(w http.ResponseWriter, r *http.Request)

HandlerRobots writes a simple robots.txt.

func Headers

func Headers(h http.Header) func(next http.Handler) http.Handler

Headers sets the given headers.

DefaultHeaders will always be set. Headers passed to this function overrides them. Use a nil value to remove a header.

func HostRoute

func HostRoute(routers map[string]http.Handler) http.HandlerFunc

HostRoute routes requests based on the Host header.

The routers can be simple domain names ("", "") or with a leading wildcard ("*", "*").

Exact matches are preferred over wildcards (e.g. "" trumps "*"). A single "*" will match any host and is used if nothing else matches.

func JSON

func JSON(w http.ResponseWriter, i interface{}) error

JSON writes i as JSON, setting the Content-Type to application/json (unless it's already set).

If i is a string or []byte it's assumed this is already JSON-encoded and sent it as-is rather than sending a JSON-fied string.

func Log

func Log(host bool, timeFmt string, ignore ...string) func(http.Handler) http.Handler

Log requests to stdout.

func MovedPermanently

func MovedPermanently(w http.ResponseWriter, url string) error

MovedPermanently redirects to the given URL with a 301.

func NoCache

func NoCache(next http.Handler) http.Handler

NoCache sets the Cache-Control header to "no-cache".

Browsers will always validate a cache (with e.g. If-Match or If-None-Match). It does NOT tell browsers to never store a cache (use NoStore for that).

func NoStore

func NoStore(next http.Handler) http.Handler

NoStore sets the Cache-Control header to "no-store, no-cache"

Browsers will never store a local copy (the no-cache is there to be sure previously stored copies from before this header are revalidated).

func PrivateIP

func PrivateIP(ip string) bool

PrivateIP reports if this is a private non-public IP address.

This will also return true for anything that is not an IP address, such as "" or "localhost".

func Ratelimit

func Ratelimit(opts RatelimitOptions) func(http.Handler) http.Handler

Ratelimit requests.

func RatelimitIP

func RatelimitIP(r *http.Request) string

RatelimitIP rate limits based IP address.

Assumes RemoteAddr is set correctly. E.g. with chi's middleware.RealIP middleware.

func RatelimitLimit

func RatelimitLimit(limit int, period int64) func(*http.Request) (int, int64)

RatelimitLimit is a simple limiter, always returning the same numbers.

func RealIP

func RealIP(h http.Handler) http.Handler

RealIP sets the RemoteAddr to X-Real-Ip, X-Forwarded-For, or the RemoteAddr without a port.

func RedirectHost

func RedirectHost(dst string) http.Handler

RedirectHost redirects all requests to the destination host.

Mainly intended for redirecting "" to "", or vice versa. The full URL is preserved, so " is redirected to

Only GET requests are redirected.

func RemovePort

func RemovePort(host string) string

RemovePort removes the "port" part of an hostname.

This only works for "host:port", and not URLs. See net.SplitHostPort.

func SafePath

func SafePath(s string) string

SafePath converts any string to a safe pathname, preventing directory traversal attacks and the like.

func SeeOther

func SeeOther(w http.ResponseWriter, url string) error

SeeOther redirects to the given URL with a 303.

"A 303 response to a GET request indicates that the origin server does not have a representation of the target resource that can be transferred by the server over HTTP. However, the Location field value refers to a resource that is descriptive of the target resource, such that making a retrieval request on that other resource might result in a representation that is useful to recipients without implying that it represents the original target resource."

func Serve

func Serve(flags uint8, testMode bool, server *http.Server) chan (struct{})

Serve a HTTP server with graceful shutdown.

The server will use TLS if the http.Server has a valid TLSConfig; it will also try to redirect port 80 to the TLS server, but will gracefully fail if the permission for this is denied.

This will return a channel which will send a value after the server is set up and ready to accept connections, and send another valiue after the server is shut down and stopped accepting connections:

ch := zhttp.Serve(..)
fmt.Println("Ready to serve!")


func SetAuthCookie

func SetAuthCookie(w http.ResponseWriter, val, domain string)

SetAuthCookie sets the authentication cookie to val for the given domain.

func Stream

func Stream(w http.ResponseWriter, fp io.Reader) error

Stream any data to the client as-is.

func String

func String(w http.ResponseWriter, s string) error

String sends the string as-is to the client.

func Template

func Template(w http.ResponseWriter, name string, data interface{}) error

Template renders a template and sends it to the client, setting the Content-Type to text/html (unless it's already set).

This requires ztpl to be set up.

func Text

func Text(w http.ResponseWriter, s string) error

Text sends the string to the client, setting the Content-Type to text/plain (unless it's already set).

func UserErrorCode

func UserErrorCode(err error) string

UserErrorCode gets a hash based on the error value.

func Wrap

func Wrap(handler HandlerFunc) http.HandlerFunc

Wrap a http.HandlerFunc and handle error returns.

func WrapWriter

func WrapWriter(next http.Handler) http.Handler

WrapWriter replaces the http.ResponseWriter with our version of http.ResponseWriter for some additional functionality.


type CSPError

type CSPError struct {
	Report Report `json:"csp-report"`

CSP errors.

type DecodeError

type DecodeError struct {
	// contains filtered or unexported fields

func (DecodeError) Error

func (e DecodeError) Error() string

func (DecodeError) Unwrap

func (e DecodeError) Unwrap() error

type FlashMessage

type FlashMessage struct {
	Level   string
	Message string

FlashMessage is a displayed flash message.

func ReadFlash

func ReadFlash(w http.ResponseWriter, r *http.Request) *FlashMessage

ReadFlash reads any existing flash message, returning the severity level and the message itself.

type HandlerFunc

type HandlerFunc func(http.ResponseWriter, *http.Request) error

HandlerFunc function.

type RatelimitMemory

type RatelimitMemory struct {
	// contains filtered or unexported fields

RatelimitMemory stores the rate limit information in the Go process' memory.

func NewRatelimitMemory

func NewRatelimitMemory() *RatelimitMemory

func (*RatelimitMemory) Grant

func (m *RatelimitMemory) Grant(key string, n int, period int64) (bool, int)

type RatelimitOptions

type RatelimitOptions struct {
	Message string                                        // Displayed when limit is reached.
	Client  func(*http.Request) string                    // String to identify client (e.g. ip address).
	Store   RatelimitStore                                // How to store the # of requests.
	Limit   func(*http.Request) (limit int, period int64) // "limit" requests over "period" seconds.

type RatelimitStore

type RatelimitStore interface {
	Grant(key string, n int, period int64) (granted bool, remaining int)

type Report

type Report struct {
	BlockedURI   string `json:"blocked-uri"`
	ColumnNumber int    `json:"column-number"`
	DocumentURI  string `json:"document-uri"`
	LineNumber   int    `json:"line-number"`
	Referrer     string `json:"referrer"`
	SourceFile   string `json:"source-file"`
	Violated     string `json:"violated-directive"`

CSP errors.

type ResponseWriter

type ResponseWriter interface {

	// Status returns the HTTP status of the request, or 0 if one has not
	// yet been sent.
	Status() int

	// BytesWritten returns the total number of bytes sent to the client.
	BytesWritten() int

	// Tee causes the response body to be written to the given io.Writer in
	// addition to proxying the writes through. Only one io.Writer can be
	// tee'd to at once: setting a second one will overwrite the first.
	// Writes will be sent to the proxy before being written to this
	// io.Writer. It is illegal for the tee'd writer to be modified
	// concurrently with writes.

	// Unwrap returns the original proxied target.
	Unwrap() http.ResponseWriter

ResponseWriter is a proxy around an http.ResponseWriter that allows you to hook into various parts of the response process.

func NewResponseWriter

func NewResponseWriter(w http.ResponseWriter, protoMajor int) ResponseWriter

NewResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to hook into various parts of the response process.

type Static

type Static struct {
	// contains filtered or unexported fields

Static file server.

func NewStatic

func NewStatic(dir, domain string, cache map[string]int, packed map[string][]byte) Static

NewStatic returns a new static fileserver.

It will serve all files in dir. Symlinks are followed (even outside of dir!), but paths containing ".." are not allowed.

The domain parameter is used for CORS.

The Cache-Control header is set with the cache parameter, which is a path → cache mapping. The path is matched with filepath.Match() and the key "" is used if nothing matches. There is no guarantee about the order if multiple keys match. One of special Cache* constants can be used.

If packed is not nil then all files will be served from the map, and the filesystem is never accessed. This is useful for self-contained production builds.

TODO: this should use http.FileSystem.

func (Static) ServeHTTP

func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request)

type User

type User interface {
	GetToken() string


Path Synopsis
ctxkey Package ctxkey stores context keys.
header Package header provides functions for parsing and setting HTTP headers.
ztpl Package ztpl implements the loading and reloading of templates.