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 = "Hello, World"; 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, " 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); }; }