handle library side of headers

This commit is contained in:
Emil Lerch 2023-05-13 12:56:47 -07:00
parent 289fb8d46c
commit ce5fc4e99f
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
3 changed files with 137 additions and 52 deletions

42
src/interface.zig Normal file
View File

@ -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;
}

View File

@ -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]);
}

View File

@ -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);