Skip to content

v0.8.0

Choose a tag to compare

@vkiryakov vkiryakov released this 18 Oct 10:17
· 18 commits to dev since this release
3abd9bc

Zefir 0.8.0 adds zefir-http, an HTTP server with routes checked at compile time.

http

A service is made of controllers: structs whose methods handle requests and which declare their routes. The compiler knows every route of the service: it checks each handler's signature, and every error a handler may return, when it builds the service. What a request brings comes into structs of your own by the field_rules of zefir-validator:

// The body of POST: the title is trimmed, then checked.
const NewTodo = struct {
    title: []const u8,

    pub const field_rules = .{ .title = .{ v.trim, v.notBlank, v.maxLength(200) } };
};

const TodosV1Controller = struct {
    pub const http_routes = http.routes(.{
        .prefix = "/v1/todos",
        .methods = .{
            .list = http.get("", .{}),
            .create = http.post("", .{}),
        },
    });

    pub fn create(self: *TodosV1Controller, ctx: *http.Context) !http.Json(Todo) {
        // Parsed, trimmed and checked: a body that does not fit never gets here.
        const body = try ctx.json(NewTodo);
        // ...
        return .{ .value = todo, .status = 201 };
    }
};

A body that does not fit is a 422 in application/problem+json that names every field.

  • Routes: http.get, http.post, http.put, http.patch, http.delete, http.head, http.options and http.sse.get, with path parameters and catch-alls; 404, 405 with Allow; every GET answers HEAD too, and an OPTIONS request gets 204 with the methods of its path in Allow.
  • Input: ctx.path, ctx.query, ctx.form, ctx.headers, ctx.cookies and ctx.json(T) read into your structs with their outside names, transforms and rules, and report every violation at once.
  • Output: http.Json(T) by the same names and transforms, text and HTML, cookies, redirects, files and streamed responses.
  • One error handler turns every error into a response, problem+json by default; the compiler checks that every error a handler may return becomes a response.
  • Middleware of your own, on the server, the controller and the route, in the order you write them; onResponse and onFinish phases, and typed ctx.locals.
  • Static files from a safe root, http.static: MIME types, ETag and Last-Modified with 304, ranges, and a fallback for single-page apps.
  • Server-sent events, http.sse.Manager: subscriptions by key, heartbeats, Last-Event-ID and bounded queues.
  • Each request runs in a task of its own: a deadline gives 504, a client that leaves cancels its request, .max_in_flight gives 503, and a stop waits .shutdown_grace for the requests in flight.
  • Two backends: http.native, on the network over std.Io, on zio in a service; and http.testing, the same server in memory for tests, with no network.
  • A strict HTTP/1.1 parser of its own (RFC 9112), with limits on heads, targets and bodies, Expect: 100-continue, pipelining and HTTP/1.0.

Not in this version yet, as the TODO list at the top of the guide says: WebSocket, templates, multipart/form-data uploads, OpenAPI, http.Patch(T), ready-made middleware, HTTP/2, HTTP/3 and TLS, compression.

On a shared GitHub runner, 4 vCPUs of an AMD EPYC 7763, a server on zio with four executors answers plain text at about 122 000 requests per second over 64 connections kept alive; the guide's benchmark shows the whole run.

See the http guide: a todo service built step by step, every example compiled and every run shown as it printed. For a first look, the README has http in a minute.

zig fetch --save git+https://github.com/vkiryakov/zefir#v0.8.0