apex

package module
v0.16.1 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 19 Imported by: 0

README

Apex   GoDoc Coverage Lint MIT licensed

Apex billboard

High-performance HTTP for Go.

Apex is the HTTP library in the Vroomy ecosystem, with zero-allocation warmed routing and SIMD-accelerated matching on ARM64 and AMD64. Use it on its own or as an HTTP foundation for your application.

Features

  • Static, named-parameter, and wildcard routing with predictable precedence
  • Middleware and route groups
  • Pooled request contexts and reusable parameter storage
  • Targeted NEON and SSE2 routing searches, with a portable fallback
  • HTTP/HTTPS serving, HTTP/2 support, and automatic TLS certificates

The zero-allocation claim applies to warmed routing with sufficient reusable storage. Response generation, application work, and cold pools can still allocate. See Routing for the implementation details and Benchmarks for measured results and their scope.

Quickstart

Requires Go 1.27 or newer. From this checkout:

go run ./example/functions

Then, in another terminal:

curl http://localhost:8080/ping
# pong

Stop the server with Ctrl-C. The function example contains the complete program below. A method-based example is also available with go run ./example/methods.

package main

import (
	"log"

	"github.com/vroomy/apex"
)

func main() {
	var (
		engine *apex.Engine
		err    error
	)

	engine = apex.New()
	defer engine.Close()

	if err = engine.GET("/ping", func(ctx *apex.Context) {
		ctx.WriteString(200, "text/plain", "pong")
	}); err != nil {
		log.Fatal(err)
	}

	engine.Set404(func(ctx *apex.Context) {
		ctx.WriteString(404, "text/plain", "Oh shoot, this page doesn't exist")
	})

	if err = engine.Listen(8080); err != nil {
		log.Fatal(err)
	}
}

Routing

Routes use a compressed prefix trie for each HTTP method. Matching tries static branches first, then named parameters, then wildcards. If a branch cannot complete the match, the router tries the next branch. For example, /users/current takes precedence over /users/:id regardless of registration order. Duplicate patterns keep their first registered handler.

The trie reserves parameter storage only after a complete match, then fills values as traversal returns and assigns names from the selected endpoint. Failed branches neither allocate capture storage nor retain captured strings.

Router.Match returns caller-owned parameters with space for exactly the winning route's captures. Static matches, parameter-free wildcard matches, and misses need no parameter allocation. Router.MatchInto(method, path, params) accepts reusable storage and returns the handler, updated parameters, and match status. Pass its returned slice into the next call; the previous values are cleared and overwritten. Concurrent callers must use separate backing arrays.

Trie prefix comparisons accept empty prefixes directly and compare one-byte prefixes after checking for an empty URL. Longer prefixes use strings.HasPrefix. These paths apply on every architecture, including purego builds. See the prefix comparison report for validation and measurements.

ServeHTTP reuses a pooled Context and clears parameter, hook, and storage references before returning it to the pool. The Context and its parameter slice are valid only during the handler. Warmed dispatch with sufficient capacity uses zero allocations in the routing benchmarks; cold pools, response generation, and application work can still allocate.

On ARM64 and AMD64, trie child selection and parameter delimiter searches use inlined empty-string and first-byte checks, scalar loads for very small inputs, and bounded SIMD searches through 31 bytes (NEON on ARM64, SSE2 on AMD64). Longer searches keep Go's standard library implementation. Other architectures and builds with -tags purego use strings.IndexByte. See the ARM64 comparison and AMD64 validation for measurements and reproduction commands. AMD64 execution was validated under Rosetta; native Intel/AMD performance measurements remain outstanding.

Benchmarks

For reproducible comparisons between Apex and Go's http.ServeMux, see the router benchmark guide. It includes fixture details, allocation measurements, and commands for comparing the routers with benchstat.

Standard library comparison

Comparison with 40 GET routes and one named parameter per route. Timings are median dispatch times; speedup is http.ServeMux time divided by Apex time:

URL fixture http.ServeMux Apex Speedup
Common (19 bytes): /api/users/00000000 128.80 ns/op 35.98 ns/op 3.58×
Short (9 bytes) 95.50 ns/op 38.14 ns/op 2.50×
Medium (39 bytes) 217.50 ns/op 38.59 ns/op 5.64×
Long (172 bytes) 646.00 ns/op 39.10 ns/op 16.52×

Across all four fixtures, Apex used 0 B/op and 0 allocations/op; http.ServeMux used 16 B/op and 1 allocation/op.

Measured on 2026-10-18 with Go 1.27.0 (darwin/arm64) on Apple M3, using one CPU and ten alternating rounds between main (97491b3) and this branch (ec85ad9), with one-second samples per case. The table uses this branch's samples. All Apex-versus-ServeMux timing differences were statistically significant in benchstat (p < 0.001). These are warmed successful-route benchmarks that include native parameter lookup and a minimal handler, excluding request construction, network I/O, and application work. They do not measure end-to-end HTTP throughput or compare all routing semantics.

See the benchmark source, raw samples, and statistical comparison. The comparison report includes reproduction commands, fixture details, and a separate comparison against Apex on main.

Migrating from httpserve

The module path is now github.com/vroomy/apex, and the root package is apex. Update imports and package qualifiers:

Before After
github.com/vroomy/httpserve github.com/vroomy/apex
github.com/vroomy/httpserve/form github.com/vroomy/apex/form
httpserve.New() apex.New()
*httpserve.Serve *apex.Engine
*httpserve.Context *apex.Context

Serve is now Engine, and New() returns *Engine. Its methods and behavior are unchanged. An explicit import alias can preserve existing package qualifiers while migrating the type name:

import httpserve "github.com/vroomy/apex"

With this alias, change explicit *httpserve.Serve declarations to *httpserve.Engine.

Use an Apex version or commit whose go.mod declares the new module path, then run go mod tidy. Tags from before the rename still declare the old path. Migrate Vroomy and application plugins together: Go treats types imported from the old and new module paths as distinct, even when their definitions match.

For maintainers, publish the repository as github.com/vroomy/apex with the updated go.mod before updating downstream dependencies. A repository redirect alone does not change module declarations in existing tags.

Historical benchmark reports retain their original HTTPServe names and raw samples; the tables above describe the same implementation under the Apex name.

Development

go test ./...
go test -tags purego ./...
go vet ./...
go build ./...

For coverage commands, measurement scope, and remaining gaps, see the testing guide. The coverage badge reports the root library's statement coverage; form has 100% statement coverage.

Documentation

Overview

Package apex provides HTTP routing, middleware, request contexts, and HTTP/HTTPS servers for the Vroomy ecosystem and standalone Go applications.

New constructs an Engine for registering routes and starting listeners.

Routes support static paths, named parameters, and wildcards. Warmed dispatch reuses pooled contexts and parameter storage. On ARM64 and AMD64, selected routing searches use SIMD, with a portable fallback for other architectures and purego builds.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidWildcardRoute is returned when an invalid wildcard route is encountered
	ErrInvalidWildcardRoute = errors.New("wildcard routes cannot have any additional characters following the asterisk")
	// ErrMissingLeadSlash is returned when a route does not begin with "/"
	ErrMissingLeadSlash = errors.New("invalid route, needs to start with a forward slash")
	// ErrInvalidParamLocation is returned when a parameter follows a character other than "/"
	ErrInvalidParamLocation = errors.New("parameters can only directly follow a forward slash")
	// ErrInvalidWildcardLocation is returned when a wildcard follows a character other than "/"
	ErrInvalidWildcardLocation = errors.New("wildcards can only directly follow a forward slash")
	// ErrContextIsClosed is returned when write actions are attempted on a closed context
	ErrContextIsClosed = errors.New("cannot perform write actions on a closed context")
)

Functions

func DecodeJSONValue

func DecodeJSONValue(r io.Reader, val any) (err error)

DecodeJSONValue will decode a JSON value

