authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-02-05 02:00:30 -08:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-02-05 02:00:30 -08:00
log42ac83cb327249a36550eae6e204da69e7164cd6
tree9f18bf6153093b85c653e940e5640850b2f9208e
parentd646e7d74311bb3ac477d7ede3ccc3d645f7849d

add HeadersMap


3 files changed, 46 insertions(+), 12 deletions(-)

http.zig+42-9
...@@ -3,6 +3,7 @@ const builtin = @import("builtin");...@@ -3,6 +3,7 @@ const builtin = @import("builtin");
3const net = @import("net");3const net = @import("net");
4const url = @import("url");4const url = @import("url");
5const nio = @import("nio");5const nio = @import("nio");
6const extras = @import("extras");
67
7pub const Method = enum {8pub const Method = enum {
8 GET,9 GET,
...@@ -214,7 +215,7 @@ pub fn open(allocator: std.mem.Allocator, method: Method, input: []const u8) !Cl...@@ -214,7 +215,7 @@ pub fn open(allocator: std.mem.Allocator, method: Method, input: []const u8) !Cl
214 .writer = bufw,215 .writer = bufw,
215 .reader = .init(conn),216 .reader = .init(conn),
216 .status = @enumFromInt(0),217 .status = @enumFromInt(0),
217 .headers_raw = "",218 .headers = .init(allocator),
218 };219 };
219}220}
220221
...@@ -224,11 +225,11 @@ pub const ClientRequest = struct {...@@ -224,11 +225,11 @@ pub const ClientRequest = struct {
224 writer: nio.BufferedWriter(4096, net.Stream),225 writer: nio.BufferedWriter(4096, net.Stream),
225 reader: nio.BufferedReader(4096, net.Stream),226 reader: nio.BufferedReader(4096, net.Stream),
226 status: Status,227 status: Status,
227 headers_raw: []const u8,228 headers: HeadersMap,
228229
229 pub fn close(req: *const ClientRequest, allocator: std.mem.Allocator) void {230 pub fn close(req: *ClientRequest) void {
230 req.stream.close();231 req.stream.close();
231 allocator.free(req.headers_raw);232 req.headers.deinit();
232 }233 }
233234
234 pub fn writeHeader(req: *ClientRequest, name: []const u8, value: []const u8) !void {235 pub fn writeHeader(req: *ClientRequest, name: []const u8, value: []const u8) !void {
...@@ -258,13 +259,19 @@ pub const ClientRequest = struct {...@@ -258,13 +259,19 @@ pub const ClientRequest = struct {
258 _ = try req.readUntilDelimitersBuf(&phrase_buf, "\r\n");259 _ = try req.readUntilDelimitersBuf(&phrase_buf, "\r\n");
259 req.status = status;260 req.status = status;
260261
261 var headers_list = std.ArrayList(u8).init(req.allocator);262 var headers_list = req.headers.data.list.toManaged(req.allocator);
262 errdefer headers_list.deinit();263 defer req.headers.data.list = headers_list.moveToUnmanaged();
263 while (true) {264 while (true) {
264 const header_len = try req.readUntilDelimitersArrayList(&headers_list, "\r\n", 1024);265 const header_line = try req.readUntilDelimitersArrayList(&headers_list, "\r\n", 1024);
265 if (header_len == 0) break;266 if (header_line.len == 0) break;
267 const colon_pos = std.mem.indexOfScalar(u8, header_line, ':') orelse return error.Bad;
268 const name = header_line[0..colon_pos];
269 if (!extras.matchesAll(u8, name, std.ascii.isAscii)) return error.Bad;
270 for (name) |*c| c.* = std.ascii.toLower(c.*);
271 if (header_line.len == colon_pos or header_line[colon_pos + 1] != ' ') return error.Bad;
272 const value = header_line[colon_pos + 2 ..];
273 try req.headers.data.lengths.appendSlice(req.allocator, &.{ name.len, 2, value.len, 2 });
266 }274 }
267 req.headers_raw = try headers_list.toOwnedSlice();
268 }275 }
269276
270 pub const ReadError = net.Stream.ReadError;277 pub const ReadError = net.Stream.ReadError;
...@@ -285,3 +292,29 @@ pub const ClientRequest = struct {...@@ -285,3 +292,29 @@ pub const ClientRequest = struct {
285 };292 };
286 }293 }
287};294};
295
296pub const HeadersMap = struct {
297 data: extras.ManyArrayList(u8),
298
299 pub fn init(allocator: std.mem.Allocator) HeadersMap {
300 return .{
301 .data = .init(allocator),
302 };
303 }
304
305 pub fn deinit(map: *HeadersMap) void {
306 map.data.deinit();
307 }
308
309 pub fn count(map: *const HeadersMap) usize {
310 return map.data.lengths.items.len / 4;
311 }
312
313 pub fn name(map: *const HeadersMap, idx: usize) []const u8 {
314 return map.data.items(idx * 4 + 0);
315 }
316
317 pub fn value(map: *const HeadersMap, idx: usize) []const u8 {
318 return map.data.items(idx * 4 + 2);
319 }
320};
test.zig+3-3
...@@ -8,7 +8,7 @@ const Scheme = enum { http };...@@ -8,7 +8,7 @@ const Scheme = enum { http };
8test {8test {
9 const allocator = std.testing.allocator;9 const allocator = std.testing.allocator;
10 var req = try http.open(allocator, .GET, "http://he.net/");10 var req = try http.open(allocator, .GET, "http://he.net/");
11 defer req.close(allocator);11 defer req.close();
12 try req.writeUA();12 try req.writeUA();
13 try req.send();13 try req.send();
14}14}
...@@ -26,7 +26,7 @@ fn httpbinMethod(comptime scheme: Scheme, comptime method: http.Method) !void {...@@ -26,7 +26,7 @@ fn httpbinMethod(comptime scheme: Scheme, comptime method: http.Method) !void {
26 const allocator = std.testing.allocator;26 const allocator = std.testing.allocator;
27 const url = @tagName(scheme) ++ "://httpbin.org/" ++ comptime extras.asciiLowerComptime(@tagName(method));27 const url = @tagName(scheme) ++ "://httpbin.org/" ++ comptime extras.asciiLowerComptime(@tagName(method));
28 var req = try http.open(allocator, method, url);28 var req = try http.open(allocator, method, url);
29 defer req.close(allocator);29 defer req.close();
30 try req.writeUA();30 try req.writeUA();
31 try req.send();31 try req.send();
32 try expect(req.status).toEqual(.ok);32 try expect(req.status).toEqual(.ok);
...@@ -40,7 +40,7 @@ fn httpbinUserAgent(comptime scheme: Scheme) !void {...@@ -40,7 +40,7 @@ fn httpbinUserAgent(comptime scheme: Scheme) !void {
40 const allocator = std.testing.allocator;40 const allocator = std.testing.allocator;
41 const url = @tagName(scheme) ++ "://httpbin.org/user-agent";41 const url = @tagName(scheme) ++ "://httpbin.org/user-agent";
42 var req = try http.open(allocator, .GET, url);42 var req = try http.open(allocator, .GET, url);
43 defer req.close(allocator);43 defer req.close();
44 try req.writeUA();44 try req.writeUA();
45 try req.send();45 try req.send();
46 try expect(req.status).toEqual(.ok);46 try expect(req.status).toEqual(.ok);
zigmod.yml+1
...@@ -7,6 +7,7 @@ dependencies:...@@ -7,6 +7,7 @@ dependencies:
7 - src: git https://github.com/nektro/zig-net7 - src: git https://github.com/nektro/zig-net
8 - src: git https://github.com/nektro/zig-whatwg-url8 - src: git https://github.com/nektro/zig-whatwg-url
9 - src: git https://github.com/nektro/zig-nio9 - src: git https://github.com/nektro/zig-nio
10 - src: git https://github.com/nektro/zig-extras
10root_dependencies:11root_dependencies:
11 - src: git https://github.com/nektro/zig-extras12 - src: git https://github.com/nektro/zig-extras
12 - src: git https://github.com/nektro/zig-json13 - src: git https://github.com/nektro/zig-json