authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-04-12 15:19:40-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-04-12 22:37:07-07:00
log6deb3e39869ec9d75be20ea2685f66239baebf39
treee01a1b61486fcebfa7d1a0220a995aac0ca22bf0
parent419753f45ea4de7b6de090f7ad574f5ccaad5669

std.http.Client: always omit port when it matches default

This makes the host http header have the port if and only if it differs from the defaults based on the protocol. This is an alternate implementation that closes #19624.

2 files changed, 33 insertions(+), 15 deletions(-)

lib/std/Uri.zig+6-1
......@@ -242,6 +242,9 @@ pub const WriteToStreamOptions = struct {
242242
243243 /// When true, include the fragment part of the URI. Ignored when `path` is false.
244244 fragment: bool = false,
245
246 /// When true, include the port part of the URI. Ignored when `port` is null.
247 port: bool = true,
245248};
246249
247250pub fn writeToStream(
......@@ -267,7 +270,9 @@ pub fn writeToStream(
267270 }
268271 if (uri.host) |host| {
269272 try writer.print("{host}", .{host});
270 if (uri.port) |port| try writer.print(":{d}", .{port});
273 if (options.port) {
274 if (uri.port) |port| try writer.print(":{d}", .{port});
275 }
271276 }
272277 }
273278 if (options.path) {
lib/std/http/Client.zig+27-14
......@@ -218,7 +218,17 @@ pub const Connection = struct {
218218 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
219219 const BufferSize = std.math.IntFittingRange(0, buffer_size);
220220
221 pub const Protocol = enum { plain, tls };
221 pub const Protocol = enum {
222 plain,
223 tls,
224
225 pub fn port(p: Protocol) u16 {
226 return switch (p) {
227 .plain => 80,
228 .tls => 443,
229 };
230 }
231 };
222232
223233 pub fn readvDirectTls(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize {
224234 return conn.tls_client.readv(conn.stream, buffers) catch |err| {
......@@ -805,7 +815,7 @@ pub const Request = struct {
805815 }
806816
807817 req.uri = valid_uri;
808 req.connection = try req.client.connect(new_host, uriPort(valid_uri, protocol), protocol);
818 req.connection = try req.client.connect(new_host, valid_uri.port.?, protocol);
809819 req.redirect_behavior.subtractOne();
810820 req.response.parser.reset();
811821
......@@ -847,8 +857,13 @@ pub const Request = struct {
847857 try w.writeAll("\r\n");
848858
849859 if (try emitOverridableHeader("host: ", req.headers.host, w)) {
860 // URI has already been validated so this cannot fail.
861 const default_port = (uriProtocol(req.uri) catch unreachable).port();
850862 try w.writeAll("host: ");
851 try req.uri.writeToStream(.{ .authority = true }, w);
863 try req.uri.writeToStream(.{
864 .authority = true,
865 .port = req.uri.port.? != default_port,
866 }, w);
852867 try w.writeAll("\r\n");
853868 }
854869
......@@ -1264,7 +1279,7 @@ fn createProxyFromEnvVar(arena: Allocator, env_var_names: []const []const u8) !?
12641279 .protocol = protocol,
12651280 .host = valid_uri.host.?.raw,
12661281 .authorization = authorization,
1267 .port = uriPort(valid_uri, protocol),
1282 .port = valid_uri.port.?,
12681283 .supports_connect = true,
12691284 };
12701285 return proxy;
......@@ -1569,29 +1584,27 @@ pub const RequestOptions = struct {
15691584 privileged_headers: []const http.Header = &.{},
15701585};
15711586
1572fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {
1587fn uriProtocol(uri: Uri) !Connection.Protocol {
15731588 const protocol_map = std.ComptimeStringMap(Connection.Protocol, .{
15741589 .{ "http", .plain },
15751590 .{ "ws", .plain },
15761591 .{ "https", .tls },
15771592 .{ "wss", .tls },
15781593 });
1579 const protocol = protocol_map.get(uri.scheme) orelse return error.UnsupportedUriScheme;
1594 return protocol_map.get(uri.scheme) orelse return error.UnsupportedUriScheme;
1595}
1596
1597fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {
1598 const protocol = try uriProtocol(uri);
15801599 var valid_uri = uri;
15811600 // The host is always going to be needed as a raw string for hostname resolution anyway.
15821601 valid_uri.host = .{
15831602 .raw = try (uri.host orelse return error.UriMissingHost).toRawMaybeAlloc(arena),
15841603 };
1604 valid_uri.port = uri.port orelse protocol.port();
15851605 return .{ protocol, valid_uri };
15861606}
15871607
1588fn uriPort(uri: Uri, protocol: Connection.Protocol) u16 {
1589 return uri.port orelse switch (protocol) {
1590 .plain => 80,
1591 .tls => 443,
1592 };
1593}
1594
15951608/// Open a connection to the host specified by `uri` and prepare to send a HTTP request.
15961609///
15971610/// `uri` must remain alive during the entire request.
......@@ -1637,7 +1650,7 @@ pub fn open(
16371650 }
16381651
16391652 const conn = options.connection orelse
1640 try client.connect(valid_uri.host.?.raw, uriPort(valid_uri, protocol), protocol);
1653 try client.connect(valid_uri.host.?.raw, valid_uri.port.?, protocol);
16411654
16421655 var req: Request = .{
16431656 .uri = valid_uri,