add standalone web server support
This commit is contained in:
		
							parent
							
								
									2fb551c77d
								
							
						
					
					
						commit
						b313be2476
					
				
					 3 changed files with 112 additions and 10 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		
		Reference in a new issue