allow handler more control in status reporting
All checks were successful
AWS-Zig Build / build-zig-0.11.0-amd64-host (push) Successful in 1m46s

This commit is contained in:
Emil Lerch 2023-10-23 12:49:18 -07:00
parent d8b5366515
commit 47e4b0d54c
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
4 changed files with 81 additions and 32 deletions

View File

@ -4,8 +4,8 @@
.dependencies = .{ .dependencies = .{
.flexilib = .{ .flexilib = .{
.url = "https://git.lerch.org/lobo/flexilib/archive/c44ad2ba84df735421bef23a2ad612968fb50f06.tar.gz", .url = "https://git.lerch.org/lobo/flexilib/archive/3d3dab9c792651477932e2b61c9f4794ac694dcb.tar.gz",
.hash = "122051fdfeefdd75653d3dd678c8aa297150c2893f5fad0728e0d953481383690dbc", .hash = "1220fd7a614fe3c9f6006b630bba528e2ec9dca9c66f5ff10f7e471ad2bdd41b6c89",
}, },
}, },
} }

View File

@ -84,19 +84,7 @@ pub const Headers = struct {
pub fn allHeaders(allocator: std.mem.Allocator, context: universal_lambda.Context) !Headers { pub fn allHeaders(allocator: std.mem.Allocator, context: universal_lambda.Context) !Headers {
switch (context) { switch (context) {
.web_request => |res| return Headers.init(allocator, &res.request.headers, false), .web_request => |res| return Headers.init(allocator, &res.request.headers, false),
.flexilib => |ctx| { .flexilib => |ctx| return Headers.init(allocator, &ctx.request.headers, false),
var headers = try allocator.create(std.http.Headers);
errdefer allocator.destroy(headers);
headers.allocator = allocator;
headers.list = .{};
headers.index = .{};
headers.owned = true;
errdefer headers.deinit();
for (ctx.request.headers) |hdr| {
try headers.append(hdr.name_ptr[0..hdr.name_len], hdr.value_ptr[0..hdr.value_len]);
}
return Headers.init(allocator, headers, true);
},
.none => return headersWithoutContext(allocator), .none => return headersWithoutContext(allocator),
} }
} }
@ -164,8 +152,9 @@ test "can get headers" {
// leaks. There doesn't seem to be a way to ignore leak detection // leaks. There doesn't seem to be a way to ignore leak detection
if (@import("builtin").os.tag == .wasi) return error.SkipZigTest; if (@import("builtin").os.tag == .wasi) return error.SkipZigTest;
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
var response = universal_lambda.Response.init(allocator);
const context = universal_lambda.Context{ const context = universal_lambda.Context{
.none = {}, .none = &response,
}; };
var headers = try allHeaders(allocator, context); var headers = try allHeaders(allocator, context);
defer headers.deinit(); defer headers.deinit();

View File

@ -3,6 +3,7 @@ const builtin = @import("builtin");
const HandlerFn = @import("universal_lambda.zig").HandlerFn; const HandlerFn = @import("universal_lambda.zig").HandlerFn;
const Context = @import("universal_lambda.zig").Context; const Context = @import("universal_lambda.zig").Context;
const UniversalLambdaResponse = @import("universal_lambda.zig").Response;
const log = std.log.scoped(.lambda); const log = std.log.scoped(.lambda);
@ -20,7 +21,7 @@ pub fn deinit() void {
/// If an allocator is not provided, an approrpriate allocator will be selected and used /// If an allocator is not provided, an approrpriate allocator will be selected and used
/// This function is intended to loop infinitely. If not used in this manner, /// This function is intended to loop infinitely. If not used in this manner,
/// make sure to call the deinit() function /// make sure to call the deinit() function
pub fn run(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !void { // TODO: remove inferred error set? pub fn run(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !u8 { // TODO: remove inferred error set?
const lambda_runtime_uri = std.os.getenv("AWS_LAMBDA_RUNTIME_API") orelse test_lambda_runtime_uri.?; const lambda_runtime_uri = std.os.getenv("AWS_LAMBDA_RUNTIME_API") orelse test_lambda_runtime_uri.?;
// TODO: If this is null, go into single use command line mode // TODO: If this is null, go into single use command line mode
@ -70,15 +71,20 @@ pub fn run(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !void { // T
// Lambda does not have context, just environment variables. API Gateway // Lambda does not have context, just environment variables. API Gateway
// might be configured to pass in lots of context, but this comes through // might be configured to pass in lots of context, but this comes through
// event data, not context. // event data, not context.
const event_response = event_handler(req_allocator, event.event_data, .{ .none = {} }) catch |err| { var response = UniversalLambdaResponse.init(allocator.?);
response.output_file = std.io.getStdOut();
const event_response = event_handler(req_allocator, event.event_data, .{ .none = &response }) catch |err| {
response.finish() catch unreachable;
event.reportError(@errorReturnTrace(), err, lambda_runtime_uri) catch unreachable; event.reportError(@errorReturnTrace(), err, lambda_runtime_uri) catch unreachable;
continue; continue;
}; };
response.finish() catch unreachable;
event.postResponse(lambda_runtime_uri, event_response) catch |err| { event.postResponse(lambda_runtime_uri, event_response) catch |err| {
event.reportError(@errorReturnTrace(), err, lambda_runtime_uri) catch unreachable; event.reportError(@errorReturnTrace(), err, lambda_runtime_uri) catch unreachable;
continue; continue;
}; };
} }
return 0;
} }
const Event = struct { const Event = struct {
@ -409,10 +415,13 @@ fn handler(allocator: std.mem.Allocator, event_data: []const u8, context: Contex
_ = context; _ = context;
return event_data; return event_data;
} }
fn thread_run(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !void {
_ = try run(allocator, event_handler);
}
fn test_run(allocator: std.mem.Allocator, event_handler: HandlerFn) !std.Thread { fn test_run(allocator: std.mem.Allocator, event_handler: HandlerFn) !std.Thread {
return try std.Thread.spawn( return try std.Thread.spawn(
.{}, .{},
run, thread_run,
.{ allocator, event_handler }, .{ allocator, event_handler },
); );
} }

View File

@ -5,22 +5,56 @@ pub const HandlerFn = *const fn (std.mem.Allocator, []const u8, Context) anyerro
const log = std.log.scoped(.universal_lambda); const log = std.log.scoped(.universal_lambda);
const FakeResponse = struct { pub const Response = struct {
allocator: std.mem.Allocator,
headers: std.http.Headers,
output_file: ?std.fs.File = null,
status: std.http.Status = .ok,
reason: ?[]const u8 = null,
request: struct { request: struct {
target: []const u8, target: []const u8,
headers: std.http.Headers, headers: std.http.Headers,
}, },
al: std.ArrayList(u8),
pub fn init(allocator: std.mem.Allocator) Response {
return .{
.allocator = allocator,
.headers = .{ .allocator = allocator },
.request = .{
.target = "/",
.headers = .{ .allocator = allocator },
},
.al = std.ArrayList(u8).init(allocator),
};
}
pub fn write(res: *Response, bytes: []const u8) !usize {
return res.al.writer().write(bytes);
}
pub fn writeAll(res: *Response, bytes: []const u8) !void {
return res.al.writer().writeAll(bytes);
}
pub fn writer(res: *Response) std.io.Writer {
return res.al.writer().writer();
}
pub fn finish(res: *Response) !void {
if (res.output_file) |f| {
try f.writer().writeAll(res.al.items);
}
res.al.deinit();
}
}; };
pub const Context = union(enum) { pub const Context = union(enum) {
web_request: switch (build_options.build_type) { web_request: switch (build_options.build_type) {
.exe_run, .cloudflare => *FakeResponse, .exe_run, .cloudflare => *Response,
else => *std.http.Server.Response, else => *std.http.Server.Response,
}, },
flexilib: struct { flexilib: *flexilib.ZigResponse,
request: flexilib.ZigRequest, none: *Response,
response: flexilib.ZigResponse,
},
none: void,
}; };
const runFn = blk: { const runFn = blk: {
@ -42,11 +76,11 @@ fn deinit() void {
/// If an allocator is not provided, an approrpriate allocator will be selected and used /// If an allocator is not provided, an approrpriate allocator will be selected and used
/// This function is intended to loop infinitely. If not used in this manner, /// This function is intended to loop infinitely. If not used in this manner,
/// make sure to call the deinit() function /// make sure to call the deinit() function
pub fn run(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !void { // TODO: remove inferred error set? pub fn run(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !u8 { // TODO: remove inferred error set?
try runFn(allocator, event_handler); return try runFn(allocator, event_handler);
} }
fn runExe(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !void { fn runExe(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !u8 {
var arena = std.heap.ArenaAllocator.init(allocator orelse std.heap.page_allocator); var arena = std.heap.ArenaAllocator.init(allocator orelse std.heap.page_allocator);
defer arena.deinit(); defer arena.deinit();
@ -56,14 +90,30 @@ fn runExe(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !void {
// We're setting up an arena allocator. While we could use a gpa and get // We're setting up an arena allocator. While we could use a gpa and get
// some additional safety, this is now "production" runtime, and those // some additional safety, this is now "production" runtime, and those
// things are better handled by unit tests // things are better handled by unit tests
const writer = std.io.getStdOut().writer(); var response = Response.init(aa);
try writer.writeAll(try event_handler(aa, data, .{ .none = {} }));
// Note here we are throwing out the status and reason. This is to make
// the console experience less "webby" and more "consoly", at the potential
// cost of data loss for not outputting the http status/reason
const output = event_handler(aa, data, .{ .none = &response }) catch |err| {
response.output_file = std.io.getStdErr();
try response.finish();
return err;
};
response.output_file = if (response.status.class() == .success) std.io.getStdOut() else std.io.getStdErr();
const writer = response.output_file.?.writer();
try response.finish();
try writer.writeAll(output);
try writer.writeAll("\n"); try writer.writeAll("\n");
// We might have gotten an error message managed directly by the event handler
// If that's the case, we will need to report back an error code
return if (response.status.class() == .success) 0 else 1;
} }
/// Will create a web server and marshall all requests back to our event handler /// Will create a web server and marshall all requests back to our event handler
/// To keep things simple, we'll have this on a single thread, at least for now /// To keep things simple, we'll have this on a single thread, at least for now
fn runStandaloneServer(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !void { fn runStandaloneServer(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !u8 {
const alloc = allocator orelse std.heap.page_allocator; const alloc = allocator orelse std.heap.page_allocator;
var arena = std.heap.ArenaAllocator.init(alloc); var arena = std.heap.ArenaAllocator.init(alloc);
@ -98,6 +148,7 @@ fn runStandaloneServer(allocator: ?std.mem.Allocator, event_handler: HandlerFn)
} }
}; };
} }
return 0;
} }
fn processRequest(aa: std.mem.Allocator, server: *std.http.Server, event_handler: HandlerFn) !void { fn processRequest(aa: std.mem.Allocator, server: *std.http.Server, event_handler: HandlerFn) !void {