From ea1bdedd6b1e93c6ea69b4746550331ffcd52a16 Mon Sep 17 00:00:00 2001 From: zachir Date: Sat, 23 Mar 2024 23:18:02 -0500 Subject: Replace GPA with FBA This replaces the General Purpose Allocator with the Fixed Buffer Allocator, so we don't make any heap reservations, as everything except for server.accept() is a known size (or maximum size) at compile time. --- src/main.zig | 94 ++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/src/main.zig b/src/main.zig index 9b85d30..b3c1b55 100644 --- a/src/main.zig +++ b/src/main.zig @@ -42,21 +42,26 @@ const getcwd = std.os.getcwd; /// The maximum file size is 1 << 21, aka (1 * 2^20), which is 2 MB. const server_addr = "127.0.0.1"; const server_port = 8080; -const MAX_PATH_BYTES = fs.MAX_PATH_BYTES; -const BUFFER_LIMIT = 1 << 21; +const max_path_bytes = fs.MAX_PATH_BYTES; +const buffer_limit = 1 << 21; -/// read_files() reads the file to a provided buffer, and returns the number of +/// readFiles() reads the file to a provided buffer, and returns the number of /// bytes read. -/// read_files() can fail from fmt.allocPrint(), fs.cwd().openFile(), +/// readFiles() can fail from fmt.allocPrint(), fs.cwd().openFile(), /// file.stat(), and file.readAll() /// fmt.allocPrint() will return an AllocPrintError /// fs.cwd().openFile() will return a File.OpenError /// file.stat() will return a StatError /// file.readAll() will return a ReadError -fn read_files(target: []const u8, buffer: []u8, allocator: mem.Allocator) !usize { - var file_path = try allocator.alloc(u8, MAX_PATH_BYTES); +fn readFiles(target: []const u8, buffer: []u8) !usize { + const whole_buffer = max_path_bytes * 2; + var fba_buffer: [whole_buffer]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&fba_buffer); + const allocator = fba.allocator(); + + var file_path = try allocator.alloc(u8, max_path_bytes); defer allocator.free(file_path); - var cwd_buffer = [_]u8{0} ** MAX_PATH_BYTES; + var cwd_buffer = [_]u8{0} ** max_path_bytes; const cwd = try getcwd(&cwd_buffer); file_path = try fmt.allocPrint(allocator, "{s}{s}", .{ cwd, target }); log.info("Loading file {s}...", .{file_path}); @@ -71,7 +76,7 @@ fn read_files(target: []const u8, buffer: []u8, allocator: mem.Allocator) !usize } else { file_path = try fmt.allocPrint(allocator, "{s}/index.html", .{target}); } - return read_files(file_path, buffer, allocator); + return readFiles(file_path, buffer); }, .file => { return try file.readAll(buffer); @@ -82,34 +87,34 @@ fn read_files(target: []const u8, buffer: []u8, allocator: mem.Allocator) !usize } } -/// handle_request() handles the requests from the server and, if necessary, -/// calls read_files to read requested files. -/// handle_request() can fail from response.headers.append(), response.do(), -/// response.writeAll(), response.finish(), and read_files() +/// handleRequest() handles the requests from the server and, if necessary, +/// calls readFiles to read requested files. +/// handleRequest() can fail from response.headers.append(), response.do(), +/// response.writeAll(), response.finish(), and readFiles() /// response.headers.append() does not define what error types it will return /// response.do() does not define what error types it will return /// response.writeAll() will return a WriteError /// response.finish() will return a FinishError -/// read_files() does not define what error types it will return -/// handle_request handles the following status codes: +/// readFiles() does not define what error types it will return +/// handleRequest handles the following status codes: /// - 200 OK /// - 403 Forbidden /// - 404 Not Found /// - 413 Payload Too Large /// - 414 URI Too Long -fn handle_request(response: *http.Server.Response, allocator: mem.Allocator) !void { +fn handleRequest(response: *http.Server.Response) !void { // Log the request details log.info("{s} {s} {s}", .{ @tagName(response.request.method), @tagName(response.request.version), response.request.target }); - // Create a []u8 to read up to BUFFER_LIMIT characters - var read = [_]u8{0} ** BUFFER_LIMIT; + // Create a []u8 to read up to buffer_limit characters + var read = [_]u8{0} ** buffer_limit; // Set "connection" header to "keep-alive" if present in request headers if (response.request.headers.contains("connection")) { try response.headers.append("connection", "keep-alive"); } - const size = read_files(response.request.target, &read, allocator) catch |err| { + const size = readFiles(response.request.target, &read) catch |err| { switch (err) { error.AccessDenied => { response.status = .forbidden; @@ -128,7 +133,7 @@ fn handle_request(response: *http.Server.Response, allocator: mem.Allocator) !vo try response.do(); return; }; - if (size >= BUFFER_LIMIT) { + if (size >= buffer_limit) { response.status = .payload_too_large; response.transfer_encoding = .{ .content_length = 0 }; try response.do(); @@ -201,15 +206,19 @@ fn handle_request(response: *http.Server.Response, allocator: mem.Allocator) !vo } } -/// run_server() accepts inputs from the server, and passes the requests on to -/// handle_request(). -/// run_server() can fail from server.accept(), response.wait(), and -/// handle_request(). +/// runServer() accepts inputs from the server, and passes the requests on to +/// handleRequest(). +/// runServer() can fail from server.accept(), response.wait(), and +/// handleRequest(). /// server.accept() will return an AcceptError /// resonse.wait() will return a WaitError -/// run_server() handles the following error codes: +/// runServer() handles the following error codes: /// - 500 (Internal Server Error) -fn run_server(server: *http.Server, allocator: mem.Allocator) !void { +fn runServer(server: *http.Server) !void { + var fba_buffer: [buffer_limit]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&fba_buffer); + const allocator = fba.allocator(); + outer: while (true) { // Accept incoming connection var response = try server.accept(.{ @@ -226,7 +235,7 @@ fn run_server(server: *http.Server, allocator: mem.Allocator) !void { }; // Process the request - handle_request(&response, allocator) catch |err| { + handleRequest(&response) catch |err| { response.status = .internal_server_error; response.transfer_encoding = .{ .content_length = 0 }; try response.do(); @@ -236,38 +245,41 @@ fn run_server(server: *http.Server, allocator: mem.Allocator) !void { } } -pub fn print_info() void { - log.info("zhttpd version 0.1.0, Copyright (C) 2024 ZachIR", .{}); - log.info("zhttpd comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions; see the included LICENSE for more details", .{}); +pub fn printInfo(stderr: fs.File.Writer) !void { + try stderr.print("zhttpd version 0.1.0, Copyright (C) 2024 ZachIR\n", .{}); + try stderr.print("zhttpd comes with ABSOLUTELY NO WARRANTY. This is ", .{}); + try stderr.print("free software, and you are welcome to ", .{}); + try stderr.print("redistribute it under certain conditions; see the ", .{}); + try stderr.print("included LICENSE for more details.\n", .{}); } -/// main() initializes the server, parses the IP and port, and beings -/// run_server. -/// main() can fail exit from server.listen() and run_server(), which do not +/// main() initializes the server, parses the IP and port, and begins +/// runServer(). +/// main() can fail exit from server.listen() and runServer(), which do not /// specify the error types they can return. -/// main() also defines the allocator used by everything else, the -/// heap.GeneralPurposeAllocator. pub fn main() !void { - // Define allocator - var gpa = heap.GeneralPurposeAllocator(.{}){}; - defer debug.assert(gpa.deinit() == .ok); - const allocator = gpa.allocator(); + var fba_buffer: [@sizeOf(http.Server)]u8 = undefined; + var fba = heap.FixedBufferAllocator.init(&fba_buffer); + const allocator = fba.allocator(); + + //const stdout = std.io.getStdOut().writer(); + const stderr = std.io.getStdErr().writer(); - print_info(); + try printInfo(stderr); // Initialize the server var server = http.Server.init(allocator, .{ .reuse_address = true }); defer server.deinit(); // Log the server address and port - log.info("Server is running at {s}:{d}", .{ server_addr, server_port }); + try stderr.print("Server is running at {s}:{d}\n", .{ server_addr, server_port }); // Parse the server address const address = Address.parseIp(server_addr, server_port) catch unreachable; try server.listen(address); // Run the server - run_server(&server, allocator) catch |err| { + runServer(&server) catch |err| { // Handle server errors log.err("server error: {}\n", .{err}); if (@errorReturnTrace()) |trace| { -- cgit v1.2.3