I’m experimenting with HTTP (not yet HTTPS) clients using some options (Accept headers, methods other than GET or POST). I run the stable 0.15.2 version on a Linux machine.
A program with GET works fine (attached at the end, comments welcome but it works).
A program with PUT (attached at the end) cannot read the body of the reply:
% ./broken-http-put
error: EndOfStream
/usr/local/zig/zig-x86_64-linux-0.15.2/lib/std/Io/Reader.zig:1671:5: 0x10cf7af in endingRebase (std.zig)
return error.EndOfStream;
^
/usr/local/zig/zig-x86_64-linux-0.15.2/lib/std/Io/Reader.zig:1276:5: 0x115b8d9 in rebase (std.zig)
return r.vtable.rebase(r, capacity);
^
/usr/local/zig/zig-x86_64-linux-0.15.2/lib/std/Io/Reader.zig:1061:5: 0x1147cd9 in fillUnbuffered (std.zig)
try rebase(r, n);
^
/usr/local/zig/zig-x86_64-linux-0.15.2/lib/std/Io/Reader.zig:1049:5: 0x112fc0f in fill (std.zig)
return fillUnbuffered(r, n);
^
/usr/local/zig/zig-x86_64-linux-0.15.2/lib/std/Io/Reader.zig:454:5: 0x111defd in peek (std.zig)
try r.fill(n);
^
/home/stephane/tmp/broken-http-put.zig:45:18: 0x1165d6e in main (broken-http-put.zig)
const data = try body_reader.peek(blength);
```
I suspect a bug in the standard library but it may be simply that I did not understand something.
The program with the GET method:
```
// Zig example of the use of the standard library HTTP client
// <https://ziglang.org/documentation/0.15.2/std/#std.http.Client> We
// retrieve JSON data from a network API. We are not very paranoid,
// more checks should be created.
const std = @import("std");
// The API we use.
const ref_url = "http://httpbin.org/get"; // For testing
// Some values
const headers_max_size = 8192;
const body_max_size = 65536;
const url_max_size = 1024;
pub fn main() !void {
// We need an allocator to create a std.http.Client
var gpa_impl = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa_impl.deinit();
const gpa = gpa_impl.allocator();
var client = std.http.Client{ .allocator = gpa };
defer client.deinit();
const url = try std.Uri.parse(ref_url);
var body = std.Io.Writer.Allocating.init(gpa);
defer body.deinit();
const mimetype: std.http.Header = .{ .name = "Accept", .value = "application/rpp+json" };
const headers = [_]std.http.Header{mimetype};
const options = std.http.Client.RequestOptions{ .extra_headers = &headers };
// Call the API endpoint. We cannot use std.http.Client.fetch
// because it apparently does not allow to retrieve the HTTP
// headers.
var request = try client.request(std.http.Method.GET, url, options);
defer request.deinit();
_ = try request.sendBodiless();
var rbuffer: [url_max_size]u8 = undefined;
var response = try request.receiveHead(&rbuffer);
// Check the HTTP return code
if (response.head.status != std.http.Status.ok) {
std.debug.print("Status: {any}\n", .{response.head.status});
return error.WrongStatusResponse;
}
// Read the headers
var hlength: usize = 0;
var it = response.head.iterateHeaders();
var header_list = std.ArrayList([]u8).empty;
var h: []u8 = undefined;
var h2: []u8 = undefined;
while (it.next()) |header| {
hlength += header.name.len;
hlength += header.value.len;
hlength += 2;
h = try std.mem.concat(gpa, u8, &.{ header.name, ": " });
h2 = try std.mem.concat(gpa, u8, &.{ h, header.value });
try header_list.append(gpa, h2);
gpa.free(h);
}
// Display the headers
std.debug.print("{d} header bytes returned:\n", .{hlength});
for (header_list.items) |item| {
std.debug.print(" {s}\n", .{item});
}
std.debug.print("\n", .{});
// Clean
for (header_list.items) |item| {
gpa.free(item);
}
_ = header_list.deinit(gpa);
// Read the body. The response is in JSON so we should here add
// JSON parsing code.
var bbuffer: [body_max_size]u8 = undefined;
const blength = response.head.content_length orelse return error.NoBodyLength;
// We trust the Content-Length returned by the server…
const body_reader = response.reader(&bbuffer);
const data = try body_reader.peek(blength);
std.debug.print("{d} bytes of body:\n{s}\n", .{ blength, data });
}
```
And here is the program failing with method PUT:
```
// TODO: DOES NOT WORK, it's a minimal example, for bug reporting. It
// fails with EndOfStream while a "curl -d '*' -X PUT
// http://httpbin.org/put" works.
const std = @import("std");
const ref_url = "http://httpbin.org/put";
// Some values
const headers_max_size = 8192;
const body_max_size = 65536;
const url_max_size = 1024;
pub fn main() !void {
var gpa_impl = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa_impl.deinit();
const gpa = gpa_impl.allocator();
var client = std.http.Client{ .allocator = gpa };
defer client.deinit();
const url = try std.Uri.parse(ref_url);
var dummy: [1]u8 = undefined;
dummy[0] = 42;
var body = std.Io.Writer.Allocating.init(gpa);
defer body.deinit();
const options = std.http.Client.RequestOptions{};
var request = try client.request(std.http.Method.PUT, url, options);
defer request.deinit();
_ = try request.sendBodyComplete(&dummy);
var rbuffer: [url_max_size]u8 = undefined;
var response = try request.receiveHead(&rbuffer);
// Read the body. This is where it fails.
var bbuffer: [body_max_size]u8 = undefined;
const blength = response.head.content_length orelse return error.NoBodyLength;
// We trust the Content-Length returned by the server…
const body_reader = response.reader(&bbuffer);
const data = try body_reader.peek(blength);
std.debug.print("{d} bytes of body:\n{s}\n", .{blength, data});
}
```
It is not specific to the test server I use (`http://httpbin.org/`), I can reproduce it with other servers.