aero

package module
v0.8.6 Latest Latest
Warning

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

Go to latest
Published: Apr 20, 2026 License: MIT Imports: 26 Imported by: 0

README

Aero

Blazing fast, zero-dependency, lightweight web framework for Go.

Go Version License Zero Dependencies

Why Aero?

Most Go frameworks either sacrifice stdlib compatibility (Fiber/FastHTTP) or leave allocations on the table (Echo, Chi). Aero stays on net/http, achieves zero allocations through careful design, and ships with a production-ready WebSocket stack. No external dependencies required.

Features

  • Zero dependencies, pure stdlib, no transitive vulnerabilities
  • Zero allocations in the hot path
  • Faster than net/http 1.8x throughput, 60x less memory per request
  • Auto HTTP2 support
  • Hybrid routing with O(1) static map for exact paths, Segment Trie for dynamic ones
  • Order-sensitive middleware
  • WebSocket support (14% higher push throughput than Fiber v3)

Guide

Installation
go get github.com/Alsond5/aero

Requires Go 1.25+

Using
package main

import (
	"fmt"
	"log/slog"
	"time"

	"github.com/Alsond5/aero"
)

func main() {
	app := aero.New()

	app.Use(Logger)

	app.GET("/api/users/:id", func(c *aero.Ctx) error {
		id := c.Param("id")
		return c.JSON(map[string]string{"id": id})
	})

	app.GET("/ws", aero.WebSocket(func(ws *aero.WSConn) {
		ws.Locals("client", "id")
		fmt.Println(ws.Locals("client"))

		for {
			mt, msg, err := ws.ReadMessage()
			if err != nil {
				fmt.Println(err)
				break
			}

			ws.WriteMessage(mt, msg)
		}
	}))

	app.Listen(":8080")
}

func Logger(c *aero.Ctx) error {
	start := time.Now()
	err := c.Next()
	duration := time.Since(start)

	slog.Info("request",
		"method", c.Method(),
		"path", c.Path(),
		"ip", c.IP(),
		"duration", duration,
		"status", c.ResponseStatus(),
	)

	return err
}

Benchmarks

Machine: Intel Core i7-10750H (unplugged, low-power mode) Go: 1.25 OS: Fedora Linux, amd64 Test: 1 middleware + 2 GET routes (1 dynamic, 1 static) + 1 POST route with JSON body parse

Standart
Framework ns/op B/op allocs/op req/s
Aero 603.8 339 3 1,656,177
Echo v5 772.7 336 4 1,294,163
net/http 727.9 333 4 1,373,815
Gin v1 955.2 368 5 1,046,901
Chi v5 1442.7 808 6 693,144
Routing
Framework ns/op B/op allocs/op req/s
Aero 194.8 0 0 5,133,470
Echo v5 232.6 16 1 4,299,226
net/http 242.2 20 1 4,128,819
Gin v1 277.6 52 2 3,602,305
Chi v5 1109.8 548 4 901,063
Parallel (12 goroutines)
Framework ns/op B/op allocs/op req/s
Aero 36.2 0 0 27,624,309
Echo v5 46.3 16 1 21,598,272
net/http 60.2 20 1 16,611,295
Gin v1 61.2 52 2 16,339,869
Chi v5 179.7 548 4 5,564,830
WebSocket (2000 concurrent connections)

Test: 2000 concurrent connections, 60s, 4 payload sizes (64B / 1KB / 4KB / 8KB), echo server

Framework msg/s p95 RTT p99 RTT max RTT Throughput
Aero 74,566 30ms 53ms 104ms 250 MB/s
Fiber v3 62,978 37ms 59ms 178ms 211 MB/s
WebSocket — Push Throughput (32 clients, 5s)

Test: Server pushes messages to 32 concurrent clients as fast as possible

Framework msg/s
Aero 5,772,032
Fiber v3 5,056,723

Design Decisions

Every feature is built on the Go standard library. No transitive vulnerabilities, no version conflicts, no go mod tidy surprises.

URL routing operates on path segments, not individual bytes. Radix Tree compression offers no benefit at segment granularity. A Segment Trie with a static map fast-path is simpler and equally fast.

Global middleware is stored once on the App. Routes store only an integer (middlewareCount). At dispatch time, the middleware slice and route handlers are indexed directly. No copying, no appending.

FastHTTP offers raw performance gains but breaks compatibility with the standard http.Handler ecosystem. Aero stays on net/http and achieves zero-alloc performance through careful design rather than a custom HTTP stack.

WebSocket frames are read with zero heap allocations. Buffers are pooled with borrow-on-demand to avoid per-connection overhead. Internally implements RFC 6455 from scratch using only the Go standard library no external dependencies.

License

MIT License

Documentation

Overview

Package aero is a zero-dependency, high-performance HTTP web framework for Go. It is designed for minimal allocations on the hot path, making it suitable for latency-sensitive applications.

Overview

Aero provides a fast trie-based router with support for named URL parameters, route groups, and a hybrid middleware chain model that allows both global and group/route-level middlewares. The framework is built entirely on top of the Go standard library with no external dependencies.

Key characteristics:

  • Zero external dependencies
  • Trie-based router with zero allocations on matched routes
  • Hybrid middleware chain: global (App.Use) and scoped (Group.Use)
  • First-class WebSocket support via a custom RFC 6455 implementation
  • Built-in request binding, content negotiation, and SSE support
  • Graceful shutdown via ServerConfig

Getting Started

Create an application with New, register routes, and start the server:

app := aero.New()

app.GET("/", func(c *aero.Ctx) error {
	return c.Res.SendString("Hello, World!")
})

