Switch to nonblocking socket

Improve test server handshake performance, allowing for a few more fuzz
iterations without making tests unbearably slow.
This commit is contained in:
Karl Seguin
2025-03-21 14:59:25 +08:00
parent 22aa126b29
commit 2a0d1b0a48
7 changed files with 42 additions and 132 deletions

View File

@@ -38,7 +38,7 @@ pub const App = struct {
.allocator = allocator, .allocator = allocator,
.telemetry = undefined, .telemetry = undefined,
.app_dir_path = app_dir_path, .app_dir_path = app_dir_path,
.http_client = try HttpClient.init(allocator, 5, null), .http_client = try HttpClient.init(allocator, 5),
}; };
app.telemetry = Telemetry.init(app, run_mode); app.telemetry = Telemetry.init(app, run_mode);

View File

@@ -48,8 +48,8 @@ pub const Client = struct {
root_ca: tls.config.CertBundle, root_ca: tls.config.CertBundle,
// we only allow passing in a root_ca for testing // we only allow passing in a root_ca for testing
pub fn init(allocator: Allocator, max_concurrent: usize, root_ca_: ?tls.config.CertBundle) !Client { pub fn init(allocator: Allocator, max_concurrent: usize) !Client {
var root_ca = root_ca_ orelse try tls.config.CertBundle.fromSystem(allocator); var root_ca = try tls.config.CertBundle.fromSystem(allocator);
errdefer root_ca.deinit(allocator); errdefer root_ca.deinit(allocator);
const state_pool = try StatePool.init(allocator, max_concurrent); const state_pool = try StatePool.init(allocator, max_concurrent);
@@ -123,6 +123,9 @@ pub const Request = struct {
// we'll set it based on `uri` before issuing the request. // we'll set it based on `uri` before issuing the request.
_has_host_header: bool, _has_host_header: bool,
// Whether or not we should verify that the host matches the certificate CN
_tls_verify_host: bool = true,
pub const Method = enum { pub const Method = enum {
GET, GET,
PUT, PUT,
@@ -202,9 +205,12 @@ pub const Request = struct {
} }
// TODO timeout // TODO timeout
const SendSyncOpts = struct {}; const SendSyncOpts = struct {
tls_verify_host: bool = true,
};
// Makes an synchronous request // Makes an synchronous request
pub fn sendSync(self: *Request, _: SendSyncOpts) anyerror!Response { pub fn sendSync(self: *Request, opts: SendSyncOpts) anyerror!Response {
self._tls_verify_host = opts.tls_verify_host;
try self.prepareInitialSend(); try self.prepareInitialSend();
return self.doSendSync(); return self.doSendSync();
} }
@@ -224,9 +230,12 @@ pub const Request = struct {
}; };
} }
const SendAsyncOpts = struct {}; const SendAsyncOpts = struct {
tls_verify_host: bool = true,
};
// Makes an asynchronous request // Makes an asynchronous request
pub fn sendAsync(self: *Request, loop: anytype, handler: anytype, _: SendAsyncOpts) !void { pub fn sendAsync(self: *Request, loop: anytype, handler: anytype, opts: SendAsyncOpts) !void {
self._tls_verify_host = opts.tls_verify_host;
try self.prepareInitialSend(); try self.prepareInitialSend();
return self.doSendAsync(loop, handler); return self.doSendAsync(loop, handler);
} }
@@ -236,8 +245,7 @@ pub const Request = struct {
} }
fn doSendAsync(self: *Request, loop: anytype, handler: anytype) !void { fn doSendAsync(self: *Request, loop: anytype, handler: anytype) !void {
// TODO: change this to nonblocking (false) when we have promise resolution const socket, const address = try self.createSocket(false);
const socket, const address = try self.createSocket(true);
const AsyncHandlerT = AsyncHandler(@TypeOf(handler), @TypeOf(loop)); const AsyncHandlerT = AsyncHandler(@TypeOf(handler), @TypeOf(loop));
const async_handler = try self.arena.create(AsyncHandlerT); const async_handler = try self.arena.create(AsyncHandlerT);
@@ -256,6 +264,7 @@ pub const Request = struct {
.tls_client = try tls.asyn.Client(AsyncHandlerT.TLSHandler).init(self.arena, .{ .handler = async_handler }, .{ .tls_client = try tls.asyn.Client(AsyncHandlerT.TLSHandler).init(self.arena, .{ .handler = async_handler }, .{
.host = self.host(), .host = self.host(),
.root_ca = self._client.root_ca, .root_ca = self._client.root_ca,
.insecure_skip_verify = self._tls_verify_host == false,
// .key_log_callback = tls.config.key_log.callback // .key_log_callback = tls.config.key_log.callback
}), }),
}; };
@@ -367,6 +376,7 @@ pub const Request = struct {
if (@hasDecl(posix.TCP, "NODELAY")) { if (@hasDecl(posix.TCP, "NODELAY")) {
try posix.setsockopt(socket, posix.IPPROTO.TCP, posix.TCP.NODELAY, &std.mem.toBytes(@as(c_int, 1))); try posix.setsockopt(socket, posix.IPPROTO.TCP, posix.TCP.NODELAY, &std.mem.toBytes(@as(c_int, 1)));
} }
self._socket = socket; self._socket = socket;
return .{ socket, address }; return .{ socket, address };
} }
@@ -544,7 +554,6 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
const n = n_ catch |err| { const n = n_ catch |err| {
return self.handleError("Read error", err); return self.handleError("Read error", err);
}; };
if (n == 0) { if (n == 0) {
return self.handleError("Connection closed", error.ConnectionResetByPeer); return self.handleError("Connection closed", error.ConnectionResetByPeer);
} }
@@ -662,12 +671,12 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
switch (self.protocol) { switch (self.protocol) {
.tls_client => |*tls_client| { .tls_client => |*tls_client| {
handler.receive();
try tls_client.onConnect(); try tls_client.onConnect();
// when TLS is active, from a network point of view // when TLS is active, from a network point of view
// it's no longer a strict REQ->RES. We pretty much // it's no longer a strict REQ->RES. We pretty much
// have to constantly receive data (e.g. to process // have to constantly receive data (e.g. to process
// the handshake) // the handshake)
handler.receive();
}, },
.plain => { .plain => {
// queue everything up // queue everything up
@@ -847,6 +856,7 @@ const SyncHandler = struct {
.tls = try tls.client(std.net.Stream{ .handle = socket }, .{ .tls = try tls.client(std.net.Stream{ .handle = socket }, .{
.host = request.host(), .host = request.host(),
.root_ca = request._client.root_ca, .root_ca = request._client.root_ca,
.insecure_skip_verify = request._tls_verify_host == false,
// .key_log_callback = tls.config.key_log.callback, // .key_log_callback = tls.config.key_log.callback,
}), }),
}; };
@@ -1704,13 +1714,12 @@ test "HttpClient: sync no body" {
} }
test "HttpClient: sync tls no body" { test "HttpClient: sync tls no body" {
// https://github.com/ianic/tls.zig/issues/10 for (0..5) |_| {
for (0..1) |_| {
var client = try testClient(); var client = try testClient();
defer client.deinit(); defer client.deinit();
var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/simple"); var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/simple");
var res = try req.sendSync(.{}); var res = try req.sendSync(.{ .tls_verify_host = false });
try testing.expectEqual(null, try res.next()); try testing.expectEqual(null, try res.next());
try testing.expectEqual(200, res.header.status); try testing.expectEqual(200, res.header.status);
@@ -1741,14 +1750,13 @@ test "HttpClient: sync tls with body" {
defer arr.deinit(testing.allocator); defer arr.deinit(testing.allocator);
try arr.ensureTotalCapacity(testing.allocator, 20); try arr.ensureTotalCapacity(testing.allocator, 20);
// https://github.com/ianic/tls.zig/issues/10 for (0..5) |_| {
for (0..1) |_| {
defer arr.clearRetainingCapacity(); defer arr.clearRetainingCapacity();
var client = try testClient(); var client = try testClient();
defer client.deinit(); defer client.deinit();
var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/body"); var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/body");
var res = try req.sendSync(.{}); var res = try req.sendSync(.{ .tls_verify_host = false });
while (try res.next()) |data| { while (try res.next()) |data| {
arr.appendSliceAssumeCapacity(data); arr.appendSliceAssumeCapacity(data);
@@ -1766,14 +1774,13 @@ test "HttpClient: sync redirect from TLS to Plaintext" {
defer arr.deinit(testing.allocator); defer arr.deinit(testing.allocator);
try arr.ensureTotalCapacity(testing.allocator, 20); try arr.ensureTotalCapacity(testing.allocator, 20);
// https://github.com/ianic/tls.zig/issues/10 for (0..5) |_| {
for (0..1) |_| {
defer arr.clearRetainingCapacity(); defer arr.clearRetainingCapacity();
var client = try testClient(); var client = try testClient();
defer client.deinit(); defer client.deinit();
var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/redirect/insecure"); var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/redirect/insecure");
var res = try req.sendSync(.{}); var res = try req.sendSync(.{ .tls_verify_host = false });
while (try res.next()) |data| { while (try res.next()) |data| {
arr.appendSliceAssumeCapacity(data); arr.appendSliceAssumeCapacity(data);
@@ -1793,14 +1800,13 @@ test "HttpClient: sync redirect plaintext to TLS" {
defer arr.deinit(testing.allocator); defer arr.deinit(testing.allocator);
try arr.ensureTotalCapacity(testing.allocator, 20); try arr.ensureTotalCapacity(testing.allocator, 20);
// https://github.com/ianic/tls.zig/issues/10 for (0..5) |_| {
for (0..1) |_| {
defer arr.clearRetainingCapacity(); defer arr.clearRetainingCapacity();
var client = try testClient(); var client = try testClient();
defer client.deinit(); defer client.deinit();
var req = try client.request(.GET, "http://127.0.0.1:9582/http_client/redirect/secure"); var req = try client.request(.GET, "http://127.0.0.1:9582/http_client/redirect/secure");
var res = try req.sendSync(.{}); var res = try req.sendSync(.{ .tls_verify_host = false });
while (try res.next()) |data| { while (try res.next()) |data| {
arr.appendSliceAssumeCapacity(data); arr.appendSliceAssumeCapacity(data);
@@ -1818,7 +1824,7 @@ test "HttpClient: sync GET redirect" {
defer client.deinit(); defer client.deinit();
var req = try client.request(.GET, "http://127.0.0.1:9582/http_client/redirect"); var req = try client.request(.GET, "http://127.0.0.1:9582/http_client/redirect");
var res = try req.sendSync(.{}); var res = try req.sendSync(.{ .tls_verify_host = false });
try testing.expectEqual("over 9000!", try res.next()); try testing.expectEqual("over 9000!", try res.next());
try testing.expectEqual(201, res.header.status); try testing.expectEqual(201, res.header.status);
@@ -1866,9 +1872,6 @@ test "HttpClient: async no body" {
var handler = try CaptureHandler.init(); var handler = try CaptureHandler.init();
defer handler.deinit(); defer handler.deinit();
var loop = try jsruntime.Loop.init(testing.allocator);
defer loop.deinit();
var req = try client.request(.GET, "HTTP://127.0.0.1:9582/http_client/simple"); var req = try client.request(.GET, "HTTP://127.0.0.1:9582/http_client/simple");
try req.sendAsync(&handler.loop, &handler, .{}); try req.sendAsync(&handler.loop, &handler, .{});
try handler.waitUntilDone(); try handler.waitUntilDone();
@@ -1886,11 +1889,8 @@ test "HttpClient: async no body" {
// var handler = try CaptureHandler.init(); // var handler = try CaptureHandler.init();
// defer handler.deinit(); // defer handler.deinit();
// var loop = try jsruntime.Loop.init(testing.allocator);
// defer loop.deinit();
// var req = try client.request(.GET, "HTTPs://127.0.0.1:9581/http_client/simple"); // var req = try client.request(.GET, "HTTPs://127.0.0.1:9581/http_client/simple");
// try req.sendAsync(&handler.loop, &handler, .{}); // try req.sendAsync(&handler.loop, &handler, .{ .tls_verify_host = false });
// try handler.waitUntilDone(); // try handler.waitUntilDone();
// const res = handler.response; // const res = handler.response;
@@ -1929,9 +1929,6 @@ test "HttpClient: async redirect" {
var handler = try CaptureHandler.init(); var handler = try CaptureHandler.init();
defer handler.deinit(); defer handler.deinit();
var loop = try jsruntime.Loop.init(testing.allocator);
defer loop.deinit();
var req = try client.request(.GET, "HTTP://127.0.0.1:9582/http_client/redirect"); var req = try client.request(.GET, "HTTP://127.0.0.1:9582/http_client/redirect");
try req.sendAsync(&handler.loop, &handler, .{}); try req.sendAsync(&handler.loop, &handler, .{});
@@ -2094,7 +2091,5 @@ fn testReader(state: *State, res: *TestResponse, data: []const u8) !void {
} }
fn testClient() !Client { fn testClient() !Client {
const test_dir = try std.fs.cwd().openDir("tests", .{}); return try Client.init(testing.allocator, 1);
const root_ca = try tls.config.CertBundle.fromFile(testing.allocator, test_dir, "test_cert.pem");
return try Client.init(testing.allocator, 1, root_ca);
} }

View File

@@ -88,7 +88,7 @@ fn testExecFn(
std.debug.print("documentHTMLClose error: {s}\n", .{@errorName(err)}); std.debug.print("documentHTMLClose error: {s}\n", .{@errorName(err)});
}; };
var http_client = try @import("http/client.zig").Client.init(alloc, 5, null ); var http_client = try @import("http/client.zig").Client.init(alloc, 5);
defer http_client.deinit(); defer http_client.deinit();
try js_env.setUserContext(.{ try js_env.setUserContext(.{

View File

@@ -52,13 +52,13 @@ test "tests:beforeAll" {
{ {
const address = try std.net.Address.parseIp("127.0.0.1", 9582); const address = try std.net.Address.parseIp("127.0.0.1", 9582);
const thread = try std.Thread.spawn(.{}, serveHTTP, .{ address }); const thread = try std.Thread.spawn(.{}, serveHTTP, .{address});
thread.detach(); thread.detach();
} }
{ {
const address = try std.net.Address.parseIp("127.0.0.1", 9581); const address = try std.net.Address.parseIp("127.0.0.1", 9581);
const thread = try std.Thread.spawn(.{}, serveHTTPS, .{ address }); const thread = try std.Thread.spawn(.{}, serveHTTPS, .{address});
thread.detach(); thread.detach();
} }
@@ -67,6 +67,7 @@ test "tests:beforeAll" {
const thread = try std.Thread.spawn(.{}, serveCDP, .{address}); const thread = try std.Thread.spawn(.{}, serveCDP, .{address});
thread.detach(); thread.detach();
} }
// need to wait for the servers to be listening, else tests will fail because // need to wait for the servers to be listening, else tests will fail because
// they aren't able to connect. // they aren't able to connect.
wg.wait(); wg.wait();
@@ -144,11 +145,6 @@ fn serveHTTP(address: std.net.Address) !void {
// isn't a jerk. // isn't a jerk.
fn serveHTTPS(address: std.net.Address) !void { fn serveHTTPS(address: std.net.Address) !void {
const allocator = gpa.allocator(); const allocator = gpa.allocator();
const test_dir = try std.fs.cwd().openDir("tests", .{});
// openssl req -x509 -newkey rsa:4096 -keyout tests/test_key.pem -out tests/test_cert.pem -sha256 -days 3650 -nodes -subj "/CN=127.0.0.1"
var auth = try tls.config.CertKeyPair.load(allocator, test_dir, "test_cert.pem", "test_key.pem");
defer auth.deinit(allocator);
var listener = try address.listen(.{ .reuse_address = true }); var listener = try address.listen(.{ .reuse_address = true });
defer listener.deinit(); defer listener.deinit();
@@ -174,7 +170,7 @@ fn serveHTTPS(address: std.net.Address) !void {
}; };
defer stream.close(); defer stream.close();
var conn = try tls.server(stream, .{ .auth = &auth }); var conn = try tls.server(stream, .{ .auth = null });
defer conn.close() catch {}; defer conn.close() catch {};
var pos: usize = 0; var pos: usize = 0;
@@ -208,7 +204,7 @@ fn serveHTTPS(address: std.net.Address) !void {
const to_send = rand.intRangeAtMost(usize, 1, unsent.len); const to_send = rand.intRangeAtMost(usize, 1, unsent.len);
const sent = try conn.write(unsent[0..to_send]); const sent = try conn.write(unsent[0..to_send]);
unsent = unsent[sent..]; unsent = unsent[sent..];
// std.time.sleep(std.time.ns_per_us * 5); std.time.sleep(std.time.ns_per_us * 5);
} }
break; break;
} }

View File

@@ -28,7 +28,7 @@ const Loop = jsruntime.Loop;
const Env = jsruntime.Env; const Env = jsruntime.Env;
const Window = @import("../html/window.zig").Window; const Window = @import("../html/window.zig").Window;
const storage = @import("../storage/storage.zig"); const storage = @import("../storage/storage.zig");
const Client = @import("asyncio").Client; const HttpClient = @import("../http/client.zig").Client;
const Types = @import("../main_wpt.zig").Types; const Types = @import("../main_wpt.zig").Types;
const UserContext = @import("../main_wpt.zig").UserContext; const UserContext = @import("../main_wpt.zig").UserContext;
@@ -55,13 +55,13 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const
var loop = try Loop.init(alloc); var loop = try Loop.init(alloc);
defer loop.deinit(); defer loop.deinit();
var cli = Client{ .allocator = alloc }; var http_client = try HttpClient.init(alloc, 2);
defer cli.deinit(); defer http_client.deinit();
var js_env: Env = undefined; var js_env: Env = undefined;
Env.init(&js_env, alloc, &loop, UserContext{ Env.init(&js_env, alloc, &loop, UserContext{
.document = html_doc, .document = html_doc,
.httpClient = &cli, .http_client = &http_client,
}); });
defer js_env.deinit(); defer js_env.deinit();

View File

@@ -1,29 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFCTCCAvGgAwIBAgIUQizPG9ybhujB5BEDeapY//wFwgwwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTI1MDMxOTEyMjYzNFoXDTM1MDMx
NzEyMjYzNFowFDESMBAGA1UEAwwJMTI3LjAuMC4xMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAzDHVQ7R3OxOxhoDILfEBkvSGcE6aeI+kEV52qmFzlFM9
HCT+m4pSaMhtlsqwUA1N5FgkPD9wmka1EqrExnGdFbpZ6uWe0nTUMhNag5pHBT2/
aYjEWcs/6C+lyve04w12jgkzvQcgbrQZTwVe2cxA2GTnggtiMrwhIuDjKpO12Yy5
Fn+SrbhLtD7gYM3EOqrP7UukA/BKjYCEBKYkjBXjdODU7+xp8AYUwMQcOgIpMVAR
yJIGbKZ8PfLrmt8EaSHNe0d44YeLgRlOd9raw9aSKxdNDNYWYfAIDtV92gRhH24q
mi+THG8MUeAM7OpssQNNcH0En5ZmOGcKhwaTYM41jNZvPosVfrRTVGQimjrz42IZ
QUx5V+JxY5VMWrwxWxjPallKVN+2LCLdBV6I+zCrwghvQgc4UL8F6hTVg7aIez7E
oNamG77ooHc53eHBmIFGokhuPhFs0HVRIQdFO7Sm1Y1Lk6kek0SlZPge1x0nODRS
tPBpYJlnfzIzu7k8c9u7u7VhUqkRphxKz5xanKslozCjzk5UnmKbtQDJKQOcv+OO
pAm1InDDlw84GKw77aXfQQIJmej0sbDg0NadTMlaOpKnbh7VZBs2VYFmf42B1r1m
4yOdqdnDS/q0m+tkFZbBhDXuSOAkutNT7MUJXdL+/S68k+oHkGXMG/xkYNqTIuUC
AwEAAaNTMFEwHQYDVR0OBBYEFC0RmoMUNOMdBwdVnq2IjDG0B4HLMB8GA1UdIwQY
MBaAFC0RmoMUNOMdBwdVnq2IjDG0B4HLMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggIBAB/2OZOz/V3Qz9RG/XQfPtfDTzV2dzEszaha8CRat5TmEcg+
s0AcqEyQd2Zn6DOcKeykRx6H6LtP0o+LyNmvRghoWbaz8zknikPotIu7Zw2cKfIq
2gjAq+sZCGkNsMoEVDurVtGmhgtnP1QVMUhW+2gsFXZQhtFjX2jYtMbuJ6A0or7q
Zf1ESCBemTFpgGvLZGiKwc4Re1vrNguyLhln2lYRfSh5UwSyL+KhnB67CpIV6rkc
jYOnN1VffxigqwBBeFWAZG12z8/5cwzmGno9f7w89s4rk1vo3YzZYq/jVs54lkS+
JlVr8Guws4w1cb7fMRPf+Z28bBcPya4dPFrxbmi/9TK/AEEF2Yc7NgBCa3UI4B8j
2hdLKGq3Tj9Pi2XGIgJ4eejP2F1S2gOq2S10ix/kRyRTIcRaq1TvZt1WzaeLCZEk
gZQH5wS0BY/RvqqQf24APmzsHSFHFW7njpIEXJW4dvnVLJDZvk9r4cJXoTKTXjTb
wyOa4zHkrXW6a3OO+dehFyII6byqOz0oD3LVFF/Cxm3cUKaCBs7QrEDDri3bDu1w
Z/KJsOUV/Rv7YhgonfnAbKfmXAabKeGBSDA1jZG/cXE2qAgW49PUGsjhXLn0ENz4
nOwCbUGCpnX8m5Myxx5rbJDliG3h+F937o7kyCDPqYaGRjGXbBjP4oaMhaHR
-----END CERTIFICATE-----

View File

@@ -1,52 +0,0 @@
-----BEGIN PRIVATE KEY-----
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
Z31M1oPIcKZ4ayKKiG5GYX/+juUlMA==
-----END PRIVATE KEY-----