From ce5fc4e99fdbb4d367e7d02ec143855de819f37a Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Sat, 13 May 2023 12:56:47 -0700 Subject: [PATCH] handle library side of headers --- src/interface.zig | 42 ++++++++++++++++++++++++++ src/main-lib.zig | 72 +++++++++++++++++++++++++++++++++------------ src/main.zig | 75 ++++++++++++++++++++++++++--------------------- 3 files changed, 137 insertions(+), 52 deletions(-) create mode 100644 src/interface.zig diff --git a/src/interface.zig b/src/interface.zig new file mode 100644 index 0000000..7cb9ded --- /dev/null +++ b/src/interface.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub const Header = extern struct { + name_ptr: [*]u8, + name_len: usize, + + value_ptr: [*]u8, + value_len: usize, +}; +pub const Response = extern struct { + ptr: [*]u8, + len: usize, + + headers: [*]Header, + headers_len: usize, +}; + +pub const Request = extern struct { + method: [*]u8, + method_len: usize, + + content: [*]u8, + content_len: usize, + + headers: [*]Header, + headers_len: usize, +}; + +pub fn toHeaders(allocator: std.mem.Allocator, headers: std.StringHashMap([]const u8)) ![*]Header { + var header_array = try std.ArrayList(Header).initCapacity(allocator, headers.count()); + var iterator = headers.iterator(); + while (iterator.next()) |kv| { + header_array.appendAssumeCapacity(.{ + .name_ptr = @constCast(kv.key_ptr.*).ptr, + .name_len = kv.key_ptr.*.len, + + .value_ptr = @constCast(kv.value_ptr.*).ptr, + .value_len = kv.value_ptr.*.len, + }); + } + return header_array.items.ptr; +} diff --git a/src/main-lib.zig b/src/main-lib.zig index 676b41a..f746068 100644 --- a/src/main-lib.zig +++ b/src/main-lib.zig @@ -1,40 +1,76 @@ const std = @import("std"); +const interface = @import("interface.zig"); const testing = std.testing; -const Response = extern struct { - ptr: [*]u8, - len: usize, -}; - +const log = std.log.scoped(.@"main-lib"); var child_allocator = std.heap.raw_c_allocator; // raw allocator recommended for use in arenas var arena: std.heap.ArenaAllocator = undefined; -export fn handle_request() *Response { +const Response = struct { + body: *std.ArrayList(u8), + headers: *std.StringHashMap([]const u8), +}; + +export fn handle_request() ?*interface.Response { arena = std.heap.ArenaAllocator.init(child_allocator); var allocator = arena.allocator(); - var al = std.ArrayList(u8).init(allocator); - var writer = al.writer(); - writer.print(" 2.", .{}) catch unreachable; + // setup response body + var response = std.ArrayList(u8).init(allocator); - // Cannot simply return &Blah. Need to assign to var first - var rc = &Response{ - .ptr = al.items.ptr, - .len = al.items.len, + // setup headers + var headers = std.StringHashMap([]const u8).init(allocator); + handleRequest(.{ + .body = &response, + .headers = &headers, + }) catch |e| { + log.err("Unexpected error processing request: {any}", .{e}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + return null; + }; + // Marshall data back for handling by server + var rc = &interface.Response{ + .ptr = response.items.ptr, + .len = response.items.len, + + .headers = interface.toHeaders(allocator, headers) catch |e| { + log.err("Unexpected error processing request: {any}", .{e}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + return null; + }, + .headers_len = headers.count(), }; return rc; } /// having request_deinit allows for a general deinit as well export fn request_deinit() void { - std.log.debug("deinit", .{}); arena.deinit(); } -export fn add(a: i32, b: i32) i32 { - return a + b; +// ************************************************************************ +// Boilerplate ^^, Custom code below +// ************************************************************************ +// +// handleRequest function here is the last line of boilerplate and the +// entry to a request +fn handleRequest(response: Response) !void { + // setup + var response_writer = response.body.writer(); + // real work + response_writer.print(" 2.", .{}) catch unreachable; + try response.headers.put("X-custom-foo", "bar"); } -test "basic add functionality" { - try testing.expect(add(3, 7) == 10); +test "handle_request" { + defer request_deinit(); + child_allocator = std.testing.allocator; + const response = handle_request().?; + try testing.expectEqualStrings(" 2.", response.ptr[0..response.len]); + try testing.expectEqualStrings("X-custom-foo", response.headers[0].name_ptr[0..response.headers[0].name_len]); + try testing.expectEqualStrings("bar", response.headers[0].value_ptr[0..response.headers[0].value_len]); } diff --git a/src/main.zig b/src/main.zig index 4deb817..05b7b0f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,32 +1,34 @@ const std = @import("std"); const builtin = @import("builtin"); - +const interface = @import("interface.zig"); const Watch = @import("Watch.zig"); -const serveFn = *const fn () *ServeReturn; +const serveFn = *const fn () ?*interface.Response; const requestDeinitFn = *const fn () void; const timeout = 250; -const ServeReturn = extern struct { - ptr: [*]u8, - len: usize, -}; - const FullReturn = struct { response: []u8, executor: *Executor, }; const Executor = struct { + // configuration path: [:0]const u8, + + // fields used at runtime to do real work library: ?*anyopaque = null, serveFn: ?serveFn = null, requestDeinitFn: ?requestDeinitFn = null, + + // fields used for internal accounting watch: ?usize = null, reload_lock: bool = false, in_request_lock: bool = false, }; +// TODO: This should be in a file that maybe gets reloaded with SIGHUP +// rather than a file watch. Touching this config is pretty dangerous var executors = [_]Executor{ .{ .path = "zig-out/lib/libfaas-proxy-sample-lib.so" }, .{ .path = "zig-out/lib/libfaas-proxy-sample-lib2.so" }, @@ -47,25 +49,16 @@ const SERVE_FN_NAME = "handle_request"; const PORT = 8069; fn serve(allocator: std.mem.Allocator, response: *std.http.Server.Response) !*FullReturn { - var null_server = std.http.Server.init(allocator, .{}); - defer null_server.deinit(); - var data: [14]u8 = @constCast(&[_]u8{0x00} ** 14).*; - var child_response = std.http.Server.Response{ - .server = &null_server, - .request = response.request, - .connection = .{ - .conn = .{ - .stream = .{ .handle = 0 }, - .protocol = .plain, - }, - }, - .address = .{ .any = .{ - .data = data, - .family = 0, - } }, - .headers = response.headers, - }; - _ = child_response; + // pub const Request = extern struct { + // method: [*]u8, + // method_len: usize, + // + // content: [*]u8, + // content_len: usize, + // + // headers: [*]Header, + // headers_len: usize, + // }; // if (some path routing thing) { // TODO: Get request body into executor // TODO: Get headers back from executor @@ -74,14 +67,27 @@ fn serve(allocator: std.mem.Allocator, response: *std.http.Server.Response) !*Fu executor.in_request_lock = true; errdefer executor.in_request_lock = false; // Call external library - var serve_result = executor.serveFn.?(); + var serve_result = executor.serveFn.?().?; // ok for this pointer deref to fail // Deal with results + // var content_type_added = false; + // for (0..serve_result.headers_len) |inx| { + // const head = serve_result.headers[inx]; + // try response.headers.append( + // head.name_ptr[0..head.name_len], + // head.value_ptr[0..head.value_len], + // ); + // + // // TODO: are these headers case insensitive? + // content_type_added = std.mem.eql(u8, head.name_ptr[0..head.name_len], "content-type"); + // } + // if (!content_type_added) + // try response.headers.append("content-type", "text/plain"); + _ = response; var slice: []u8 = serve_result.ptr[0..serve_result.len]; - var rc = &FullReturn{ - .executor = executor, - .response = slice, - }; + var rc = try allocator.create(FullReturn); + rc.executor = executor; + rc.response = slice; return rc; } fn getExecutor(key: usize) !*Executor { @@ -231,7 +237,7 @@ pub fn main() !void { try installSignalHandler(); while (true) { - var arena = std.heap.ArenaAllocator.init(std.heap.c_allocator); + var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); processRequest(arena.allocator(), &server) catch |e| { @@ -283,7 +289,6 @@ fn processRequest(allocator: std.mem.Allocator, server: *std.http.Server) !void if (full_response) |f| response_bytes = f.response; res.transfer_encoding = .{ .content_length = response_bytes.len }; - try res.headers.append("content-type", "text/plain"); try res.headers.append("connection", "close"); if (builtin.is_test) writeToTestBuffers(response_bytes, res); try res.do(); @@ -306,6 +311,8 @@ fn writeToTestBuffers(response: []const u8, res: *std.http.Server.Response) void } fn testRequest(request_bytes: []const u8) !void { const allocator = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); var server = std.http.Server.init(allocator, .{ .reuse_address = true }); defer server.deinit(); @@ -317,7 +324,7 @@ fn testRequest(request_bytes: []const u8) !void { const server_thread = try std.Thread.spawn( .{}, processRequest, - .{ allocator, &server }, + .{ arena.allocator(), &server }, ); const stream = try std.net.tcpConnectToHost(allocator, "127.0.0.1", server_port);