Skip to content

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:

try req.io.sleep(.fromMilliseconds(100), .awake);

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.