func UnmarshalJSONValue

func UnmarshalJSONValue(bs []byte, val any) (err error)

UnmarshalJSONValue will unmarshal a JSON value

Types

type AdoptResponse

type AdoptResponse struct {
}

AdoptResponse is a basic adopt response

func NewAdoptResponse

func NewAdoptResponse() *AdoptResponse

NewAdoptResponse will return a new adopt response

func (*AdoptResponse) ContentType

func (t *AdoptResponse) ContentType() (contentType string)

ContentType returns the content type

func (*AdoptResponse) StatusCode

func (t *AdoptResponse) StatusCode() (code int)

StatusCode returns the status code

func (*AdoptResponse) WriteTo

func (t *AdoptResponse) WriteTo(w io.Writer) (n int64, err error)

WriteTo will write the internal reader to a provided io.Writer

type AutoCertConfig

type AutoCertConfig struct {
	DirCache string

	// For TLS hosts, use either Hosts or HostPolicy depending on your use-case:
	//	- Use Hosts when your list is static
	//	- Host HostPolicy when your list is dynamic
	// Hosts is a static list of acceptable TLS hosts
	Hosts []string
	// HostPolicy is a TLS host policy that can be dynamically updated
	HostPolicy autocert.HostPolicy
}

type Config

type Config struct {
	ReadTimeout    time.Duration
	WriteTimeout   time.Duration
	IdleTimeout    time.Duration
	MaxHeaderBytes int
}

Config is the basic configuration

type Context

type Context struct {
	Params Params
	// contains filtered or unexported fields
}

Context is the request context. It and its Params are valid only for the duration of the handler; copy values that must outlive the request.

func (*Context) AddHook

func (c *Context) AddHook(fn Hook)

AddHook will add a hook function to be ran after the context has completed

func (*Context) Bind

func (c *Context) Bind(value any) (err error)

Bind is a helper function which binds the request body to a provided value to be parsed as the inbound content type

func (*Context) BindForm

func (c *Context) BindForm(value any) (err error)

BindForm is a helper function which binds the request body to a provided value to be parsed as an HTML form

func (*Context) BindJSON

func (c *Context) BindJSON(value any) (err error)

BindJSON is a helper function which binds the request body to a provided value to be parsed as JSON

func (*Context) Get

func (c *Context) Get(key string) (value string)

Get will retrieve a value for a provided key from the Context's internal storage

func (*Context) Param

func (c *Context) Param(key string) (value string)

Param will return the associated parameter value with the provided key

func (*Context) Put

func (c *Context) Put(key, value string)

Put will set a value for a provided key into the Context's internal storage

func (*Context) Redirect

func (c *Context) Redirect(statusCode int, destination string)

Redirect will redirect the client to the provided destination

func (*Context) Request

func (c *Context) Request() *http.Request

Request will return the underlying http.Request

func (*Context) WriteBytes

func (c *Context) WriteBytes(statusCode int, contentType string, bs []byte)

WriteBytes will write a byte slice

func (*Context) WriteJSON

func (c *Context) WriteJSON(statusCode int, value any)

WriteJSON will write JSON bytes to the http response body

func (*Context) WriteNoContent

func (c *Context) WriteNoContent()

WriteNoContent will write a no content response

func (*Context) WriteReader

func (c *Context) WriteReader(statusCode int, contentType string, r io.Reader)

WriteReader will copy reader bytes to the http response body

func (*Context) WriteString

func (c *Context) WriteString(statusCode int, contentType, str string)

WriteString will write a string

func (*Context) Writer

func (c *Context) Writer() http.ResponseWriter

Writer will return the underlying http.ResponseWriter

type Engine

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

Engine manages routing, middleware, and HTTP/HTTPS listeners.

func New

func New() *Engine

New constructs an Engine with an empty router.

func (*Engine) Close

func (e *Engine) Close() error

Close closes both servers and prevents listeners from starting afterward.

func (*Engine) DELETE