app.Listen(":8080")

Route Groups

Routes can be grouped under a common prefix with shared middlewares:

api := app.Group("/api", authMiddleware)

api.GET("/users", listUsers)        // GET /api/users
api.POST("/users", createUser)      // POST /api/users

Middleware

Middlewares are regular HandlerFunc functions that call Ctx.Next to pass control to the next handler in the chain:

app.Use(func(c *aero.Ctx) error {
	start := time.Now()
	err := c.Next()
	log.Printf("%s %s — %v", c.Req.Method(), c.Req.Path(), time.Since(start))
	return err
})

WebSocket

WebSocket endpoints are registered as regular routes using WebSocket:

app.GET("/ws", aero.WebSocket(func(ws *aero.WSConn) {
	for {
		mt, msg, err := ws.ReadMessage()
		if err != nil {
			return
		}
		ws.WriteMessage(mt, msg)
	}
}))

Request Binding

Incoming request data can be bound directly into structs. Each field's struct tag determines the source (json, form, query, param, header):

type CreateUserReq struct {
	OrgID string `param:"orgId"`
	Token string `header:"X-Auth-Token"`
	Name  string `json:"name"`
}

var req CreateUserReq
if err := c.Req.Bind(&req); err != nil {
	return err
}

Graceful Shutdown

For production use, ServerConfig provides context-aware lifecycle control:

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

sc := aero.ServerConfig{
	Addr:            ":8080",
	GracefulTimeout: 10 * time.Second,
}

if err := sc.Start(ctx, app); err != nil {
	log.Fatal(err)
}

Index

Constants

View Source
const (
	HeaderTransferEncoding    = "Transfer-Encoding"
	HeaderContentType         = "Content-Type"
	HeaderContentLength       = "Content-Length"
	HeaderLink                = "Link"
	HeaderContentDisposition  = "Content-Disposition"
	HeaderXContentTypeOptions = "X-Content-Type-Options"
	HeaderReferer             = "Referer"
	HeaderLocation            = "Location"
	HeaderVary                = "Vary"
	HeaderCacheControl        = "Cache-Control"
	HeaderConnection          = "Connection"
)
View Source
const (
	MIMEApplicationJSON = "application/json"
	MIMEApplicationXML  = "application/xml"
	MIMETextXML         = "text/xml"
	MIMEApplicationForm = "application/x-www-form-urlencoded"
	MIMEMultipartForm   = "multipart/form-data"
)
View Source
const (
	RangeUnsatisfiable = -1
	RangeMalformed     = -2
)
View Source
const (
	MB int64 = 1 << 20
)

MB is a convenience constant representing one megabyte (1 << 20 bytes). Used as a base unit for body size limits in Config.

View Source
const (
	Version = "0.8.6"
)

Variables

View Source
var (
	ErrBindingTargetInvalid = errors.New("aero: binding target must be a pointer to a struct")
	ErrUnsupportedFieldType = errors.New("aero: unsupported field type")
)
View Source
var (
	ErrBodyAlreadyRead      = errors.New("aero: request body already read")
	ErrBodyTooLarge         = errors.New("aero: request body too large")
	ErrBodyEmpty            = errors.New("aero: request body is empty")
	ErrFormAlreadyParsed    = errors.New("aero: form already parsed")
	ErrNotMultipart         = errors.New("aero: content type is not multipart/form-data")
	ErrRangeNotPresent      = errors.New("aero: Range header not present")
	ErrRangeUnsatisfiable   = errors.New("aero: range not satisfiable")
	ErrRangeMalformed       = errors.New("aero: range header malformed")
	ErrUnsupportedMediaType = errors.New("aero: unsupported content encoding")
)
View Source
var (
	ErrFileNotFound    = errors.New("aero: file not found")
	ErrIsDirectory     = errors.New("aero: path is a directory")
	ErrPathRequired    = errors.New("aero: path is required")
	ErrPathNotAbsolute = errors.New("aero: path must be absolute or root must be specified")
)
View Source
var ErrTLSCertRequired = errors.New("aero: TLS cert and key are required")

Functions

This section is empty.

Types

type App

type App struct {

	// NotFoundHandler is called when no route matches the request path.
	// Defaults to a 404 JSON response if not set.
	NotFoundHandler NotFoundHandler

	// MethodNotAllowedHandler is called when a route exists for the
	// requested path but not for the requested HTTP method.
	// Defaults to a 405 response with an Allow header if not set.
	MethodNotAllowedHandler MethodNotAllowedHandler

	// ErrorHandler is called whenever a HandlerFunc returns a non-nil error.
	// Defaults to a 500 JSON response if not set.
	ErrorHandler ErrorHandler

	// OptionsHandler is called for OPTIONS requests that do not have
	// an explicitly registered OPTIONS route.
	// Defaults to a 204 response with an Allow header if not set.
	OptionsHandler OptionsHandler
	// contains filtered or unexported fields
}

App is the core Aero application instance. It holds the router, middleware chain, configuration, and lifecycle hooks. Create a new instance with New.

App implements http.Handler, so it can be passed directly to any standard Go HTTP server.

func New

func New(config ...Config) *App

New creates and returns a new Aero application instance. An optional Config can be provided to override defaults.

app := aero.New()
app := aero.New(aero.Config{ ... })

func (*App) DELETE

func (a *App) DELETE(path string, h HandlerFunc, m ...HandlerFunc)

DELETE registers a route for HTTP DELETE requests on the given path.

func (*App) GET

func (a *App) GET(path string, h HandlerFunc, m ...HandlerFunc)

