| 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 | 3 | pub const protocol = @import("http/protocol.zig"); |
| 4 | 4 | pub const HeadParser = @import("http/HeadParser.zig"); |
| 5 | 5 | pub const ChunkParser = @import("http/ChunkParser.zig"); |
| 6 | pub const HeaderIterator = @import("http/HeaderIterator.zig"); | |
| 6 | 7 | |
| 7 | 8 | pub const Version = enum { |
| 8 | 9 | @"HTTP/1.0", |
lib/std/http/Client.zig+2-2| ... | ... | @@ -568,8 +568,8 @@ pub const Response = struct { |
| 568 | 568 | try expectEqual(@as(u10, 999), parseInt3("999")); |
| 569 | 569 | } |
| 570 | 570 | |
| 571 | pub fn iterateHeaders(r: Response) proto.HeaderIterator { | |
| 572 | return proto.HeaderIterator.init(r.parser.get()); | |
| 571 | pub fn iterateHeaders(r: Response) http.HeaderIterator { | |
| 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 | 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 | 280 | pub const RespondOptions = struct { |
| 277 | 281 | version: http.Version = .@"HTTP/1.1", |
| 278 | 282 | status: http.Status = .ok, |
lib/std/http/protocol.zig-62| ... | ... | @@ -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 | 253 | inline fn int16(array: *const [2]u8) u16 { |
| 316 | 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 | 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 | 345 | test "general client/server API coverage" { |
| 294 | 346 | if (builtin.os.tag == .windows) { |
| 295 | 347 | // This test was never passing on Windows. |