authorgravatar for nwtgck@nwtgck.orgRyo Ota <nwtgck@nwtgck.org> 2023-04-24 02:07:52+09:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-04-24 15:36:35-07:00
logbba90b8863102d75578d521cebdf4c2443451f59
treebdfcd4e5bec28517739df2936cce3d18ce070384
parent1d1255b43315912f63281f8b35cb1bed95960bf2

fix HTTP server to handle a chunked transfer coding request


2 files changed, 77 insertions(+), 2 deletions(-)

lib/std/http/Server.zig+71
......@@ -661,3 +661,74 @@ pub fn accept(server: *Server, options: HeaderStrategy) AcceptError!*Response {
661661
662662 return res;
663663}
664
665test "HTTP server handles a chunked transfer coding request" {
666 const builtin = @import("builtin");
667
668 // This test requires spawning threads.
669 if (builtin.single_threaded) {
670 return error.SkipZigTest;
671 }
672
673 const native_endian = comptime builtin.cpu.arch.endian();
674 if (builtin.zig_backend == .stage2_llvm and native_endian == .Big) {
675 // https://github.com/ziglang/zig/issues/13782
676 return error.SkipZigTest;
677 }
678
679 if (builtin.os.tag == .wasi) return error.SkipZigTest;
680
681 const allocator = std.testing.allocator;
682 const expect = std.testing.expect;
683
684 const max_header_size = 8192;
685 var server = std.http.Server.init(allocator, .{ .reuse_address = true });
686 defer server.deinit();
687
688 const address = try std.net.Address.parseIp("127.0.0.1", 0);
689 try server.listen(address);
690 const server_port = server.socket.listen_address.in.getPort();
691
692 const server_thread = try std.Thread.spawn(.{}, (struct {
693 fn apply(s: *std.http.Server) !void {
694 const res = try s.accept(.{ .dynamic = max_header_size });
695 defer res.deinit();
696 defer res.reset();
697 try res.wait();
698
699 try expect(res.request.transfer_encoding.? == .chunked);
700
701 const server_body: []const u8 = "message from server!\n";
702 res.transfer_encoding = .{ .content_length = server_body.len };
703 try res.headers.append("content-type", "text/plain");
704 try res.headers.append("connection", "close");
705 try res.do();
706
707 var buf: [128]u8 = undefined;
708 const n = try res.readAll(&buf);
709 try expect(std.mem.eql(u8, buf[0..n], "ABCD"));
710 _ = try res.writer().writeAll(server_body);
711 try res.finish();
712 }
713 }).apply, .{&server});
714
715 const request_bytes =
716 "POST / HTTP/1.1\r\n" ++
717 "Content-Type: text/plain\r\n" ++
718 "Transfer-Encoding: chunked\r\n" ++
719 "\r\n" ++
720 "1\r\n" ++
721 "A\r\n" ++
722 "1\r\n" ++
723 "B\r\n" ++
724 "2\r\n" ++
725 "CD\r\n" ++
726 "0\r\n" ++
727 "\r\n";
728
729 const stream = try std.net.tcpConnectToHost(allocator, "127.0.0.1", server_port);
730 defer stream.close();
731 _ = try stream.writeAll(request_bytes[0..]);
732
733 server_thread.join();
734}
lib/std/http/protocol.zig+6-2
......@@ -556,8 +556,12 @@ pub const HeadersParser = struct {
556556 switch (r.state) {
557557 .invalid => return error.HttpChunkInvalid,
558558 .chunk_data => if (r.next_chunk_length == 0) {
559 // The trailer section is formatted identically to the header section.
560 r.state = .seen_rn;
559 if (std.mem.eql(u8, bconn.peek(), "\r\n")) {
560 r.state = .finished;
561 } else {
562 // The trailer section is formatted identically to the header section.
563 r.state = .seen_rn;
564 }
561565 r.done = true;
562566
563567 return out_index;