http.zig
Description
http.zig is a low level HTTP server written fully in Zig. It counts 425 stars on Github and has 12 contributors. It describes itself as an alternative to the currently slow std.http.Server, the standard library HTTP server we talk about in an other chapter aswell. By default it uses non-blocking IO by using either epoll or kqueue and make it work on multiple workers. On Windows the IO is blocking so the library spawns a thread per request.
This library is used under the hood by Jetzig and there are plans to have it under Tokamak aswell making it by far the most popular 100% Zig solution.
Basic example
Getting started with a basic httpz server is easy and straightforward.
const std = @import("std");
const httpz = @import("httpz");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var server = try httpz.Server().init(allocator, .{ .port = 5882 });
var router = server.router();
router.get("/hello", hello);
try server.listen();
}
fn hello(req: *httpz.Request, res: *httpz.Response) !void {
_ = req;
try res.json(.{ .message = "hello" }, .{});
}
To go further you have a lot of examples on the github repository of the project.
Cookies
"Cookies are just a specific header. You can always add a Set-Cookie header to the response and read the "cookie" header in the request."
There is no built-in way to handle cookies in httpz, we have to do it manually.
To do so we can use the header Set-Cookie
to set a cookie and read the cookie
header in the request from the client to get the cookies.
There are obviously many ways to do it, but you can see here that just to handle cookies it took me quite a lot of code.
const std = @import("std");
const httpz = @import("httpz");
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
pub fn main() !void {
var server = try httpz.Server().init(allocator, .{ .port = 5882 });
var router = server.router();
router.get("/hello", hello);
try server.listen();
}
fn parseCookie(req: *httpz.Request, buffer: []u8, key: []const u8) ![]const u8 {
var index: usize = 0;
var w_index: usize = 0;
var next_word_is_value: bool = false;
const cookies = req.header("cookie").?;
while (index < cookies.len) : ({
index += 1;
}) {
const current_char = cookies[index];
if (current_char == '=') {
if (std.mem.eql(u8, key, buffer[0..key.len])) {
next_word_is_value = true;
std.debug.print("Key found = {s}\n", .{buffer[0..key.len]});
}
w_index = 0;
} else if (current_char == ';') {
// End of value
if (next_word_is_value) {
std.debug.print("Value found = {s}\n", .{buffer[0..w_index]});
return buffer[0..w_index];
}
w_index = 0;
} else if (current_char == ' ') {
// White character
continue;
} else {
// Word is building
buffer[w_index] = current_char;
w_index += 1;
}
}
if (next_word_is_value) {
std.debug.print("Value found = {s}\n", .{buffer[0..w_index]});
return buffer[0..w_index];
}
return error.CookieNotFound;
}
fn hello(req: *httpz.Request, res: *httpz.Response) !void {
var cookieBuffer: [128]u8 = undefined;
const value = try parseCookie(req, &cookieBuffer, "cookiename");
std.debug.print("cookiename: {s}\n", .{value});
res.headers.add("Set-Cookie", "cookiename=ilovezigsomuch");
try res.json(.{ .message = value }, .{});
}
Conclusion
httpz is a low level HTTP server written fully in Zig. It is an alternative to the currently slow std.http.Server. It uses non-blocking IO by using either epoll or kqueue and make it work on multiple workers. It is easy to use and has a lot of examples to get started with it.
At least two main new zig frameworks are written on top of it, so it is safe to say that it is a good choice for a low level HTTP server in Zig since it probably is the most battle tested one.
The main issues you are going to encounter when working with this framework is having to build by yourself some features like I did here with cookies.