Last active
May 8, 2023 14:44
-
-
Save zigster64/7a2bc21c00629573f59765ff4f8d336b to your computer and use it in GitHub Desktop.
simple threadpool webserver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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