| ... | ... | @@ -14,7 +14,11 @@ const proto = @import("protocol.zig"); |
| 14 | 14 | |
| 15 | 15 | pub const disable_tls = std.options.http_disable_tls; |
| 16 | 16 | |
| 17 | /// Allocator used for all allocations made by the client. |
| 18 | /// |
| 19 | /// This allocator must be thread-safe. |
| 17 | 20 | allocator: Allocator, |
| 21 | |
| 18 | 22 | ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{}, |
| 19 | 23 | ca_bundle_mutex: std.Thread.Mutex = .{}, |
| 20 | 24 | |
| ... | ... | @@ -26,10 +30,10 @@ next_https_rescan_certs: bool = true, |
| 26 | 30 | connection_pool: ConnectionPool = .{}, |
| 27 | 31 | |
| 28 | 32 | /// This is the proxy that will handle http:// connections. It *must not* be modified when the client has any active connections. |
| 29 | | http_proxy: ?ProxyInformation = null, |
| 33 | http_proxy: ?Proxy = null, |
| 30 | 34 | |
| 31 | 35 | /// This is the proxy that will handle https:// connections. It *must not* be modified when the client has any active connections. |
| 32 | | https_proxy: ?ProxyInformation = null, |
| 36 | https_proxy: ?Proxy = null, |
| 33 | 37 | |
| 34 | 38 | /// A set of linked lists of connections that can be reused. |
| 35 | 39 | pub const ConnectionPool = struct { |
| ... | ... | @@ -61,6 +65,8 @@ pub const ConnectionPool = struct { |
| 61 | 65 | while (next) |node| : (next = node.prev) { |
| 62 | 66 | if (node.data.protocol != criteria.protocol) continue; |
| 63 | 67 | if (node.data.port != criteria.port) continue; |
| 68 | |
| 69 | // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4) |
| 64 | 70 | if (!std.ascii.eqlIgnoreCase(node.data.host, criteria.host)) continue; |
| 65 | 71 | |
| 66 | 72 | pool.acquireUnsafe(node); |
| ... | ... | @@ -88,6 +94,9 @@ pub const ConnectionPool = struct { |
| 88 | 94 | |
| 89 | 95 | /// Tries to release a connection back to the connection pool. This function is threadsafe. |
| 90 | 96 | /// If the connection is marked as closing, it will be closed instead. |
| 97 | /// |
| 98 | /// The allocator must be the owner of all nodes in this pool. |
| 99 | /// The allocator must be the owner of all resources associated with the connection. |
| 91 | 100 | pub fn release(pool: *ConnectionPool, allocator: Allocator, connection: *Connection) void { |
| 92 | 101 | pool.mutex.lock(); |
| 93 | 102 | defer pool.mutex.unlock(); |
| ... | ... | @@ -195,7 +204,7 @@ pub const Connection = struct { |
| 195 | 204 | |
| 196 | 205 | pub fn readvDirectTls(conn: *Connection, buffers: []std.os.iovec) ReadError!usize { |
| 197 | 206 | return conn.tls_client.readv(conn.stream, buffers) catch |err| { |
| 198 | | // TODO: https://github.com/ziglang/zig/issues/2473 |
| 207 | // https://github.com/ziglang/zig/issues/2473 |
| 199 | 208 | if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert; |
| 200 | 209 | |
| 201 | 210 | switch (err) { |
| ... | ... | @@ -978,7 +987,7 @@ pub const Request = struct { |
| 978 | 987 | } |
| 979 | 988 | }; |
| 980 | 989 | |
| 981 | | pub const ProxyInformation = struct { |
| 990 | pub const Proxy = struct { |
| 982 | 991 | allocator: Allocator, |
| 983 | 992 | headers: http.Headers, |
| 984 | 993 | |
| ... | ... | @@ -990,8 +999,12 @@ pub const ProxyInformation = struct { |
| 990 | 999 | }; |
| 991 | 1000 | |
| 992 | 1001 | /// Release all associated resources with the client. |
| 993 | | /// TODO: currently leaks all request allocated data |
| 1002 | /// |
| 1003 | /// All pending requests must be de-initialized and all active connections released |
| 1004 | /// before calling this function. |
| 994 | 1005 | pub fn deinit(client: *Client) void { |
| 1006 | assert(client.connection_pool.used.first == null); // There are still active requests. |
| 1007 | |
| 995 | 1008 | client.connection_pool.deinit(client.allocator); |
| 996 | 1009 | |
| 997 | 1010 | if (client.http_proxy) |*proxy| { |
| ... | ... | @@ -1013,6 +1026,12 @@ pub fn deinit(client: *Client) void { |
| 1013 | 1026 | /// Uses the *_proxy environment variable to set any unset proxies for the client. |
| 1014 | 1027 | /// This function *must not* be called when the client has any active connections. |
| 1015 | 1028 | pub fn loadDefaultProxies(client: *Client) !void { |
| 1029 | // Prevent any new connections from being created. |
| 1030 | client.connection_pool.mutex.lock(); |
| 1031 | defer client.connection_pool.mutex.unlock(); |
| 1032 | |
| 1033 | assert(client.connection_pool.used.first == null); // There are still active requests. |
| 1034 | |
| 1016 | 1035 | if (client.http_proxy == null) http: { |
| 1017 | 1036 | const content: []const u8 = if (std.process.hasEnvVarConstant("http_proxy")) |
| 1018 | 1037 | try std.process.getEnvVarOwned(client.allocator, "http_proxy") |
| ... | ... | @@ -1203,7 +1222,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti |
| 1203 | 1222 | /// This function is threadsafe. |
| 1204 | 1223 | pub fn connectTunnel( |
| 1205 | 1224 | client: *Client, |
| 1206 | | proxy: *ProxyInformation, |
| 1225 | proxy: *Proxy, |
| 1207 | 1226 | tunnel_host: []const u8, |
| 1208 | 1227 | tunnel_port: u16, |
| 1209 | 1228 | ) !*Connection { |
| ... | ... | @@ -1217,7 +1236,7 @@ pub fn connectTunnel( |
| 1217 | 1236 | return node; |
| 1218 | 1237 | |
| 1219 | 1238 | var maybe_valid = false; |
| 1220 | | _ = tunnel: { |
| 1239 | (tunnel: { |
| 1221 | 1240 | const conn = try client.connectTcp(proxy.host, proxy.port, proxy.protocol); |
| 1222 | 1241 | errdefer { |
| 1223 | 1242 | conn.closing = true; |
| ... | ... | @@ -1241,7 +1260,7 @@ pub fn connectTunnel( |
| 1241 | 1260 | var req = client.open(.CONNECT, uri, proxy.headers, .{ |
| 1242 | 1261 | .handle_redirects = false, |
| 1243 | 1262 | .connection = conn, |
| 1244 | | .header_strategy = .{ .static = buffer[0..] }, |
| 1263 | .header_strategy = .{ .static = &buffer }, |
| 1245 | 1264 | }) catch |err| { |
| 1246 | 1265 | std.log.debug("err {}", .{err}); |
| 1247 | 1266 | break :tunnel err; |
| ... | ... | @@ -1269,7 +1288,7 @@ pub fn connectTunnel( |
| 1269 | 1288 | conn.closing = false; |
| 1270 | 1289 | |
| 1271 | 1290 | return conn; |
| 1272 | | } catch { |
| 1291 | }) catch { |
| 1273 | 1292 | // something went wrong with the tunnel |
| 1274 | 1293 | proxy.supports_connect = maybe_valid; |
| 1275 | 1294 | return error.TunnelNotSupported; |
| ... | ... | @@ -1287,7 +1306,7 @@ pub const ConnectError = ConnectErrorPartial || RequestError; |
| 1287 | 1306 | /// This function is threadsafe. |
| 1288 | 1307 | pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectError!*Connection { |
| 1289 | 1308 | // pointer required so that `supports_connect` can be updated if a CONNECT fails |
| 1290 | | const potential_proxy: ?*ProxyInformation = switch (protocol) { |
| 1309 | const potential_proxy: ?*Proxy = switch (protocol) { |
| 1291 | 1310 | .plain => if (client.http_proxy) |*proxy_info| proxy_info else null, |
| 1292 | 1311 | .tls => if (client.https_proxy) |*proxy_info| proxy_info else null, |
| 1293 | 1312 | }; |
| ... | ... | @@ -1298,12 +1317,12 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio |
| 1298 | 1317 | return client.connectTcp(host, port, protocol); |
| 1299 | 1318 | } |
| 1300 | 1319 | |
| 1301 | | _ = if (proxy.supports_connect) tunnel: { |
| 1320 | if (proxy.supports_connect) tunnel: { |
| 1302 | 1321 | return connectTunnel(client, proxy, host, port) catch |err| switch (err) { |
| 1303 | 1322 | error.TunnelNotSupported => break :tunnel, |
| 1304 | 1323 | else => |e| return e, |
| 1305 | 1324 | }; |
| 1306 | | }; |
| 1325 | } |
| 1307 | 1326 | |
| 1308 | 1327 | // fall back to using the proxy as a normal http proxy |
| 1309 | 1328 | const conn = try client.connectTcp(proxy.host, proxy.port, proxy.protocol); |