State of http middlewares in zig 0.16

Hello all,

Continuing on my Zig studies, i got this sample code of the standard zig http client and server libraries as they are presented in zig 0.16

// 3-networking.zig

const std = @import("std");
const http = std.http;
const Io = std.Io;
const net = Io.net;
const IpAddress = net.IpAddress;

fn httpClient(io: Io, allocator: std.mem.Allocator, addr: IpAddress) !void {
    // Wait for server
    io.sleep(Io.Duration.fromMilliseconds(100), .awake) catch {};

    var client = http.Client{
        .allocator = allocator,
        .io = io,
    };
    defer client.deinit();

    var uri_buf: [128]u8 = undefined;
    const uri_str = try std.fmt.bufPrint(&uri_buf, "http://127.0.0.1:{d}/", .{addr.getPort()});
    const uri = try std.Uri.parse(uri_str);

    var req = try client.request(.GET, uri, .{});
    defer req.deinit();

    try req.sendBodiless();

    var redirect_buffer: [1024]u8 = undefined;
    var response = try req.receiveHead(&redirect_buffer);

    std.log.info("Client received response: {d} {s}", .{@intFromEnum(response.head.status), response.head.reason});

    var body_buffer: [1024]u8 = undefined;
    var transfer_buffer: [1024]u8 = undefined;
    var body_reader = response.reader(&transfer_buffer);
    const n = try body_reader.readSliceShort(&body_buffer);

    std.log.info("Client received body: {s}", .{body_buffer[0..n]});
}

fn httpServer(io: Io, addr: IpAddress) !void {
    var srv = try addr.listen(io, .{ .reuse_address = true });
    defer srv.deinit(io);

    std.log.info("Server listening on {any}", .{addr});

    const stream = try srv.accept(io);
    defer stream.close(io);

    var in_buffer: [8192]u8 = undefined;
    var out_buffer: [1024]u8 = undefined;

    var reader_obj = stream.reader(io, &in_buffer);
    var writer_obj = stream.writer(io, &out_buffer);

    var server = http.Server.init(&reader_obj.interface, &writer_obj.interface);
    var req = try server.receiveHead();

    std.log.info("Server received request: {s} {s}", .{@tagName(req.head.method), req.head.target});

    try req.respond("Hello from Zig HTTP Server!\n", .{});
    std.log.info("Server responded and closing", .{});
}

pub fn main(init: std.process.Init) !void {
    const io = init.io;
    const addr = try IpAddress.parse("127.0.0.1", 8080);

    var server_task = io.async(httpServer, .{ io, addr });
    var client_task = io.async(httpClient, .{ io, init.gpa, addr });

    try server_task.await(io);
    try client_task.await(io);
}
 

If i understood correctly, old examples no longer work on 0.16

By the sake of learning, i have 2 questions:

1: How often one could expect extensive rewriting like this one in 0.16?

2: Is there a way to optimize this sample? any redundant, wasteful code?

Thanks in advance for any guidance!

Re 1: There’s no crystal ball that tells what might change significantly and require rewriting along Zig’s path to 1.0, especially the std lib. Any estimate is a gamble. The conservative assumption is that you will have to rewrite. This is a fact of life when choosing to use a pre 1.0 language.

I am writing a web-server based service and currently using this approach:


pub const Server = struct {
    host: []const u8,
    port: u16,
    addr: std.Io.net.IpAddress,
    io: std.Io,
    db: *Database,
    config: Config,

    pub fn init(io: std.Io, cfg: Config, database: *Database) !Server {
        const host: []const u8 = "0.0.0.0";
        const port: u16 = cfg.port;
        const addr = try std.Io.net.IpAddress.parseIp4(host, port);

        return .{ .host = host, .port = port, .addr = addr, .io = io, .db = database, .config = cfg };
    }

    pub fn run(self: Server) !void {
        var listening = try self.listen();
        while (true) {
            const connection = try listening.accept(self.io);
            self.handleConnection(connection) catch |err| {
                std.log.err("connection error: {any}", .{err});
            };
        }
    }

    fn handleConnection(self: Server, connection: std.Io.net.Stream) !void {
        defer connection.close(self.io);

        var read_buffer: [4096]u8 = undefined;
        var write_buffer: [4096]u8 = undefined;

        var stream_reader = std.Io.net.Stream.Reader.init(connection, self.io, &read_buffer);
        var stream_writer = std.Io.net.Stream.Writer.init(connection, self.io, &write_buffer);

        var http_server = std.http.Server.init(&stream_reader.interface, &stream_writer.interface);

        var request = try http_server.receiveHead();
        try self.handleRequest(&request);
    }

    fn handleRequest(self: Server, request: *std.http.Server.Request) !void {
        const route = matchRoute(request.head.method, request.head.target) orelse {
            try respondNotFound(request);
            return;
        };

        if (route.requiresAdmin() and !self.isAuthorized(request)) {
            try respondLoginForm(request, .unauthorized);
            return;
        }

        switch (route) {
            .event => try self.handleEvent(request),
            .login => try self.handleLogin(request),
            .index => try self.handleIndex(request),
            .app => try self.handleApp(request),
            .createDaily => try self.handleCreateDaily(request),
        }
    }
}

Does it look clearer?