GitHub - jiacai2050/zig-curl: Libcurl bindings for Zig · GitHub
Skip to content
jiacai2050Public

Repository files navigation

zig-curl

https://img.shields.io/badge/zig%20version-0.17.0-blue.svg https://github.com/jiacai2050/zig-curl/actions/workflows/CI.yml/badge.svg

Zig bindings for libcurl, a free and easy-to-use client-side URL transfer library.

This package supports the following Zig versions:

Zig versionBranch
0.17main
0.160.16
0.150.15

The vendored libraries consist of:

LibraryVersion
libcurl8.19.0
zlib1.3.1
mbedtls3.6.0

Usage

zig-curl provides two client interfaces designed for different use cases:

ClientStyleConcurrency ModelBest For
ClientHigh-level (fluent builder)Non-blocking via std.IoConcurrent requests, REST APIs, JSON parsing, automatic CA/connection pooling
EasyLow-level (direct libcurl wrapper)Synchronous / blockingSimple scripts, custom stream writers/readers, fine-grained libcurl option control

Client (Concurrent / High-Level)

Client is modeled after Rust’s reqwest. It features fluent request chaining, strongly-typed JSON deserialization, and native integration with std.Io’s concurrency model. It also manages connection reuse and DNS caching across requests.

const std = @import("std");
const curl = @import("curl");

pub fn main(init: std.process.Init) !void {
    var gpa: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{});
    defer if (gpa.deinit() != 0) @panic("memory leak detected");
    const allocator = gpa.allocator();

    // 1. Initialize client (automatically loads system CA bundle & shares DNS/SSL cache)
    var client = try curl.Client.init(allocator, init.io, .{});
    defer client.deinit();

    // 2. Fluent GET request
    var res = try client.get("https://edgebin.liujiacai.net/ip")
        .headers(&.{.{ .name = "Accept", .value = "application/json" }})
        .timeout(10000)
        .send(init.io);
    defer res.deinit();

    std.debug.print("Status: {d}\n", .{res.status_code});

    // 3. Strongly-typed JSON deserialization directly from response
    const IpGeo = struct { origin: []const u8, country: []const u8 };
    const parsed = try res.json(IpGeo, .{ .ignore_unknown_fields = true });
    defer parsed.deinit();
    std.debug.print("IP: {s}, Country: {s}\n", .{ parsed.value.origin, parsed.value.country });

    // 4. Asynchronous POST with body
    var post_res = try client.post("https://edgebin.liujiacai.net/anything")
        .headers(&.{.{ .name = "Content-Type", .value = "application/json" }})
        .body("{\"message\": \"hello zig-curl\"}")
        .send(init.io);
    defer post_res.deinit();
}

For concurrent batch requests using std.Io.Group, Bearer authentication, and worker pipeline patterns, see examples/client.zig.

Easy (Synchronous / Direct)

Easy wraps libcurl’s curl_easy_* interface, providing direct control over transfers and streaming I/O.

const std = @import("std");
const curl = @import("curl");

const URL = "https://edgebin.liujiacai.net/anything";

pub fn main(init: std.process.Init) !void {
    var gpa: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{});
    defer if (gpa.deinit() != 0) @panic("memory leak detected");
    const allocator = gpa.allocator();

    var ca_bundle = try curl.allocCABundle(allocator, init.io);
    defer ca_bundle.deinit(allocator);

    var easy = try curl.Easy.init(.{
        .ca_bundle = ca_bundle,
    });
    defer easy.deinit();

    // 1. Simple GET without body
    {
        std.debug.print("GET without body\n", .{});
        const response = try easy.fetch(URL, .{});
        std.debug.print("Status code: {d}\n", .{response.status_code});
    }

    // 2. GET with streaming writer
    {
        std.debug.print("\nGET with fixed buffer as body\n", .{});
        var buffer: [1024]u8 = undefined;
        var writer = std.Io.Writer.fixed(&buffer);
        const response = try easy.fetch(URL, .{ .writer = &writer });
        std.debug.print("Status code: {d}\nBody: {s}\n", .{
            response.status_code,
            writer.buffered(),
        });
    }
}

See examples/post.zig, examples/upload.zig, and examples/header.zig for more synchronous patterns.

Error Diagnostics

When a curl operation fails, it returns a generic error.Curl. To get detailed libcurl error diagnostics, inspect the diagnostics field in Easy or Multi:

var easy = try curl.Easy.init(.{ ... });
defer easy.deinit();

easy.perform() catch |err| {
    if (err == error.Curl) {
        if (easy.diagnostics.getMessage()) |msg| {
            std.log.err("curl failed: {s}", .{msg});
        }
    }
    return err;
};

Installation

# Latest version
zig fetch --save git+https://github.com/jiacai2050/zig-curl.git
# Tagged version
zig fetch --save git+https://github.com/jiacai2050/zig-curl.git#v0.5.0

The latest tag can be found on release page.

After fetch, import curl like this in your build.zig:

const dep_curl = b.dependency("curl", .{});
exe.root_module.addImport("curl", dep_curl.module("curl"));
exe.root_module.link_libc = true;

This library links to a vendored libcurl by default. To disable and link against system libcurl:

const dep_curl = b.dependency("curl", .{ .link_vendor = false });
exe.root_module.linkSystemLibrary("curl", .{});
exe.root_module.link_libc = true;

Documentation

See https://jiacai2050.github.io/zig-curl/

License

MIT

Releases

Packages

Used by

Contributors

Languages