| ... | @@ -12,8 +12,7 @@ const assert = std.debug.assert; | ... | @@ -12,8 +12,7 @@ const assert = std.debug.assert; |
| 12 | const Client = @This(); | 12 | const Client = @This(); |
| 13 | const proto = @import("protocol.zig"); | 13 | const proto = @import("protocol.zig"); |
| 14 | | 14 | |
| 15 | pub const default_connection_pool_size = 32; | 15 | pub const disable_tls = std.options.http_disable_tls; |
| 16 | pub const connection_pool_size = std.options.http_connection_pool_size; | | |
| 17 | | 16 | |
| 18 | allocator: Allocator, | 17 | allocator: Allocator, |
| 19 | ca_bundle: std.crypto.Certificate.Bundle = .{}, | 18 | ca_bundle: std.crypto.Certificate.Bundle = .{}, |
| ... | @@ -50,7 +49,7 @@ pub const ConnectionPool = struct { | ... | @@ -50,7 +49,7 @@ pub const ConnectionPool = struct { |
| 50 | /// Open connections that are not currently in use. | 49 | /// Open connections that are not currently in use. |
| 51 | free: Queue = .{}, | 50 | free: Queue = .{}, |
| 52 | free_len: usize = 0, | 51 | free_len: usize = 0, |
| 53 | free_size: usize = connection_pool_size, | 52 | free_size: usize = 32, |
| 54 | | 53 | |
| 55 | /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe. | 54 | /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe. |
| 56 | /// If no connection is found, null is returned. | 55 | /// If no connection is found, null is returned. |
| ... | @@ -127,23 +126,43 @@ pub const ConnectionPool = struct { | ... | @@ -127,23 +126,43 @@ pub const ConnectionPool = struct { |
| 127 | pool.used.append(node); | 126 | pool.used.append(node); |
| 128 | } | 127 | } |
| 129 | | 128 | |
| 130 | pub fn deinit(pool: *ConnectionPool, client: *Client) void { | 129 | /// Resizes the connection pool. This function is threadsafe. |
| | 130 | /// |
| | 131 | /// If the new size is smaller than the current size, then idle connections will be closed until the pool is the new size. |
| | 132 | pub fn resize(pool: *ConnectionPool, allocator: Allocator, new_size: usize) void { |
| | 133 | pool.mutex.lock(); |
| | 134 | defer pool.mutex.unlock(); |
| | 135 | |
| | 136 | var next = pool.free.first; |
| | 137 | _ = next; |
| | 138 | while (pool.free_len > new_size) { |
| | 139 | const popped = pool.free.popFirst() orelse unreachable; |
| | 140 | pool.free_len -= 1; |
| | 141 | |
| | 142 | popped.data.close(allocator); |
| | 143 | allocator.destroy(popped); |
| | 144 | } |
| | 145 | |
| | 146 | pool.free_size = new_size; |
| | 147 | } |
| | 148 | |
| | 149 | pub fn deinit(pool: *ConnectionPool, allocator: Allocator) void { |
| 131 | pool.mutex.lock(); | 150 | pool.mutex.lock(); |
| 132 | | 151 | |
| 133 | var next = pool.free.first; | 152 | var next = pool.free.first; |
| 134 | while (next) |node| { | 153 | while (next) |node| { |
| 135 | defer client.allocator.destroy(node); | 154 | defer allocator.destroy(node); |
| 136 | next = node.next; | 155 | next = node.next; |
| 137 | | 156 | |
| 138 | node.data.close(client.allocator); | 157 | node.data.close(allocator); |
| 139 | } | 158 | } |
| 140 | | 159 | |
| 141 | next = pool.used.first; | 160 | next = pool.used.first; |
| 142 | while (next) |node| { | 161 | while (next) |node| { |
| 143 | defer client.allocator.destroy(node); | 162 | defer allocator.destroy(node); |
| 144 | next = node.next; | 163 | next = node.next; |
| 145 | | 164 | |
| 146 | node.data.close(client.allocator); | 165 | node.data.close(allocator); |
| 147 | } | 166 | } |
| 148 | | 167 | |
| 149 | pool.* = undefined; | 168 | pool.* = undefined; |
| ... | @@ -159,7 +178,7 @@ pub const Connection = struct { | ... | @@ -159,7 +178,7 @@ pub const Connection = struct { |
| 159 | | 178 | |
| 160 | stream: net.Stream, | 179 | stream: net.Stream, |
| 161 | /// undefined unless protocol is tls. | 180 | /// undefined unless protocol is tls. |
| 162 | tls_client: *std.crypto.tls.Client, | 181 | tls_client: if (!disable_tls) *std.crypto.tls.Client else void, |
| 163 | | 182 | |
| 164 | protocol: Protocol, | 183 | protocol: Protocol, |
| 165 | host: []u8, | 184 | host: []u8, |
| ... | @@ -174,11 +193,8 @@ pub const Connection = struct { | ... | @@ -174,11 +193,8 @@ pub const Connection = struct { |
| 174 | read_buf: [buffer_size]u8 = undefined, | 193 | read_buf: [buffer_size]u8 = undefined, |
| 175 | write_buf: [buffer_size]u8 = undefined, | 194 | write_buf: [buffer_size]u8 = undefined, |
| 176 | | 195 | |
| 177 | pub fn readvDirect(conn: *Connection, buffers: []std.os.iovec) ReadError!usize { | 196 | pub fn readvDirectTls(conn: *Connection, buffers: []std.os.iovec) ReadError!usize { |
| 178 | return switch (conn.protocol) { | 197 | return conn.tls_client.readv(conn.stream, buffers) catch |err| { |
| 179 | .plain => conn.stream.readv(buffers), | | |
| 180 | .tls => conn.tls_client.readv(conn.stream, buffers), | | |
| 181 | } catch |err| { | | |
| 182 | // TODO: https://github.com/ziglang/zig/issues/2473 | 198 | // TODO: https://github.com/ziglang/zig/issues/2473 |
| 183 | if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert; | 199 | if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert; |
| 184 | | 200 | |
| ... | @@ -191,6 +207,20 @@ pub const Connection = struct { | ... | @@ -191,6 +207,20 @@ pub const Connection = struct { |
| 191 | }; | 207 | }; |
| 192 | } | 208 | } |
| 193 | | 209 | |
| | 210 | pub fn readvDirect(conn: *Connection, buffers: []std.os.iovec) ReadError!usize { |
| | 211 | if (conn.protocol == .tls) { |
| | 212 | if (disable_tls) unreachable; |
| | 213 | |
| | 214 | return conn.readvDirectTls(buffers); |
| | 215 | } |
| | 216 | |
| | 217 | return conn.stream.readv(buffers) catch |err| switch (err) { |
| | 218 | error.ConnectionTimedOut => return error.ConnectionTimedOut, |
| | 219 | error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer, |
| | 220 | else => return error.UnexpectedReadFailure, |
| | 221 | }; |
| | 222 | } |
| | 223 | |
| 194 | pub fn fill(conn: *Connection) ReadError!void { | 224 | pub fn fill(conn: *Connection) ReadError!void { |
| 195 | if (conn.read_end != conn.read_start) return; | 225 | if (conn.read_end != conn.read_start) return; |
| 196 | | 226 | |
| ... | @@ -257,11 +287,21 @@ pub const Connection = struct { | ... | @@ -257,11 +287,21 @@ pub const Connection = struct { |
| 257 | return Reader{ .context = conn }; | 287 | return Reader{ .context = conn }; |
| 258 | } | 288 | } |
| 259 | | 289 | |
| | 290 | pub fn writeAllDirectTls(conn: *Connection, buffer: []const u8) WriteError!void { |
| | 291 | return conn.tls_client.writeAll(conn.stream, buffer) catch |err| switch (err) { |
| | 292 | error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer, |
| | 293 | else => return error.UnexpectedWriteFailure, |
| | 294 | }; |
| | 295 | } |
| | 296 | |
| 260 | pub fn writeAllDirect(conn: *Connection, buffer: []const u8) WriteError!void { | 297 | pub fn writeAllDirect(conn: *Connection, buffer: []const u8) WriteError!void { |
| 261 | return switch (conn.protocol) { | 298 | if (conn.protocol == .tls) { |
| 262 | .plain => conn.stream.writeAll(buffer), | 299 | if (disable_tls) unreachable; |
| 263 | .tls => conn.tls_client.writeAll(conn.stream, buffer), | 300 | |
| 264 | } catch |err| switch (err) { | 301 | return conn.writeAllDirectTls(buffer); |
| | 302 | } |
| | 303 | |
| | 304 | return conn.stream.writeAll(buffer) catch |err| switch (err) { |
| 265 | error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer, | 305 | error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer, |
| 266 | else => return error.UnexpectedWriteFailure, | 306 | else => return error.UnexpectedWriteFailure, |
| 267 | }; | 307 | }; |
| ... | @@ -303,6 +343,8 @@ pub const Connection = struct { | ... | @@ -303,6 +343,8 @@ pub const Connection = struct { |
| 303 | | 343 | |
| 304 | pub fn close(conn: *Connection, allocator: Allocator) void { | 344 | pub fn close(conn: *Connection, allocator: Allocator) void { |
| 305 | if (conn.protocol == .tls) { | 345 | if (conn.protocol == .tls) { |
| | 346 | if (disable_tls) unreachable; |
| | 347 | |
| 306 | // try to cleanly close the TLS connection, for any server that cares. | 348 | // try to cleanly close the TLS connection, for any server that cares. |
| 307 | _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {}; | 349 | _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {}; |
| 308 | allocator.destroy(conn.tls_client); | 350 | allocator.destroy(conn.tls_client); |
| ... | @@ -932,7 +974,7 @@ pub const ProxyInformation = struct { | ... | @@ -932,7 +974,7 @@ pub const ProxyInformation = struct { |
| 932 | /// Release all associated resources with the client. | 974 | /// Release all associated resources with the client. |
| 933 | /// TODO: currently leaks all request allocated data | 975 | /// TODO: currently leaks all request allocated data |
| 934 | pub fn deinit(client: *Client) void { | 976 | pub fn deinit(client: *Client) void { |
| 935 | client.connection_pool.deinit(client); | 977 | client.connection_pool.deinit(client.allocator); |
| 936 | | 978 | |
| 937 | if (client.http_proxy) |*proxy| { | 979 | if (client.http_proxy) |*proxy| { |
| 938 | proxy.allocator.free(proxy.host); | 980 | proxy.allocator.free(proxy.host); |
| ... | @@ -1046,6 +1088,9 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec | ... | @@ -1046,6 +1088,9 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec |
| 1046 | })) |node| | 1088 | })) |node| |
| 1047 | return node; | 1089 | return node; |
| 1048 | | 1090 | |
| | 1091 | if (disable_tls and protocol == .tls) |
| | 1092 | return error.TlsInitializationFailed; |
| | 1093 | |
| 1049 | const conn = try client.allocator.create(ConnectionPool.Node); | 1094 | const conn = try client.allocator.create(ConnectionPool.Node); |
| 1050 | errdefer client.allocator.destroy(conn); | 1095 | errdefer client.allocator.destroy(conn); |
| 1051 | conn.* = .{ .data = undefined }; | 1096 | conn.* = .{ .data = undefined }; |
| ... | @@ -1073,17 +1118,16 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec | ... | @@ -1073,17 +1118,16 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec |
| 1073 | }; | 1118 | }; |
| 1074 | errdefer client.allocator.free(conn.data.host); | 1119 | errdefer client.allocator.free(conn.data.host); |
| 1075 | | 1120 | |
| 1076 | switch (protocol) { | 1121 | if (protocol == .tls) { |
| 1077 | .plain => {}, | 1122 | if (disable_tls) unreachable; |
| 1078 | .tls => { | | |
| 1079 | conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client); | | |
| 1080 | errdefer client.allocator.destroy(conn.data.tls_client); | | |
| 1081 | | 1123 | |
| 1082 | conn.data.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed; | 1124 | conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client); |
| 1083 | // This is appropriate for HTTPS because the HTTP headers contain | 1125 | errdefer client.allocator.destroy(conn.data.tls_client); |
| 1084 | // the content length which is used to detect truncation attacks. | 1126 | |
| 1085 | conn.data.tls_client.allow_truncation_attacks = true; | 1127 | conn.data.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed; |
| 1086 | }, | 1128 | // This is appropriate for HTTPS because the HTTP headers contain |
| | 1129 | // the content length which is used to detect truncation attacks. |
| | 1130 | conn.data.tls_client.allow_truncation_attacks = true; |
| 1087 | } | 1131 | } |
| 1088 | | 1132 | |
| 1089 | client.connection_pool.addUsed(conn); | 1133 | client.connection_pool.addUsed(conn); |