GET registers a route for HTTP GET requests on the given path. Route-level middlewares can be appended after the handler.

app.GET("/users", listUsers)
app.GET("/users/:id", getUser, authMiddleware)

func (*App) Group added in v0.4.0

func (a *App) Group(prefix string, m ...HandlerFunc) (g *Group)

Group creates a new route group with the given path prefix and optional group-level middlewares. Routes registered on the group inherit the prefix and its middlewares.

api := app.Group("/api", authMiddleware)
api.GET("/users", listUsers)  // → GET /api/users

func (*App) HEAD

func (a *App) HEAD(path string, h HandlerFunc, m ...HandlerFunc)

HEAD registers a route for HTTP HEAD requests on the given path.

func (*App) Listen

func (a *App) Listen(addr string) error

Listen starts the HTTP server on the given address. The address format follows standard Go net/http conventions (e.g. ":8080").

app.Listen(":8080")

func (*App) ListenTLS

func (a *App) ListenTLS(addr, cert, key string) error

ListenTLS starts an HTTPS server on the given address using the provided TLS certificate and key files.

app.ListenTLS(":443", "cert.pem", "key.pem")

func (*App) OPTIONS

func (a *App) OPTIONS(path string, h HandlerFunc, m ...HandlerFunc)

OPTIONS registers a route for HTTP OPTIONS requests on the given path.

func (*App) PATCH

func (a *App) PATCH(path string, h HandlerFunc, m ...HandlerFunc)

PATCH registers a route for HTTP PATCH requests on the given path.

func (*App) POST

func (a *App) POST(path string, h HandlerFunc, m ...HandlerFunc)

POST registers a route for HTTP POST requests on the given path.

func (*App) PUT

func (a *App) PUT(path string, h HandlerFunc, m ...HandlerFunc)

PUT registers a route for HTTP PUT requests on the given path.

func (*App) ServeHTTP

func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler, enabling Aero to be used with any standard Go HTTP server or middleware.

func (*App) SetValidator added in v0.8.0

func (a *App) SetValidator(v Validator)

SetValidator sets the validator used by Req.Validate. The validator must implement the Validator interface.

func (*App) TRACE added in v0.7.1

func (a *App) TRACE(path string, h HandlerFunc, m ...HandlerFunc)

TRACE registers a route for HTTP TRACE requests on the given path.

func (*App) Use

func (a *App) Use(handlers ...HandlerFunc)

Use registers one or more global middlewares that run on every request, regardless of the route. Middlewares are executed in the order they are added.

app.Use(logger, recover, cors)

type Config

type Config struct {
	// TrustProxy enables parsing of proxy-forwarded headers such as
	// X-Forwarded-For and X-Forwarded-Proto when determining the
	// client IP and protocol. Enable only if Aero sits behind a
	// trusted reverse proxy. Default: false.
	TrustProxy bool

	// SubdomainOffset controls how many dot-separated segments are
	// stripped from the right of the hostname when extracting subdomains
	// via [Req.Subdomains]. For example, an offset of 2 on "a.b.example.com"
	// yields ["a", "b"]. Default: 2.
	SubdomainOffset int

	// MaxBodySize is the maximum allowed size in bytes for a request body.
	// Requests exceeding this limit are rejected before the body is read.
	// Default: 4 MB.
	MaxBodySize int64

	// MaxMultipartMemory is the maximum amount of memory in bytes used
	// when parsing multipart/form-data requests. Parts exceeding this
	// limit are stored in temporary files. Default: 32 MB.
	MaxMultipartMemory int64
}

Config holds application-level configuration options. All fields are optional; unset fields fall back to the defaults defined in [defaultConfig].

app := aero.New(aero.Config{
	TrustProxy:  true,
	MaxBodySize: 8 * aero.MB,
})

type CookieOptions

type CookieOptions struct {
	// MaxAge sets the cookie's Max-Age attribute in seconds.
	// A negative value instructs the client to delete the cookie immediately.
	MaxAge int

	// Path restricts the cookie to the given URL path prefix.
	// Defaults to "/" if empty.
	Path string

	// Domain sets the cookie's Domain attribute, controlling which hosts
	// receive the cookie. Omit to default to the current host only.
	Domain string

	// Secure marks the cookie to be sent only over HTTPS connections.
	Secure bool

	// HttpOnly prevents the cookie from being accessed via JavaScript,
	// mitigating XSS exposure.
	HttpOnly bool

	// SameSite controls cross-site request behaviour for the cookie.
	// Accepts [http.SameSiteStrictMode], [http.SameSiteLaxMode], or
	// [http.SameSiteNoneMode].
	SameSite http.SameSite
}

CookieOptions configures the attributes of a cookie set via Res.SetCookie or cleared via Res.ClearCookie.

type Ctx

type Ctx struct {
	// Req provides access to all incoming request data: headers, params,
	// query string, body, cookies, and more.
	Req

	// Res provides methods for building and sending the HTTP response:
	// status codes, headers, JSON, files, redirects, and more.
	Res
	// contains filtered or unexported fields
}

Ctx is the request context passed to every HandlerFunc. It embeds Req and Res directly, so request reading and response writing methods are accessible without any extra field access.

app.GET("/hello", func(c *aero.Ctx) error {
	name := c.Req.Param("name")
	return c.Res.SendString("Hello, " + name)
})

func (*Ctx) Next

func (c *Ctx) Next() error

Next executes the next handler or middleware in the chain for the current route. It must be called inside a middleware to pass control forward. Calling Next after the chain is exhausted is a no-op and returns nil.

