Skip to content

Commit bba90b8

Browse files
nwtgckandrewrk
authored andcommitted
fix HTTP server to handle a chunked transfer coding request
1 parent 1d1255b commit bba90b8

File tree

2 files changed

+77
-2
lines changed

2 files changed

+77
-2
lines changed

lib/std/http/Server.zig

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,3 +661,74 @@ pub fn accept(server: *Server, options: HeaderStrategy) AcceptError!*Response {
661661

662662
return res;
663663
}
664+
665+
test "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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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;

0 commit comments

Comments
 (0)