Centralizes configuration, eliminates unnecessary copying of config

This commit is contained in:
Nikolay Govorov
2026-01-26 03:22:45 +00:00
parent 068ec68917
commit 0764a44e1d
13 changed files with 836 additions and 742 deletions

View File

@@ -21,6 +21,7 @@ const log = @import("../log.zig");
const builtin = @import("builtin");
const Http = @import("Http.zig");
const Config = @import("../Config.zig");
const URL = @import("../browser/URL.zig");
const Notification = @import("../Notification.zig");
const CookieJar = @import("../browser/webapi/storage/Cookie.zig").Jar;
@@ -124,7 +125,7 @@ pub const CDPClient = struct {
const TransferQueue = std.DoublyLinkedList;
pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, opts: Http.Opts) !*Client {
pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, config: *const Config) !*Client {
var transfer_pool = std.heap.MemoryPool(Transfer).init(allocator);
errdefer transfer_pool.deinit();
@@ -134,11 +135,19 @@ pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, opts: Http.Opts) !*Clie
const multi = c.curl_multi_init() orelse return error.FailedToInitializeMulti;
errdefer _ = c.curl_multi_cleanup(multi);
try errorMCheck(c.curl_multi_setopt(multi, c.CURLMOPT_MAX_HOST_CONNECTIONS, @as(c_long, opts.max_host_open)));
try errorMCheck(c.curl_multi_setopt(multi, c.CURLMOPT_MAX_HOST_CONNECTIONS, @as(c_long, config.httpMaxHostOpen())));
var handles = try Handles.init(allocator, client, ca_blob, &opts);
const user_agent = try config.userAgent(allocator);
var proxy_bearer_header: ?[:0]const u8 = null;
if (config.proxyBearerToken()) |bt| {
proxy_bearer_header = try std.fmt.allocPrintSentinel(allocator, "Proxy-Authorization: Bearer {s}", .{bt}, 0);
}
var handles = try Handles.init(allocator, client, ca_blob, config, user_agent, proxy_bearer_header);
errdefer handles.deinit(allocator);
const http_proxy = config.httpProxy();
client.* = .{
.queue = .{},
.active = 0,
@@ -146,9 +155,9 @@ pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, opts: Http.Opts) !*Clie
.multi = multi,
.handles = handles,
.allocator = allocator,
.http_proxy = opts.http_proxy,
.use_proxy = opts.http_proxy != null,
.user_agent = opts.user_agent,
.http_proxy = http_proxy,
.use_proxy = http_proxy != null,
.user_agent = user_agent,
.transfer_pool = transfer_pool,
};
@@ -657,16 +666,23 @@ const Handles = struct {
const HandleList = std.DoublyLinkedList;
// pointer to opts is not stable, don't hold a reference to it!
fn init(allocator: Allocator, client: *Client, ca_blob: ?c.curl_blob, opts: *const Http.Opts) !Handles {
const count = if (opts.max_concurrent == 0) 1 else opts.max_concurrent;
fn init(
allocator: Allocator,
client: *Client,
ca_blob: ?c.curl_blob,
config: *const Config,
user_agent: [:0]const u8,
proxy_bearer_header: ?[:0]const u8,
) !Handles {
const count: usize = config.httpMaxConcurrent();
if (count == 0) return error.InvalidMaxConcurrent;
const handles = try allocator.alloc(Handle, count);
errdefer allocator.free(handles);
var available: HandleList = .{};
for (0..count) |i| {
handles[i] = try Handle.init(client, ca_blob, opts);
handles[i] = try Handle.init(client, ca_blob, config, user_agent, proxy_bearer_header);
available.append(&handles[i].node);
}
@@ -713,9 +729,14 @@ pub const Handle = struct {
conn: Http.Connection,
node: Handles.HandleList.Node,
// pointer to opts is not stable, don't hold a reference to it!
fn init(client: *Client, ca_blob: ?c.curl_blob, opts: *const Http.Opts) !Handle {
const conn = try Http.Connection.init(ca_blob, opts);
fn init(
client: *Client,
ca_blob: ?c.curl_blob,
config: *const Config,
user_agent: [:0]const u8,
proxy_bearer_header: ?[:0]const u8,
) !Handle {
const conn = try Http.Connection.init(ca_blob, config, user_agent, proxy_bearer_header);
errdefer conn.deinit();
const easy = conn.easy;

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
@@ -18,6 +18,7 @@
const std = @import("std");
const lp = @import("lightpanda");
const Config = @import("../Config.zig");
pub const c = @cImport({
@cInclude("curl/curl.h");
@@ -40,12 +41,14 @@ const ArenaAllocator = std.heap.ArenaAllocator;
// once for all http connections is a win.
const Http = @This();
opts: Opts,
config: *const Config,
client: *Client,
ca_blob: ?c.curl_blob,
arena: ArenaAllocator,
user_agent: [:0]const u8,
proxy_bearer_header: ?[:0]const u8,
pub fn init(allocator: Allocator, opts: Opts) !Http {
pub fn init(allocator: Allocator, config: *const Config) !Http {
try errorCheck(c.curl_global_init(c.CURL_GLOBAL_SSL));
errdefer c.curl_global_cleanup();
@@ -56,24 +59,28 @@ pub fn init(allocator: Allocator, opts: Opts) !Http {
var arena = ArenaAllocator.init(allocator);
errdefer arena.deinit();
var adjusted_opts = opts;
if (opts.proxy_bearer_token) |bt| {
adjusted_opts.proxy_bearer_token = try std.fmt.allocPrintSentinel(arena.allocator(), "Proxy-Authorization: Bearer {s}", .{bt}, 0);
const user_agent = try config.userAgent(arena.allocator());
var proxy_bearer_header: ?[:0]const u8 = null;
if (config.proxyBearerToken()) |bt| {
proxy_bearer_header = try std.fmt.allocPrintSentinel(arena.allocator(), "Proxy-Authorization: Bearer {s}", .{bt}, 0);
}
var ca_blob: ?c.curl_blob = null;
if (opts.tls_verify_host) {
if (config.tlsVerifyHost()) {
ca_blob = try loadCerts(allocator, arena.allocator());
}
var client = try Client.init(allocator, ca_blob, adjusted_opts);
var client = try Client.init(allocator, ca_blob, config);
errdefer client.deinit();
return .{
.arena = arena,
.client = client,
.ca_blob = ca_blob,
.opts = adjusted_opts,
.config = config,
.user_agent = user_agent,
.proxy_bearer_header = proxy_bearer_header,
};
}
@@ -100,59 +107,60 @@ pub fn removeCDPClient(self: *Http) void {
}
pub fn newConnection(self: *Http) !Connection {
return Connection.init(self.ca_blob, &self.opts);
return Connection.init(self.ca_blob, self.config, self.user_agent, self.proxy_bearer_header);
}
pub fn newHeaders(self: *const Http) Headers {
return Headers.init(self.opts.user_agent);
return Headers.init(self.user_agent);
}
pub const Connection = struct {
easy: *c.CURL,
opts: Connection.Opts,
user_agent: [:0]const u8,
proxy_bearer_header: ?[:0]const u8,
const Opts = struct {
proxy_bearer_token: ?[:0]const u8,
pub fn init(
ca_blob_: ?c.curl_blob,
config: *const Config,
user_agent: [:0]const u8,
};
// pointer to opts is not stable, don't hold a reference to it!
pub fn init(ca_blob_: ?c.curl_blob, opts: *const Http.Opts) !Connection {
proxy_bearer_header: ?[:0]const u8,
) !Connection {
const easy = c.curl_easy_init() orelse return error.FailedToInitializeEasy;
errdefer _ = c.curl_easy_cleanup(easy);
// timeouts
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_TIMEOUT_MS, @as(c_long, @intCast(opts.timeout_ms))));
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_CONNECTTIMEOUT_MS, @as(c_long, @intCast(opts.connect_timeout_ms))));
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_TIMEOUT_MS, @as(c_long, @intCast(config.httpTimeout()))));
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_CONNECTTIMEOUT_MS, @as(c_long, @intCast(config.httpConnectTimeout()))));
// redirect behavior
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_MAXREDIRS, @as(c_long, @intCast(opts.max_redirects))));
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_MAXREDIRS, @as(c_long, @intCast(config.httpMaxRedirects()))));
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_FOLLOWLOCATION, @as(c_long, 2)));
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_REDIR_PROTOCOLS_STR, "HTTP,HTTPS")); // remove FTP and FTPS from the default
// proxy
if (opts.http_proxy) |proxy| {
const http_proxy = config.httpProxy();
if (http_proxy) |proxy| {
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY, proxy.ptr));
}
// tls
if (ca_blob_) |ca_blob| {
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_CAINFO_BLOB, ca_blob));
if (opts.http_proxy != null) {
if (http_proxy != null) {
// Note, this can be difference for the proxy and for the main
// request. Might be something worth exposting as command
// line arguments at some point.
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY_CAINFO_BLOB, ca_blob));
}
} else {
lp.assert(opts.tls_verify_host == false, "Http.init tls_verify_host", .{});
lp.assert(config.tlsVerifyHost() == false, "Http.init tls_verify_host", .{});
// Verify peer checks that the cert is signed by a CA, verify host makes sure the
// cert contains the server name.
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_SSL_VERIFYHOST, @as(c_long, 0)));
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_SSL_VERIFYPEER, @as(c_long, 0)));
if (opts.http_proxy != null) {
if (http_proxy != null) {
// Note, this can be difference for the proxy and for the main
// request. Might be something worth exposting as command
// line arguments at some point.
@@ -180,10 +188,8 @@ pub const Connection = struct {
return .{
.easy = easy,
.opts = .{
.user_agent = opts.user_agent,
.proxy_bearer_token = opts.proxy_bearer_token,
},
.user_agent = user_agent,
.proxy_bearer_header = proxy_bearer_header,
};
}
@@ -237,7 +243,7 @@ pub const Connection = struct {
// These are headers that may not be send to the users for inteception.
pub fn secretHeaders(self: *const Connection, headers: *Headers) !void {
if (self.opts.proxy_bearer_token) |hdr| {
if (self.proxy_bearer_header) |hdr| {
try headers.add(hdr);
}
}
@@ -245,7 +251,7 @@ pub const Connection = struct {
pub fn request(self: *const Connection) !u16 {
const easy = self.easy;
var header_list = try Headers.init(self.opts.user_agent);
var header_list = try Headers.init(self.user_agent);
defer header_list.deinit();
try self.secretHeaders(&header_list);
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_HTTPHEADER, header_list.headers));
@@ -347,18 +353,6 @@ pub fn errorMCheck(code: c.CURLMcode) errors.Multi!void {
return errors.fromMCode(code);
}
pub const Opts = struct {
timeout_ms: u31,
max_host_open: u8,
max_concurrent: u8,
connect_timeout_ms: u31,
max_redirects: u8 = 10,
tls_verify_host: bool = true,
http_proxy: ?[:0]const u8 = null,
proxy_bearer_token: ?[:0]const u8 = null,
user_agent: [:0]const u8,
};
pub const Method = enum(u8) {
GET = 0,
PUT = 1,