@@ -46,13 +46,15 @@ const MAX_HEADER_LINE_LEN = 4096;
4646pub const Client = struct {
4747 allocator : Allocator ,
4848 state_pool : StatePool ,
49+ http_proxy : ? Uri ,
4950 root_ca : tls.config.CertBundle ,
5051 tls_verify_host : bool = true ,
5152 idle_connections : IdleConnections ,
5253 connection_pool : std .heap .MemoryPool (Connection ),
5354
5455 const Opts = struct {
5556 tls_verify_host : bool = true ,
57+ http_proxy : ? std.Uri = null ,
5658 max_idle_connection : usize = 10 ,
5759 };
5860
@@ -70,6 +72,7 @@ pub const Client = struct {
7072 .root_ca = root_ca ,
7173 .allocator = allocator ,
7274 .state_pool = state_pool ,
75+ .http_proxy = opts .http_proxy ,
7376 .idle_connections = idle_connections ,
7477 .tls_verify_host = opts .tls_verify_host ,
7578 .connection_pool = std .heap .MemoryPool (Connection ).init (allocator ),
@@ -150,10 +153,14 @@ pub const Request = struct {
150153 method : Method ,
151154
152155 // The URI we're requested
153- uri : * const Uri ,
156+ request_uri : * const Uri ,
157+
158+ // The URI that we're connecting to. Can be different than request_uri when
159+ // proxying is enabled
160+ connect_uri : * const Uri ,
154161
155162 // If we're redirecting, this is where we're redirecting to. The only reason
156- // we really have this is so that we can set self.uri = &self.redirect_url.?
163+ // we really have this is so that we can set self.request_uri = &self.redirect_url.?
157164 redirect_uri : ? Uri = null ,
158165
159166 // Optional body
@@ -174,9 +181,12 @@ pub const Request = struct {
174181 // for other requests
175182 _keepalive : bool ,
176183
177- _port : u16 ,
184+ // extracted from request_uri
185+ _request_host : []const u8 ,
178186
179- _host : []const u8 ,
187+ // extracted from connect_uri
188+ _connect_port : u16 ,
189+ _connect_host : []const u8 ,
180190
181191 // whether or not the socket comes from the connection pool. If it does,
182192 // and we get an error sending the header, we might retry on a new connection
@@ -222,16 +232,18 @@ pub const Request = struct {
222232 };
223233
224234 fn init (client : * Client , state : * State , method : Method , uri : * const Uri ) ! Request {
225- const secure , const host , const port = try decomposeURL (uri );
235+ const decomposed = try decomposeURL (client , uri );
226236 return .{
227- .uri = uri ,
237+ .request_uri = uri ,
238+ .connect_uri = decomposed .connect_uri ,
228239 .body = null ,
229240 .headers = .{},
230241 .method = method ,
231242 .arena = state .arena .allocator (),
232- ._secure = secure ,
233- ._host = host ,
234- ._port = port ,
243+ ._secure = decomposed .secure ,
244+ ._connect_host = decomposed .connect_host ,
245+ ._connect_port = decomposed .connect_port ,
246+ ._request_host = decomposed .request_host ,
235247 ._state = state ,
236248 ._client = client ,
237249 ._connection = null ,
@@ -249,26 +261,44 @@ pub const Request = struct {
249261 self ._client .state_pool .release (self ._state );
250262 }
251263
252- fn decomposeURL (uri : * const Uri ) ! struct { bool , []const u8 , u16 } {
264+ const DecomposedURL = struct {
265+ secure : bool ,
266+ connect_port : u16 ,
267+ connect_host : []const u8 ,
268+ connect_uri : * const std.Uri ,
269+ request_host : []const u8 ,
270+ };
271+ fn decomposeURL (client : * const Client , uri : * const Uri ) ! DecomposedURL {
253272 if (uri .host == null ) {
254273 return error .UriMissingHost ;
255274 }
275+ const request_host = uri .host .? .percent_encoded ;
256276
257- var secure : bool = undefined ;
277+ var connect_uri = uri ;
278+ var connect_host = request_host ;
279+ if (client .http_proxy ) | * proxy | {
280+ connect_uri = proxy ;
281+ connect_host = proxy .host .? .percent_encoded ;
282+ }
258283
259- const scheme = uri .scheme ;
284+ var secure : bool = undefined ;
285+ const scheme = connect_uri .scheme ;
260286 if (std .ascii .eqlIgnoreCase (scheme , "https" )) {
261287 secure = true ;
262288 } else if (std .ascii .eqlIgnoreCase (scheme , "http" )) {
263289 secure = false ;
264290 } else {
265291 return error .UnsupportedUriScheme ;
266292 }
293+ const connect_port : u16 = connect_uri .port orelse if (secure ) 443 else 80 ;
267294
268- const host = uri .host .? .percent_encoded ;
269- const port : u16 = uri .port orelse if (secure ) 443 else 80 ;
270-
271- return .{ secure , host , port };
295+ return .{
296+ .secure = secure ,
297+ .connect_port = connect_port ,
298+ .connect_host = connect_host ,
299+ .connect_uri = connect_uri ,
300+ .request_host = request_host ,
301+ };
272302 }
273303
274304 // Called in deinit, but also called when we're redirecting to another page
@@ -293,11 +323,11 @@ pub const Request = struct {
293323 errdefer client .connection_pool .destroy (connection );
294324
295325 connection .* = .{
296- .socket = socket ,
297326 .tls = null ,
298- .port = self . _port ,
327+ .socket = socket ,
299328 .blocking = blocking ,
300- .host = try client .allocator .dupe (u8 , self ._host ),
329+ .port = self ._connect_port ,
330+ .host = try client .allocator .dupe (u8 , self ._connect_host ),
301331 };
302332
303333 return connection ;
@@ -374,12 +404,10 @@ pub const Request = struct {
374404 return err ;
375405 };
376406
377- errdefer self .destroyConnection (connection );
378-
379407 if (self ._secure ) {
380408 connection .tls = .{
381409 .blocking = try tls .client (std.net.Stream { .handle = socket }, .{
382- .host = connection . host ,
410+ .host = self . _connect_host ,
383411 .root_ca = self ._client .root_ca ,
384412 .insecure_skip_verify = self ._tls_verify_host == false ,
385413 // .key_log_callback = tls.config.key_log.callback,
@@ -391,11 +419,9 @@ pub const Request = struct {
391419 self ._connection_from_keepalive = false ;
392420 }
393421
394- errdefer self .destroyConnection (self ._connection .? );
395-
396422 var handler = SyncHandler { .request = self };
397423 return handler .send () catch | err | {
398- log .warn ("HTTP error: {any} ({any} {any} {d})" , .{ err , self .method , self .uri , self ._redirect_count });
424+ log .warn ("HTTP error: {any} ({any} {any} {d})" , .{ err , self .method , self .request_uri , self ._redirect_count });
399425 return err ;
400426 };
401427 }
@@ -461,7 +487,7 @@ pub const Request = struct {
461487 if (self ._secure ) {
462488 connection .tls = .{
463489 .nonblocking = try tls .nb .Client ().init (self ._client .allocator , .{
464- .host = connection . host ,
490+ .host = self . _connect_host ,
465491 .root_ca = self ._client .root_ca ,
466492 .insecure_skip_verify = self ._tls_verify_host == false ,
467493 .key_log_callback = tls .config .key_log .callback ,
@@ -501,7 +527,7 @@ pub const Request = struct {
501527 }
502528
503529 if (! self ._has_host_header ) {
504- try self .headers .append (arena , .{ .name = "Host" , .value = self ._host });
530+ try self .headers .append (arena , .{ .name = "Host" , .value = self ._request_host });
505531 }
506532
507533 try self .headers .append (arena , .{ .name = "User-Agent" , .value = "Lightpanda/1.0" });
@@ -512,7 +538,7 @@ pub const Request = struct {
512538 self .releaseConnection ();
513539
514540 // CANNOT reset the arena (╥﹏╥)
515- // We need it for self.uri (which we're about to use to resolve
541+ // We need it for self.request_uri (which we're about to use to resolve
516542 // redirect.location, and it might own some/all headers)
517543
518544 const redirect_count = self ._redirect_count ;
@@ -522,14 +548,16 @@ pub const Request = struct {
522548
523549 var buf = try self .arena .alloc (u8 , 2048 );
524550
525- const previous_host = self ._host ;
526- self .redirect_uri = try self .uri .resolve_inplace (redirect .location , & buf );
551+ const previous_request_host = self ._request_host ;
552+ self .redirect_uri = try self .request_uri .resolve_inplace (redirect .location , & buf );
527553
528- self .uri = & self .redirect_uri .? ;
529- const secure , const host , const port = try decomposeURL (self .uri );
530- self ._host = host ;
531- self ._port = port ;
532- self ._secure = secure ;
554+ self .request_uri = & self .redirect_uri .? ;
555+ const decomposed = try decomposeURL (self ._client , self .request_uri );
556+ self .connect_uri = decomposed .connect_uri ;
557+ self ._request_host = decomposed .request_host ;
558+ self ._connect_host = decomposed .connect_host ;
559+ self ._connect_port = decomposed .connect_port ;
560+ self ._secure = decomposed .secure ;
533561 self ._keepalive = false ;
534562 self ._redirect_count = redirect_count + 1 ;
535563
@@ -538,7 +566,7 @@ pub const Request = struct {
538566 // to a GET.
539567 self .method = .GET ;
540568 }
541- log .info ("redirecting to: {any} {any}" , .{ self .method , self .uri });
569+ log .info ("redirecting to: {any} {any}" , .{ self .method , self .request_uri });
542570
543571 if (self .body != null and self .method == .GET ) {
544572 // If we have a body and the method is a GET, then we must be following
@@ -553,10 +581,10 @@ pub const Request = struct {
553581 }
554582 }
555583
556- if (std .mem .eql (u8 , previous_host , host ) == false ) {
584+ if (std .mem .eql (u8 , previous_request_host , self . _request_host ) == false ) {
557585 for (self .headers .items ) | * hdr | {
558586 if (std .mem .eql (u8 , hdr .name , "Host" )) {
559- hdr .value = host ;
587+ hdr .value = self . _request_host ;
560588 break ;
561589 }
562590 }
@@ -577,11 +605,11 @@ pub const Request = struct {
577605 return null ;
578606 }
579607
580- return self ._client .idle_connections .get (self ._secure , self ._host , self ._port , blocking );
608+ return self ._client .idle_connections .get (self ._secure , self ._connect_host , self ._connect_port , blocking );
581609 }
582610
583611 fn createSocket (self : * Request , blocking : bool ) ! struct { posix .socket_t , std .net .Address } {
584- const addresses = try std .net .getAddressList (self .arena , self ._host , self ._port );
612+ const addresses = try std .net .getAddressList (self .arena , self ._connect_host , self ._connect_port );
585613 if (addresses .addrs .len == 0 ) {
586614 return error .UnknownHostName ;
587615 }
@@ -600,13 +628,15 @@ pub const Request = struct {
600628 }
601629
602630 fn buildHeader (self : * Request ) ! []const u8 {
631+ const proxied = self .connect_uri != self .request_uri ;
632+
603633 const buf = self ._state .header_buf ;
604634 var fbs = std .io .fixedBufferStream (buf );
605635 var writer = fbs .writer ();
606636
607637 try writer .writeAll (@tagName (self .method ));
608638 try writer .writeByte (' ' );
609- try self .uri .writeToStream (.{ .path = true , .query = true }, writer );
639+ try self .request_uri .writeToStream (.{ . scheme = proxied , . authority = proxied , .path = true , .query = true }, writer );
610640 try writer .writeAll (" HTTP/1.1\r \n " );
611641 for (self .headers .items ) | header | {
612642 try writer .writeAll (header .name );
@@ -906,7 +936,7 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
906936 }
907937
908938 fn handleError (self : * Self , comptime msg : []const u8 , err : anyerror ) void {
909- log .err (msg ++ ": {any} ({any} {any})" , .{ err , self .request .method , self .request .uri });
939+ log .err (msg ++ ": {any} ({any} {any})" , .{ err , self .request .method , self .request .request_uri });
910940 self .handler .onHttpResponse (err ) catch {};
911941 // just to be safe
912942 self .request ._keepalive = false ;
@@ -1127,7 +1157,7 @@ const SyncHandler = struct {
11271157 // See CompressedReader for an explanation. This isn't great code. Sorry.
11281158 if (reader .response .get ("content-encoding" )) | ce | {
11291159 if (std .ascii .eqlIgnoreCase (ce , "gzip" ) == false ) {
1130- log .err ("unsupported content encoding '{s}' for: {}" , .{ ce , request .uri });
1160+ log .err ("unsupported content encoding '{s}' for: {}" , .{ ce , request .request_uri });
11311161 return error .UnsupportedContentEncoding ;
11321162 }
11331163
0 commit comments