Zig NEWS

Andrew Brent Gossage
Andrew Brent Gossage

Posted on

Easy web requests in Zig with Client.fetch

I wanted to do a really short to write-up after struggling to find examples of people using zig for making calls to web based services. So here is a quick example running that runs both in zig 0.13 and 0.14-dev:

The Easy Way

const std = @import("std");
const writer = std.io.getStdOut().writer();

pub fn main() !void {
    // Where we are going we need dynamic allocation
    const alloc = std.heap.page_allocator;
    var arena = std.heap.ArenaAllocator.init(alloc);
    const allocator = arena.allocator();

    //I like simple lifetimes so I am just clearing all allocations together
    defer arena.deinit();

    //The client is what handles making and receiving the request for us
    var client = std.http.Client{
        .allocator = allocator,
    };

    //We can set up any headers we want
    const headers = &[_]std.http.Header{
        .{ .name = "X-Custom-Header", .value = "application" },
        // if we wanted to do a post request with JSON payload we would add
        // .{ .name = "Content-Type", .value = "application/json" },
    };

    // I moved this part into a seperate function just to keep it clean
    const response = try get("https://jsonplaceholder.typicode.com/posts/1", headers, &client, alloc);

    // .ignore_unknown_fields will just omit any fields the server returns that are not in our type
    // otherwise an unknown field causes an error
    const result = try std.json.parseFromSlice(Result, allocator, response.items, .{ .ignore_unknown_fields = true });

    try writer.print("title: {s}\n", .{result.value.title});
}

//This is what we are going to parse the response into
const Result = struct {
    userId: i32,
    id: i32,
    title: []u8,
    body: []u8,
};

fn get(
    url: []const u8,
    headers: []const std.http.Header,
    client: *std.http.Client,
    allocator: std.mem.Allocator,
) !std.ArrayList(u8) {
    try writer.print("\nURL: {s} GET\n", .{url});

    var response_body = std.ArrayList(u8).init(allocator);

    try writer.print("Sending request...\n", .{});
    const response = try client.fetch(.{
        .method = .GET,
        .location = .{ .url = url },
        .extra_headers = headers, //put these here instead of .headers
        .response_storage = .{ .dynamic = &response_body }, // this allows us to get a response of unknown size
        // if we were doing a post request we would include the payload here
        //.payload = "<some string>"
    });

    try writer.print("Response Status: {d}\n Response Body:{s}\n", .{ response.status, response_body.items });

    // Return the response body to the caller
    return response_body;
}



Enter fullscreen mode Exit fullscreen mode

It turns out to be incredibly simple and easy to work with web requests in Zig.

The Slightly Harder Way

There are other lower level ways of doing it if there are implementation level things you care about. For instance here is a function I wrote that times just one part of the process

fn getLowLevel(
    url: []const u8,
    headers: []const std.http.Header,
    client: *std.http.Client,
    allocator: std.mem.Allocator,
) ![]u8 {
    var buf: [4096]u8 = undefined;
    var readBuff: [4096]u8 = undefined;
    const uri = try std.Uri.parse(url);
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = &buf,
        .extra_headers = headers,
    });
    defer req.deinit();
    try req.send();
    try req.finish();

    // timing just the response from the server
    const start = std.time.milliTimestamp();
    try req.wait();
    const stop = std.time.milliTimestamp();

    const size = try req.read(&readBuff);

    try writer.print("\n{s} {d} status={d}\n{s}\n", .{ url, stop - start, req.response.status, readBuff[0..size] });
    const out = try allocator.alloc(u8, size);
    std.mem.copyForwards(u8, out, readBuff[0..size]);
    return out;
}
Enter fullscreen mode Exit fullscreen mode

In most cases though something like this is overkill and I would just use fetch.

The Hacker Way

If you really really wanted to you could also write your own client implementation but I will leave that as an exercise for you if you are interested.

Latest comments (0)