Zero-allocation request parsing with fluent response building, Sonic JSON, and compatibility APIs for streaming, file serving, and hijack.
Every handler receives a *Request containing parsed HTTP data:
type Request struct { Method string Path string Proto string Host string RemoteAddr string Headers [][2]string Body []byte StreamID uint32 IsH2 bool Params [8]Param ParamCount int StreamWriter StreamWriter }
| Field | Type | Description |
|---|---|---|
Method |
string |
HTTP method: "GET", "POST", etc. |
Path |
string |
Request path: "/users/42" |
Proto |
string |
Protocol: "HTTP/1.1" or "HTTP/2.0" |
Host |
string |
Request host: "example.com" |
RemoteAddr |
string |
Client IP address |
Headers |
[][2]string |
Header key-value pairs |
Body |
[]byte |
Request body bytes |
StreamID |
uint32 |
HTTP/2 stream ID |
IsH2 |
bool |
True if HTTP/2 request |
Params |
[8]Param |
Route parameters (zero-alloc, fixed-size) |
ParamCount |
int |
Number of params captured |
StreamWriter |
StreamWriter |
Compatibility stream writer when the request path supports streamed responses;
nil in the native io_uring worker runtimes |
Use Header() for case-insensitive header lookup:
s.Router.GET("/info", func(req *core.Request, resp *core.Response) { contentType := req.Header("Content-Type") auth := req.Header("Authorization") userAgent := req.Header("User-Agent") for _, h := range req.Headers { key, val := h[0], h[1] fmt.Printf("%s: %s\n", key, val) } })
Access named parameters from the URL path:
s.Router.GET("/users/:id/posts/:postId", func(req *core.Request, resp *core.Response) { userId := req.ParamValue("id") postId := req.ParamValue("postId") resp.String("User " + userId + ", Post " + postId) }) req.SetParam("key", "value")
The body is available as raw bytes on req.Body:
s.Router.POST("/upload", func(req *core.Request, resp *core.Response) { body := req.Body if len(body) == 0 { resp.Status(400).String("Empty body") return } resp.Status(201).String("Received") })
Body size is enforced by MaxBodySize in the server config. Requests
exceeding this limit are rejected before reaching your handler.
The *Response provides a fluent API for building HTTP responses. Methods are
chainable:
resp.Status(201). SetHeader("X-Custom", "value"). JSON([]byte(`{"created":true}`))
Set HTTP status code. Defaults to 200 if not set:
resp.Status(200) resp.Status(201) resp.Status(204) resp.Status(301) resp.Status(400) resp.Status(401) resp.Status(404) resp.Status(500)
Multiple methods for setting the response body:
| Method | Content-Type | Description |
|---|---|---|
String(s) |
text/plain |
Plain text response |
HTML(s) |
text/html |
HTML response |
Bytes(b) |
inherited | Raw byte response |
JSON(b) |
application/json |
Pre-serialized JSON bytes |
JSONString(s) |
application/json |
JSON from string |
JSONMarshal(v) |
application/json |
Marshal any value to JSON |
resp.String("Hello!") resp.HTML("<h1>Welcome</h1>") resp.JSON([]byte(`{"status":"ok"}`)) type User struct { Name string `json:"name"` Age int `json:"age"` } resp.JSONMarshal(User{Name: "Alice", Age: 30})
For high-performance JSON, use JSON() with pre-serialized bytes to avoid
reflection. For convenience, JSONMarshal() handles encoding:
resp.JSON([]byte(`{"users":[],"total":0}`)) err := resp.JSONMarshal(map[string]any{ "users": users, "total": len(users), })
For high-performance JSON serialization, ALOS provides a pooled Sonic JSON encoder that handles acquisition, marshaling, and recycling automatically:
j := core.AcquireJSON() resp.Status(200).JSON(j.Marshal(map[string]any{ "name": "Alice", "age": 30, "active": true, "tags": []string{"admin", "user"}, })) j.Release()
You can also marshal structs directly:
type User struct { Name string `json:"name"` Age int `json:"age"` Active bool `json:"active"` Tags []string `json:"tags"` } j := core.AcquireJSON() resp.Status(200).JSON(j.Marshal(User{ Name: "Alice", Age: 30, Active: true, Tags: []string{"admin", "user"}, })) j.Release()
| Method | Description |
|---|---|
AcquireJSON() |
Get a pooled JSON encoder |
Marshal(v any) |
Serialize any value to JSON bytes using Sonic |
Release() |
Return the encoder to the pool for reuse |
Set custom headers on the response. Headers are chainable:
resp.SetHeader("X-Custom", "value"). SetHeader("Cache-Control", "no-cache"). SetHeader("X-Request-ID", requestId). Status(200). JSON(data)
Low-level body access for advanced use cases:
| Method | Description |
|---|---|
SetBody(b []byte) |
Replace the response body with raw bytes |
GetBody() []byte |
Get the current response body bytes |
BodyLen() int |
Get the current body length |
s.Router.GET("/raw", func(req *core.Request, resp *core.Response) { resp.SetBody([]byte("raw bytes")) resp.ContentType = "application/octet-stream" }) s.Router.GET("/transform", func(req *core.Request, resp *core.Response) { resp.JSON([]byte(`{"data":"original"}`)) body := resp.GetBody() modified := bytes.Replace(body, []byte("original"), []byte("modified"), 1) resp.SetBody(modified) })
Use SetStreamer and Streamer() for streaming responses. When a
streamer is set, the normal body is bypassed and the StreamWriter handles output directly:
The native Linux io_uring worker backends currently reject streamed
responses, so req.StreamWriter is nil there and
SendFile is not available on that path yet. The examples below describe
the compatibility behavior.
| Method | Description |
|---|---|
SetStreamer(sw StreamWriter) |
Enable streaming mode with the given writer |
Streamer() StreamWriter |
Get the current streamer |
IsStreamed() bool |
Check if response is in streaming mode |
SendFile(path string, opts ...SendFileOption) |
Serve a file with MIME detection and range support |
s.Router.GET("/stream", func(req *core.Request, resp *core.Response) { sw := req.StreamWriter sw.WriteHeader(200, nil, "text/event-stream") for i := 0; i < 5; i++ { sw.WriteChunk([]byte(fmt.Sprintf("data: %d\n\n", i))) sw.Flush() time.Sleep(time.Second) } sw.Close() })
Serve files with options:
s.Router.GET("/download", func(req *core.Request, resp *core.Response) { resp.SendFile("./files/report.pdf", core.WithAttachment("report.pdf"), core.WithRateLimit(100), ) })
See the Streaming & Files page for the full StreamWriter API and SendFile options.
Take over the raw TCP connection for custom protocols. Returns the underlying
net.Conn:
HijackConn is a compatibility API. The native Linux io_uring
worker backends do not allow handlers to hijack connections today, which also means
hijack-based features such as WebSocket upgrade are not available on that path.
s.Router.GET("/hijack", func(req *core.Request, resp *core.Response) { conn := req.HijackConn() if conn == nil { resp.Status(500).String("hijack failed") return } defer conn.Close() conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello")) })
When using ALOS's custom TLS 1.3, HijackConn returns a wrapper that
transparently handles AEAD encryption/decryption. You read and write plaintext; the
wrapper handles record-layer framing.