authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 02:58:02-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 02:58:02-07:00
log653d4158cdcb20be82ff525e122277064e6acb92
tree2f7019c7d02e4cc12dc9cbd25c0331147fb673d3
parent5b34a1b718a839b8f1cee5655a1587100e8f60f2

std.http.Server: expose arbitrary HTTP headers

Ultimate flexibility, just be sure to destroy the correct amount of information when looking at them.

6 files changed, 121 insertions(+), 64 deletions(-)

lib/std/http.zig+1
...@@ -3,6 +3,7 @@ pub const Server = @import("http/Server.zig");...@@ -3,6 +3,7 @@ pub const Server = @import("http/Server.zig");
3pub const protocol = @import("http/protocol.zig");3pub const protocol = @import("http/protocol.zig");
4pub const HeadParser = @import("http/HeadParser.zig");4pub const HeadParser = @import("http/HeadParser.zig");
5pub const ChunkParser = @import("http/ChunkParser.zig");5pub const ChunkParser = @import("http/ChunkParser.zig");
6pub const HeaderIterator = @import("http/HeaderIterator.zig");
67
7pub const Version = enum {8pub const Version = enum {
8 @"HTTP/1.0",9 @"HTTP/1.0",
lib/std/http/Client.zig+2-2
...@@ -568,8 +568,8 @@ pub const Response = struct {...@@ -568,8 +568,8 @@ pub const Response = struct {
568 try expectEqual(@as(u10, 999), parseInt3("999"));568 try expectEqual(@as(u10, 999), parseInt3("999"));
569 }569 }
570570
571 pub fn iterateHeaders(r: Response) proto.HeaderIterator {571 pub fn iterateHeaders(r: Response) http.HeaderIterator {
572 return proto.HeaderIterator.init(r.parser.get());572 return http.HeaderIterator.init(r.parser.get());
573 }573 }
574};574};
575575
lib/std/http/HeaderIterator.zig created+62
...@@ -0,0 +1,62 @@
1bytes: []const u8,
2index: usize,
3is_trailer: bool,
4
5pub fn init(bytes: []const u8) HeaderIterator {
6 return .{
7 .bytes = bytes,
8 .index = std.mem.indexOfPosLinear(u8, bytes, 0, "\r\n").? + 2,
9 .is_trailer = false,
10 };
11}
12
13pub fn next(it: *HeaderIterator) ?std.http.Header {
14 const end = std.mem.indexOfPosLinear(u8, it.bytes, it.index, "\r\n").?;
15 var kv_it = std.mem.splitSequence(u8, it.bytes[it.index..end], ": ");
16 const name = kv_it.next().?;
17 const value = kv_it.rest();
18 if (value.len == 0) {
19 if (it.is_trailer) return null;
20 const next_end = std.mem.indexOfPosLinear(u8, it.bytes, end + 2, "\r\n") orelse
21 return null;
22 it.is_trailer = true;
23 it.index = next_end + 2;
24 kv_it = std.mem.splitSequence(u8, it.bytes[end + 2 .. next_end], ": ");
25 return .{
26 .name = kv_it.next().?,
27 .value = kv_it.rest(),
28 };
29 }
30 it.index = end + 2;
31 return .{
32 .name = name,
33 .value = value,
34 };
35}
36
37test next {
38 var it = HeaderIterator.init("200 OK\r\na: b\r\nc: d\r\n\r\ne: f\r\n\r\n");
39 try std.testing.expect(!it.is_trailer);
40 {
41 const header = it.next().?;
42 try std.testing.expect(!it.is_trailer);
43 try std.testing.expectEqualStrings("a", header.name);
44 try std.testing.expectEqualStrings("b", header.value);
45 }
46 {
47 const header = it.next().?;
48 try std.testing.expect(!it.is_trailer);
49 try std.testing.expectEqualStrings("c", header.name);
50 try std.testing.expectEqualStrings("d", header.value);
51 }
52 {
53 const header = it.next().?;
54 try std.testing.expect(it.is_trailer);
55 try std.testing.expectEqualStrings("e", header.name);
56 try std.testing.expectEqualStrings("f", header.value);
57 }
58 try std.testing.expectEqual(null, it.next());
59}
60
61const HeaderIterator = @This();
62const std = @import("../std.zig");
lib/std/http/Server.zig+4
...@@ -273,6 +273,10 @@ pub const Request = struct {...@@ -273,6 +273,10 @@ pub const Request = struct {
273 }273 }
274 };274 };
275275
276 pub fn iterateHeaders(r: *Request) http.HeaderIterator {
277 return http.HeaderIterator.init(r.server.read_buffer[0..r.head_end]);
278 }
279
276 pub const RespondOptions = struct {280 pub const RespondOptions = struct {
277 version: http.Version = .@"HTTP/1.1",281 version: http.Version = .@"HTTP/1.1",
278 status: http.Status = .ok,282 status: http.Status = .ok,
lib/std/http/protocol.zig-62
...@@ -250,68 +250,6 @@ pub const HeadersParser = struct {...@@ -250,68 +250,6 @@ pub const HeadersParser = struct {
250 }250 }
251};251};
252252
253pub const HeaderIterator = struct {
254 bytes: []const u8,
255 index: usize,
256 is_trailer: bool,
257
258 pub fn init(bytes: []const u8) HeaderIterator {
259 return .{
260 .bytes = bytes,
261 .index = std.mem.indexOfPosLinear(u8, bytes, 0, "\r\n").? + 2,
262 .is_trailer = false,
263 };
264 }
265
266 pub fn next(it: *HeaderIterator) ?std.http.Header {
267 const end = std.mem.indexOfPosLinear(u8, it.bytes, it.index, "\r\n").?;
268 var kv_it = std.mem.splitSequence(u8, it.bytes[it.index..end], ": ");
269 const name = kv_it.next().?;
270 const value = kv_it.rest();
271 if (value.len == 0) {
272 if (it.is_trailer) return null;
273 const next_end = std.mem.indexOfPosLinear(u8, it.bytes, end + 2, "\r\n") orelse
274 return null;
275 it.is_trailer = true;
276 it.index = next_end + 2;
277 kv_it = std.mem.splitSequence(u8, it.bytes[end + 2 .. next_end], ": ");
278 return .{
279 .name = kv_it.next().?,
280 .value = kv_it.rest(),
281 };
282 }
283 it.index = end + 2;
284 return .{
285 .name = name,
286 .value = value,
287 };
288 }
289
290 test next {
291 var it = HeaderIterator.init("200 OK\r\na: b\r\nc: d\r\n\r\ne: f\r\n\r\n");
292 try std.testing.expect(!it.is_trailer);
293 {
294 const header = it.next().?;
295 try std.testing.expect(!it.is_trailer);
296 try std.testing.expectEqualStrings("a", header.name);
297 try std.testing.expectEqualStrings("b", header.value);
298 }
299 {
300 const header = it.next().?;
301 try std.testing.expect(!it.is_trailer);
302 try std.testing.expectEqualStrings("c", header.name);
303 try std.testing.expectEqualStrings("d", header.value);
304 }
305 {
306 const header = it.next().?;
307 try std.testing.expect(it.is_trailer);
308 try std.testing.expectEqualStrings("e", header.name);
309 try std.testing.expectEqualStrings("f", header.value);
310 }
311 try std.testing.expectEqual(null, it.next());
312 }
313};
314
315inline fn int16(array: *const [2]u8) u16 {253inline fn int16(array: *const [2]u8) u16 {
316 return @as(u16, @bitCast(array.*));254 return @as(u16, @bitCast(array.*));
317}255}
lib/std/http/test.zig+52
...@@ -290,6 +290,58 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -290,6 +290,58 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
290 try expectEqualStrings(expected_response.items, response);290 try expectEqualStrings(expected_response.items, response);
291}291}
292292
293test "receiving arbitrary http headers from the client" {
294 const test_server = try createTestServer(struct {
295 fn run(net_server: *std.net.Server) anyerror!void {
296 var read_buffer: [666]u8 = undefined;
297 var remaining: usize = 1;
298 while (remaining != 0) : (remaining -= 1) {
299 const conn = try net_server.accept();
300 defer conn.stream.close();
301
302 var server = http.Server.init(conn, &read_buffer);
303 try expectEqual(.ready, server.state);
304 var request = try server.receiveHead();
305 try expectEqualStrings("/bar", request.head.target);
306 var it = request.iterateHeaders();
307 {
308 const header = it.next().?;
309 try expectEqualStrings("CoNneCtIoN", header.name);
310 try expectEqualStrings("close", header.value);
311 try expect(!it.is_trailer);
312 }
313 {
314 const header = it.next().?;
315 try expectEqualStrings("aoeu", header.name);
316 try expectEqualStrings("asdf", header.value);
317 try expect(!it.is_trailer);
318 }
319 try request.respond("", .{});
320 }
321 }
322 });
323 defer test_server.destroy();
324
325 const request_bytes = "GET /bar HTTP/1.1\r\n" ++
326 "CoNneCtIoN: close\r\n" ++
327 "aoeu: asdf\r\n" ++
328 "\r\n";
329 const gpa = std.testing.allocator;
330 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
331 defer stream.close();
332 try stream.writeAll(request_bytes);
333
334 const response = try stream.reader().readAllAlloc(gpa, 8192);
335 defer gpa.free(response);
336
337 var expected_response = std.ArrayList(u8).init(gpa);
338 defer expected_response.deinit();
339
340 try expected_response.appendSlice("HTTP/1.1 200 OK\r\n");
341 try expected_response.appendSlice("content-length: 0\r\n\r\n");
342 try expectEqualStrings(expected_response.items, response);
343}
344
293test "general client/server API coverage" {345test "general client/server API coverage" {
294 if (builtin.os.tag == .windows) {346 if (builtin.os.tag == .windows) {
295 // This test was never passing on Windows.347 // This test was never passing on Windows.