move executor config to external file

This commit is contained in:
Emil Lerch 2023-05-27 14:01:40 -07:00
parent c393792cd1
commit 02017da98e
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
3 changed files with 60 additions and 23 deletions

8
proxy.ini Normal file
View File

@ -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

View File

@ -4,18 +4,18 @@ const std = @import("std");
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
pub const ParsedConfig = struct { pub const ParsedConfig = struct {
key_value_map: *std.StringArrayHashMap([]u8), key_value_map: *std.StringArrayHashMap([:0]u8),
value_key_map: *std.StringArrayHashMap(*std.ArrayList([]u8)), value_key_map: *std.StringArrayHashMap(*std.ArrayList([:0]u8)),
config_allocator: std.mem.Allocator, config_allocator: std.mem.Allocator,
const SelfConfig = @This(); const SelfConfig = @This();
var by_keys: std.StringArrayHashMap([]u8) = undefined; var by_keys: std.StringArrayHashMap([:0]u8) = undefined;
var by_values: std.StringArrayHashMap(*std.ArrayList([]u8)) = undefined; var by_values: std.StringArrayHashMap(*std.ArrayList([:0]u8)) = undefined;
pub fn init(config_allocator: std.mem.Allocator) SelfConfig { pub fn init(config_allocator: std.mem.Allocator) SelfConfig {
by_keys = std.StringArrayHashMap([]u8).init(config_allocator); by_keys = std.StringArrayHashMap([:0]u8).init(config_allocator);
by_values = std.StringArrayHashMap(*std.ArrayList([]u8)).init(config_allocator); by_values = std.StringArrayHashMap(*std.ArrayList([:0]u8)).init(config_allocator);
return SelfConfig{ return SelfConfig{
.config_allocator = config_allocator, .config_allocator = config_allocator,
.key_value_map = &by_keys, .key_value_map = &by_keys,
@ -25,7 +25,8 @@ pub const ParsedConfig = struct {
pub fn deinit(self: *SelfConfig) void { pub fn deinit(self: *SelfConfig) void {
for (self.key_value_map.keys(), self.key_value_map.values()) |k, v| { 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); 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. // keys should be putNoClobber, but values can be put.
// Because we have to dup the memory here though, we want to // Because we have to dup the memory here though, we want to
// manage duplicate values seperately // manage duplicate values seperately
var dup_key = try self.allocator.dupe(u8, key); var dup_key = try self.allocator.dupeZ(u8, key);
var dup_value = try self.allocator.dupe(u8, value); var dup_value = try self.allocator.dupeZ(u8, value);
try rc.key_value_map.putNoClobber(dup_key, dup_value); try rc.key_value_map.putNoClobber(dup_key, dup_value);
if (!rc.value_key_map.contains(value)) { if (!rc.value_key_map.contains(value)) {
var keys = try self.allocator.create(std.ArrayList([]u8)); var keys = try self.allocator.create(std.ArrayList([:0]u8));
keys.* = std.ArrayList([]u8).init(self.allocator); keys.* = std.ArrayList([:0]u8).init(self.allocator);
try rc.value_key_map.put(dup_value, keys); try rc.value_key_map.put(dup_value, keys);
} }
try rc.value_key_map.get(value).?.append(dup_key); try rc.value_key_map.get(value).?.append(dup_key);

View File

@ -16,6 +16,7 @@ const FullReturn = struct {
const Executor = struct { const Executor = struct {
// configuration // configuration
target_prefix: []const u8,
path: [:0]const u8, path: [:0]const u8,
// fields used at runtime to do real work // fields used at runtime to do real work
@ -30,13 +31,6 @@ const Executor = struct {
in_request_lock: bool = false, 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 = Watch.init(executorChanged);
var watcher_thread: ?std.Thread = null; var watcher_thread: ?std.Thread = null;
var timer: ?std.time.Timer = null; // timer used by processRequest 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 SERVE_FN_NAME = "handle_request";
const PORT = 8069; const PORT = 8069;
var executors: []Executor = undefined;
fn serve(allocator: *std.mem.Allocator, response: *std.http.Server.Response) !*FullReturn { fn serve(allocator: *std.mem.Allocator, response: *std.http.Server.Response) !*FullReturn {
// if (some path routing thing) { // if (some path routing thing) {
const executor = try getExecutor(0); const executor = try getExecutor(response.request.target);
if (executor.zigInitFn) |f| if (executor.zigInitFn) |f|
f(allocator); f(allocator);
@ -104,8 +100,16 @@ fn serve(allocator: *std.mem.Allocator, response: *std.http.Server.Response) !*F
rc.response = slice; rc.response = slice;
return rc; return rc;
} }
fn getExecutor(key: usize) !*Executor { fn getExecutor(requested_path: []const u8) !*Executor {
var executor = &executors[key]; 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; if (executor.serveFn != null) return executor;
executor.library = blk: { executor.library = blk: {
@ -146,7 +150,7 @@ fn loadOptionalSymbols(executor: *Executor) void {
fn executorChanged(watch: usize) void { fn executorChanged(watch: usize) void {
// NOTE: This will be called off the main thread // NOTE: This will be called off the main thread
log.debug("executor with watch {d} changed", .{watch}); log.debug("executor with watch {d} changed", .{watch});
for (&executors) |*executor| { for (executors) |*executor| {
if (executor.watch) |w| { if (executor.watch) |w| {
if (w == watch) { if (w == watch) {
if (executor.library) |l| { if (executor.library) |l| {
@ -231,9 +235,13 @@ pub fn main() !void {
var bw = std.io.bufferedWriter(stdout_file); var bw = std.io.bufferedWriter(stdout_file);
const stdout = bw.writer(); 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}); 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 }); var server = std.http.Server.init(allocator, .{ .reuse_address = true });
defer server.deinit(); defer server.deinit();
@ -270,6 +278,23 @@ pub fn main() !void {
try bw.flush(); 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 { fn processRequest(allocator: *std.mem.Allocator, server: *std.http.Server, writer: anytype) !void {
const max_header_size = 8192; 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 { fn testRequest(request_bytes: []const u8) !void {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
executors = try loadConfig(allocator);
defer allocator.free(executors);
defer parsed_config.deinit();
var arena = std.heap.ArenaAllocator.init(allocator); var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit(); defer arena.deinit();