Hi friends, I am relatively new to the Zig programming language. I am trying to write a program that uses an external API that I want to call in my program. I have already tried different ways to do this, but I am really confused with the httpclient, fetchoptions, requestoptions. Can someone give me a brief introduction to the http std lib and how to retrieve an API?
Thank you very much
const std = @import("std");
pub fn main() !void {
// Create a general purpose allocator
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
// Create a HTTP client
var client = std.http.Client{ .allocator = gpa.allocator() };
defer client.deinit();
// Allocate a buffer for server headers
var buf: [4096]u8 = undefined;
// Start the HTTP request
const uri = try std.Uri.parse("https://www.google.com?q=zig");
var req = try client.open(.GET, uri, .{ .server_header_buffer = &buf });
defer req.deinit();
// Send the HTTP request headers
try req.send();
// Finish the body of a request
try req.finish();
// Waits for a response from the server and parses any headers that are sent
try req.wait();
std.debug.print("status={d}\n", .{req.response.status});
}
hi @dimdin, thank you! I really love zig but the lack of documentation makes it hard for noobies like me to learn the language. But I think I slowly get used to it and that’s why we have this community!
I thought so too at first. Now I actually think this is a good thing - it forces you to read and dig through source code. And that is a valuable skill to develop IMHO. But, I also think that the whole networking stuff in Zig has relatively less documentation compared to say std.fs. Might be that’s because it came in more recently into std.
By the way, the Zig CC has two http examples in the Zig cookbook. There’s also a section on TCP.
To complete the answer given by @dimdin here is code which also reads and displays the HTTP response headers and body:
// Zig example of the use of the standard library HTTP client
// <https://ziglang.org/documentation/0.12.0/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 = "https://api.coindesk.com/v1/bpi/currentprice.json";
// Some values
const headers_max_size = 1024;
const body_max_size = 65536;
pub fn main() !void {
const url = try std.Uri.parse(ref_url);
// 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();
var hbuffer: [headers_max_size]u8 = undefined;
const options = std.http.Client.RequestOptions{ .server_header_buffer = &hbuffer };
// Call the API endpoint
var request = try client.open(std.http.Method.GET, url, options);
defer request.deinit();
_ = try request.send();
_ = try request.finish();
_ = try request.wait();
// Check the HTTP return code
if (request.response.status != std.http.Status.ok) {
return error.WrongStatusResponse;
}
// Read the body
var bbuffer: [body_max_size]u8 = undefined;
const hlength = request.response.parser.header_bytes_len;
_ = try request.readAll(&bbuffer);
const blength = request.response.content_length orelse return error.NoBodyLength; // We trust
// the Content-Length returned by the server…
// Display the result
std.debug.print("{d} header bytes returned:\n{s}\n", .{ hlength, hbuffer[0..hlength] });
// The response is in JSON so we should here add JSON parsing code.
std.debug.print("{d} body bytes returned:\n{s}\n", .{ blength, bbuffer[0..blength] });
}
I tried the linked examples from the linked Zig cookbook and they didn’t work for me. I got no response body when executing the request… The header values returned indicate a non-zero content length, but then the code to read the body doesn’t read any bytes. I also tried modifying the cookbook example with slightly different code to read the body:
The API may have changed some since this was originally asked.
Here is a complete working example with comments using the latest Zig version and latest version of the standard library.
This uses a JSON API, but the fundamentals are the same regardless.
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
pub fn main() !void {
const allocator, const is_debug = gpa: {
break :gpa switch (builtin.mode) {
.Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true },
.ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false },
};
};
defer if (is_debug) {
_ = debug_allocator.deinit();
};
// Create the client
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
// Initialize an array list that we will use for storage of the response body
var body = std.ArrayList(u8).init(allocator);
defer body.deinit();
// Parse a URI. Conversely the "location" field below will also
// accept a URL string, but using URI here for clarity.
const uri = try std.Uri.parse("https://api.quotable.kurokeita.dev/api/quotes/random");
// Make the request
const response = try client.fetch(.{
.method = .GET,
.location = .{ .uri = uri },
.response_storage = .{ .dynamic = &body },
.headers = .{
.accept_encoding = .{ .override = "application/json" },
},
});
// Do whatever you need to in case of HTTP error.
if (response.status != .ok) {
@panic("Handle errors");
}
// This is the struct used to describe the JSON response
// technically there could be error responses and error messages in this struct,
// but that is beyond the cope of this example
const QuoteResponse = struct {
quote: struct {
author: struct {
bio: []u8,
description: []u8,
id: []u8,
link: []u8,
name: []u8,
slug: []u8,
},
content: []u8,
id: []u8,
tags: []struct {
id: []u8,
name: []u8,
},
},
};
// Parse the reply into a struct
const parsed = try std.json.parseFromSlice(QuoteResponse, allocator, body.items, .{
.allocate = .alloc_always,
.parse_numbers = true,
.ignore_unknown_fields = true,
});
defer parsed.deinit();
// Print the output
const quote = parsed.value.quote;
var stdout = std.io.getStdOut();
var writer = stdout.writer();
try writer.print("\"{s}\"\n\n- {s}", .{ quote.content, quote.author.name });
}
const std = @import("std");
const builtin = @import("builtin");
The API definition can be found here if you need it.