summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.zig194
1 files changed, 194 insertions, 0 deletions
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..dc762d1
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,194 @@
+const std = @import("std");
+const mime = @import("mime.zig");
+
+const Address = std.net.Address;
+const ArrayList = std.ArrayList;
+const debug = std.debug;
+const fmt = std.fmt;
+const heap = std.heap;
+const http = std.http;
+const fs = std.fs;
+const log = std.log.scoped(.server);
+const mem = std.mem;
+const bufferedReader = std.io.bufferedReader;
+const getcwd = std.os.getcwd;
+
+const MAX_PATH_BYTES = fs.MAX_PATH_BYTES;
+
+const server_addr = "127.0.0.1";
+const server_port = 8080;
+
+const BUFFER_LIMIT = 1 << 21;
+
+const resp = "HTTP/1.0 200 OK\r\nServer: zhttpd\r\nContent-type: text/html\r\n\r\n";
+const def = "<html>Hello, World</html>";
+
+fn read_files(target: []const u8, buffer: []u8, allocator: mem.Allocator) !usize {
+ var file_path = try allocator.alloc(u8, MAX_PATH_BYTES);
+ defer allocator.free(file_path);
+ 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});
+ // Determine if the requested file exists
+ if (fs.cwd().openFile(file_path, .{})) |file| {
+ defer file.close();
+ var stat = try file.stat();
+ switch (stat.kind) {
+ .directory => {
+ if (mem.endsWith(u8, target, "/")) {
+ file_path = try fmt.allocPrint(allocator, "{s}index.html", .{target});
+ } else {
+ file_path = try fmt.allocPrint(allocator, "{s}/index.html", .{target});
+ }
+ return read_files(file_path, buffer, allocator);
+ },
+ .file => {
+ return try file.readAll(buffer);
+ },
+ else => {
+ return 0;
+ },
+ }
+ } else |err| {
+ switch (err) {
+ error.FileNotFound => {
+ return 0;
+ },
+ else => {
+ return err;
+ },
+ }
+ }
+}
+
+fn handle_request(response: *http.Server.Response, allocator: mem.Allocator) !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;
+
+ // 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 = try read_files(response.request.target, &read, allocator);
+ if (size > 0) {
+ // Get the file extension, and set the content-type header if it exists
+ // (using mime.zig)
+ // To do this, we iterate through response.request.target in reverse
+ // looking for a '/' or a '.'
+ // a '/' indicates a directory, so there is no extension
+ // a '.' indicates a file extension
+ var i: usize = response.request.target.len;
+ var mime_type: ?mime.Type = undefined;
+ if (mem.endsWith(u8, response.request.target, "/")) {
+ log.warn("Dir requested, returning index.html!", .{});
+ try response.headers.append("content-type", "text/html");
+ } else {
+ while (i > 0) {
+ i -= 1;
+ switch (response.request.target[i]) {
+ '/' => {
+ i = 0;
+ break;
+ },
+ '.' => {
+ break;
+ },
+ else => {
+ continue;
+ },
+ }
+ }
+ if (i <= 0) {
+ log.warn("No extension detected!", .{});
+ if (mem.indexOf(u8, &read, "<html")) |_| {
+ try response.headers.append("content-type", "text/html");
+ } else {
+ try response.headers.append("content-type", "text/plain");
+ }
+ } else {
+ log.info("Extension {s} detected!", .{response.request.target[i..]});
+ mime_type = mime.extension_map.get(response.request.target[i..]);
+ if (mime_type) |mime_val| {
+ try response.headers.append("content-type", @tagName(mime_val));
+ } else {
+ try response.headers.append("content-type", "text/plain");
+ }
+ }
+ }
+ // Create a []u8 that is exactly the intended size and no larger
+ const body = read[0..size];
+ log.info("{}", .{body.len});
+ // Check if the request target contains "?chunked"
+ if (mem.indexOf(u8, response.request.target, "?chunked") != null) {
+ response.transfer_encoding = .chunked;
+ } else {
+ response.transfer_encoding = .{ .content_length = size };
+ }
+ // Transmit the response
+ try response.do();
+ if (response.request.method != .HEAD) {
+ try response.writeAll(body);
+ try response.finish();
+ }
+ } else {
+ // If the file was not found, return error 404
+ response.status = .not_found;
+ response.transfer_encoding = .{ .content_length = 0 };
+ try response.do();
+ }
+}
+
+fn run_server(server: *http.Server, allocator: mem.Allocator) !void {
+ outer: while (true) {
+ // Accept incoming connection
+ var response = try server.accept(.{
+ .allocator = allocator,
+ });
+ defer response.deinit();
+
+ while (response.reset() != .closing) {
+ // Handle errors during request processing
+ response.wait() catch |err| switch (err) {
+ error.HttpHeadersInvalid => continue :outer,
+ error.EndOfStream => continue,
+ else => return err,
+ };
+
+ // Process the request
+ try handle_request(&response, allocator);
+ }
+ }
+}
+
+pub fn main() !void {
+ // Define allocator
+ var gpa = heap.GeneralPurposeAllocator(.{}){};
+ defer debug.assert(gpa.deinit() == .ok);
+ const allocator = gpa.allocator();
+
+ // 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 });
+
+ // 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| {
+ // Handle server errors
+ log.err("server error: {}\n", .{err});
+ if (@errorReturnTrace()) |trace| {
+ debug.dumpStackTrace(trace.*);
+ }
+ std.os.exit(1);
+ };
+}