func (e *Engine) DELETE(route string, hs ...Handler) (err error)

DELETE will set a DELETE endpoint

func (*Engine) GET

func (e *Engine) GET(route string, hs ...Handler) (err error)

GET will set a GET endpoint

func (*Engine) Group

func (e *Engine) Group(route string, hs ...Handler) Group

Group will return a new group for a given route and handlers

func (*Engine) Handle

func (e *Engine) Handle(method, route string, hs ...Handler) (err error)

Handle will create a route for any method

func (*Engine) Listen

func (e *Engine) Listen(port uint16) (err error)

Listen will listen on a given port

func (*Engine) ListenAutoCertTLS

func (e *Engine) ListenAutoCertTLS(port uint16, ac AutoCertConfig) (err error)

ListenAutoCertTLS will listen using the TLS procol on a given port using the certificate being provided by LetsEncrypt

func (*Engine) ListenAutoCertTLSWithConfig

func (e *Engine) ListenAutoCertTLSWithConfig(port uint16, ac AutoCertConfig, c Config) (err error)

ListenAutoCertTLSWithConfig will listen using the TLS procol on a given port using configurations and the certificate being provided by LetsEncrypt

func (*Engine) ListenTLS

func (e *Engine) ListenTLS(port uint16, certificateDir string) (err error)

ListenTLS will listen using the TLS procol on a given port

func (*Engine) ListenTLSWithConfig

func (e *Engine) ListenTLSWithConfig(port uint16, certificateDir string, c Config) (err error)

ListenTLSWithConfig will listen using the TLS procol on a given port using the specified configuration

func (*Engine) ListenWithConfig

func (e *Engine) ListenWithConfig(port uint16, c Config) (err error)

ListenWithConfig will listen on a given port using the specified configuration

func (*Engine) OPTIONS

func (e *Engine) OPTIONS(route string, hs ...Handler) (err error)

OPTIONS will set a OPTIONS endpoint

func (*Engine) POST

func (e *Engine) POST(route string, hs ...Handler) (err error)

POST will set a POST endpoint

func (*Engine) PUT

func (e *Engine) PUT(route string, hs ...Handler) (err error)

PUT will set a PUT endpoint

func (*Engine) Set404

func (e *Engine) Set404(h Handler)

Set404 will set the 404 handler

func (*Engine) SetOnError

func (e *Engine) SetOnError(fn func(error))

SetPanic will set the panic handler

func (*Engine) SetPanic

func (e *Engine) SetPanic(h PanicHandler)

SetPanic will set the panic handler

type Group

type Group interface {
	GET(route string, hs ...Handler) error
	POST(route string, hs ...Handler) error
	PUT(route string, hs ...Handler) error
	DELETE(route string, hs ...Handler) error
	OPTIONS(route string, hs ...Handler) error

	Handle(method, route string, hs ...Handler) error
	Group(route string, hs ...Handler) Group
}

Group is a grouping interface

type HTMLResponse

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

HTMLResponse is a basic text response

func NewHTMLResponse

func NewHTMLResponse(code int, body []byte) *HTMLResponse

NewHTMLResponse will return a new text response

func (*HTMLResponse) ContentType

func (h *HTMLResponse) ContentType() (contentType string)

ContentType returns the content type

func (*HTMLResponse) StatusCode

func (h *HTMLResponse) StatusCode() (code int)

StatusCode returns the status code

func (*HTMLResponse) WriteTo

func (h *HTMLResponse) WriteTo(w io.Writer) (n int64, err error)

WriteTo will write the internal reader to a provided io.Writer

type Handler

type Handler func(ctx *Context)

Handler is the HTTP handler type

type Hook

type Hook func(statusCode int, ctx *Context)

Hook is a function called after the response has been completed to the requester

type JSONPResponse

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

JSONPResponse is a basic text response

func NewJSONPResponse

func NewJSONPResponse(callback string, value any) *JSONPResponse

NewJSONPResponse will return a new text response

func (*JSONPResponse) ContentType

