Skip to content

HTTP client hangs trying to read a response body of 204 No Content #25181

@Darkyenus

Description

@Darkyenus

Zig Version

0.16.0-dev.195+ac42eaaad

Steps to Reproduce and Observed Behavior

The HTTP Client will try to read/discard response body even if no body is expected to appear, which will hang forever/until the server closes the connection. Which can take a while with Connection: keep-alive.

const std = @import("std");
test {
    var client: std.http.Client = .{
        .allocator = std.testing.allocator,
    };
    defer client.deinit();

    for ([_]bool{ false, true }) |keep_alive| {// The problem does not happen when keep_alive is false
        for ([_][]const u8{ "http", "https" }) |protocol| {
            const host_url = "httpbun.org";// Arbitrary service to reproduce the problem
            const method: std.http.Method = .DELETE;// Zig expects DELETE response to have a body
            const path = "status/204";// No Content, causes the server to return no Content-Length header

            var response: std.Io.Writer.Allocating = .init(std.testing.allocator);
            defer response.deinit();

            var url_buffer: [512]u8 = undefined;
            const result = try client.fetch(.{
                .response_writer = &response.writer,
                .location = .{ .url = try std.fmt.bufPrint(&url_buffer, "{s}://{s}/{s}", .{ protocol, host_url, path }) },
                .method = method,
                .payload = null,
                .keep_alive = keep_alive,
            });

            try std.testing.expectEqual(.no_content, result.status);
            try std.testing.expectEqual(0, response.written().len);

            std.debug.print("Done {s} {}\n", .{protocol, keep_alive});
        }
    }
}

In this example, when keep_alive is false, everything will work as expected, because while Client will attempt to errorniously discard the body, the server will close the connection and no hang will occur.

But with keep_alive true, the connection will stay open, with no traffic. Because the response code is 204 No Content, the response will not even contain Content-Length: 0 header. The Client will still assume that the response body is everything left in the stream (despite response headers containing HTTP 1.1 and Connection: keep-alive). It will then try to read the body forever and hang here.

Expected Behavior

zig.http.Client (probably function bodyReader in particular) should respect HTTP 1.1 specification on when the response contains the body.

For response messages, whether or not a message-body is included with a message is dependent on both the request method and the response status code. All responses to the HEAD request method MUST NOT include a message-body, even though the presence of entity-header fields might lead one to believe they do. All 1xx (informational), 204 (no content), and 304 (not modified) responses MUST NOT include a message-body. All other responses do include a message-body, although it MAY be of zero length. (RFC-2616, section 4.3)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behaviorstandard libraryThis issue involves writing Zig code for the standard library.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions