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;
}
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;
}
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.
Oldest comments (0)