Remove HTTP client generic Loop parameter
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled

I think we initially thought we might need different clients for different
parts of the system, each with a unique loop  (e.g. we thought telemetry might
need some isolation). But that never happened, so it's just needless now,
especially since the async connect uses the non-generic *Loop type directly.
This commit is contained in:
Karl Seguin
2025-07-02 16:48:14 +08:00
committed by Sjors
parent bf1db50667
commit 455ed79872
3 changed files with 200 additions and 173 deletions

View File

@@ -55,7 +55,7 @@ pub const App = struct {
.telemetry = undefined,
.app_dir_path = app_dir_path,
.notification = notification,
.http_client = try http.Client.init(allocator, .{
.http_client = try http.Client.init(allocator, loop, .{
.max_concurrent = 3,
.http_proxy = config.http_proxy,
.proxy_type = config.proxy_type,

View File

@@ -458,7 +458,6 @@ pub const XMLHttpRequest = struct {
&self.url.?.uri,
self,
onHttpRequestReady,
self.loop,
);
}
@@ -494,7 +493,7 @@ pub const XMLHttpRequest = struct {
}
}
try request.sendAsync(self.loop, self, .{});
try request.sendAsync(self, .{});
self.request = request;
}

View File

@@ -77,6 +77,7 @@ pub const ProxyAuth = union(enum) {
// Thread-safe. Holds our root certificate, connection pool and state pool
// Used to create Requests.
pub const Client = struct {
loop: *Loop,
req_id: usize,
allocator: Allocator,
state_pool: StatePool,
@@ -97,7 +98,7 @@ pub const Client = struct {
max_idle_connection: usize = 10,
};
pub fn init(allocator: Allocator, opts: Opts) !Client {
pub fn init(allocator: Allocator, loop: *Loop, opts: Opts) !Client {
var root_ca: std.crypto.Certificate.Bundle = if (builtin.is_test) .{} else try tls.config.cert.fromSystem(allocator);
errdefer root_ca.deinit(allocator);
@@ -109,6 +110,7 @@ pub const Client = struct {
return .{
.req_id = 0,
.loop = loop,
.root_ca = root_ca,
.allocator = allocator,
.state_pool = state_pool,
@@ -153,7 +155,6 @@ pub const Client = struct {
uri: *const Uri,
ctx: *anyopaque,
callback: AsyncQueue.Callback,
loop: *Loop,
opts: RequestOpts,
) !void {
@@ -184,7 +185,7 @@ pub const Client = struct {
.callback = callback,
.node = .{ .func = AsyncQueue.check },
};
_ = try loop.timeout(10 * std.time.ns_per_ms, &queue.node);
_ = try self.loop.timeout(10 * std.time.ns_per_ms, &queue.node);
}
// Either called directly from initAsync (if we have a state ready)
@@ -257,9 +258,8 @@ pub const RequestFactory = struct {
uri: *const Uri,
ctx: *anyopaque,
callback: AsyncQueue.Callback,
loop: *Loop,
) !void {
return self.client.initAsync(arena, method, uri, ctx, callback, loop, self.opts);
return self.client.initAsync(arena, method, uri, ctx, callback, self.opts);
}
};
@@ -690,19 +690,19 @@ pub const Request = struct {
tls_verify_host: ?bool = null,
};
// Makes an asynchronous request
pub fn sendAsync(self: *Request, loop: anytype, handler: anytype, opts: SendAsyncOpts) !void {
pub fn sendAsync(self: *Request, handler: anytype, opts: SendAsyncOpts) !void {
if (opts.tls_verify_host) |override| {
self._tls_verify_host = override;
}
try self.prepareInitialSend();
return self.doSendAsync(loop, handler, true);
return self.doSendAsync(handler, true);
}
pub fn redirectAsync(self: *Request, redirect: Reader.Redirect, loop: anytype, handler: anytype) !void {
pub fn redirectAsync(self: *Request, redirect: Reader.Redirect, handler: anytype) !void {
try self.prepareToRedirect(redirect);
return self.doSendAsync(loop, handler, true);
return self.doSendAsync(handler, true);
}
fn doSendAsync(self: *Request, loop: anytype, handler: anytype, use_pool: bool) !void {
fn doSendAsync(self: *Request, handler: anytype, use_pool: bool) !void {
if (use_pool) {
if (self.findExistingConnection(false)) |connection| {
self._connection = connection;
@@ -729,7 +729,9 @@ pub const Request = struct {
const connection = self._connection.?;
errdefer self.destroyConnection(connection);
const AsyncHandlerT = AsyncHandler(@TypeOf(handler), @TypeOf(loop));
const loop = self._client.loop;
const AsyncHandlerT = AsyncHandler(@TypeOf(handler));
const async_handler = try self.arena.create(AsyncHandlerT);
const state = self._state;
@@ -998,9 +1000,9 @@ pub const Request = struct {
};
// Handles asynchronous requests
fn AsyncHandler(comptime H: type, comptime L: type) type {
fn AsyncHandler(comptime H: type) type {
return struct {
loop: L,
loop: *Loop,
handler: H,
request: *Request,
read_buf: []u8,
@@ -1279,7 +1281,7 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
return;
};
self.request.redirectAsync(redirect, self.loop, self.handler) catch |err| {
self.request.redirectAsync(redirect, self.handler) catch |err| {
self.handleError("Setup async redirect", err);
return;
};
@@ -1319,7 +1321,7 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
std.debug.assert(request._keepalive == false);
request.releaseConnection();
request.doSendAsync(self.loop, self.handler, false) catch |conn_err| {
request.doSendAsync(self.handler, false) catch |conn_err| {
// You probably think it's weird that we fallthrough to the:
// return true;
// The caller will take the `true` and just exit. This is what
@@ -3054,30 +3056,30 @@ test "HttpClient Reader: fuzz" {
}
test "HttpClient: invalid url" {
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
const uri = try Uri.parse("http:///");
try testing.expectError(error.UriMissingHost, client.request(.GET, &uri));
try testing.expectError(error.UriMissingHost, tc.client.request(.GET, &uri));
}
test "HttpClient: sync connect error" {
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
const uri = try Uri.parse("HTTP://127.0.0.1:9920");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
try testing.expectError(error.ConnectionRefused, req.sendSync(.{}));
}
test "HttpClient: sync no body" {
for (0..2) |i| {
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
for (0..2) |i| {
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/simple");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
var res = try req.sendSync(.{});
@@ -3094,12 +3096,12 @@ test "HttpClient: sync no body" {
}
test "HttpClient: sync tls no body" {
for (0..1) |_| {
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
for (0..2) |_| {
const uri = try Uri.parse("https://127.0.0.1:9581/http_client/simple");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
var res = try req.sendSync(.{ .tls_verify_host = false });
@@ -3113,12 +3115,12 @@ test "HttpClient: sync tls no body" {
}
test "HttpClient: sync with body" {
for (0..2) |i| {
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
for (0..2) |i| {
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/echo");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
var res = try req.sendSync(.{});
@@ -3138,13 +3140,13 @@ test "HttpClient: sync with body" {
}
test "HttpClient: sync with body proxy CONNECT" {
for (0..2) |i| {
const proxy_uri = try Uri.parse("http://127.0.0.1:9582/");
var client = try testClient(.{ .proxy_type = .connect, .http_proxy = proxy_uri });
defer client.deinit();
var tc = try TestContext.init(.{ .proxy_type = .connect, .http_proxy = proxy_uri });
defer tc.deinit();
for (0..2) |i| {
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/echo");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
var res = try req.sendSync(.{});
@@ -3167,11 +3169,11 @@ test "HttpClient: sync with body proxy CONNECT" {
test "HttpClient: basic authentication CONNECT" {
const proxy_uri = try Uri.parse("http://127.0.0.1:9582/");
var client = try testClient(.{ .proxy_type = .connect, .http_proxy = proxy_uri, .proxy_auth = .{ .basic = .{ .user_pass = "user:pass" } } });
defer client.deinit();
var tc = try TestContext.init(.{ .proxy_type = .connect, .http_proxy = proxy_uri, .proxy_auth = .{ .basic = .{ .user_pass = "user:pass" } } });
defer tc.deinit();
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/echo");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
var res = try req.sendSync(.{});
@@ -3184,13 +3186,14 @@ test "HttpClient: basic authentication CONNECT" {
try testing.expectEqual(null, res.header.get("__authorization"));
try testing.expectEqual("Basic dXNlcjpwYXNz", res.header.get("__proxy-authorization"));
}
test "HttpClient: bearer authentication CONNECT" {
const proxy_uri = try Uri.parse("http://127.0.0.1:9582/");
var client = try testClient(.{ .proxy_type = .connect, .http_proxy = proxy_uri, .proxy_auth = .{ .bearer = .{ .token = "fruitsalad" } } });
defer client.deinit();
var tc = try TestContext.init(.{ .proxy_type = .connect, .http_proxy = proxy_uri, .proxy_auth = .{ .bearer = .{ .token = "fruitsalad" } } });
defer tc.deinit();
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/echo");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
var res = try req.sendSync(.{});
@@ -3205,12 +3208,12 @@ test "HttpClient: bearer authentication CONNECT" {
}
test "HttpClient: sync with gzip body" {
for (0..2) |i| {
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
for (0..2) |i| {
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/gzip");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
var res = try req.sendSync(.{});
@@ -3224,17 +3227,18 @@ test "HttpClient: sync with gzip body" {
}
test "HttpClient: sync tls with body" {
var tc = try TestContext.init(.{});
defer tc.deinit();
var arr: std.ArrayListUnmanaged(u8) = .{};
defer arr.deinit(testing.allocator);
try arr.ensureTotalCapacity(testing.allocator, 20);
var client = try testClient(.{});
defer client.deinit();
for (0..5) |_| {
defer arr.clearRetainingCapacity();
const uri = try Uri.parse("https://127.0.0.1:9581/http_client/body");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
var res = try req.sendSync(.{ .tls_verify_host = false });
@@ -3252,17 +3256,18 @@ test "HttpClient: sync tls with body" {
}
test "HttpClient: sync redirect from TLS to Plaintext" {
var tc = try TestContext.init(.{});
defer tc.deinit();
var arr: std.ArrayListUnmanaged(u8) = .{};
defer arr.deinit(testing.allocator);
try arr.ensureTotalCapacity(testing.allocator, 20);
for (0..5) |_| {
defer arr.clearRetainingCapacity();
var client = try testClient(.{});
defer client.deinit();
const uri = try Uri.parse("https://127.0.0.1:9581/http_client/redirect/insecure");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
var res = try req.sendSync(.{ .tls_verify_host = false });
@@ -3282,17 +3287,18 @@ test "HttpClient: sync redirect from TLS to Plaintext" {
}
test "HttpClient: sync redirect plaintext to TLS" {
var tc = try TestContext.init(.{});
defer tc.deinit();
var arr: std.ArrayListUnmanaged(u8) = .{};
defer arr.deinit(testing.allocator);
try arr.ensureTotalCapacity(testing.allocator, 20);
for (0..5) |_| {
defer arr.clearRetainingCapacity();
var client = try testClient(.{});
defer client.deinit();
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/redirect/secure");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
var res = try req.sendSync(.{ .tls_verify_host = false });
@@ -3309,11 +3315,11 @@ test "HttpClient: sync redirect plaintext to TLS" {
}
test "HttpClient: sync GET redirect" {
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/redirect");
var req = try client.request(.GET, &uri);
var req = try tc.client.request(.GET, &uri);
defer req.deinit();
var res = try req.sendSync(.{ .tls_verify_host = false });
@@ -3329,8 +3335,9 @@ test "HttpClient: sync GET redirect" {
test "HttpClient: async connect error" {
defer testing.reset();
var loop = try Loop.init(testing.allocator);
defer loop.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
const Handler = struct {
loop: *Loop,
@@ -3338,7 +3345,7 @@ test "HttpClient: async connect error" {
fn requestReady(ctx: *anyopaque, req: *Request) !void {
const self: *@This() = @alignCast(@ptrCast(ctx));
try req.sendAsync(self.loop, self, .{});
try req.sendAsync(self, .{});
}
fn onHttpResponse(self: *@This(), res: anyerror!Progress) !void {
@@ -3355,27 +3362,24 @@ test "HttpClient: async connect error" {
};
var reset: Thread.ResetEvent = .{};
var client = try testClient(.{});
defer client.deinit();
var handler = Handler{
.loop = &loop,
.loop = tc.loop,
.reset = &reset,
};
const uri = try Uri.parse("HTTP://127.0.0.1:9920");
try client.initAsync(
try tc.client.initAsync(
testing.arena_allocator,
.GET,
&uri,
&handler,
Handler.requestReady,
&loop,
.{},
);
for (0..10) |_| {
try loop.io.run_for_ns(std.time.ns_per_ms * 10);
try tc.loop.io.run_for_ns(std.time.ns_per_ms * 10);
if (reset.isSet()) {
break;
}
@@ -3387,14 +3391,14 @@ test "HttpClient: async connect error" {
test "HttpClient: async no body" {
defer testing.reset();
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
var handler = try CaptureHandler.init();
var handler = tc.captureHandler();
defer handler.deinit();
const uri = try Uri.parse("HTTP://127.0.0.1:9582/http_client/simple");
try client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, &handler.loop, .{});
try tc.client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, .{});
try handler.waitUntilDone();
const res = handler.response;
@@ -3406,14 +3410,14 @@ test "HttpClient: async no body" {
test "HttpClient: async with body" {
defer testing.reset();
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
var handler = try CaptureHandler.init();
var handler = tc.captureHandler();
defer handler.deinit();
const uri = try Uri.parse("HTTP://127.0.0.1:9582/http_client/echo");
try client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, &handler.loop, .{});
try tc.client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, .{});
try handler.waitUntilDone();
const res = handler.response;
@@ -3431,14 +3435,14 @@ test "HttpClient: async with body" {
test "HttpClient: async with gzip body" {
defer testing.reset();
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
var handler = try CaptureHandler.init();
var handler = tc.captureHandler();
defer handler.deinit();
const uri = try Uri.parse("HTTP://127.0.0.1:9582/http_client/gzip");
try client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, &handler.loop, .{});
try tc.client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, .{});
try handler.waitUntilDone();
const res = handler.response;
@@ -3454,14 +3458,14 @@ test "HttpClient: async with gzip body" {
test "HttpClient: async redirect" {
defer testing.reset();
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
var handler = try CaptureHandler.init();
var handler = tc.captureHandler();
defer handler.deinit();
const uri = try Uri.parse("HTTP://127.0.0.1:9582/http_client/redirect");
try client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, &handler.loop, .{});
try tc.client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, .{});
// Called twice on purpose. The initial GET resutls in the # of pending
// events to reach 0. This causes our `run_for_ns` to return. But we then
@@ -3484,14 +3488,15 @@ test "HttpClient: async redirect" {
test "HttpClient: async tls no body" {
defer testing.reset();
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
for (0..5) |_| {
var handler = try CaptureHandler.init();
var handler = tc.captureHandler();
defer handler.deinit();
const uri = try Uri.parse("HTTPs://127.0.0.1:9581/http_client/simple");
try client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, &handler.loop, .{});
try tc.client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, .{});
try handler.waitUntilDone();
const res = handler.response;
@@ -3508,15 +3513,15 @@ test "HttpClient: async tls no body" {
test "HttpClient: async tls with body" {
defer testing.reset();
for (0..5) |_| {
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
var handler = try CaptureHandler.init();
for (0..5) |_| {
var handler = tc.captureHandler();
defer handler.deinit();
const uri = try Uri.parse("HTTPs://127.0.0.1:9581/http_client/body");
try client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, &handler.loop, .{});
try tc.client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, .{});
try handler.waitUntilDone();
const res = handler.response;
@@ -3532,15 +3537,15 @@ test "HttpClient: async tls with body" {
test "HttpClient: async redirect from TLS to Plaintext" {
defer testing.reset();
for (0..1) |_| {
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
var handler = try CaptureHandler.init();
for (0..2) |_| {
var handler = tc.captureHandler();
defer handler.deinit();
const uri = try Uri.parse("https://127.0.0.1:9581/http_client/redirect/insecure");
try client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, &handler.loop, .{});
try tc.client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, .{});
try handler.waitUntilDone();
const res = handler.response;
@@ -3558,15 +3563,15 @@ test "HttpClient: async redirect from TLS to Plaintext" {
test "HttpClient: async redirect plaintext to TLS" {
defer testing.reset();
for (0..5) |_| {
var client = try testClient(.{});
defer client.deinit();
var tc = try TestContext.init(.{});
defer tc.deinit();
var handler = try CaptureHandler.init();
for (0..5) |_| {
var handler = tc.captureHandler();
defer handler.deinit();
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/redirect/secure");
try client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, &handler.loop, .{});
try tc.client.initAsync(testing.arena_allocator, .GET, &uri, &handler, CaptureHandler.requestReady, .{});
try handler.waitUntilDone();
const res = handler.response;
@@ -3670,67 +3675,6 @@ const TestResponse = struct {
}
};
const CaptureHandler = struct {
loop: Loop,
reset: Thread.ResetEvent,
response: TestResponse,
fn init() !CaptureHandler {
return .{
.reset = .{},
.response = TestResponse.init(),
.loop = try Loop.init(testing.allocator),
};
}
fn deinit(self: *CaptureHandler) void {
self.response.deinit();
self.loop.deinit();
}
fn requestReady(ctx: *anyopaque, req: *Request) !void {
const self: *CaptureHandler = @alignCast(@ptrCast(ctx));
try req.sendAsync(&self.loop, self, .{ .tls_verify_host = false });
}
fn onHttpResponse(self: *CaptureHandler, progress_: anyerror!Progress) !void {
self.process(progress_) catch |err| {
std.debug.print("capture handler error: {}\n", .{err});
};
}
fn process(self: *CaptureHandler, progress_: anyerror!Progress) !void {
const progress = try progress_;
const allocator = self.response.arena.allocator();
try self.response.body.appendSlice(allocator, progress.data orelse "");
if (progress.first) {
std.debug.assert(!progress.done);
self.response.status = progress.header.status;
try self.response.headers.ensureTotalCapacity(allocator, progress.header.headers.items.len);
for (progress.header.headers.items) |header| {
self.response.headers.appendAssumeCapacity(.{
.name = try allocator.dupe(u8, header.name),
.value = try allocator.dupe(u8, header.value),
});
}
}
if (progress.done) {
self.reset.set();
}
}
fn waitUntilDone(self: *CaptureHandler) !void {
for (0..20) |_| {
try self.loop.io.run_for_ns(std.time.ns_per_ms * 25);
if (self.reset.isSet()) {
return;
}
}
return error.TimeoutWaitingForRequestToComplete;
}
};
fn testReader(state: *State, res: *TestResponse, data: []const u8) !void {
var status: u16 = 0;
var r = Reader.init(state);
@@ -3772,8 +3716,92 @@ fn testReader(state: *State, res: *TestResponse, data: []const u8) !void {
return error.NeverDone;
}
fn testClient(opts: Client.Opts) !Client {
const TestContext = struct {
loop: *Loop,
client: Client,
fn init(opts: Client.Opts) !TestContext {
const loop = try testing.allocator.create(Loop);
errdefer testing.allocator.destroy(loop);
loop.* = try Loop.init(testing.allocator);
errdefer loop.deinit();
var o = opts;
o.max_concurrent = 1;
return try Client.init(testing.allocator, o);
}
const client = try Client.init(testing.allocator, loop, o);
errdefer client.deinit();
return .{
.loop = loop,
.client = client,
};
}
fn deinit(self: *TestContext) void {
self.loop.deinit();
self.client.deinit();
testing.allocator.destroy(self.loop);
}
fn captureHandler(self: *TestContext) CaptureHandler {
return .{
.reset = .{},
.loop = self.loop,
.response = TestResponse.init(),
};
}
};
const CaptureHandler = struct {
loop: *Loop,
reset: Thread.ResetEvent,
response: TestResponse,
fn deinit(self: *CaptureHandler) void {
self.response.deinit();
}
fn requestReady(ctx: *anyopaque, req: *Request) !void {
const self: *CaptureHandler = @alignCast(@ptrCast(ctx));
try req.sendAsync(self, .{ .tls_verify_host = false });
}
fn onHttpResponse(self: *CaptureHandler, progress_: anyerror!Progress) !void {
self.process(progress_) catch |err| {
std.debug.print("capture handler error: {}\n", .{err});
};
}
fn process(self: *CaptureHandler, progress_: anyerror!Progress) !void {
const progress = try progress_;
const allocator = self.response.arena.allocator();
try self.response.body.appendSlice(allocator, progress.data orelse "");
if (progress.first) {
std.debug.assert(!progress.done);
self.response.status = progress.header.status;
try self.response.headers.ensureTotalCapacity(allocator, progress.header.headers.items.len);
for (progress.header.headers.items) |header| {
self.response.headers.appendAssumeCapacity(.{
.name = try allocator.dupe(u8, header.name),
.value = try allocator.dupe(u8, header.value),
});
}
}
if (progress.done) {
self.reset.set();
}
}
fn waitUntilDone(self: *CaptureHandler) !void {
for (0..20) |_| {
try self.loop.io.run_for_ns(std.time.ns_per_ms * 25);
if (self.reset.isSet()) {
return;
}
}
return error.TimeoutWaitingForRequestToComplete;
}
};