func logger(c *aero.Ctx) error {
	start := time.Now()
	err := c.Next()
	log.Printf("%s %s — %v", c.Req.Method(), c.Req.Path(), time.Since(start))
	return err
}

func (*Ctx) SSE added in v0.8.1

func (c *Ctx) SSE() (*SSEWriter, bool)

SSE upgrades the current request to a Server-Sent Events stream and returns an SSEWriter for pushing events to the client. The second return value reports whether the upgrade succeeded; it is false if the underlying ResponseWriter does not support flushing (i.e. does not implement http.Flusher).

The response Content-Type is automatically set to "text/event-stream" and caching is disabled. The connection is kept open until the handler returns.

app.GET("/events", func(c *aero.Ctx) error {
	sse, ok := c.SSE()
	if !ok {
		return c.Res.SendStatus(http.StatusInternalServerError)
	}
	sse.Send("hello")
	return nil
})

type ErrorHandler added in v0.2.0

type ErrorHandler func(c *Ctx, err error)

type Group added in v0.4.0

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

Group represents a set of routes sharing a common path prefix and an optional set of group-level middlewares. Create a group via App.Group or Group.Group.

func (*Group) DELETE added in v0.4.0

func (g *Group) DELETE(path string, h HandlerFunc, m ...HandlerFunc)

DELETE registers a DELETE route under this group's prefix.

func (*Group) GET added in v0.4.0

func (g *Group) GET(path string, h HandlerFunc, m ...HandlerFunc)

GET registers a GET route under this group's prefix.

v1 := app.Group("/v1")
v1.GET("/ping", pingHandler)  // → GET /v1/ping

func (*Group) Group added in v0.4.0

func (g *Group) Group(prefix string, m ...HandlerFunc) (group *Group)

Group creates a nested sub-group under the current group's prefix. The sub-group inherits the parent's middlewares and prepends its own prefix.

api := app.Group("/api")
v2 := api.Group("/v2", versionMiddleware)  // → prefix: /api/v2

func (*Group) HEAD added in v0.4.0

func (g *Group) HEAD(path string, h HandlerFunc, m ...HandlerFunc)

HEAD registers a HEAD route under this group's prefix.

func (*Group) OPTIONS added in v0.4.0

func (g *Group) OPTIONS(path string, h HandlerFunc, m ...HandlerFunc)

OPTIONS registers an OPTIONS route under this group's prefix.

func (*Group) PATCH added in v0.4.0

func (g *Group) PATCH(path string, h HandlerFunc, m ...HandlerFunc)

PATCH registers a PATCH route under this group's prefix.

func (*Group) POST added in v0.4.0

func (g *Group) POST(path string, h HandlerFunc, m ...HandlerFunc)

POST registers a POST route under this group's prefix.

func (*Group) PUT added in v0.4.0

func (g *Group) PUT(path string, h HandlerFunc, m ...HandlerFunc)

PUT registers a PUT route under this group's prefix.

func (*Group) Use added in v0.4.0

func (g *Group) Use(middleware ...HandlerFunc)

Use registers one or more middlewares scoped to this group. These middlewares run after global middlewares and before the route handler.

admin := app.Group("/admin")
admin.Use(adminAuthMiddleware)

type HandlerFunc

type HandlerFunc func(c *Ctx) error

HandlerFunc is the core handler type in Aero. Every route handler and middleware must match this signature. Returning a non-nil error passes it to the application's ErrorHandler.

app.GET("/hello", func(c *aero.Ctx) error {
	return c.Res.SendString("Hello, World!")
})

func WebSocket added in v0.6.0

func WebSocket(fn WebSocketHandler, config ...WSConfig) HandlerFunc

WebSocket returns a HandlerFunc that upgrades the HTTP connection to WebSocket and calls fn with the established WSConn. The connection is closed automatically when fn returns. An optional WSConfig can be provided to configure subprotocols, origin policy, and message limits.

app.GET("/ws", aero.WebSocket(func(ws *aero.WSConn) {
	mt, msg, err := ws.ReadMessage()
	if err != nil {
		return
	}
	ws.WriteMessage(mt, msg)
}))

type MethodNotAllowedHandler added in v0.2.0

type MethodNotAllowedHandler func(allowed string, c *Ctx)

type NotFoundHandler added in v0.2.0

type NotFoundHandler func(c *Ctx)

type OptionsHandler added in v0.3.0

type OptionsHandler func(allowed string, c *Ctx) error

type Param

type Param struct {
	Key   string
	Value string
}

type ParamValues

type ParamValues [maxParamCount]Param

type Range

type Range struct {
	Start int64
	End   int64
}

type RangeResult

type RangeResult struct {
	Type   string
	Ranges []Range
}

type Req

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

Req provides access to all incoming HTTP request data. It is embedded in Ctx and should not be instantiated directly.

func (*Req) Accepts

func (req *Req) Accepts(types ...string) string

Accepts checks the request's Accept header against the provided MIME types and returns the best match, respecting quality values (q-factors). Returns an empty string if none of the types are acceptable.

best := c.Req.Accepts("text/html", "application/json")

func (*Req) AcceptsCharsets

func (req *Req) AcceptsCharsets(charsets ...string) string

AcceptsCharsets checks the Accept-Charset header against the provided charsets and returns the best match. Returns an empty string if none match.

func (*Req) AcceptsEncodings

func (req *Req) AcceptsEncodings(encodings ...string) string

AcceptsEncodings checks the Accept-Encoding header against the provided encodings and returns the best match. Returns an empty string if none match.

