How to make a call #
In this post we’ll explore GET, POST, PUT and DELETE HTTP methods, focusing on the most common. Using this knowledge we’ll be able to make any HTTP call we need.
Helper functions #
This helper function isn’t entirely necessary but it makes the call site cleaner and the functionality more reusable, easier to work with. I recommend using it while learning and go back later to read what the httpRequest function is doing. I placed comments in the function to help explain what’s going on.
const std = @import("std");
pub const HttpResponse = struct {
status: std.http.Status,
body: []const u8,
allocator: std.mem.Allocator,
pub fn deinit(self: *HttpResponse) void {
self.allocator.free(self.body);
}
};
/// Makes an HTTP request with the specified parameters.
/// Caller owns the returned HttpResponse and must call deinit() on it.
fn httpRequest(
allocator: std.mem.Allocator,
uri: []const u8,
method: std.http.Method,
headers: ?[]const std.http.Header,
payload: ?[]const u8,
) !HttpResponse {
const parsed_uri = std.Uri.parse(uri) catch return error.InvalidUri;
var redirect_buffer: [8 * 1024]u8 = undefined; // we need a buffer to read response headers.
var body: std.Io.Writer.Allocating = .init(allocator);
defer body.deinit();
try body.ensureUnusedCapacity(64);
var client: std.http.Client = .{ .allocator = allocator };
defer client.deinit();
// Buffer for response body.
var response_body: std.ArrayList(u8) = .empty;
defer response_body.deinit(allocator);
// Make the call.
const result = try client.fetch(.{
.location = .{ .uri = parsed_uri },
.method = method,
.redirect_buffer = &redirect_buffer, // accumulate response header bytes to read.
.response_writer = &body.writer,
.extra_headers = headers orelse &[_]std.http.Header{},
.payload = payload,
});
return HttpResponse{
.status = result.status,
.body = try body.toOwnedSlice(),
.allocator = allocator,
};
}
Making the calls #
GET #
The GET method requests should only be used to ask the server for data and should not contain a body or a message. If used properly everything the server needs should be in the uri.
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
const allocator = gpa.allocator();
defer {
const deinit_status = gpa.deinit();
if (deinit_status == .leak) std.testing.expect(false) catch @panic("memory leak detected");
}
const headers_get = &[_]std.http.Header{
.{ .name = "User-Agent", .value = "potato/0.0.1" },
.{ .name = "Accept", .value = "application/json" },
};
var response_get = try httpRequest(
allocator,
"https://postman-echo.com/get?key1=val1&key2=val2",
.GET,
headers_get,
null,
);
defer response_get.deinit();
std.debug.print("GET Status: {}\n", .{response_get.status});
std.debug.print("GET Body: {s}\n\n", .{response_get.body});
}
POST #
The POST method contains a body or a message. Use this when you want to create a new target resource like a new user.
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
const allocator = gpa.allocator();
defer {
const deinit_status = gpa.deinit();
if (deinit_status == .leak) std.testing.expect(false) catch @panic("memory leak detected");
}
const post_payload =
\\{
\\ "key1": "val1",
\\ "key2": "val2"
\\}
;
const payload_length = try std.fmt.allocPrint(allocator, "{}", .{post_payload.len});
defer allocator.free(payload_length);
const headers_post = &[_]std.http.Header{
.{ .name = "Content-Type", .value = "application/json" },
.{ .name = "Accept", .value = "application/json" },
.{ .name = "Content-Length", .value = payload_length },
};
var response_post = try httpRequest(
allocator,
"https://jsonplaceholder.typicode.com/posts",
.POST,
headers_post,
post_payload,
);
defer response_post.deinit();
std.debug.print("POST Status: {}\n", .{response_post.status});
std.debug.print("POST Body: {s}\n\n", .{response_post.body});
}
PUT #
The PUT method contains a body or a message. This call is designed to replace all or parts of an existing target resource like updating a user.
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
const allocator = gpa.allocator();
defer {
const deinit_status = gpa.deinit();
if (deinit_status == .leak) std.testing.expect(false) catch @panic("memory leak detected");
}
const put_payload =
\\{
\\ "id": 1,
\\ "username": "potato",
\\ "avatar": "potato.png"
\\}
;
var response_put = try httpRequest(
allocator,
"https://postman-echo.com/posts/1",
.PUT,
headers_post,
put_payload,
);
defer response_put.deinit();
std.debug.print("PUT Status: {}\n", .{response_put.status});
std.debug.print("PUT Body: {s}\n\n", .{response_put.body});
}
DELETE #
The DELETE method doesn’t contain a body or a message. It’s strictly for removing an existing target resource.
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
const allocator = gpa.allocator();
defer {
const deinit_status = gpa.deinit();
if (deinit_status == .leak) std.testing.expect(false) catch @panic("memory leak detected");
}
var response_delete = try httpRequest(
allocator,
"https://postman-echo.com/delete",
.DELETE,
headers_post,
null,
);
defer response_delete.deinit();
std.debug.print("DELETE Status: {}\n", .{response_delete.status});
std.debug.print("DELETE Body: {s}\n", .{response_delete.body});
}
Final complete example code #
You can put this in a single file and run it.
const std = @import("std");
const json = @import("json");
pub const HttpResponse = struct {
status: std.http.Status,
body: []const u8,
allocator: std.mem.Allocator,
pub fn deinit(self: *HttpResponse) void {
self.allocator.free(self.body);
}
};
fn httpRequest(
allocator: std.mem.Allocator,
uri: []const u8,
method: std.http.Method,
headers: ?[]const std.http.Header,
payload: ?[]const u8,
) !HttpResponse {
const parsed_uri = std.Uri.parse(uri) catch return error.InvalidUri;
var redirect_buffer: [8 * 1024]u8 = undefined;
var body: std.Io.Writer.Allocating = .init(allocator);
defer body.deinit();
try body.ensureUnusedCapacity(64);
var client: std.http.Client = .{ .allocator = allocator };
defer client.deinit();
var response_body: std.ArrayList(u8) = .empty;
defer response_body.deinit(allocator);
const result = try client.fetch(.{
.location = .{ .uri = parsed_uri },
.method = method,
.redirect_buffer = &redirect_buffer,
.response_writer = &body.writer,
.extra_headers = headers orelse &[_]std.http.Header{},
.payload = payload,
});
return HttpResponse{
.status = result.status,
.body = try body.toOwnedSlice(),
.allocator = allocator,
};
}
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
const allocator = gpa.allocator();
defer {
const deinit_status = gpa.deinit();
if (deinit_status == .leak) std.testing.expect(false) catch @panic("memory leak detected");
}
// Start GET.
const headers_get = &[_]std.http.Header{
.{ .name = "User-Agent", .value = "potato/0.0.1" },
.{ .name = "Accept", .value = "application/json" },
};
var response_get = try httpRequest(
allocator,
"https://postman-echo.com/get?key1=val1&key2=val2",
.GET,
headers_get,
null,
);
defer response_get.deinit();
std.debug.print("GET Status: {}\n", .{response_get.status});
std.debug.print("GET Body: {s}\n\n", .{response_get.body});
// End GET.
// Start POST.
const post_payload =
\\{
\\ "key1": "val1",
\\ "key2": "val2"
\\}
;
const payload_length = try std.fmt.allocPrint(allocator, "{}", .{post_payload.len});
defer allocator.free(payload_length);
const headers_post = &[_]std.http.Header{
.{ .name = "Content-Type", .value = "application/json" },
.{ .name = "Accept", .value = "application/json" },
// .{ .name = "Content-Length", .value = payload_length },
};
var response_post = try httpRequest(
allocator,
"https://jsonplaceholder.typicode.com/posts",
.POST,
headers_post,
post_payload,
);
defer response_post.deinit();
std.debug.print("POST Status: {}\n", .{response_post.status});
std.debug.print("POST Body: {s}\n\n", .{response_post.body});
// End POST.
// Start PUT.
const put_payload =
\\{
\\ "id": 1,
\\ "username": "potato",
\\ "avatar": "potato.png"
\\}
;
var response_put = try httpRequest(
allocator,
"https://postman-echo.com/posts/1",
.PUT,
headers_post,
put_payload,
);
defer response_put.deinit();
std.debug.print("PUT Status: {}\n", .{response_put.status});
std.debug.print("PUT Body: {s}\n\n", .{response_put.body});
// End PUT.
// Start DELETE.
var response_delete = try httpRequest(
allocator,
"https://postman-echo.com/delete",
.DELETE,
headers_post,
null,
);
defer response_delete.deinit();
std.debug.print("DELETE Status: {}\n", .{response_delete.status});
std.debug.print("DELETE Body: {s}\n", .{response_delete.body});
// End DELETE.
}