Skip to content

Instantly share code, notes, and snippets.

@lmllrjr
Last active June 4, 2023 19:50
Show Gist options
  • Save lmllrjr/5120f5ac0070dc74c9519c39fc534e2e to your computer and use it in GitHub Desktop.
Save lmllrjr/5120f5ac0070dc74c9519c39fc534e2e to your computer and use it in GitHub Desktop.
[Zig⚡️] basic http server with multiplexer
// zig version 0.10.1
const std = @import("std");
const net = std.net;
const mem = std.mem;
const log = std.log;
const stdout = std.io.getStdOut().writer();
const BUFSIZE = 8192;
const HTTPHEAD =
"HTTP/1.1 {s}\r\n" ++
"Connection: close\r\n" ++
"Content-Type: {s}\r\n" ++
"Content-Length: {}\r\n\r\n" ++
"{s}";
const ServeFileError = error{
RecvHeaderEOF,
RecvHeaderExceededBuffer,
HeaderDidNotMatch,
};
pub fn main() anyerror!void {
const self_addr = try net.Address.resolveIp("127.0.0.1", 9000);
var listener = net.StreamServer.init(.{});
try (&listener).listen(self_addr);
// define routes
const routes = comptime [_]route{
route{ .method = "GET", .path = "/t", .handler = teapotHandler },
route{ .method = "GET", .path = "/hello", .handler = helloWorldHandler },
};
log.info("starting server, listen on {}", .{self_addr});
// start listen and serve
while ((&listener).accept()) |conn| {
log.info("{{\"conn\":\"{}\"}}", .{conn.address});
router(&conn.stream, routes) catch |err| {
if (@errorReturnTrace()) |bt| {
log.err("{{\"err\":\"{}\",\"stacktrace\":\"{}\"}}", .{ err, bt });
} else {
log.err("{{\"err\":\"{}\"}}", .{err});
}
};
conn.stream.close();
} else |err| {
log.info("{{\"err\":\"{}\"}}", .{err});
return err;
}
}
const route = struct {
handler: *const fn (stream: *const net.Stream) void,
method: []const u8,
path: []const u8,
};
fn helloWorldHandler(stream: *const net.Stream) void {
const hello = "hello world";
stream.writer().print(HTTPHEAD, .{ "200 OK", "text/html", hello.len, hello }) catch unreachable;
}
fn teapotHandler(stream: *const net.Stream) void {
const teapod = "418 i'm a teapot";
stream.writer().print(HTTPHEAD, .{ "418 OK", "text/html", teapod.len, teapod }) catch unreachable;
}
fn notFoundHandler(stream: *const net.Stream) void {
stream.writer().print(HTTPHEAD, .{ "404", "text/html", 13, "404 not found" }) catch unreachable;
}
/// router is a very basic multiplexer
fn router(stream: *const net.Stream, comptime routes: [2]route) !void {
var recv_buf: [BUFSIZE]u8 = undefined;
var recv_total: usize = 0;
while (stream.read(recv_buf[recv_total..])) |recv_len| {
if (recv_len == 0)
return ServeFileError.RecvHeaderEOF;
recv_total += recv_len;
if (mem.containsAtLeast(u8, recv_buf[0..recv_total], 1, "\r\n\r\n"))
break;
if (recv_total >= recv_buf.len)
return ServeFileError.RecvHeaderExceededBuffer;
} else |read_err| {
return read_err;
}
const recv_slice = recv_buf[0..recv_total];
var tok_itr = mem.tokenize(u8, recv_slice, " ");
const request_method = tok_itr.next() orelse "";
const path = tok_itr.next() orelse "";
if (path[0] != '/')
return ServeFileError.HeaderDidNotMatch;
// loop through routes and check if path matches
for (routes) |rt| {
if (!mem.eql(u8, request_method, rt.method))
return ServeFileError.HeaderDidNotMatch;
if (mem.eql(u8, path, rt.path)) {
rt.handler(stream);
log.info("{{\"method\":\"{s}\",\"path\":\"{s}\"}}", .{ rt.method, rt.path });
}
}
notFoundHandler(stream);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment