Skip to content

Commit e918a0b

Browse files
committed
add direct http proxy support
1 parent 7bb6506 commit e918a0b

File tree

5 files changed

+121
-52
lines changed

5 files changed

+121
-52
lines changed

src/app.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub const App = struct {
3030
run_mode: RunMode,
3131
gc_hints: bool = false,
3232
tls_verify_host: bool = true,
33+
http_proxy: ?std.Uri = null,
3334
};
3435

3536
pub fn init(allocator: Allocator, config: Config) !*App {
@@ -54,6 +55,7 @@ pub const App = struct {
5455
.app_dir_path = app_dir_path,
5556
.notification = notification,
5657
.http_client = try HttpClient.init(allocator, 5, .{
58+
.http_proxy = config.http_proxy,
5759
.tls_verify_host = config.tls_verify_host,
5860
}),
5961
.config = config,

src/browser/browser.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ pub const Page = struct {
356356
var response = try request.sendSync(.{});
357357

358358
// would be different than self.url in the case of a redirect
359-
self.url = try URL.fromURI(arena, request.uri);
359+
self.url = try URL.fromURI(arena, request.request_uri);
360360

361361
const header = response.header;
362362
try session.cookie_jar.populateFromResponse(&self.url.uri, &header);

src/browser/xhr/xhr.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ pub const XMLHttpRequest = struct {
497497
self.state = .loading;
498498
self.dispatchEvt("readystatechange");
499499

500-
try self.cookie_jar.populateFromResponse(self.request.?.uri, &header);
500+
try self.cookie_jar.populateFromResponse(self.request.?.request_uri, &header);
501501
}
502502

503503
if (progress.data) |data| {

src/http/client.zig

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@ const MAX_HEADER_LINE_LEN = 4096;
4646
pub 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

Comments
 (0)