func (j *JSONPResponse) ContentType() (contentType string)

ContentType returns the content type

func (*JSONPResponse) StatusCode

func (j *JSONPResponse) StatusCode() (code int)

StatusCode returns the status code

func (*JSONPResponse) WriteTo

func (j *JSONPResponse) WriteTo(w io.Writer) (n int64, err error)

WriteTo will write to a given io.Writer

type JSONResponse

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

JSONResponse is a basic text response

func NewJSONResponse

func NewJSONResponse(code int, value any) *JSONResponse

NewJSONResponse will return a new text response

func (*JSONResponse) ContentType

func (j *JSONResponse) ContentType() (contentType string)

ContentType returns the content type

func (*JSONResponse) StatusCode

func (j *JSONResponse) StatusCode() (code int)

StatusCode returns the status code

func (*JSONResponse) WriteTo

func (j *JSONResponse) WriteTo(w io.Writer) (n int64, err error)

WriteTo will write to a given io.Writer

type JSONValue

type JSONValue struct {
	Data   any     `json:"data,omitempty"`
	Errors []error `json:"errors,omitempty"`
}

JSONValue represents a basic JSON value

func (*JSONValue) PushErrors

func (j *JSONValue) PushErrors(errs ...error)

type NoContentResponse

type NoContentResponse struct {
}

NoContentResponse is a basic text response

func NewNoContentResponse

func NewNoContentResponse() *NoContentResponse

NewNoContentResponse will return a new text response

func (*NoContentResponse) ContentType

func (t *NoContentResponse) ContentType() (contentType string)

ContentType returns the content type

func (*NoContentResponse) StatusCode

func (t *NoContentResponse) StatusCode() (code int)

StatusCode returns the status code

func (*NoContentResponse) WriteTo

func (t *NoContentResponse) WriteTo(w io.Writer) (n int64, err error)

WriteTo will write the internal reader to a provided io.Writer

type PanicHandler

type PanicHandler func(v any)

PanicHandler is a panic handler

type Param

type Param struct {
	Key   string
	Value string
}

Param represents a key/value pair

type Params

type Params []Param

Params represent route parameters

func (Params) ByName

func (p Params) ByName(key string) (value string)

ByName will return a value for a given key

type RedirectResponse

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

RedirectResponse will redirect the client

func NewRedirectResponse

func NewRedirectResponse(code int, url string) *RedirectResponse

NewRedirectResponse will return a redirect response

func (*RedirectResponse) ContentType

func (r *RedirectResponse) ContentType() (contentType string)

ContentType returns the content type

func (*RedirectResponse) StatusCode

func (r *RedirectResponse) StatusCode() (code int)

StatusCode returns the status code

func (*RedirectResponse) WriteTo

func (r *RedirectResponse) WriteTo(w io.Writer) (n int64, err error)

WriteTo will write to a given io.Writer

type Response

type Response struct {
	StatusCode  int
	ContentType string
	Value       any

	// Optional fields used by a minority of responses
	Adopted  bool
	Callback string
}

Response determines how the server will respond

func NewAdopedtResponse

func NewAdopedtResponse() *Response

NewAdopedtResponse will return a new adopted Response

func NewResponse

func NewResponse(statusCode int, contentType string, value any) *Response

NewResponse will return a new Response

type Router

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

Router handles routes using a compressed prefix trie for each HTTP method. Static branches take precedence over parameters, followed by wildcards. Register routes and configure handlers before serving requests; lookups can then run concurrently.

func (*Router) DELETE

func (r *Router) DELETE(url string, h Handler) error

DELETE will create a DELETE route

func (*Router) GET

func (r *Router) GET(url string, h Handler) error

GET will create a GET route

func (*Router) Handle

func (r *Router) Handle(method, url string, h Handler) (err error)

Handle registers a route for a supported HTTP method. The path must be nonempty and start with "/". Patterns are parsed only during registration.

func (*Router) Match

func (r *Router) Match(method, url string) (h Handler, p Params, ok bool)

