| author | |
| committer | |
| log | 653d4158cdcb20be82ff525e122277064e6acb92 |
| tree | 2f7019c7d02e4cc12dc9cbd25c0331147fb673d3 |
| parent | 5b34a1b718a839b8f1cee5655a1587100e8f60f2 |
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"); |
| 3 | pub const protocol = @import("http/protocol.zig"); | 3 | pub const protocol = @import("http/protocol.zig"); |
| 4 | pub const HeadParser = @import("http/HeadParser.zig"); | 4 | pub const HeadParser = @import("http/HeadParser.zig"); |
| 5 | pub const ChunkParser = @import("http/ChunkParser.zig"); | 5 | pub const ChunkParser = @import("http/ChunkParser.zig"); |
| 6 | pub const HeaderIterator = @import("http/HeaderIterator.zig"); | ||
| 6 | 7 | ||
| 7 | pub const Version = enum { | 8 | pub 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 | } |
| 570 | 570 | ||
| 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 | }; |
| 575 | 575 |
lib/std/http/HeaderIterator.zig created+62| ... | @@ -0,0 +1,62 @@ | ||
| 1 | bytes: []const u8, | ||
| 2 | index: usize, | ||
| 3 | is_trailer: bool, | ||
| 4 | |||
| 5 | pub 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 | |||
| 13 | pub 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 | |||
| 37 | test 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 | |||
| 61 | const HeaderIterator = @This(); | ||
| 62 | const 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 | }; |
| 275 | 275 | ||
| 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 | }; |
| 252 | 252 | ||
| 253 | pub 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 | |||
| 315 | inline fn int16(array: *const [2]u8) u16 { | 253 | inline 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 | } |
| 292 | 292 | ||
| 293 | test "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 | |||
| 293 | test "general client/server API coverage" { | 345 | test "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. |