enc := c.Req.AcceptsEncodings("gzip", "deflate", "identity")

func (*Req) AcceptsLanguages

func (req *Req) AcceptsLanguages(langs ...string) string

AcceptsLanguages checks the Accept-Language header against the provided language tags and returns the best match. Returns an empty string if none match.

lang := c.Req.AcceptsLanguages("en", "tr", "de")

func (*Req) AppendBody added in v0.8.0

func (r *Req) AppendBody(dst *[]byte) error

AppendBody reads the full request body into dst, growing the slice as needed. It transparently decompresses gzip and deflate encoded bodies. The body can only be read once; subsequent calls return ErrBodyAlreadyRead. MaxBodySize from Config is enforced; exceeding it returns ErrBodyTooLarge.

Prefer this over Req.Body when you already have a buffer to reuse.

func (*Req) BaseURL

func (req *Req) BaseURL() string

BaseURL returns the base URL of the request, consisting of the protocol and host only (e.g. "https://example.com").

func (*Req) Bind added in v0.8.0

func (req *Req) Bind(v any) error

Bind parses the request into v by reading each struct field's tag to determine the source. Supported tags are "json", "xml", "form", "query", "param", and "header". A single struct can mix multiple sources; Bind fills each field from the appropriate location.

type CreateUserReq struct {
	OrgID  string `param:"orgId"`
	Token  string `header:"X-Auth-Token"`
	Page   int    `query:"page"`
	Name   string `json:"name"`
}

var req CreateUserReq
if err := c.Req.Bind(&req); err != nil {
	return err
}

func (*Req) BindForm added in v0.8.0

func (req *Req) BindForm(v any) error

BindForm parses the request body as application/x-www-form-urlencoded or multipart/form-data and maps the values into v using the "form" struct tag.

func (*Req) BindHeaders added in v0.8.0

func (req *Req) BindHeaders(v any) error

BindHeaders maps request headers into v using the "header" struct tag. Header names are case-insensitive.

type AuthReq struct {
	Token string `header:"X-Auth-Token"`
}

func (*Req) BindJSON added in v0.8.0

func (req *Req) BindJSON(v any) error

BindJSON decodes the request body as JSON into v. Returns an error if the Content-Type is not application/json or if decoding fails.

func (*Req) BindParams added in v0.8.0

func (req *Req) BindParams(v any) error

BindParams maps URL path parameters into v using the "param" struct tag.

type UserReq struct {
	ID string `param:"id"`
}

func (*Req) BindQuery added in v0.8.0

func (req *Req) BindQuery(v any) error

BindQuery maps URL query parameters into v using the "query" struct tag.

type SearchReq struct {
	Q    string `query:"q"`
	Page int    `query:"page"`
}

func (*Req) BindXML added in v0.8.0

func (req *Req) BindXML(v any) error

BindXML decodes the request body as XML into v. Returns an error if the Content-Type is not application/xml or if decoding fails.

func (*Req) Body

func (r *Req) Body() ([]byte, error)

Body reads and returns the full request body as a byte slice. It transparently decompresses gzip and deflate encoded bodies. The body can only be read once; subsequent calls return ErrBodyAlreadyRead. MaxBodySize from Config is enforced; exceeding it returns ErrBodyTooLarge.

Use Req.AppendBody to read into an existing buffer, or Req.BodyReader to stream the body without buffering.

func (*Req) BodyReader

func (r *Req) BodyReader() (io.ReadCloser, error)

BodyReader returns the raw request body as an io.ReadCloser for streaming reads without buffering the entire body into memory. The caller is responsible for closing the reader. The body can only be read once; subsequent calls return ErrBodyAlreadyRead.

Note: Content-Encoding decompression is not applied. Use Req.Body or Req.AppendBody if transparent decompression is needed.

func (*Req) Context

func (req *Req) Context() context.Context

Context returns the standard context.Context associated with the current request, as provided by the underlying http.Request.

func (*Req) Cookie

func (req *Req) Cookie(name string) (*http.Cookie, error)

Cookie returns the named cookie from the request. Returns http.ErrNoCookie if the cookie is not present.

func (*Req) Cookies

func (req *Req) Cookies() []*http.Cookie

Cookies returns all cookies attached to the request.

func (*Req) FormFile

func (req *Req) FormFile(key string) (multipart.File, *multipart.FileHeader, error)

FormFile returns the first uploaded file associated with the given form field name. The caller is responsible for closing the returned multipart.File.

func (*Req) FormFiles

func (req *Req) FormFiles(key string) ([]*multipart.FileHeader, error)

FormFiles returns all uploaded files for the given form field name. Useful when a single field accepts multiple file uploads.

func (*Req) FormValue

func (req *Req) FormValue(key string) string

FormValue returns the value of the named field from a parsed application/x-www-form-urlencoded or multipart/form-data body. Returns an empty string if the field is not present.

func (*Req) FormValues

func (req *Req) FormValues() map[string][]string

FormValues returns all form fields as a map of key to value slices, supporting both application/x-www-form-urlencoded and multipart/form-data.

func (*Req) Fresh

func (req *Req) Fresh() bool

Fresh reports whether the request is considered fresh by comparing the ETag and Last-Modified response headers against the corresponding If-None-Match and If-Modified-Since request headers. Typically used to implement conditional GET responses.

func (*Req) Get

func (req *Req) Get(name string) string

Get returns the value of the named request header. The name is case-insensitive. Returns an empty string if not present.

func (*Req) Header

func (req *Req) Header(name string) string

Header returns the value of the named request header. The name is case-insensitive. Returns an empty string if not present. Alias of Req.Get.

