README

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 signature):

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 github.com/teamwork/reload:

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.

Documentation

Index

Constants

View Source
const (
	ContentUnsupported uint8 = iota
	ContentQuery
	ContentForm
	ContentJSON
)
View Source
const (
	LevelInfo  = "i"
	LevelError = "e"
)

Level constants.

View Source
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.

View Source
const (
	ServeRedirect = uint8(0b0001)
)

Variables

View Source
var (
	CookieSecure   = false
	CookieSameSite = http.SameSiteLaxMode
)

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

View Source
var ErrPage = DefaultErrPage

ErrPage is the error page; this gets called whenever a "wrapped" handler returns an error.

This is expected to write a status code and some content; for example a template with the error or some JSON data for an API request.

nil errors should do nothing.

View Source
var LogUnknownFields bool

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

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

Functions

func Bytes

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

Bytes sends the bytes as-is to the client.

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, reported error)

DefaultErrPage is the default error page.

Any unknown errors are displayed as an error code, with the real error being logged. This ensures people don't see entire stack traces or whatnot, which isn't too useful.

The data written depends on the content type:

Regular HTML GET requests try to render error.gohtml with ztpl if it's loaded, or writes a simple default HTML document instead. The Code and Error parameters are set for the HTML template.

JSON requests write {"error" "the error message"}

Forms add the error as a flash message and redirect back to the previous page (via the Referer header), or render the error.gohtml template if the header isn't set.

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 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 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 ("example.com", "foo.example.com") or with a leading wildcard ("*.example.com", "*.foo.example.com").

Exact matches are preferred over wildcards (e.g. "foo.example.com" trumps "*.example.com"). 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 LogWrap

func LogWrap(prefixes ...string) *log.Logger

LogWrap returns a log.Logger which ignores any lines starting with prefixes.

func MovedPermanently

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

MovedPermanently redirects to the given URL with a 301.

func RedirectHost

func RedirectHost(dst string) http.Handler

RedirectHost redirects all requests to the destination host.

Mainly intended for redirecting "example.com" to "www.example.com", or vice versa. The full URL is preserved, so "example.com/a?x is redirected to www.example.com/a?x

Only GET requests are redirected.

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, stop chan struct{}, server *http.Server) (chan (struct{}), error)

Serve a HTTP server with graceful shutdown and reasonable timeouts.

The ReadHeader, Read, Write, or Idle timeouts are set to 10, 60, 60, and 120 seconds respectively if they are 0.

Errors from the net/http package are logged via zgo.at/zlog instead of the default log package. "TLS handshake error" are silenced, since there's rarely anything that can be done with that. Define server.ErrorLog if you want the old behaviour back.

The server will use TLS if the http.Server has a valid TLSConfig, and will to redirect port 80 to the TLS server if ServeRedirect is in flags, but will fail gracefully with a warning to stderr if the permission for this is denied.

The returned channel sends a value after the server is set up and ready to accept connections, and another one after the server is shut down and stopped accepting connections:

ch, _ := zhttp.Serve(..)
<-ch
fmt.Println("Ready to serve!")

<-ch
cleanup()

The stop channel can be used to tell the server to shut down gracefully; this is especially useful for tests:

stop := make(chan struct{})
go zhttp.Serve(0, stop, [..])
<-ch // Ready to serve

time.Sleep(1 * time.Second)
stop <- struct{}{}

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 UserError

func UserError(err error) (int, error)

UserError modifies an error for user display.

- Removes any stack traces from zgo.at/errors or github.com/pkg/errors.
- Reformats some messages to be more user-friendly.
- "Hides" messages behind an error code for 5xx errors (you need to log those yourself).

func UserErrorCode

func UserErrorCode(err error) string

UserErrorCode gets a hash of the error value.

func Wrap

Wrap a http.HandlerFunc and handle error returns.

Types

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

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 {
	http.ResponseWriter

	// 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.
	Tee(io.Writer)

	// 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(domain string, files fs.FS, cache map[string]int) Static

NewStatic returns a new static fileserver.

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.

func (Static) ServeHTTP

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

Directories

Path Synopsis
Package ctxkey stores context keys.
Package ctxkey stores context keys.
Package header provides functions for parsing and setting HTTP headers.
Package header provides functions for parsing and setting HTTP headers.
Package mware contains HTTP middlewares.
Package mware contains HTTP middlewares.
internal
isatty
Package isatty implements interface to isatty
Package isatty implements interface to isatty