Requests
A handler that takes *http.Request can read what the client sent:
fn info(req: *http.Request, res: *http.Response) !void {
const agent = req.headers.get("User-Agent") orelse "unknown";
const page = req.query.get("page") orelse "1";
try res.print(.text, "{f} {s}, page {s}, from {s}\n", .{ req.method, req.path, page, agent });
}
req.method is the method, and req.url is the request target as it arrived. req.path is the
decoded part before the ?. req.query has the query parameters. req.headers.get looks up a
header by name, ignoring case. Path parameters are in req.params. See
Routing.
For a value you need in many handlers, http.Query and http.Header give it to the handler already
parsed. See Handlers.
Body
The server doesn't read the body until the handler asks for it. req.body() reads all of it into
memory:
fn upload(req: *http.Request, res: *http.Response) !void {
const body = try req.body() orelse "";
try res.print(.text, "Got {d} bytes\n", .{body.len});
}
It returns null when the request has no body. request.max_body_size limits the body to 1 MiB by
default. A larger body fails with error.BodyTooBig, and if the handler returns that error, the
client gets a 413. A body sent with gzip or deflate Content-Encoding is decoded as it's read.
For a body too large to keep in memory, read it piece by piece with req.reader:
fn count(req: *http.Request, res: *http.Response) !void {
var buf: [4096]u8 = undefined;
var body = try req.reader(&buf);
var lines: usize = 0;
while (try body.interface.takeDelimiter('\n')) |_| lines += 1;
try res.print(.text, "Got {d} lines\n", .{lines});
}
body.interface is a std.Io.Reader. The body is decoded the same way, but max_body_size doesn't
apply. When a read fails with error.ReadFailed, body.err has the underlying error. A handler
that has taken the reader can't call req.body() after it.
Forms
req.formData() reads a form sent as application/x-www-form-urlencoded, which is what an HTML
form sends by default:
fn login(req: *http.Request, res: *http.Response) !void {
const form = try req.formData();
const user = form.get("user") orelse {
res.status = .bad_request;
return res.print(.text, "Missing user\n", .{});
};
try res.print(.text, "Hello, {s}\n", .{user});
}
A form with file uploads is sent as multipart/form-data, and req.multiFormData() reads it. Each
entry has the value, and the filename for a file:
fn avatar(req: *http.Request, res: *http.Response) !void {
const form = try req.multiFormData();
const file = form.get("avatar") orelse {
res.status = .bad_request;
return res.print(.text, "Missing avatar\n", .{});
};
try res.print(.text, "{s}, {d} bytes\n", .{ file.filename orelse "no name", file.value.len });
}
Both read the whole body with req.body(), so the same size limit applies. If the request has a
different content type, they fail with error.NotForm or error.NotMultipartForm.
JSON
req.json(T) parses the body with std.json, and returns null when there is no body.
Serialization shows how to decode it with json.zig, which is faster.
Cookies
req.cookies().get(name) returns the value of a cookie the client sent:
fn theme(req: *http.Request, res: *http.Response) !void {
const value = req.cookies().get("theme") orelse "light";
try res.print(.text, "Theme: {s}\n", .{value});
}
To set a cookie, see Responses.
The Client
req.remote_address is the address of the client. Behind a reverse proxy it's the proxy's address.
Configuration shows how to get the client's address from
X-Forwarded-For.
req.secure is true when the connection uses TLS. On a server with more than one listener,
req.listener is the one the request came in on.
Memory
req.arena is an allocator for anything that only has to live as long as the request. It's freed
after the response has been sent. Values the request gives you, like headers, the body and form
fields, live in the same memory, so copy them if you need them after that.
I/O
req.io is the std.Io the server runs on. Pass it to anything in a handler that takes an Io,
like reading a file, sleeping or a database client:
That way the work runs on the same I/O as the server, for example on zio in production.
Next Steps
- Responses covers status codes, headers and the ways to write a body.
- Middleware covers code that runs before and after the handlers.