func (*Req) Headers

func (req *Req) Headers() http.Header

Headers returns all request headers as an http.Header map.

func (*Req) Host

func (req *Req) Host() string

Host returns the host portion of the request, including the port if present (e.g. "example.com:8080"). It reads from the Host header or the URL.

func (*Req) Hostname

func (req *Req) Hostname() string

Hostname returns the host portion of the request without the port (e.g. "example.com").

func (*Req) IP

func (req *Req) IP() string

IP returns the originating IP address of the request. When [Config.TrustProxy] is enabled, the leftmost address in the X-Forwarded-For header is returned.

func (*Req) IPs

func (req *Req) IPs() []string

IPs returns all IP addresses in the X-Forwarded-For header, in order from client to the nearest proxy. Returns a nil slice if the header is absent or [Config.TrustProxy] is disabled.

func (*Req) Method

func (req *Req) Method() string

Method returns the HTTP method of the request (e.g. "GET", "POST").

func (*Req) MultipartReader

func (req *Req) MultipartReader() (*multipart.Reader, error)

MultipartReader returns a multipart.Reader for streaming a multipart/form-data body part by part, without buffering it into memory. Returns an error if the request is not multipart.

func (*Req) OriginalURL

func (req *Req) OriginalURL() string

OriginalURL returns the full request URL as received, including path and raw query string.

func (*Req) Param

func (req *Req) Param(key string) string

Param returns the value of the named URL path parameter. Returns an empty string if the parameter is not defined on the route.

// Route: /users/:id
id := c.Req.Param("id")

func (*Req) Params

func (req *Req) Params() []Param

Params returns all URL path parameters for the current route as a slice of Param key-value pairs.

func (*Req) Path

func (req *Req) Path() string

Path returns the URL path of the current request (e.g. "/users/42").

func (*Req) Protocol

func (req *Req) Protocol() string

Protocol returns the request protocol string, either "http" or "https". When [Config.TrustProxy] is enabled, the X-Forwarded-Proto header is consulted first.

func (*Req) Query

func (req *Req) Query(key string) string

Query returns the value of the named URL query parameter. Returns an empty string if the key is not present.

// GET /search?q=aero
q := c.Req.Query("q")

func (*Req) QueryAll

func (req *Req) QueryAll() url.Values

QueryAll returns all URL query parameters as url.Values.

func (*Req) Range

func (req *Req) Range(size int64, combine ...bool) (*RangeResult, error)

Range parses the Range header for a resource of the given size in bytes and returns the requested byte ranges. If combine is true, overlapping or adjacent ranges are merged into a single range. Returns an error if the header is malformed or ranges are unsatisfiable.

func (*Req) Secure

func (req *Req) Secure() bool

Secure reports whether the request was made over HTTPS. Equivalent to checking Protocol() == "https".

func (*Req) Stale

func (req *Req) Stale() bool

Stale reports whether the request is considered stale. It is the inverse of Req.Fresh.

func (*Req) Subdomains

func (req *Req) Subdomains() []string

Subdomains returns the subdomains of the request hostname as a slice, ordered from most specific to least specific. The number of segments trimmed from the right is controlled by [Config.SubdomainOffset].

// Host: api.v2.example.com, SubdomainOffset: 2
// → ["api", "v2"]

func (*Req) Validate added in v0.8.0

func (req *Req) Validate(i any) error

Validate runs the application's configured Validator against i. Returns an error if validation fails or if no validator has been set via App.SetValidator.

func (*Req) XHR

func (req *Req) XHR() bool

XHR reports whether the request was made via XMLHttpRequest, by checking if the X-Requested-With header equals "XMLHttpRequest".

type Res

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

Res provides methods for building and sending the HTTP response. It is embedded in Ctx and should not be instantiated directly.

func (*Res) AddHeader

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

AddHeader appends a value to a response header without replacing existing values. Returns *Res for chaining.

func (*Res) Attachment

func (res *Res) Attachment(filename ...string) *Res

Attachment sets the Content-Disposition header to "attachment", prompting the browser to download the response rather than display it. An optional filename is included in the header when provided.

c.Res.Attachment("report.pdf")

func (*Res) ClearCookie

func (res *Res) ClearCookie(name string, opts ...CookieOptions) *Res

ClearCookie expires the named cookie on the client by setting its Max-Age to -1. Optional CookieOptions can be provided to match the original cookie's path and domain. Returns *Res for chaining.

func (*Res) DeleteHeader

func (res *Res) DeleteHeader(key string) *Res

DeleteHeader removes the named header from the response. Returns *Res for chaining.

func (*Res) Download

func (res *Res) Download(path string, filename ...string) error

Download serves a file as a downloadable attachment. It behaves like Res.SendFile but also sets Content-Disposition to "attachment". An optional filename overrides the name shown in the browser's save dialog.

func (*Res) DownloadFS

func (res *Res) DownloadFS(fsys http.FileSystem, path string, filename ...string) error

DownloadFS serves a file from the provided http.FileSystem as a downloadable attachment. See Res.Download and Res.SendFileFS.

func (*Res) Format

func (res *Res) Format(handlers map[string]func() error) error

Format performs content negotiation using the request's Accept header. handlers is a map of MIME type to handler function; the best matching handler is called. Returns an error if no match is found.

c.Res.Format(map[string]func(...){
	"application/json": func(...) { c.Res.JSON(data) },
	"text/html":        func(...) { c.Res.SendString("<p>data</p>") },
})

func (*Res) GetHeader

func (res *Res) GetHeader(key string) string

GetHeader returns the current value of the named response header. Returns an empty string if the header has not been set.

func (*Res) JSON

func (res *Res) JSON(body any) error

JSON serializes body to JSON and writes it with Content-Type application/json. Returns an error if serialization fails.

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

func (*Res) JSONP

func (res *Res) JSONP(body any, callback string) error

JSONP sends a JSONP response by wrapping the JSON-serialized body in the provided JavaScript callback. The callback name is validated against a safe identifier allowlist to prevent XSS.

c.Res.JSONP(data, "cb")
// → cb({"key":"value"});
func (res *Res) Links(links map[string]string) *Res

Links sets the Link response header from the provided map of relation to URL. Multiple relations are comma-separated per RFC 8288.

c.Res.Links(map[string]string{
	"next": "/page/3",
	"prev": "/page/1",
})

func (*Res) Location

func (res *Res) Location(url string) *Res

Location sets the Location response header to the given URL. Returns *Res for chaining.

func (*Res) Redirect

func (res *Res) Redirect(url string, code ...int) error

Redirect sends an HTTP redirect to the given URL. The status code defaults to 302 (Found) if not provided; any 3xx code is accepted.

c.Res.Redirect("/login")
c.Res.Redirect("/new-path", http.StatusMovedPermanently)

func (*Res) ResponseStatus

func (res *Res) ResponseStatus() int

ResponseStatus returns the HTTP status code that will be (or has been) written for this response. Defaults to 200 if Res.Status was not called.

func (*Res) Send

func (res *Res) Send(body any) error

Send writes body to the response. The Content-Type is inferred from the value type: []byte and string are sent as-is, all other types are JSON-encoded. For 204, 304 responses the body is automatically discarded and content-related headers are stripped. HEAD requests are handled correctly.

c.Res.Send("hello")
c.Res.Send(myStruct)

func (*Res) SendBytes

func (res *Res) SendBytes(body []byte) error

SendBytes writes a raw byte slice body. Content-Type defaults to application/octet-stream unless set beforehand.

func (*Res) SendFile

func (res *Res) SendFile(path string, opts ...SendFileOptions) error

SendFile serves a file from the local filesystem at the given path. It sets Content-Type based on the file extension, supports Range requests, and accepts optional SendFileOptions (e.g. root directory restriction). Path traversal outside the configured root is rejected.

func (*Res) SendFileFS

func (res *Res) SendFileFS(fsys http.FileSystem, path string) error

SendFileFS serves a file from the provided http.FileSystem at the given path. Useful for serving embedded files (e.g. via go:embed).

func (*Res) SendStatus

func (res *Res) SendStatus(code int) error

SendStatus writes the given HTTP status code and immediately flushes the response with no body. Useful for sending header-only responses such as 204 No Content or 401 Unauthorized.

return c.Res.SendStatus(http.StatusNoContent)

func (*Res) SendString

func (res *Res) SendString(body string) error

SendString writes a plain string body with Content-Type text/plain.

func (*Res) SetCookie

func (res *Res) SetCookie(name, value string, opts ...CookieOptions) *Res

SetCookie adds a Set-Cookie header to the response with the given name, value, and optional CookieOptions (path, domain, expiry, flags, etc.). Returns *Res for chaining.

func (*Res) SetHeader

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

SetHeader sets a response header, replacing any existing value. Returns *Res for chaining.

func (*Res) Status

func (res *Res) Status(code int) *Res

Status sets the HTTP status code for the response. Returns *Res to allow method chaining before a final send call.

c.Res.Status(http.StatusCreated).JSON(body)

func (*Res) Type

func (res *Res) Type(t string) *Res

Type sets the Content-Type response header. Short aliases such as "json", "html", "text", and "xml" are resolved to their full MIME types. Returns *Res for chaining.

c.Res.Type("html").SendString("<h1>Hello</h1>")

func (*Res) Vary

func (res *Res) Vary(field string) *Res

Vary appends the given field to the Vary response header, indicating that the response may differ based on that request header.

c.Res.Vary("Accept-Encoding")

type SSEWriter added in v0.8.1

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

SSEWriter writes Server-Sent Events to the client over an open HTTP connection. Obtain an instance via Ctx.SSE.

func (*SSEWriter) Send added in v0.8.1

func (s *SSEWriter) Send(data string) error

Send writes a data-only SSE event to the client and flushes immediately.

sse.Send("ping")
// event stream: "data: ping\n\n"

func (*SSEWriter) SendEvent added in v0.8.1

func (s *SSEWriter) SendEvent(event, data string) error

SendEvent writes a named SSE event with data to the client and flushes.

sse.SendEvent("update", `{"count":42}`)
// event stream: "event: update\ndata: {\"count\":42}\n\n"

func (*SSEWriter) SendID added in v0.8.1

func (s *SSEWriter) SendID(id, data string) error

SendID writes a data event with an explicit event ID to the client and flushes. The ID allows clients to resume the stream from the last seen event via the Last-Event-ID header on reconnect.

sse.SendID("msg-42", "hello again")
// event stream: "id: msg-42\ndata: hello again\n\n"

type SendFileOptions

type SendFileOptions struct {
	// MaxAge sets the Cache-Control max-age directive in seconds for the
	// served file. A value of 0 disables caching headers.
	MaxAge int

	// Headers is a map of additional response headers to set when serving
	// the file (e.g. custom Cache-Control directives, CORS headers).
	Headers map[string]string

	// Root restricts file serving to the given directory. Any path that
	// resolves outside Root is rejected to prevent directory traversal attacks.
	// If empty, no restriction is applied.
	Root string
}

SendFileOptions configures the behaviour of Res.SendFile and Res.Download.

type ServerConfig

type ServerConfig struct {
	// Addr is the TCP address to listen on (e.g. ":8080", "0.0.0.0:443").
	// Ignored if Listener is provided.
	Addr string

	// Listener is an optional pre-configured [net.Listener]. When set,
	// Addr is ignored and the server accepts connections from this listener.
	// Useful for testing or socket activation scenarios.
	Listener net.Listener

	// TLSCert is the path to the TLS certificate file (PEM encoded).
	// Required for [ServerConfig.StartTLS].
	TLSCert string

	// TLSKey is the path to the TLS private key file (PEM encoded).
	// Required for [ServerConfig.StartTLS].
	TLSKey string

	// GracefulTimeout is the maximum duration to wait for in-flight requests
	// to complete during shutdown. If zero, the server waits indefinitely
	// until ctx is cancelled.
	GracefulTimeout time.Duration

	// OnShutdownError is an optional callback invoked if the graceful shutdown
	// itself returns an error (e.g. timeout exceeded). If nil, shutdown errors
	// are silently discarded.
	OnShutdownError func(err error)
}

ServerConfig holds the configuration for starting an Aero server with context-aware lifecycle management and graceful shutdown. Use ServerConfig.Start or ServerConfig.StartTLS instead of App.Listen when you need shutdown control via context.Context.

sc := aero.ServerConfig{Addr: ":8080"}
if err := sc.Start(ctx, app); err != nil {
	log.Fatal(err)
}

func (ServerConfig) Start

func (sc ServerConfig) Start(ctx context.Context, app *App) error

Start starts the HTTP server and blocks until ctx is cancelled, at which point it initiates a graceful shutdown — waiting for in-flight requests to complete before returning.

func (ServerConfig) StartTLS

func (sc ServerConfig) StartTLS(ctx context.Context, app *App) error

StartTLS starts an HTTPS server using the certificate and key files configured in ServerConfig. Blocks and shuts down gracefully on ctx cancellation, same as ServerConfig.Start.

type Validator added in v0.8.0

type Validator interface {
	Validate(i any) error
}

type WSConfig added in v0.6.0

type WSConfig struct {
	// Subprotocols is the list of supported WebSocket subprotocols.
	// The server negotiates the best match with the client's
	// Sec-WebSocket-Protocol header.
	Subprotocols []string

	// WriteTimeout is the maximum duration allowed for a single write
	// operation. A zero value means no timeout.
	WriteTimeout time.Duration

	// Origins is the list of allowed Origin header values for the handshake.
	// Requests with an origin not in this list are rejected with 403.
	// If empty, all origins are permitted.
	Origins []string

	// AllowEmptyOrigin permits WebSocket upgrade requests that carry no
	// Origin header. Useful for non-browser clients. Default: false.
	AllowEmptyOrigin bool

	// MaxMessageSize is the maximum allowed incoming message size in bytes.
	// Messages exceeding this limit cause the connection to be closed.
	// A zero value means no limit.
	MaxMessageSize uint64
}

WSConfig holds configuration options for a WebSocket endpoint. All fields are optional; unset fields fall back to sensible defaults.

type WSConn added in v0.6.0

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

WSConn is an Aero WebSocket connection. It wraps the underlying low-level connection and provides a high-level API for reading, writing, and connection-scoped storage. Obtain a WSConn inside a WebSocketHandler.

func (*WSConn) Close added in v0.6.0

func (ws *WSConn) Close() error

Close sends a normal closure frame and closes the underlying connection.

func (*WSConn) CloseWithReason added in v0.6.0

func (ws *WSConn) CloseWithReason(code websocket.CloseStatusCode, reason string) error

CloseWithReason sends a close frame with the given status code and reason string before closing the connection.

ws.CloseWithReason(websocket.CloseNormalClosure, "bye")

func (*WSConn) Locals added in v0.6.0

func (ws *WSConn) Locals(key string, value ...any) any

Locals stores or retrieves a connection-scoped value by key. When called with a value argument, it sets the key and returns the new value. When called with the key only, it returns the current value or nil.

ws.Locals("userID", 42)
id := ws.Locals("userID").(int)

func (*WSConn) ReadMessage added in v0.6.0

func (ws *WSConn) ReadMessage() (int, []byte, error)

ReadMessage reads the next message from the connection. Returns the message type (aero.TextMessage or aero.BinaryMessage), the payload, and any error. A non-nil error typically means the connection was closed or the message exceeded [WSConfig.MaxMessageSize].

func (*WSConn) WriteMessage added in v0.6.0

func (ws *WSConn) WriteMessage(mt int, payload []byte) error

WriteMessage sends a message of the given type to the client. mt should be aero.TextMessage or aero.BinaryMessage.

type WebSocketHandler added in v0.6.0

type WebSocketHandler func(*WSConn)

WebSocketHandler is the function signature for WebSocket connection handlers. The connection is automatically closed when the handler returns.

aero.WebSocket(func(ws *aero.WSConn) {
	for {
		mt, msg, err := ws.ReadMessage()
		if err != nil {
			break
		}
		ws.WriteMessage(mt, msg)
	}
})

Directories

Path Synopsis
internal
color
Package color provides ANSI escape code constants and helper functions for writing colored text to the terminal.
Package color provides ANSI escape code constants and helper functions for writing colored text to the terminal.
middleware
Package websocket provides a low-level, zero-dependency WebSocket implementation following RFC 6455.
Package websocket provides a low-level, zero-dependency WebSocket implementation following RFC 6455.