Match returns a matching handler and its parameters, preferring static branches over parameters and parameters over wildcards. Failed branches fall back to less specific matches. Duplicate patterns retain their first handler. The returned Params belong to the caller. A miss returns the not-found handler. Parameter storage is allocated only for a successful match with named parameters. Matches without named parameters and misses return nil Params.

func (*Router) MatchInto

func (r *Router) MatchInto(method, url string, params Params) (h Handler, p Params, ok bool)

MatchInto matches a route using caller-owned parameter storage. It clears the supplied slice's existing elements and replaces them with the selected route's parameters. It returns an empty slice on a miss and grows storage only when a successful match exceeds its capacity. A nil slice is valid. Retain the returned slice for subsequent calls. Reusing storage overwrites prior results; concurrent calls must use separate backing arrays. Strings reference the request path and registered parameter names without copying their contents.

func (*Router) OPTIONS

func (r *Router) OPTIONS(url string, h Handler) error

OPTIONS will create an OPTIONS route

func (*Router) POST

func (r *Router) POST(url string, h Handler) error

POST will create a POST route

func (*Router) PUT

func (r *Router) PUT(url string, h Handler) error

PUT will create a PUT route

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request)

func (*Router) SetNotFound

func (r *Router) SetNotFound(hs ...Handler)

SetNotFound will set the not found handler (404)

func (*Router) SetOnError

func (r *Router) SetOnError(onError func(error))

SetOnError sets the error callback. A nil callback restores default logging. Configure the callback before serving requests.

func (*Router) SetPanic

func (r *Router) SetPanic(h PanicHandler)

SetPanic will set panic handler

type Storage

type Storage map[string]string

Storage is used as a basic form of Param storage for a Context TODO: Determine with team if it seems valuable to change this to map[string]any. I'd prefer if we can keep it as-is, due to the fact that map[string]string has much less GC overhead. Additionally, avoiding type assertion would be fantastic.

type TextResponse

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

TextResponse is a basic text response

func NewTextResponse

func NewTextResponse(code int, body []byte) *TextResponse

NewTextResponse will return a new text response

func (*TextResponse) ContentType

func (t *TextResponse) ContentType() (contentType string)

ContentType returns the content type

func (*TextResponse) StatusCode

func (t *TextResponse) StatusCode() (code int)

StatusCode returns the status code

func (*TextResponse) WriteTo

func (t *TextResponse) WriteTo(w io.Writer) (n int64, err error)

WriteTo will write the internal reader to a provided io.Writer

type Upgrader

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

Upgrader will upgrade all requests to SSL, and also handle forwarding to virtual hosts

func NewUpgrader

func NewUpgrader(port uint16, virtualHosts ...VirtualHost) (out *Upgrader, err error)

NewUpgrader will return an upgrader

func (*Upgrader) Close

func (u *Upgrader) Close() error

Close releases the redirect listener and prevents subsequent startup.

func (*Upgrader) Listen

func (u *Upgrader) Listen(port uint16) (err error)

Listen will listen to a given port

type VirtualHost

type VirtualHost struct {
	// Domain is the Host to parse for the redirect
	Domain string
	// Port is the localhost port forwarded to when Domain is parsed
	Port uint16
}

VirtualHost represents a registered port to forward on the local host for a request to the given Domain

type XMLResponse

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

XMLResponse is a basic text response

func NewXMLResponse

func NewXMLResponse(code int, value []byte) *XMLResponse

NewXMLResponse will return a new text response

func (*XMLResponse) ContentType

func (j *XMLResponse) ContentType() (contentType string)

ContentType returns the content type

func (*XMLResponse) StatusCode

func (j *XMLResponse) StatusCode() (code int)

StatusCode returns the status code

func (*XMLResponse) WriteTo

func (j *XMLResponse) WriteTo(w io.Writer) (n int64, err error)

WriteTo will write to a given io.Writer

Directories

Path Synopsis
example
functions command
methods command

Jump to

Keyboard shortcuts

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