diff --git a/src/interface.zig b/src/interface.zig index 751532e..588539f 100644 --- a/src/interface.zig +++ b/src/interface.zig @@ -14,6 +14,11 @@ pub const Response = extern struct { headers: [*]Header, headers_len: usize, + + status: usize, + + reason_ptr: [*]u8, + reason_len: usize, }; pub const Request = extern struct { @@ -39,7 +44,7 @@ pub const ZigRequest = struct { target: []const u8, method: [:0]u8, content: []u8, - headers: []Header, + headers: std.http.Headers, }; pub const ZigHeader = struct { @@ -48,11 +53,33 @@ pub const ZigHeader = struct { }; pub const ZigResponse = struct { + status: std.http.Status = .ok, + reason: ?[]const u8 = null, body: *std.ArrayList(u8), - headers: *std.StringHashMap([]const u8), + headers: std.http.Headers, + request: ZigRequest, + prepend: std.ArrayList(u8), + + pub fn write(res: *ZigResponse, bytes: []const u8) !usize { + return res.prepend.writer().write(bytes); + } + + pub fn writeAll(res: *ZigResponse, bytes: []const u8) !void { + return res.prepend.writer().writeAll(bytes); + } + + pub fn writer(res: *ZigResponse) std.io.Writer { + return res.prepend.writer().writer(); + } + + pub fn finish(res: *ZigResponse) !void { + if (res.prepend.items.len > 0) + try res.body.insertSlice(0, res.prepend.items); + res.prepend.deinit(); + } }; -pub const ZigRequestHandler = *const fn (std.mem.Allocator, ZigRequest, ZigResponse) anyerror!void; +pub const ZigRequestHandler = *const fn (std.mem.Allocator, *ZigResponse) anyerror!void; /// This function is optional and can be exported by zig libraries for /// initialization. If exported, it will be called once in the beginning of @@ -64,28 +91,25 @@ pub fn zigInit(parent_allocator: *anyopaque) callconv(.C) void { allocator = @ptrCast(@alignCast(parent_allocator)); } -pub fn toZigHeader(header: Header) ZigHeader { - return .{ - .name = header.name_ptr[0..header.name_len], - .value = header.value_ptr[0..header.value_len], - }; -} - /// Converts a StringHashMap to the structure necessary for passing through the /// C boundary. This will be called automatically for you via the handleRequest function /// and is also used by the main processing loop to coerce request headers -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| { +fn toHeaders(alloc: std.mem.Allocator, headers: std.http.Headers) ![*]Header { + var header_array = try std.ArrayList(Header).initCapacity(alloc, headers.list.items.len); + log.err("enter ({d})", .{headers.list.items.len}); + for (headers.list.items) |*field| { + // var name = try alloc.dupe(u8, field.name); + // var val = try alloc.dupe(u8, field.value); + // log.err(" response header name {s}, val {s}", .{ name, val }); header_array.appendAssumeCapacity(.{ - .name_ptr = @constCast(kv.key_ptr.*).ptr, - .name_len = kv.key_ptr.*.len, + .name_ptr = @constCast(field.name.ptr), + .name_len = field.name.len, - .value_ptr = @constCast(kv.value_ptr.*).ptr, - .value_len = kv.value_ptr.*.len, + .value_ptr = @constCast(field.value.ptr), + .value_len = field.value.len, }); } + log.err("exit", .{}); return header_array.items.ptr; } @@ -104,19 +128,43 @@ pub fn handleRequest(request: *Request, zigRequestHandler: ZigRequestHandler) ?* var response = std.ArrayList(u8).init(alloc); // setup headers - var headers = std.StringHashMap([]const u8).init(alloc); - zigRequestHandler( - alloc, - .{ + var response_headers = std.http.Headers.init(alloc); + var request_headers = std.http.Headers.init(alloc); + for (0..request.headers_len) |i| + request_headers.append( + request.headers[i].name_ptr[0..request.headers[i].name_len], + request.headers[i].value_ptr[0..request.headers[i].value_len], + ) catch |e| { + log.err("Unexpected error processing request: {any}", .{e}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + return null; + }; + + // if (serve_result.prepended_request_data_len > 0) { + // var al = std.ArrayList(u8).initCapacity(allocator, slice.len + serve_result.prepended_request_data_len); + // defer al.deinit(); + // al.appendSliceAssumeCapacity(serve_result.prepended_request_data[0..serve_result.prepended_request_data_len]); + // al.appendSliceAssumeCapacity(slice); + // slice = al.toOwnedSlice(); + // } + + var prepend = std.ArrayList(u8).init(alloc); + var zig_response = ZigResponse{ + .headers = response_headers, + .body = &response, + .prepend = prepend, + .request = .{ + .content = request.content[0..request.content_len], .target = request.target[0..request.target_len], .method = request.method[0..request.method_len :0], - .content = request.content[0..request.content_len], - .headers = request.headers[0..request.headers_len], - }, - .{ - .body = &response, - .headers = &headers, + .headers = request_headers, }, + }; + zigRequestHandler( + alloc, + &zig_response, ) catch |e| { log.err("Unexpected error processing request: {any}", .{e}); if (@errorReturnTrace()) |trace| { @@ -131,15 +179,27 @@ pub fn handleRequest(request: *Request, zigRequestHandler: ZigRequestHandler) ?* log.err("Could not allocate memory for response object. This may be fatal", .{}); return null; }; + zig_response.finish() catch { + log.err("Could not allocate memory for response object. This may be fatal", .{}); + return null; + }; rc.ptr = response.items.ptr; rc.len = response.items.len; - rc.headers = toHeaders(alloc, headers) catch |e| { + rc.headers = toHeaders(alloc, response_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(); + //log.err("headers len: {d} header[0] name: {s}", .{ rc.headers_len, rc.headers[0].name_ptr[0..rc.headers[0].name_len] }); + rc.headers_len = response_headers.list.items.len; + rc.status = if (zig_response.status == .ok) 0 else @intFromEnum(zig_response.status); + rc.reason_len = 0; + if (zig_response.reason) |*r| { + rc.reason_ptr = @constCast(r.ptr); + rc.reason_len = r.len; + } + return rc; } diff --git a/src/main-lib.zig b/src/main-lib.zig index 9db4506..d05fd67 100644 --- a/src/main-lib.zig +++ b/src/main-lib.zig @@ -57,27 +57,31 @@ export fn handle_request(request: *interface.Request) callconv(.C) ?*interface.R // // handleRequest function here is the last line of boilerplate and the // entry to a request -fn handleRequest(allocator: std.mem.Allocator, request: interface.ZigRequest, response: interface.ZigResponse) !void { +fn handleRequest(allocator: std.mem.Allocator, response: *interface.ZigResponse) !void { _ = allocator; // setup var response_writer = response.body.writer(); // real work - for (request.headers) |h| { - const header = interface.toZigHeader(h); - // std.debug.print("\n{s}: {s}\n", .{ header.name, header.value }); - if (std.ascii.eqlIgnoreCase(header.name, "host") and std.mem.startsWith(u8, header.value, "iam")) { + if (response.request.headers.getFirstValue("host")) |host| { + if (std.mem.startsWith(u8, host, "iam")) { try response_writer.print("iam response", .{}); return; } - if (std.ascii.eqlIgnoreCase(header.name, "x-slow")) { - std.time.sleep(std.time.ns_per_ms * (std.fmt.parseInt(usize, header.value, 10) catch 1000)); - try response_writer.print("i am slow\n\n", .{}); - return; - } } - try response_writer.print(" {d}", .{request.headers.len}); - try response.headers.put("X-custom-foo", "bar"); - log.info("handlerequest header count {d}", .{response.headers.count()}); + if (response.request.headers.getFirstValue("x-slow")) |ms| { + std.time.sleep(std.time.ns_per_ms * (std.fmt.parseInt(usize, ms, 10) catch 1000)); + try response_writer.print("i am slow\n\n", .{}); + return; + } + if (response.request.headers.getFirstValue("x-log-this")) |l| { + try response.writeAll(l); + } + if (response.request.headers.getFirstValue("x-status")) |s| { + response.status = @enumFromInt(std.fmt.parseInt(u10, s, 10) catch 500); + } + try response_writer.print(" {d}", .{response.request.headers.list.items.len}); + try response.headers.append("X-custom-foo", "bar"); + log.info("handlerequest header count {d}", .{response.headers.list.items.len}); } test "handle_request" { @@ -103,6 +107,7 @@ test "handle_request" { }; const response = handle_request(&req).?; try testing.expectEqualStrings(" 1", response.ptr[0..response.len]); + try testing.expectEqual(@as(usize, 1), response.headers_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 4445e64..377114f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -100,6 +100,12 @@ fn serve(allocator: *std.mem.Allocator, response: *std.http.Server.Response) !*F var slice: []u8 = serve_result.ptr[0..serve_result.len]; log.debug("response body: {s}", .{slice}); + if (serve_result.status != 0) { + response.status = @enumFromInt(serve_result.status); + if (serve_result.reason_len > 0) { + response.reason = serve_result.reason_ptr[0..serve_result.reason_len]; + } + } // Deal with results var content_type_added = false; for (0..serve_result.headers_len) |inx| {