aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Ashby <martin@ashbysoft.com>2024-03-02 21:45:34 +0000
committerMartin Ashby <martin@ashbysoft.com>2024-03-02 21:45:34 +0000
commitb50102af01d07154b8bf59571596722684dc5183 (patch)
treed50202b40f074c7f6f5eefdb7255bbaf4dde4734
parentcb2b9eff709e6adba4e8ff7bd0535a59fe5d53bf (diff)
downloadmfashby.net-b50102af01d07154b8bf59571596722684dc5183.tar.gz
mfashby.net-b50102af01d07154b8bf59571596722684dc5183.tar.bz2
mfashby.net-b50102af01d07154b8bf59571596722684dc5183.tar.xz
mfashby.net-b50102af01d07154b8bf59571596722684dc5183.zip
More work on self-hosted server
-rw-r--r--src/main.zig111
1 files changed, 100 insertions, 11 deletions
diff --git a/src/main.zig b/src/main.zig
index 96dafc9..f9f204b 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -18,29 +18,37 @@ const log = std.log.scoped(.server);
// - and maybe even cgit (although I might scrap it in favour of stagit if I can figure out archive downloads
// - do something about efficiency :) it's thread-per-request right now
pub fn main() !void {
- var gpa = std.heap.GeneralPurposeAllocator(.{
- .thread_safe = true,
- }){};
- defer {
- log.info("deinit gpa", .{});
- _ = gpa.deinit();
- }
+ var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){};
+ defer _ = gpa.deinit();
const a = gpa.allocator();
-
var pool: std.Thread.Pool = undefined;
try std.Thread.Pool.init(&pool, .{ .allocator = a, .n_jobs = 32 });
defer pool.deinit();
-
var svr = std.http.Server.init(.{ .reuse_address = true, .reuse_port = true });
- const addr = try std.net.Address.parseIp("0.0.0.0", 8081);
+ defer svr.deinit();
+ const port = 8081;
+ const addr = std.net.Address.initIp4(.{0,0,0,0}, port);
try svr.listen(addr);
log.info("listening on {}", .{addr});
- while (true) {
+ try acceptLoop(a, &svr, &pool, struct{
+ fn cancelled() bool {
+ return false;
+ }
+ });
+}
+
+fn acceptLoop(a: std.mem.Allocator, svr: *std.http.Server, pool: *std.Thread.Pool, cancel: anytype) !void {
+ while (!cancel.cancelled()) {
// Create the Response into the heap so that the ownership goes to the handling thread
const res = try a.create(std.http.Server.Response);
errdefer a.destroy(res);
res.* = try svr.accept(.{ .allocator = a });
errdefer res.deinit();
+ if (cancel.cancelled()) {
+ res.deinit();
+ a.destroy(res);
+ break;
+ }
try pool.spawn(handle, .{res});
}
}
@@ -201,6 +209,7 @@ fn serveStatic(res: *std.http.Server.Response, dirname: []const u8) !void {
};
var requested_path = uri.path;
requested_path = try std.fs.path.join(res.allocator, &.{ dirpath, requested_path });
+ defer res.allocator.free(requested_path);
const path = std.fs.realpathAlloc(res.allocator, requested_path) catch |e| {
@@ -340,3 +349,83 @@ fn sendError(res: *std.http.Server.Response, e: anyerror) !void {
},
}
}
+
+const Fixture = struct {
+ a: std.mem.Allocator,
+ port: u16,
+ t: std.Thread,
+ cancel: std.atomic.Value(bool),
+ base_url: []const u8,
+ thread_pool: std.Thread.Pool,
+ server: std.http.Server,
+ client: std.http.Client,
+
+ fn init(a: std.mem.Allocator) !*Fixture {
+ var res: *Fixture = try a.create(Fixture);
+ errdefer a.destroy(res);
+ res.a = a;
+ try std.Thread.Pool.init(&res.thread_pool, .{ .allocator = a, .n_jobs = 32 });
+ errdefer res.thread_pool.deinit();
+ res.server = std.http.Server.init(.{ .reuse_address = true, .reuse_port = true });
+ errdefer res.server.deinit();
+ const addr = std.net.Address.initIp4(.{127,0,0,1}, 0);
+ try res.server.listen(addr);
+ res.port = res.server.socket.listen_address.in.getPort();
+ res.base_url = try std.fmt.allocPrint(a, "http://localhost:{}", .{res.port});
+ errdefer a.free(res.base_url);
+ res.cancel = std.atomic.Value(bool).init(false);
+ res.t = try std.Thread.spawn(.{}, acceptLoop, .{a, &res.server, &res.thread_pool, res});
+ res.client = .{.allocator = a};
+ return res;
+ }
+
+ fn deinit(self: *Fixture) void {
+ self.client.deinit();
+ self.a.free(self.base_url);
+ self.cancel.store(true, .Unordered);
+ // Trigger the server's accept() method to wake it up
+ if (std.net.tcpConnectToAddress(std.net.Address.initIp4(.{127,0,0,1}, self.port))) |stream| {
+ stream.close();
+ } else |_| {}
+ self.t.join();
+ self.thread_pool.deinit();
+ self.server.deinit();
+ self.a.destroy(self);
+ }
+
+ fn cancelled(self: *Fixture) bool {
+ return self.cancel.load(.Unordered);
+ }
+};
+
+
+test "static pages" {
+ const a = std.testing.allocator;
+ var f = try Fixture.init(a);
+ defer f.deinit();
+ const TestCase = struct {
+ path: []const u8,
+ file: []const u8,
+ };
+ const test_cases = [_]TestCase{
+ .{ .path = "/", .file = "public/index.html"},
+ .{ .path = "/posts/2018-05-31-new-site/", .file = "public/posts/2018-05-31-new-site/index.html"},
+ };
+
+ for (test_cases) |test_case| {
+ const url = try std.fmt.allocPrint(a, "{s}{s}", .{f.base_url, test_case.path});
+ defer a.free(url);
+ var fr = try f.client.fetch(a, .{
+ .location = .{.url = url}
+ });
+ defer fr.deinit();
+ try std.testing.expectEqual(std.http.Status.ok, fr.status);
+ const expected = try std.fs.cwd().readFileAlloc(a, test_case.file, 1_000_000);
+ defer a.free(expected);
+ try std.testing.expectEqualStrings(expected, fr.body.?);
+ }
+}
+
+// test "404" {
+
+// } \ No newline at end of file