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