2023-05-13 19:56:47 +00:00
|
|
|
const std = @import("std");
|
2024-05-02 22:13:50 +00:00
|
|
|
const builtin = @import("builtin");
|
2023-05-13 19:56:47 +00:00
|
|
|
|
2023-05-30 23:03:40 +00:00
|
|
|
// C interfaces between main and libraries
|
2023-05-13 19:56:47 +00:00
|
|
|
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,
|
2023-10-23 16:58:44 +00:00
|
|
|
|
|
|
|
status: usize,
|
|
|
|
|
|
|
|
reason_ptr: [*]u8,
|
|
|
|
reason_len: usize,
|
2023-05-13 19:56:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
pub const Request = extern struct {
|
2023-10-03 20:27:18 +00:00
|
|
|
target: [*]const u8,
|
|
|
|
target_len: usize,
|
|
|
|
|
2023-05-18 00:43:17 +00:00
|
|
|
method: [*:0]u8,
|
2023-05-13 19:56:47 +00:00
|
|
|
method_len: usize,
|
|
|
|
|
|
|
|
content: [*]u8,
|
|
|
|
content_len: usize,
|
|
|
|
|
|
|
|
headers: [*]Header,
|
|
|
|
headers_len: usize,
|
|
|
|
};
|
|
|
|
|
2023-05-30 23:03:40 +00:00
|
|
|
// If the library is Zig, we can use these helpers
|
2023-06-16 03:03:51 +00:00
|
|
|
threadlocal var allocator: ?*std.mem.Allocator = null;
|
2023-05-30 23:12:29 +00:00
|
|
|
|
|
|
|
const log = std.log.scoped(.interface);
|
|
|
|
|
2023-05-30 23:03:40 +00:00
|
|
|
pub const ZigRequest = struct {
|
2023-10-05 22:58:36 +00:00
|
|
|
target: []const u8,
|
2023-05-30 23:03:40 +00:00
|
|
|
method: [:0]u8,
|
|
|
|
content: []u8,
|
2024-05-02 21:38:04 +00:00
|
|
|
headers: []std.http.Header,
|
2023-05-30 23:03:40 +00:00
|
|
|
};
|
|
|
|
|
2023-05-31 17:18:09 +00:00
|
|
|
pub const ZigHeader = struct {
|
|
|
|
name: []u8,
|
|
|
|
value: []u8,
|
|
|
|
};
|
|
|
|
|
2023-05-30 23:12:29 +00:00
|
|
|
pub const ZigResponse = struct {
|
2023-10-23 16:58:44 +00:00
|
|
|
status: std.http.Status = .ok,
|
|
|
|
reason: ?[]const u8 = null,
|
2023-05-30 23:12:29 +00:00
|
|
|
body: *std.ArrayList(u8),
|
2024-05-06 18:18:08 +00:00
|
|
|
headers: []const std.http.Header,
|
2023-10-23 16:58:44 +00:00
|
|
|
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();
|
|
|
|
}
|
2023-05-30 23:12:29 +00:00
|
|
|
};
|
|
|
|
|
2023-10-23 16:58:44 +00:00
|
|
|
pub const ZigRequestHandler = *const fn (std.mem.Allocator, *ZigResponse) anyerror!void;
|
2023-05-30 23:12:29 +00:00
|
|
|
|
|
|
|
/// 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. If you're planning on using
|
|
|
|
/// the handleRequest helper below, you must use zigInit or otherwise
|
|
|
|
/// set the interface allocator in your own version of zigInit
|
|
|
|
pub fn zigInit(parent_allocator: *anyopaque) callconv(.C) void {
|
2023-07-03 22:27:21 +00:00
|
|
|
allocator = @ptrCast(@alignCast(parent_allocator));
|
2023-05-30 23:12:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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
|
2024-05-02 21:38:04 +00:00
|
|
|
fn toHeaders(alloc: std.mem.Allocator, headers: []const std.http.Header) ![*]Header {
|
|
|
|
var header_array = try std.ArrayList(Header).initCapacity(alloc, headers.len);
|
|
|
|
for (headers) |*field| {
|
2023-05-13 19:56:47 +00:00
|
|
|
header_array.appendAssumeCapacity(.{
|
2023-10-23 16:58:44 +00:00
|
|
|
.name_ptr = @constCast(field.name.ptr),
|
|
|
|
.name_len = field.name.len,
|
2023-05-13 19:56:47 +00:00
|
|
|
|
2023-10-23 16:58:44 +00:00
|
|
|
.value_ptr = @constCast(field.value.ptr),
|
|
|
|
.value_len = field.value.len,
|
2023-05-13 19:56:47 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
return header_array.items.ptr;
|
|
|
|
}
|
2023-05-30 19:46:23 +00:00
|
|
|
|
2023-05-30 23:12:29 +00:00
|
|
|
/// handles a request, implementing the C interface to communicate between the
|
|
|
|
/// main program and a zig library. Most importantly, it will catch/report
|
|
|
|
/// errors appropriately and allow zig code to use standard Zig error semantics
|
2023-05-30 19:46:23 +00:00
|
|
|
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
|
2024-05-02 21:38:04 +00:00
|
|
|
const alloc = if (allocator) |a| a.* else {
|
2023-05-30 23:14:14 +00:00
|
|
|
log.err("zigInit not called prior to handle_request. This is a coding error", .{});
|
|
|
|
return null;
|
|
|
|
};
|
2023-05-30 19:46:23 +00:00
|
|
|
|
|
|
|
// setup response body
|
|
|
|
var response = std.ArrayList(u8).init(alloc);
|
|
|
|
|
|
|
|
// setup headers
|
2024-05-02 21:38:04 +00:00
|
|
|
var request_headers = std.ArrayList(std.http.Header).init(alloc);
|
2023-10-23 16:58:44 +00:00
|
|
|
for (0..request.headers_len) |i|
|
2024-05-02 21:38:04 +00:00
|
|
|
request_headers.append(.{
|
|
|
|
.name = request.headers[i].name_ptr[0..request.headers[i].name_len],
|
|
|
|
.value = request.headers[i].value_ptr[0..request.headers[i].value_len],
|
|
|
|
}) catch |e| {
|
2024-05-02 22:13:50 +00:00
|
|
|
logError("Unexpected error processing request: {any}", .{e}, @errorReturnTrace());
|
2023-10-23 16:58:44 +00:00
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
2024-05-02 21:38:04 +00:00
|
|
|
const prepend = std.ArrayList(u8).init(alloc);
|
2023-10-23 16:58:44 +00:00
|
|
|
var zig_response = ZigResponse{
|
2024-05-02 21:38:04 +00:00
|
|
|
.headers = &.{},
|
2023-10-23 16:58:44 +00:00
|
|
|
.body = &response,
|
|
|
|
.prepend = prepend,
|
|
|
|
.request = .{
|
|
|
|
.content = request.content[0..request.content_len],
|
2023-10-05 22:58:36 +00:00
|
|
|
.target = request.target[0..request.target_len],
|
2023-05-30 23:03:40 +00:00
|
|
|
.method = request.method[0..request.method_len :0],
|
2024-05-02 21:38:04 +00:00
|
|
|
.headers = request_headers.toOwnedSlice() catch |e| {
|
2024-05-02 22:13:50 +00:00
|
|
|
logError("Unexpected error processing request: {any}", .{e}, @errorReturnTrace());
|
2024-05-02 21:38:04 +00:00
|
|
|
return null;
|
|
|
|
},
|
2023-05-30 23:03:40 +00:00
|
|
|
},
|
2023-10-23 16:58:44 +00:00
|
|
|
};
|
|
|
|
zigRequestHandler(
|
|
|
|
alloc,
|
|
|
|
&zig_response,
|
2023-05-30 23:03:40 +00:00
|
|
|
) catch |e| {
|
2024-05-02 22:13:50 +00:00
|
|
|
logError("Unexpected error processing request: {any}", .{e}, @errorReturnTrace());
|
2023-10-23 18:17:29 +00:00
|
|
|
if (zig_response.status == .ok) // this was an unexpected throw
|
|
|
|
zig_response.status = .internal_server_error;
|
|
|
|
return buildResponse(alloc, &zig_response);
|
2023-05-30 19:46:23 +00:00
|
|
|
};
|
|
|
|
// Marshall data back for handling by server
|
2023-10-23 18:17:29 +00:00
|
|
|
return buildResponse(alloc, &zig_response);
|
|
|
|
}
|
2023-05-30 19:46:23 +00:00
|
|
|
|
2024-05-02 22:13:50 +00:00
|
|
|
fn logError(comptime format: []const u8, args: anytype, stack_trace: anytype) void {
|
|
|
|
if (builtin.is_test) return;
|
|
|
|
log.err(format, args);
|
|
|
|
if (stack_trace) |trace| {
|
|
|
|
std.debug.dumpStackTrace(trace.*);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-23 18:17:29 +00:00
|
|
|
fn buildResponse(alloc: std.mem.Allocator, zig_response: *ZigResponse) ?*Response {
|
2023-05-30 23:14:14 +00:00
|
|
|
var rc = alloc.create(Response) catch {
|
|
|
|
log.err("Could not allocate memory for response object. This may be fatal", .{});
|
|
|
|
return null;
|
|
|
|
};
|
2023-10-23 16:58:44 +00:00
|
|
|
zig_response.finish() catch {
|
|
|
|
log.err("Could not allocate memory for response object. This may be fatal", .{});
|
|
|
|
return null;
|
|
|
|
};
|
2023-10-23 18:17:29 +00:00
|
|
|
|
|
|
|
rc.ptr = zig_response.body.items.ptr;
|
|
|
|
rc.len = zig_response.body.items.len;
|
|
|
|
rc.headers = toHeaders(alloc, zig_response.headers) catch |e| {
|
2023-05-30 19:46:23 +00:00
|
|
|
log.err("Unexpected error processing request: {any}", .{e});
|
|
|
|
if (@errorReturnTrace()) |trace| {
|
|
|
|
std.debug.dumpStackTrace(trace.*);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
2024-05-02 21:38:04 +00:00
|
|
|
rc.headers_len = zig_response.headers.len;
|
2023-10-23 16:58:44 +00:00
|
|
|
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;
|
|
|
|
}
|
2023-05-30 19:46:23 +00:00
|
|
|
return rc;
|
|
|
|
}
|