Skip to content

Instantly share code, notes, and snippets.

@zigster64
Last active May 8, 2023 14:44
Show Gist options
  • Save zigster64/7a2bc21c00629573f59765ff4f8d336b to your computer and use it in GitHub Desktop.
Save zigster64/7a2bc21c00629573f59765ff4f8d336b to your computer and use it in GitHub Desktop.
simple threadpool webserver
const std = @import("std");
const Self = @This();
fn runLoop(self: *Self) !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var server = std.http.Server.init(allocator, .{ .reuse_address = true, .kernel_backlog = 64 });
defer server.deinit();
var listen_address = try std.net.Address.resolveIp("0.0.0.0", 8080);
try server.listen(listen_address);
var thread_id: usize = 0;
var thread_pool: std.Thread.Pool = undefined;
var pool = &thread_pool;
try std.Thread.Pool.init(pool, .{ .allocator = allocator, .n_jobs = 4 });
while (true) {
thread_id += 1;
const res = try server.accept(.{ .allocator = allocator });
// clone the stack based response to the heap so it can be owned by the thread, and freed up there where the thread is done
var response_clone_owned = try allocator.create(std.http.Server.Response);
response_clone_owned.* = res;
try pool.spawn(handler, .{ self, thread_id, response_clone_owned });
}
}
fn handler(self: *Self, thread_id: usize, response: *std.http.Server.Response) void {
defer {
// cleanup resources owned by this thread
response.deinit();
response.allocator.destroy(response);
}
while (true) {
response.wait() catch break;
response.headers.append("server", "Muh-Server") catch return;
// This is a bit simplistic, but demonstrates some basic decision tree
// You will want to frontend this with a router, and wrap it in some code
// that injects middleware in the handler chain, and manage errors nicely
// so that response headers can be set to reflect every edge case
switch (response.request.method) {
.GET => {
self.handleGet(response);
},
.POST => {
self.handlePost(thread_id, response);
},
else => {
response.status = .bad_request;
response.do() catch return;
},
}
response.finish() catch return;
if (response.reset() == .closing) {
break;
}
}
}
fn handleGet(self: Self, res: *std.http.Server.Response) void {
// Do the GET request
//
// Catch all errors, on error set the res header, call res.do() and return
//
// for a file server it would be something like :
// - read the file
// - set the res.transfer_encoding = .{ .content_length = size of output }
// - set the mime type
// - res.do()
// - res.write (conntents of the file)
}
fn handlePost(self: *Self, thread_id: usize, res: *std.http.Server.Response) void {
// Do the POST request
// the thread_id shows how you can pass context from the main loop through
// to the handler - using a simple int here, but you could pass some more
// complex context through, as required
// Catch all errors, on error set the res header, call res.do() and return
// - read and alloc the res.request.body input payload
// - decode and parse the input payload
// - calculate the response payload
// - set the res headers
// - res.do()
// - res.write() the response payload
// - free the res.request.body allocated at the top
// - free the payload parser that may have been used to parse the request
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment