summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzachir <zachir@librem.one>2024-03-23 23:18:02 -0500
committerzachir <zachir@librem.one>2024-03-23 23:18:02 -0500
commitea1bdedd6b1e93c6ea69b4746550331ffcd52a16 (patch)
treeaa04a97abad5afc4c01f110b8ca6aa7bb6f238db
parent5f5aba042b51653dd6a8abcaa082fa5efcffd639 (diff)
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.
-rw-r--r--src/main.zig94
1 files 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| {