implement header matching in proxy.ini
This commit is contained in:
parent
2004d97919
commit
1f9fd19771
|
@ -38,6 +38,11 @@ pub const ZigRequest = struct {
|
|||
headers: []Header,
|
||||
};
|
||||
|
||||
pub const ZigHeader = struct {
|
||||
name: []u8,
|
||||
value: []u8,
|
||||
};
|
||||
|
||||
pub const ZigResponse = struct {
|
||||
body: *std.ArrayList(u8),
|
||||
headers: *std.StringHashMap([]const u8),
|
||||
|
@ -55,6 +60,13 @@ pub fn zigInit(parent_allocator: *anyopaque) callconv(.C) void {
|
|||
allocator = @ptrCast(*std.mem.Allocator, @alignCast(@alignOf(*std.mem.Allocator), parent_allocator));
|
||||
}
|
||||
|
||||
pub fn toZigHeader(header: Header) ZigHeader {
|
||||
return .{
|
||||
.name = header.name_ptr[0..header.name_len],
|
||||
.value = header.value_ptr[0..header.value_len],
|
||||
};
|
||||
}
|
||||
|
||||
/// Converts a StringHashMap to the structure necessary for passing through the
|
||||
/// C boundary. This will be called automatically for you via the handleRequest function
|
||||
/// and is also used by the main processing loop to coerce request headers
|
||||
|
|
|
@ -40,7 +40,16 @@ fn handleRequest(allocator: std.mem.Allocator, request: interface.ZigRequest, re
|
|||
// setup
|
||||
var response_writer = response.body.writer();
|
||||
// real work
|
||||
response_writer.print(" {d}", .{request.headers.len}) catch unreachable;
|
||||
for (request.headers) |h| {
|
||||
const header = interface.toZigHeader(h);
|
||||
if (!std.ascii.eqlIgnoreCase(header.name, "host")) continue;
|
||||
if (std.mem.startsWith(u8, header.value, "iam")) {
|
||||
try response_writer.print("iam response", .{});
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
try response_writer.print(" {d}", .{request.headers.len});
|
||||
try response.headers.put("X-custom-foo", "bar");
|
||||
log.info("handlerequest header count {d}", .{response.headers.count()});
|
||||
}
|
||||
|
|
46
src/main.zig
46
src/main.zig
|
@ -37,7 +37,7 @@ const FullReturn = struct {
|
|||
// applies
|
||||
const Executor = struct {
|
||||
// configuration
|
||||
target_prefix: []const u8,
|
||||
match_data: []const u8,
|
||||
path: [:0]const u8,
|
||||
|
||||
// fields used at runtime to do real work
|
||||
|
@ -62,8 +62,7 @@ var parsed_config: config.ParsedConfig = undefined;
|
|||
/// Serves a single request. Finds executor, marshalls request data for the C
|
||||
/// interface, calls the executor and marshalls data back
|
||||
fn serve(allocator: *std.mem.Allocator, response: *std.http.Server.Response) !*FullReturn {
|
||||
// if (some path routing thing) {
|
||||
const executor = try getExecutor(response.request.target);
|
||||
const executor = try getExecutor(response.request.target, response.request.headers);
|
||||
if (executor.zigInitFn) |f|
|
||||
f(allocator);
|
||||
|
||||
|
@ -115,10 +114,10 @@ fn serve(allocator: *std.mem.Allocator, response: *std.http.Server.Response) !*F
|
|||
}
|
||||
|
||||
/// Gets and executor based on request data
|
||||
fn getExecutor(requested_path: []const u8) !*Executor {
|
||||
fn getExecutor(requested_path: []const u8, headers: std.http.Headers) !*Executor {
|
||||
var executor = blk: {
|
||||
for (executors) |*exec| {
|
||||
if (std.mem.startsWith(u8, requested_path, exec.target_prefix)) {
|
||||
if (executorIsMatch(exec.match_data, requested_path, headers)) {
|
||||
break :blk exec;
|
||||
}
|
||||
}
|
||||
|
@ -154,6 +153,41 @@ fn getExecutor(requested_path: []const u8) !*Executor {
|
|||
return executor;
|
||||
}
|
||||
|
||||
fn executorIsMatch(match_data: []const u8, requested_path: []const u8, headers: std.http.Headers) bool {
|
||||
if (!std.mem.containsAtLeast(u8, match_data, 1, ":")) {
|
||||
// match_data does not have a ':'. This means this is a straight path, without
|
||||
// any header requirement. We can simply return a match prefix on the
|
||||
// requested path
|
||||
const rc = std.mem.startsWith(u8, requested_path, match_data);
|
||||
if (rc) log.debug("executor match for path prefix '{s}'", .{match_data});
|
||||
return rc;
|
||||
}
|
||||
const colon = std.mem.indexOf(u8, match_data, ":").?;
|
||||
const header_needle = match_data[0..colon];
|
||||
const header_inx = headers.firstIndexOf(header_needle) orelse return false;
|
||||
// Apparently std.mem.split will return an empty first when the haystack starts
|
||||
// with the delimiter
|
||||
var split = std.mem.split(u8, std.mem.trim(u8, match_data[colon + 1 ..], "\t "), " ");
|
||||
const header_value_needle = split.first();
|
||||
const path_needle = split.next() orelse {
|
||||
std.log.warn(
|
||||
"Incorrect configuration. Header matching requires both header value and path prefix, space delimited. Key was '{s}'",
|
||||
.{match_data},
|
||||
);
|
||||
return false;
|
||||
};
|
||||
// match_data includes some sort of header match as well. We assume the
|
||||
// header match is a full match on the key (handled above)
|
||||
// but a prefix match on the value
|
||||
const request_header_value = headers.list.items[header_inx].value;
|
||||
// (shoud this be case insensitive?)
|
||||
if (!std.mem.startsWith(u8, request_header_value, header_value_needle)) return false;
|
||||
// header value matches...return the path prefix match
|
||||
const rc = std.mem.startsWith(u8, requested_path, path_needle);
|
||||
if (rc) log.debug("executor match for header and path prefix '{s}'", .{match_data});
|
||||
return rc;
|
||||
}
|
||||
|
||||
/// Loads all optional symbols from the dynamic library. This has two entry
|
||||
/// points, though the logic around the primary request handler is slighly
|
||||
/// different in each case so we can't combine those two.
|
||||
|
@ -346,7 +380,7 @@ fn loadConfig(allocator: std.mem.Allocator) ![]Executor {
|
|||
defer al.deinit();
|
||||
for (parsed_config.key_value_map.keys(), parsed_config.key_value_map.values()) |k, v| {
|
||||
al.appendAssumeCapacity(.{
|
||||
.target_prefix = k,
|
||||
.match_data = k,
|
||||
.path = v,
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user