Skip to content

Instantly share code, notes, and snippets.

@Zorgatone
Last active December 30, 2025 08:10
Show Gist options
  • Select an option

  • Save Zorgatone/968ce86711aecea984a2c4a9771eed5f to your computer and use it in GitHub Desktop.

Select an option

Save Zorgatone/968ce86711aecea984a2c4a9771eed5f to your computer and use it in GitHub Desktop.
Zig 0.15.1 http-client GET example
const builtin = @import("builtin");
const std = @import("std");
// This version won't read/print headers, just the response
pub fn main() !void {
var writer_buffer: [8 * 1024]u8 = undefined;
var redirect_buffer: [8 * 1024]u8 = undefined;
var writer = std.fs.File.stdout().writer(&writer_buffer);
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer switch (builtin.mode) {
.Debug => std.debug.assert(debug_allocator.deinit() == .ok),
.ReleaseFast, .ReleaseSmall, .ReleaseSafe => {
// Nothing
},
};
const allocator = switch (builtin.mode) {
.Debug => debug_allocator.allocator(),
.ReleaseFast, .ReleaseSmall, .ReleaseSafe => std.heap.smp_allocator,
};
const uri = try std.Uri.parse("https://postman-echo.com/get");
var client: std.http.Client = .{ .allocator = allocator };
defer client.deinit();
const result = try client.fetch(.{
.location = .{ .uri = uri },
.method = .GET,
.redirect_buffer = &redirect_buffer,
.response_writer = &writer.interface,
});
if (builtin.mode == .Debug) {
std.debug.assert(result.status == .ok);
}
try writer.interface.flush();
}
const builtin = @import("builtin");
const std = @import("std");
// Manual http request printing also the response headers (won't work if chunked or compressed)
pub fn main() !void {
var writer_buffer: [8 * 1024]u8 = undefined;
var redirect_buffer: [8 * 1024]u8 = undefined;
var transfer_buffer: [8 * 1024]u8 = undefined;
var reader_buffer: [8 * 1024]u8 = undefined;
var writer = std.fs.File.stdout().writer(&writer_buffer);
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer switch (builtin.mode) {
.Debug => std.debug.assert(debug_allocator.deinit() == .ok),
.ReleaseFast, .ReleaseSmall, .ReleaseSafe => {
// Nothing
},
};
const allocator = switch (builtin.mode) {
.Debug => debug_allocator.allocator(),
.ReleaseFast, .ReleaseSmall, .ReleaseSafe => std.heap.smp_allocator,
};
const uri = try std.Uri.parse("https://postman-echo.com/get");
var client: std.http.Client = .{ .allocator = allocator };
defer client.deinit();
var request = try client.request(.GET, uri, .{});
defer request.deinit();
try request.sendBodiless();
const response = try request.receiveHead(&redirect_buffer);
_ = try writer.interface.write(response.head.bytes);
const content_length = response.head.content_length;
const reader = request.reader.bodyReader(&transfer_buffer, .none, content_length);
var done = false;
var bytes_read: usize = 0;
while (!done) {
const size = try reader.readSliceShort(&reader_buffer);
if (size > 0) {
bytes_read += size;
_ = try writer.interface.write(reader_buffer[0..size]);
}
if (content_length) |c_len| {
if (bytes_read >= c_len) {
done = true;
}
}
if (size < reader_buffer.len) {
done = true;
}
}
try writer.interface.flush();
}
@michaelchiche
Copy link

Hi, this code does not seem to work anymore using latest zig version: 0.16.0-dev.1657+985a3565c

@Zorgatone
Copy link
Author

Hi @michaelchiche zig 0.16 is not stable yet, only on the master branch in development. Async IO and the std library are being worked on, so the example will have to change once the API for 0.16 are final, and I'm not maintaining a working example for every time the master branch changes (can be frequently).

I will update the code once 0.16.x is released

@Zorgatone
Copy link
Author

The variable names made me think you were accidentally using the reader's internal fifo buffer as your temporary bounce buffer, but no you gave the reader transfer_buffer instead of reader_buffer.

Maybe this change would make more sense?

-   const reader = request.reader.bodyReader(&transfer_buffer, .none, content_length);
+   const reader = request.reader.bodyReader(&reader_buffer, .none, content_length);

    var done = false;
    var bytes_read: usize = 0;

    while (!done) {
-       const size = try reader.readSliceShort(&reader_buffer);
+       const size = try reader.readSliceShort(&transfer_buffer);

        if (size > 0) {
            bytes_read += size;
-           _ = try writer.interface.write(reader_buffer[0..size]);
+           _ = try writer.interface.write(transfer_buffer[0..size]);

// ...

-       if (size < reader_buffer.len) {
+       if (size < transfer_buffer.len) {
            done = true;
        }

Yeah the names are confusing indeed, I used transfer since it's in the "middle", used for piping the data in the buffer I finally use for reading the response. But it would make more sense to change the names, yes. It doesn't change the example code much anyway

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment