add standalone web server support
This commit is contained in:
parent
2fb551c77d
commit
b313be2476
17
src/standalone_server_build.zig
Normal file
17
src/standalone_server_build.zig
Normal file
|
@ -0,0 +1,17 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
/// adds a build step to the build
|
||||
///
|
||||
/// * standalone_server: This will run the handler as a standalone web server
|
||||
///
|
||||
pub fn configureBuild(b: *std.build.Builder, exe: *std.Build.Step.Compile) !void {
|
||||
_ = exe;
|
||||
// We don't actually need to do much here. Basically we need a dummy step,
|
||||
// but one which the user will select, which will allow our options mechanism
|
||||
// to kick in
|
||||
|
||||
// Package step
|
||||
const standalone_step = b.step("standalone_server", "Run the function in its own web server");
|
||||
standalone_step.dependOn(b.getInstallStep());
|
||||
}
|
|
@ -8,6 +8,14 @@ const log = std.log.scoped(.universal_lambda);
|
|||
// TODO: Should this be union?
|
||||
pub const Context = struct {};
|
||||
|
||||
const runFn = blk: {
|
||||
switch (build_options.build_type) {
|
||||
.standalone_server => break :blk runStandaloneServer,
|
||||
.exe_run => break :blk runExe,
|
||||
else => @compileError("Provider interface for " ++ @tagName(build_options.build_type) ++ " has not yet been implemented"),
|
||||
}
|
||||
};
|
||||
|
||||
fn deinit() void {
|
||||
// if (client) |*c| c.deinit();
|
||||
// client = null;
|
||||
|
@ -19,19 +27,95 @@ fn deinit() void {
|
|||
/// This function is intended to loop infinitely. If not used in this manner,
|
||||
/// make sure to call the deinit() function
|
||||
pub fn run(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !void { // TODO: remove inferred error set?
|
||||
switch (build_options.build_type) {
|
||||
.exe_run => try runExe(allocator, event_handler),
|
||||
else => return error.NotImplemented,
|
||||
}
|
||||
try runFn(allocator, event_handler);
|
||||
}
|
||||
|
||||
fn runExe(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const gpa_alloc = allocator orelse gpa.allocator();
|
||||
var arena = std.heap.ArenaAllocator.init(allocator orelse std.heap.page_allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
// TODO: set up an arena for this? Are we doing an arena for every type?
|
||||
const aa = arena.allocator();
|
||||
|
||||
// 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
|
||||
// things are better handled by unit tests
|
||||
const writer = std.io.getStdOut().writer();
|
||||
try writer.writeAll(try event_handler(gpa_alloc, "", .{}));
|
||||
try writer.writeAll(try event_handler(aa, "", .{}));
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
/// 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
|
||||
fn runStandaloneServer(allocator: ?std.mem.Allocator, event_handler: HandlerFn) !void {
|
||||
const alloc = allocator orelse std.heap.page_allocator;
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
||||
defer arena.deinit();
|
||||
|
||||
var aa = arena.allocator();
|
||||
var server = std.http.Server.init(aa, .{ .reuse_address = true });
|
||||
defer server.deinit();
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 8080); // TODO: allow config
|
||||
try server.listen(address);
|
||||
const server_port = server.socket.listen_address.in.getPort();
|
||||
var uri: ["http://127.0.0.1:99999".len]u8 = undefined;
|
||||
_ = try std.fmt.bufPrint(&uri, "http://127.0.0.1:{d}", .{server_port});
|
||||
log.info("server listening at {s}", .{uri});
|
||||
|
||||
// No threads, maybe later
|
||||
//log.info("starting server thread, tid {d}", .{std.Thread.getCurrentId()});
|
||||
while (true) {
|
||||
defer {
|
||||
if (!arena.reset(.{ .retain_with_limit = 1024 * 1024 })) {
|
||||
// reallocation failed, arena is degraded
|
||||
log.warn("Arena reset failed and is degraded. Resetting arena", .{});
|
||||
arena.deinit();
|
||||
arena = std.heap.ArenaAllocator.init(alloc);
|
||||
aa = arena.allocator();
|
||||
}
|
||||
}
|
||||
processRequest(aa, &server, event_handler) catch |e| {
|
||||
log.err("Unexpected error processing request: {any}", .{e});
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn processRequest(aa: std.mem.Allocator, server: *std.http.Server, event_handler: HandlerFn) !void {
|
||||
var res = try server.accept(.{ .allocator = aa });
|
||||
defer {
|
||||
_ = res.reset();
|
||||
if (res.headers.owned and res.headers.list.items.len > 0) res.headers.deinit();
|
||||
res.deinit();
|
||||
}
|
||||
try res.wait(); // wait for client to send a complete request head
|
||||
|
||||
const errstr = "Internal Server Error\n";
|
||||
var errbuf: [errstr.len]u8 = undefined;
|
||||
@memcpy(&errbuf, errstr);
|
||||
var response_bytes: []const u8 = errbuf[0..];
|
||||
|
||||
var body =
|
||||
if (res.request.content_length) |l|
|
||||
try res.reader().readAllAlloc(aa, @as(usize, l))
|
||||
else
|
||||
try aa.dupe(u8, "");
|
||||
// no need to free - will be handled by arena
|
||||
|
||||
response_bytes = event_handler(aa, body, .{}) catch |e| brk: {
|
||||
res.status = .internal_server_error;
|
||||
// TODO: more about this particular request
|
||||
log.err("Unexpected error from executor processing request: {any}", .{e});
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
break :brk "Unexpected error generating request to lambda";
|
||||
};
|
||||
res.transfer_encoding = .{ .content_length = response_bytes.len };
|
||||
|
||||
try res.do();
|
||||
_ = try res.writer().writeAll(response_bytes);
|
||||
try res.finish();
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ const std = @import("std");
|
|||
pub const BuildType = enum {
|
||||
awslambda,
|
||||
exe_run,
|
||||
standalone_run,
|
||||
standalone_server,
|
||||
cloudflare,
|
||||
flexilib,
|
||||
};
|
||||
|
@ -15,6 +15,7 @@ pub const BuildType = enum {
|
|||
pub fn configureBuild(b: *std.Build, exe: *std.Build.Step.Compile) !void {
|
||||
// Add steps
|
||||
try @import("lambdabuild.zig").configureBuild(b, exe);
|
||||
try @import("standalone_server_build.zig").configureBuild(b, exe);
|
||||
// Add options module so we can let our universal_lambda know what
|
||||
// type of interface is necessary
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user