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.
This commit is contained in:
Andrew Kelley 2024-04-12 15:19:40 -07:00
parent 419753f45e
commit 6deb3e3986
2 changed files with 33 additions and 15 deletions

View File

@ -242,6 +242,9 @@ pub const WriteToStreamOptions = struct {
/// When true, include the fragment part of the URI. Ignored when `path` is false.
fragment: bool = false,
/// When true, include the port part of the URI. Ignored when `port` is null.
port: bool = true,
};
pub fn writeToStream(
@ -267,7 +270,9 @@ pub fn writeToStream(
}
if (uri.host) |host| {
try writer.print("{host}", .{host});
if (uri.port) |port| try writer.print(":{d}", .{port});
if (options.port) {
if (uri.port) |port| try writer.print(":{d}", .{port});
}
}
}
if (options.path) {

View File

@ -218,7 +218,17 @@ pub const Connection = struct {
pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
const BufferSize = std.math.IntFittingRange(0, buffer_size);
pub const Protocol = enum { plain, tls };
pub const Protocol = enum {
plain,
tls,
pub fn port(p: Protocol) u16 {
return switch (p) {
.plain => 80,
.tls => 443,
};
}
};
pub fn readvDirectTls(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize {
return conn.tls_client.readv(conn.stream, buffers) catch |err| {
@ -805,7 +815,7 @@ pub const Request = struct {
}
req.uri = valid_uri;
req.connection = try req.client.connect(new_host, uriPort(valid_uri, protocol), protocol);
req.connection = try req.client.connect(new_host, valid_uri.port.?, protocol);
req.redirect_behavior.subtractOne();
req.response.parser.reset();
@ -847,8 +857,13 @@ pub const Request = struct {
try w.writeAll("\r\n");
if (try emitOverridableHeader("host: ", req.headers.host, w)) {
// URI has already been validated so this cannot fail.
const default_port = (uriProtocol(req.uri) catch unreachable).port();
try w.writeAll("host: ");
try req.uri.writeToStream(.{ .authority = true }, w);
try req.uri.writeToStream(.{
.authority = true,
.port = req.uri.port.? != default_port,
}, w);
try w.writeAll("\r\n");
}
@ -1264,7 +1279,7 @@ fn createProxyFromEnvVar(arena: Allocator, env_var_names: []const []const u8) !?
.protocol = protocol,
.host = valid_uri.host.?.raw,
.authorization = authorization,
.port = uriPort(valid_uri, protocol),
.port = valid_uri.port.?,
.supports_connect = true,
};
return proxy;
@ -1569,29 +1584,27 @@ pub const RequestOptions = struct {
privileged_headers: []const http.Header = &.{},
};
fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {
fn uriProtocol(uri: Uri) !Connection.Protocol {
const protocol_map = std.ComptimeStringMap(Connection.Protocol, .{
.{ "http", .plain },
.{ "ws", .plain },
.{ "https", .tls },
.{ "wss", .tls },
});
const protocol = protocol_map.get(uri.scheme) orelse return error.UnsupportedUriScheme;
return protocol_map.get(uri.scheme) orelse return error.UnsupportedUriScheme;
}
fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {
const protocol = try uriProtocol(uri);
var valid_uri = uri;
// The host is always going to be needed as a raw string for hostname resolution anyway.
valid_uri.host = .{
.raw = try (uri.host orelse return error.UriMissingHost).toRawMaybeAlloc(arena),
};
valid_uri.port = uri.port orelse protocol.port();
return .{ protocol, valid_uri };
}
fn uriPort(uri: Uri, protocol: Connection.Protocol) u16 {
return uri.port orelse switch (protocol) {
.plain => 80,
.tls => 443,
};
}
/// Open a connection to the host specified by `uri` and prepare to send a HTTP request.
///
/// `uri` must remain alive during the entire request.
@ -1637,7 +1650,7 @@ pub fn open(
}
const conn = options.connection orelse
try client.connect(valid_uri.host.?.raw, uriPort(valid_uri, protocol), protocol);
try client.connect(valid_uri.host.?.raw, valid_uri.port.?, protocol);
var req: Request = .{
.uri = valid_uri,