add flexilib support
This commit is contained in:
parent
d23dbd126b
commit
db061ecbdc
140
src/flexilib-interface.zig
Normal file
140
src/flexilib-interface.zig
Normal file
|
@ -0,0 +1,140 @@
|
|||
const std = @import("std");
|
||||
|
||||
// C interfaces between main and libraries
|
||||
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: [*:0]u8,
|
||||
method_len: usize,
|
||||
|
||||
content: [*]u8,
|
||||
content_len: usize,
|
||||
|
||||
headers: [*]Header,
|
||||
headers_len: usize,
|
||||
};
|
||||
|
||||
// If the library is Zig, we can use these helpers
|
||||
threadlocal var allocator: ?*std.mem.Allocator = null;
|
||||
|
||||
const log = std.log.scoped(.interface);
|
||||
|
||||
pub const ZigRequest = struct {
|
||||
method: [:0]u8,
|
||||
content: []u8,
|
||||
headers: []Header,
|
||||
};
|
||||
|
||||
pub const ZigHeader = struct {
|
||||
name: []u8,
|
||||
value: []u8,
|
||||
};
|
||||
|
||||
pub const ZigResponse = struct {
|
||||
body: *std.ArrayList(u8),
|
||||
headers: *std.StringHashMap([]const u8),
|
||||
};
|
||||
|
||||
pub const ZigRequestHandler = *const fn (std.mem.Allocator, ZigRequest, 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
|
||||
/// 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 {
|
||||
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| {
|
||||
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;
|
||||
}
|
||||
|
||||
/// 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
|
||||
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 {
|
||||
log.err("zigInit not called prior to handle_request. This is a coding error", .{});
|
||||
return null;
|
||||
};
|
||||
|
||||
// setup response body
|
||||
var response = std.ArrayList(u8).init(alloc);
|
||||
|
||||
// setup headers
|
||||
var headers = std.StringHashMap([]const u8).init(alloc);
|
||||
zigRequestHandler(
|
||||
alloc,
|
||||
.{
|
||||
.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,
|
||||
},
|
||||
) 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 = alloc.create(Response) 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| {
|
||||
log.err("Unexpected error processing request: {any}", .{e});
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
rc.headers_len = headers.count();
|
||||
return rc;
|
||||
}
|
107
src/flexilib.zig
Normal file
107
src/flexilib.zig
Normal file
|
@ -0,0 +1,107 @@
|
|||
const std = @import("std");
|
||||
const interface = @import("flexilib-interface.zig"); // TODO: pull in flexilib directly
|
||||
const testing = std.testing;
|
||||
|
||||
const log = std.log.scoped(.@"main-lib");
|
||||
|
||||
const client_handler = @import("flexilib_handler");
|
||||
|
||||
// The main program will look for exports during the request lifecycle:
|
||||
// zigInit (optional): called at the beginning of a request, includes pointer to an allocator
|
||||
// handle_request: called with request data, expects response data
|
||||
// request_deinit (optional): called at the end of a request to allow resource cleanup
|
||||
//
|
||||
// Setup for these is aided by the interface library as shown below
|
||||
|
||||
// zigInit is an optional export called at the beginning of a request. It will
|
||||
// be passed an allocator (which...shh...is an arena allocator). Since the
|
||||
// interface library provides a request handler that requires a built-in allocator,
|
||||
// if you are using the interface's handleRequest function as shown above,
|
||||
// you will need to also include this export. To customize, just do something
|
||||
// like this:
|
||||
//
|
||||
// export fn zigInit(parent_allocator: *anyopaque) callconv(.C) void {
|
||||
// // your code here, just include the next line
|
||||
// interface.zigInit(parent_allocator);
|
||||
// }
|
||||
//
|
||||
comptime {
|
||||
@export(interface.zigInit, .{ .name = "zigInit", .linkage = .Strong });
|
||||
}
|
||||
|
||||
/// handle_request will be called on a single request, but due to the preservation
|
||||
/// of restrictions imposed by the calling interface, it should generally be more
|
||||
/// useful to call into the interface library to let it do the conversion work
|
||||
/// on your behalf
|
||||
export fn handle_request(request: *interface.Request) callconv(.C) ?*interface.Response {
|
||||
// The interface library provides a handleRequest function that will handle
|
||||
// marshalling data back and forth from the C format used for the interface
|
||||
// to a more Zig friendly format. It also allows usage of zig errors. To
|
||||
// use, pass in the request and the zig function used to handle the request
|
||||
// (here called "handleRequest"). The function signature must be:
|
||||
//
|
||||
// fn (std.mem.Allocator, interface.ZigRequest, interface.ZigResponse) !void
|
||||
//
|
||||
return interface.handleRequest(request, handleRequest);
|
||||
}
|
||||
|
||||
// request_deinit is an optional export and will be called a the end of the
|
||||
// request. Useful for deallocating memory. Since this is zig code and the
|
||||
// allocator used is an arena allocator, all allocated memory will be automatically
|
||||
// cleaned up by the main program at the end of a request
|
||||
//
|
||||
// export fn request_deinit() void {
|
||||
// }
|
||||
|
||||
// ************************************************************************
|
||||
// Boilerplate ^^, Custom code vv
|
||||
// ************************************************************************
|
||||
//
|
||||
// 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 {
|
||||
// setup
|
||||
var response_writer = response.body.writer();
|
||||
try response_writer.writeAll(try client_handler.handler(allocator, request.content, .{}));
|
||||
// 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")) {
|
||||
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.headers.put("X-custom-foo", "bar");
|
||||
log.info("handlerequest header count {d}", .{response.headers.count()});
|
||||
}
|
||||
// Need to figure out how tests would work
|
||||
test "handle_request" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
var aa = arena.allocator();
|
||||
interface.zigInit(&aa);
|
||||
var headers: []interface.Header = @constCast(&[_]interface.Header{.{
|
||||
.name_ptr = @ptrCast(@constCast("GET".ptr)),
|
||||
.name_len = 3,
|
||||
.value_ptr = @ptrCast(@constCast("GET".ptr)),
|
||||
.value_len = 3,
|
||||
}});
|
||||
var req = interface.Request{
|
||||
.method = @ptrCast(@constCast("GET".ptr)),
|
||||
.method_len = 3,
|
||||
.content = @ptrCast(@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]);
|
||||
}
|
47
src/flexilib_build.zig
Normal file
47
src/flexilib_build.zig
Normal file
|
@ -0,0 +1,47 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
/// flexilib will create a dynamic library for use with flexilib.
|
||||
/// Flexilib will need to get the exe compiled as a library
|
||||
/// For flexilib, we will need the main file to have a pub fn named
|
||||
/// "handler". If it is not called that, a pub const handler = ... can be
|
||||
/// used instead
|
||||
pub fn configureBuild(b: *std.build.Builder, exe: *std.Build.Step.Compile, build_root_src: []const u8) !void {
|
||||
const package_step = b.step("flexilib", "Create a flexilib dynamic library");
|
||||
|
||||
// const exe = b.addExecutable(.{
|
||||
// .name = "universal-lambda-example",
|
||||
// // In this case the main source file is merely a path, however, in more
|
||||
// // complicated build scripts, this could be a generated file.
|
||||
// .root_source_file = .{ .path = "src/main.zig" },
|
||||
// .target = target,
|
||||
// .optimize = optimize,
|
||||
// });
|
||||
//
|
||||
const lib = b.addSharedLibrary(.{
|
||||
.name = exe.name,
|
||||
.root_source_file = .{ .path = b.pathJoin(&[_][]const u8{ build_root_src, "flexilib.zig" }) },
|
||||
.target = exe.target,
|
||||
.optimize = exe.optimize,
|
||||
});
|
||||
|
||||
// We will not free this, as the rest of the build system will use it.
|
||||
// This should be ok because our allocator is, I believe, an arena
|
||||
var module_dependencies = try b.allocator.alloc(std.Build.ModuleDependency, exe.modules.count());
|
||||
var iterator = exe.modules.iterator();
|
||||
|
||||
var i: usize = 0;
|
||||
while (iterator.next()) |entry| : (i += 1) {
|
||||
module_dependencies[i] = .{
|
||||
.name = entry.key_ptr.*,
|
||||
.module = entry.value_ptr.*,
|
||||
};
|
||||
lib.addModule(entry.key_ptr.*, entry.value_ptr.*);
|
||||
}
|
||||
lib.addAnonymousModule("flexilib_handler", .{
|
||||
// Source file can be anywhere on disk, does not need to be a subdirectory
|
||||
.source_file = exe.root_src.?,
|
||||
.dependencies = module_dependencies,
|
||||
});
|
||||
package_step.dependOn(&b.addInstallArtifact(lib, .{}).step);
|
||||
}
|
|
@ -25,7 +25,7 @@ pub fn configureBuild(b: *std.Build, exe: *std.Build.Step.Compile) !void {
|
|||
});
|
||||
|
||||
// Add steps
|
||||
try @import("lambdabuild.zig").configureBuild(b, exe);
|
||||
try @import("lambda_build.zig").configureBuild(b, exe);
|
||||
try @import("standalone_server_build.zig").configureBuild(b, exe);
|
||||
try @import("flexilib_build.zig").configureBuild(b, exe, file_location);
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user