diff --git a/src/interface.zig b/src/interface.zig index 7e6e6b5..820c874 100644 --- a/src/interface.zig +++ b/src/interface.zig @@ -26,8 +26,8 @@ pub const Request = extern struct { 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()); +pub fn toHeaders(alloc: std.mem.Allocator, headers: std.StringHashMap([]const u8)) ![*]Header { + var header_array = try std.ArrayList(Header).initCapacity(alloc, headers.count()); var iterator = headers.iterator(); while (iterator.next()) |kv| { header_array.appendAssumeCapacity(.{ @@ -40,3 +40,59 @@ pub fn toHeaders(allocator: std.mem.Allocator, headers: std.StringHashMap([]cons } return header_array.items.ptr; } + +var allocator: ?*std.mem.Allocator = null; + +pub const ZigResponse = struct { + body: *std.ArrayList(u8), + headers: *std.StringHashMap([]const u8), +}; + +/// This function is optional and can be exported by zig libraries for +/// initialization. If exported, it will be called once in the beginning of +/// a request and will be provided a pointer to std.mem.Allocator, which is +/// useful for reusing the parent allocator +pub fn zigInit(parent_allocator: *anyopaque) callconv(.C) void { + allocator = @ptrCast(*std.mem.Allocator, @alignCast(@alignOf(*std.mem.Allocator), parent_allocator)); +} + +pub const ZigRequestHandler = *const fn (std.mem.Allocator, Request, ZigResponse) anyerror!void; + +const log = std.log.scoped(.interface); +pub fn handleRequest(request: *Request, zigRequestHandler: ZigRequestHandler) ?*Response { + // TODO: implement another library in C or Rust or something to show + // that anything using a C ABI can be successful + var alloc = if (allocator) |a| a.* else @panic("zigInit not called prior to handle_request. This is a coding error"); + + // setup response body + var response = std.ArrayList(u8).init(alloc); + + // setup headers + var headers = std.StringHashMap([]const u8).init(alloc); + zigRequestHandler(alloc, request.*, .{ + .body = &response, + .headers = &headers, + }) catch |e| { + log.err("Unexpected error processing request: {any}", .{e}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + return null; + }; + + log.debug("response ptr: {*}", .{response.items.ptr}); + // Marshall data back for handling by server + + var rc = alloc.create(Response) catch @panic("OOM"); + rc.ptr = response.items.ptr; + rc.len = response.items.len; + rc.headers = toHeaders(alloc, headers) catch |e| { + log.err("Unexpected error processing request: {any}", .{e}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + return null; + }; + rc.headers_len = headers.count(); + return rc; +} diff --git a/src/main-lib.zig b/src/main-lib.zig index 85bd0ae..e2948c3 100644 --- a/src/main-lib.zig +++ b/src/main-lib.zig @@ -4,61 +4,20 @@ const testing = std.testing; const log = std.log.scoped(.@"main-lib"); -var allocator: ?*std.mem.Allocator = null; -const Response = struct { - body: *std.ArrayList(u8), - headers: *std.StringHashMap([]const u8), -}; - -/// This function is optional and can be exported by zig libraries for -/// initialization. If exported, it will be called once in the beginning of -/// a request and will be provided a pointer to std.mem.Allocator, which is -/// useful for reusing the parent allocator -export fn zigInit(parent_allocator: *anyopaque) void { - allocator = @ptrCast(*std.mem.Allocator, @alignCast(@alignOf(*std.mem.Allocator), parent_allocator)); -} -export fn handle_request() ?*interface.Response { - // TODO: implement another library in C or Rust or something to show - // that anything using a C ABI can be successful - var alloc = if (allocator) |a| a.* else @panic("zigInit not called prior to handle_request. This is a coding error"); - - // setup response body - var response = std.ArrayList(u8).init(alloc); - - // setup headers - var headers = std.StringHashMap([]const u8).init(alloc); - handleRequest(.{ - .body = &response, - .headers = &headers, - }) catch |e| { - log.err("Unexpected error processing request: {any}", .{e}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - return null; - }; - - log.debug("response ptr: {*}", .{response.items.ptr}); - // Marshall data back for handling by server - - var rc = alloc.create(interface.Response) catch @panic("OOM"); - rc.ptr = response.items.ptr; - rc.len = response.items.len; - rc.headers = interface.toHeaders(alloc, headers) catch |e| { - log.err("Unexpected error processing request: {any}", .{e}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - return null; - }; - rc.headers_len = headers.count(); - return rc; -} - -/// request_deinit is an optional export and will be called a the end of the -/// request. Useful for deallocating memory +// request_deinit is an optional export and will be called a the end of the +// request. Useful for deallocating memory // export fn request_deinit() void { // } +export fn handle_request(request: *interface.Request) callconv(.C) ?*interface.Response { + return interface.handleRequest(request, handleRequest); +} + +comptime { + @export( + interface.zigInit, + .{ .name = "zigInit", .linkage = .Strong }, + ); +} // ************************************************************************ // Boilerplate ^^, Custom code vv @@ -66,11 +25,12 @@ export fn handle_request() ?*interface.Response { // // handleRequest function here is the last line of boilerplate and the // entry to a request -fn handleRequest(response: Response) !void { +fn handleRequest(allocator: std.mem.Allocator, request: interface.Request, response: interface.ZigResponse) !void { + _ = allocator; // setup var response_writer = response.body.writer(); // real work - response_writer.print(" 2.", .{}) catch unreachable; + response_writer.print(" {d}", .{request.headers_len}) catch unreachable; try response.headers.put("X-custom-foo", "bar"); log.info("handlerequest header count {d}", .{response.headers.count()}); } @@ -79,9 +39,23 @@ test "handle_request" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); var aa = arena.allocator(); - allocator = &aa; - const response = handle_request().?; - try testing.expectEqualStrings(" 2.", response.ptr[0..response.len]); + interface.zigInit(&aa); + var headers: []interface.Header = @constCast(&[_]interface.Header{.{ + .name_ptr = @ptrCast([*:0]u8, @constCast("GET".ptr)), + .name_len = 3, + .value_ptr = @ptrCast([*:0]u8, @constCast("GET".ptr)), + .value_len = 3, + }}); + var req = interface.Request{ + .method = @ptrCast([*:0]u8, @constCast("GET".ptr)), + .method_len = 3, + .content = @ptrCast([*:0]u8, @constCast("GET".ptr)), + .content_len = 3, + .headers = headers.ptr, + .headers_len = 1, + }; + const response = handle_request(&req).?; + try testing.expectEqualStrings(" 1", 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 f3254a8..fae57f6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -477,6 +477,6 @@ test "root path get" { std.testing.log_level = .debug; log.debug("", .{}); try testGet("/"); - try std.testing.expectEqual(@as(usize, 3), test_resp_buf_len); - try std.testing.expectEqualStrings(" 2.", test_resp_buf[0..test_resp_buf_len]); + try std.testing.expectEqual(@as(usize, 2), test_resp_buf_len); + try std.testing.expectEqualStrings(" 1", test_resp_buf[0..test_resp_buf_len]); }