From 02017da98eddec91bf4d80ae446505f1d80df7c8 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Sat, 27 May 2023 14:01:40 -0700 Subject: [PATCH] move executor config to external file --- proxy.ini | 8 ++++++++ src/config.zig | 23 +++++++++++----------- src/main.zig | 52 ++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 60 insertions(+), 23 deletions(-) create mode 100644 proxy.ini diff --git a/proxy.ini b/proxy.ini new file mode 100644 index 0000000..fd9eede --- /dev/null +++ b/proxy.ini @@ -0,0 +1,8 @@ +# This is a simple "path prefix" = dynamic library path mapping +# no reordering will be done, so you must do things most -> least specific +# because all paths start with a '/', we may be able to later add the ability +# for libraries to self-select whether they can handle a request, which opens +# up additional possibilities + +/c = zig-out/lib/libfaas-proxy-sample-lib-in-c.so +/ = zig-out/lib/libfaas-proxy-sample-lib.so diff --git a/src/config.zig b/src/config.zig index eab3bea..4fdad63 100644 --- a/src/config.zig +++ b/src/config.zig @@ -4,18 +4,18 @@ const std = @import("std"); allocator: std.mem.Allocator, pub const ParsedConfig = struct { - key_value_map: *std.StringArrayHashMap([]u8), - value_key_map: *std.StringArrayHashMap(*std.ArrayList([]u8)), + key_value_map: *std.StringArrayHashMap([:0]u8), + value_key_map: *std.StringArrayHashMap(*std.ArrayList([:0]u8)), config_allocator: std.mem.Allocator, const SelfConfig = @This(); - var by_keys: std.StringArrayHashMap([]u8) = undefined; - var by_values: std.StringArrayHashMap(*std.ArrayList([]u8)) = undefined; + var by_keys: std.StringArrayHashMap([:0]u8) = undefined; + var by_values: std.StringArrayHashMap(*std.ArrayList([:0]u8)) = undefined; pub fn init(config_allocator: std.mem.Allocator) SelfConfig { - by_keys = std.StringArrayHashMap([]u8).init(config_allocator); - by_values = std.StringArrayHashMap(*std.ArrayList([]u8)).init(config_allocator); + by_keys = std.StringArrayHashMap([:0]u8).init(config_allocator); + by_values = std.StringArrayHashMap(*std.ArrayList([:0]u8)).init(config_allocator); return SelfConfig{ .config_allocator = config_allocator, .key_value_map = &by_keys, @@ -25,7 +25,8 @@ pub const ParsedConfig = struct { pub fn deinit(self: *SelfConfig) void { for (self.key_value_map.keys(), self.key_value_map.values()) |k, v| { - self.config_allocator.free(k); // this is also the key in value_key_map + // StringArrayHashMap assumes []const u8, but what we've allocated is null terminated + self.config_allocator.free(@ptrCast([:0]const u8, k)); // this is also the key in value_key_map self.config_allocator.free(v); } @@ -70,12 +71,12 @@ pub fn parse(self: Self, reader: anytype) !ParsedConfig { // keys should be putNoClobber, but values can be put. // Because we have to dup the memory here though, we want to // manage duplicate values seperately - var dup_key = try self.allocator.dupe(u8, key); - var dup_value = try self.allocator.dupe(u8, value); + var dup_key = try self.allocator.dupeZ(u8, key); + var dup_value = try self.allocator.dupeZ(u8, value); try rc.key_value_map.putNoClobber(dup_key, dup_value); if (!rc.value_key_map.contains(value)) { - var keys = try self.allocator.create(std.ArrayList([]u8)); - keys.* = std.ArrayList([]u8).init(self.allocator); + var keys = try self.allocator.create(std.ArrayList([:0]u8)); + keys.* = std.ArrayList([:0]u8).init(self.allocator); try rc.value_key_map.put(dup_value, keys); } try rc.value_key_map.get(value).?.append(dup_key); diff --git a/src/main.zig b/src/main.zig index 6f3632e..b8e95e4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -16,6 +16,7 @@ const FullReturn = struct { const Executor = struct { // configuration + target_prefix: []const u8, path: [:0]const u8, // fields used at runtime to do real work @@ -30,13 +31,6 @@ const Executor = struct { 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" }, -}; - var watcher = Watch.init(executorChanged); var watcher_thread: ?std.Thread = null; var timer: ?std.time.Timer = null; // timer used by processRequest @@ -52,9 +46,11 @@ pub const std_options = struct { const SERVE_FN_NAME = "handle_request"; const PORT = 8069; +var executors: []Executor = undefined; + fn serve(allocator: *std.mem.Allocator, response: *std.http.Server.Response) !*FullReturn { // if (some path routing thing) { - const executor = try getExecutor(0); + const executor = try getExecutor(response.request.target); if (executor.zigInitFn) |f| f(allocator); @@ -104,8 +100,16 @@ fn serve(allocator: *std.mem.Allocator, response: *std.http.Server.Response) !*F rc.response = slice; return rc; } -fn getExecutor(key: usize) !*Executor { - var executor = &executors[key]; +fn getExecutor(requested_path: []const u8) !*Executor { + var executor = blk: { + for (executors) |*exec| { + if (std.mem.startsWith(u8, requested_path, exec.target_prefix)) { + break :blk exec; + } + } + log.err("Could not find executor for target path '{s}'", .{requested_path}); + return error.NoApplicableExecutor; + }; if (executor.serveFn != null) return executor; executor.library = blk: { @@ -146,7 +150,7 @@ fn loadOptionalSymbols(executor: *Executor) void { fn executorChanged(watch: usize) void { // NOTE: This will be called off the main thread log.debug("executor with watch {d} changed", .{watch}); - for (&executors) |*executor| { + for (executors) |*executor| { if (executor.watch) |w| { if (w == watch) { if (executor.library) |l| { @@ -231,9 +235,13 @@ pub fn main() !void { var bw = std.io.bufferedWriter(stdout_file); const stdout = bw.writer(); + var allocator = std.heap.raw_c_allocator; // raw allocator recommended for use in arenas + executors = try loadConfig(allocator); + defer allocator.free(executors); + defer parsed_config.deinit(); + watcher_thread = try std.Thread.spawn(.{}, Watch.startWatch, .{&watcher}); - var allocator = std.heap.raw_c_allocator; // raw allocator recommended for use in arenas var server = std.http.Server.init(allocator, .{ .reuse_address = true }); defer server.deinit(); @@ -270,6 +278,23 @@ pub fn main() !void { try bw.flush(); } } +var parsed_config: config.ParsedConfig = undefined; +fn loadConfig(allocator: std.mem.Allocator) ![]Executor { + log.info("loading config", .{}); + // We will not watch this file - let it reload on SIGHUP + var config_file = try std.fs.cwd().openFile("proxy.ini", .{}); + defer config_file.close(); + parsed_config = try config.init(allocator).parse(config_file.reader()); + var al = try std.ArrayList(Executor).initCapacity(allocator, parsed_config.key_value_map.keys().len); + defer al.deinit(); + for (parsed_config.key_value_map.keys(), parsed_config.key_value_map.values()) |k, v| { + al.appendAssumeCapacity(.{ + .target_prefix = k, + .path = v, + }); + } + return al.toOwnedSlice(); +} fn processRequest(allocator: *std.mem.Allocator, server: *std.http.Server, writer: anytype) !void { const max_header_size = 8192; @@ -371,6 +396,9 @@ fn writeToTestBuffers(response: []const u8, res: *std.http.Server.Response) void } fn testRequest(request_bytes: []const u8) !void { const allocator = std.testing.allocator; + executors = try loadConfig(allocator); + defer allocator.free(executors); + defer parsed_config.deinit(); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit();