From 613904e3a4b0e091d06958d08b808ff4f63205fb Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 16 Apr 2025 14:05:21 +0800 Subject: [PATCH] Make HTTP Response header values mutable The HTTP response values _are_ mutable, but because we're using std.http.Header the type is a `[]const u8`. This introduce a custom `Header` type where the value is `[]u8`. The goal is largely to allow more efficient value-comparison, by allowing calling code to lower-case in-place. I specifically have the Mime parser in mind: https://github.com/lightpanda-io/browser/blob/25dcae76487ab2aaa8ac8b468f465e55007a76ee/src/browser/mime.zig#L134 --- src/http/client.zig | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/http/client.zig b/src/http/client.zig index b6a4c4f4..c50f3a7f 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -38,8 +38,6 @@ const BUFFER_LEN = 32 * 1024; // The longest individual header line that we support const MAX_HEADER_LINE_LEN = 4096; -const HeaderList = std.ArrayListUnmanaged(std.http.Header); - // Thread-safe. Holds our root certificate, connection pool and state pool // Used to create Requests. pub const Client = struct { @@ -113,7 +111,7 @@ pub const Request = struct { arena: Allocator, // List of request headers - headers: HeaderList, + headers: std.ArrayListUnmanaged(std.http.Header), // Used to limit the # of redirects we'll follow _redirect_count: u16, @@ -1437,9 +1435,10 @@ const Reader = struct { pub const ResponseHeader = struct { status: u16 = 0, keepalive: bool = false, - headers: HeaderList = .{}, + headers: std.ArrayListUnmanaged(Header) = .{}, - // Stored header has already been lower-cased, we expect name to be lowercased + // Stored header has already been lower-cased + // `name` parameter should be passed in lower-cased pub fn get(self: *const ResponseHeader, name: []const u8) ?[]const u8 { for (self.headers.items) |h| { if (std.mem.eql(u8, name, h.name)) { @@ -1462,12 +1461,23 @@ pub const ResponseHeader = struct { } }; +// We don't want to use std.http.Header, because the value is `[]const u8`. +// We _could_ use it and @constCast, but this gives us more safety. +// The main reason we want to do this is that a caller could lower-case the +// value in-place. +// The value (and key) are both safe to mutate because they're cloned from +// the byte stream by our arena. +const Header = struct { + name: []const u8, + value: []u8, +}; + const HeaderIterator = struct { index: usize, name: []const u8, - headers: HeaderList, + headers: std.ArrayListUnmanaged(Header), - pub fn next(self: *HeaderIterator) ?[]const u8 { + pub fn next(self: *HeaderIterator) ?[]u8 { const name = self.name; const index = self.index; for (self.headers.items[index..], index..) |h, i| { @@ -2107,11 +2117,13 @@ test "HttpClient: HeaderIterator" { try testing.expectEqual(null, it.next()); } - try header.headers.append(testing.allocator, .{ .name = "h1", .value = "value1" }); - try header.headers.append(testing.allocator, .{ .name = "h2", .value = "value2" }); - try header.headers.append(testing.allocator, .{ .name = "h3", .value = "value3" }); - try header.headers.append(testing.allocator, .{ .name = "h1", .value = "value4" }); - try header.headers.append(testing.allocator, .{ .name = "h1", .value = "value5" }); + // @constCast is totally unsafe here, but it's just a test, and we know + // nothing is going to write to it, so it works. + try header.headers.append(testing.allocator, .{ .name = "h1", .value = @constCast("value1") }); + try header.headers.append(testing.allocator, .{ .name = "h2", .value = @constCast("value2") }); + try header.headers.append(testing.allocator, .{ .name = "h3", .value = @constCast("value3") }); + try header.headers.append(testing.allocator, .{ .name = "h1", .value = @constCast("value4") }); + try header.headers.append(testing.allocator, .{ .name = "h1", .value = @constCast("value5") }); { var it = header.iterate("nope"); @@ -2148,7 +2160,7 @@ const TestResponse = struct { keepalive: ?bool, arena: std.heap.ArenaAllocator, body: std.ArrayListUnmanaged(u8), - headers: std.ArrayListUnmanaged(std.http.Header), + headers: std.ArrayListUnmanaged(Header), fn init() TestResponse { return .{