Merge pull request #17407 from truemedian/http-ng

std.http: more proxy support, buffer writes, tls toggle
This commit is contained in:
Andrew Kelley 2023-10-22 17:48:03 -04:00 committed by GitHub
commit b82459fa43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 771 additions and 373 deletions

View File

@ -208,24 +208,45 @@ pub fn parseWithoutScheme(text: []const u8) ParseError!Uri {
return uri;
}
pub fn format(
pub const WriteToStreamOptions = struct {
/// When true, include the scheme part of the URI.
scheme: bool = false,
/// When true, include the user and password part of the URI. Ignored if `authority` is false.
authentication: bool = false,
/// When true, include the authority part of the URI.
authority: bool = false,
/// When true, include the path part of the URI.
path: bool = false,
/// When true, include the query part of the URI. Ignored when `path` is false.
query: bool = false,
/// When true, include the fragment part of the URI. Ignored when `path` is false.
fragment: bool = false,
/// When true, do not escape any part of the URI.
raw: bool = false,
};
pub fn writeToStream(
uri: Uri,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
options: WriteToStreamOptions,
writer: anytype,
) @TypeOf(writer).Error!void {
_ = options;
const needs_absolute = comptime std.mem.indexOf(u8, fmt, "+") != null;
const needs_path = comptime std.mem.indexOf(u8, fmt, "/") != null or fmt.len == 0;
const raw_uri = comptime std.mem.indexOf(u8, fmt, "r") != null;
const needs_fragment = comptime std.mem.indexOf(u8, fmt, "#") != null;
if (needs_absolute) {
if (options.scheme) {
try writer.writeAll(uri.scheme);
try writer.writeAll(":");
if (uri.host) |host| {
if (options.authority and uri.host != null) {
try writer.writeAll("//");
}
}
if (options.authority) {
if (options.authentication and uri.host != null) {
if (uri.user) |user| {
try writer.writeAll(user);
if (uri.password) |password| {
@ -234,7 +255,9 @@ pub fn format(
}
try writer.writeAll("@");
}
}
if (uri.host) |host| {
try writer.writeAll(host);
if (uri.port) |port| {
@ -244,39 +267,62 @@ pub fn format(
}
}
if (needs_path) {
if (options.path) {
if (uri.path.len == 0) {
try writer.writeAll("/");
} else if (options.raw) {
try writer.writeAll(uri.path);
} else {
if (raw_uri) {
try writer.writeAll(uri.path);
} else {
try Uri.writeEscapedPath(writer, uri.path);
}
try writeEscapedPath(writer, uri.path);
}
if (uri.query) |q| {
if (options.query) if (uri.query) |q| {
try writer.writeAll("?");
if (raw_uri) {
if (options.raw) {
try writer.writeAll(q);
} else {
try Uri.writeEscapedQuery(writer, q);
try writeEscapedQuery(writer, q);
}
}
};
if (needs_fragment) {
if (uri.fragment) |f| {
try writer.writeAll("#");
if (raw_uri) {
try writer.writeAll(f);
} else {
try Uri.writeEscapedQuery(writer, f);
}
if (options.fragment) if (uri.fragment) |f| {
try writer.writeAll("#");
if (options.raw) {
try writer.writeAll(f);
} else {
try writeEscapedQuery(writer, f);
}
}
};
}
}
pub fn format(
uri: Uri,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) @TypeOf(writer).Error!void {
_ = options;
const scheme = comptime std.mem.indexOf(u8, fmt, ":") != null or fmt.len == 0;
const authentication = comptime std.mem.indexOf(u8, fmt, "@") != null or fmt.len == 0;
const authority = comptime std.mem.indexOf(u8, fmt, "+") != null or fmt.len == 0;
const path = comptime std.mem.indexOf(u8, fmt, "/") != null or fmt.len == 0;
const query = comptime std.mem.indexOf(u8, fmt, "?") != null or fmt.len == 0;
const fragment = comptime std.mem.indexOf(u8, fmt, "#") != null or fmt.len == 0;
const raw = comptime std.mem.indexOf(u8, fmt, "r") != null or fmt.len == 0;
return writeToStream(uri, .{
.scheme = scheme,
.authentication = authentication,
.authority = authority,
.path = path,
.query = query,
.fragment = fragment,
.raw = raw,
}, writer);
}
/// Parses the URI or returns an error.
/// The return value will contain unescaped strings pointing into the
/// original `text`. Each component that is provided, will be non-`null`.
@ -711,7 +757,7 @@ test "URI query escaping" {
const parsed = try Uri.parse(address);
// format the URI to escape it
const formatted_uri = try std.fmt.allocPrint(std.testing.allocator, "{}", .{parsed});
const formatted_uri = try std.fmt.allocPrint(std.testing.allocator, "{/?}", .{parsed});
defer std.testing.allocator.free(formatted_uri);
try std.testing.expectEqualStrings("/?response-content-type=application%2Foctet-stream", formatted_uri);
}
@ -729,6 +775,6 @@ test "format" {
};
var buf = std.ArrayList(u8).init(std.testing.allocator);
defer buf.deinit();
try uri.format("+/", .{}, buf.writer());
try uri.format(":/?#", .{}, buf.writer());
try std.testing.expectEqualSlices(u8, "file:/foo/bar/baz", buf.items);
}

View File

@ -881,7 +881,7 @@ pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize {
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial reads from the underlying stream layer.
pub fn readv(c: *Client, stream: anytype, iovecs: []std.os.iovec) !usize {
return readvAtLeast(c, stream, iovecs);
return readvAtLeast(c, stream, iovecs, 1);
}
/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.

View File

@ -35,7 +35,8 @@ pub const Method = enum(u64) { // TODO: should be u192 or u256, but neither is s
/// Asserts that `s` is 24 or fewer bytes.
pub fn parse(s: []const u8) u64 {
var x: u64 = 0;
@memcpy(std.mem.asBytes(&x)[0..s.len], s);
const len = @min(s.len, @sizeOf(@TypeOf(x)));
@memcpy(std.mem.asBytes(&x)[0..len], s[0..len]);
return x;
}
@ -289,14 +290,17 @@ pub const Status = enum(u10) {
pub const TransferEncoding = enum {
chunked,
none,
// compression is intentionally omitted here, as std.http.Client stores it as content-encoding
};
pub const ContentEncoding = enum {
identity,
compress,
@"x-compress",
deflate,
gzip,
@"x-gzip",
zstd,
};

File diff suppressed because it is too large Load Diff

View File

@ -14,15 +14,18 @@ pub const CaseInsensitiveStringContext = struct {
pub fn hash(self: @This(), s: []const u8) u64 {
_ = self;
var buf: [64]u8 = undefined;
var i: u8 = 0;
var i: usize = 0;
var h = std.hash.Wyhash.init(0);
while (i < s.len) : (i += 64) {
const left = @min(64, s.len - i);
const ret = ascii.lowerString(buf[0..], s[i..][0..left]);
while (i + 64 < s.len) : (i += 64) {
const ret = ascii.lowerString(buf[0..], s[i..][0..64]);
h.update(ret);
}
const left = @min(64, s.len - i);
const ret = ascii.lowerString(buf[0..], s[i..][0..left]);
h.update(ret);
return h.final();
}

View File

@ -14,7 +14,7 @@ allocator: Allocator,
socket: net.StreamServer,
/// An interface to either a plain or TLS connection.
/// An interface to a plain connection.
pub const Connection = struct {
pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
pub const Protocol = enum { plain };
@ -178,7 +178,7 @@ pub const Request = struct {
};
pub fn parse(req: *Request, bytes: []const u8) ParseError!void {
var it = mem.tokenizeAny(u8, bytes[0 .. bytes.len - 4], "\r\n");
var it = mem.tokenizeAny(u8, bytes, "\r\n");
const first_line = it.next() orelse return error.HttpHeadersInvalid;
if (first_line.len < 10)
@ -228,27 +228,23 @@ pub const Request = struct {
// Transfer-Encoding: deflate, chunked
var iter = mem.splitBackwardsScalar(u8, header_value, ',');
if (iter.next()) |first| {
const trimmed = mem.trim(u8, first, " ");
const first = iter.first();
const trimmed_first = mem.trim(u8, first, " ");
if (std.meta.stringToEnum(http.TransferEncoding, trimmed)) |te| {
if (req.transfer_encoding != null) return error.HttpHeadersInvalid;
req.transfer_encoding = te;
} else if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
if (req.transfer_compression != null) return error.HttpHeadersInvalid;
req.transfer_compression = ce;
} else {
return error.HttpTransferEncodingUnsupported;
}
var next: ?[]const u8 = first;
if (std.meta.stringToEnum(http.TransferEncoding, trimmed_first)) |transfer| {
if (req.transfer_encoding != .none) return error.HttpHeadersInvalid; // we already have a transfer encoding
req.transfer_encoding = transfer;
next = iter.next();
}
if (iter.next()) |second| {
if (req.transfer_compression != null) return error.HttpTransferEncodingUnsupported;
if (next) |second| {
const trimmed_second = mem.trim(u8, second, " ");
const trimmed = mem.trim(u8, second, " ");
if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
req.transfer_compression = ce;
if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| {
if (req.transfer_compression != .identity) return error.HttpHeadersInvalid; // double compression is not supported
req.transfer_compression = transfer;
} else {
return error.HttpTransferEncodingUnsupported;
}
@ -256,7 +252,7 @@ pub const Request = struct {
if (iter.next()) |_| return error.HttpTransferEncodingUnsupported;
} else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) {
if (req.transfer_compression != null) return error.HttpHeadersInvalid;
if (req.transfer_compression != .identity) return error.HttpHeadersInvalid;
const trimmed = mem.trim(u8, header_value, " ");
@ -277,9 +273,14 @@ pub const Request = struct {
target: []const u8,
version: http.Version,
/// The length of the request body, if known.
content_length: ?u64 = null,
transfer_encoding: ?http.TransferEncoding = null,
transfer_compression: ?http.ContentEncoding = null,
/// The transfer encoding of the request body, or .none if not present.
transfer_encoding: http.TransferEncoding = .none,
/// The compression of the request body, or .identity (no compression) if not present.
transfer_compression: http.ContentEncoding = .identity,
headers: http.Headers,
parser: proto.HeadersParser,
@ -315,6 +316,7 @@ pub const Response = struct {
finished,
};
/// Free all resources associated with this response.
pub fn deinit(res: *Response) void {
res.connection.close();
@ -390,10 +392,10 @@ pub const Response = struct {
}
}
pub const DoError = Connection.WriteError || error{ UnsupportedTransferEncoding, InvalidContentLength };
pub const SendError = Connection.WriteError || error{ UnsupportedTransferEncoding, InvalidContentLength };
/// Send the response headers.
pub fn do(res: *Response) DoError!void {
/// Send the HTTP response headers to the client.
pub fn send(res: *Response) SendError!void {
switch (res.state) {
.waited => res.state = .responded,
.first, .start, .responded, .finished => unreachable,
@ -511,8 +513,9 @@ pub const Response = struct {
res.request.headers = .{ .allocator = res.allocator, .owned = true };
try res.request.parse(res.request.parser.header_bytes.items);
if (res.request.transfer_encoding) |te| {
switch (te) {
if (res.request.transfer_encoding != .none) {
switch (res.request.transfer_encoding) {
.none => unreachable,
.chunked => {
res.request.parser.next_chunk_length = 0;
res.request.parser.state = .chunk_head_size;
@ -527,19 +530,19 @@ pub const Response = struct {
}
if (!res.request.parser.done) {
if (res.request.transfer_compression) |tc| switch (tc) {
switch (res.request.transfer_compression) {
.identity => res.request.compression = .none,
.compress => return error.CompressionNotSupported,
.compress, .@"x-compress" => return error.CompressionNotSupported,
.deflate => res.request.compression = .{
.deflate = std.compress.zlib.decompressStream(res.allocator, res.transferReader()) catch return error.CompressionInitializationFailed,
},
.gzip => res.request.compression = .{
.gzip, .@"x-gzip" => res.request.compression = .{
.gzip = std.compress.gzip.decompress(res.allocator, res.transferReader()) catch return error.CompressionInitializationFailed,
},
.zstd => res.request.compression = .{
.zstd = std.compress.zstd.decompressStream(res.allocator, res.transferReader()),
},
};
}
}
}
@ -551,6 +554,7 @@ pub const Response = struct {
return .{ .context = res };
}
/// Reads data from the response body. Must be called after `wait`.
pub fn read(res: *Response, buffer: []u8) ReadError!usize {
switch (res.state) {
.waited, .responded, .finished => {},
@ -586,6 +590,7 @@ pub const Response = struct {
return out_index;
}
/// Reads data from the response body. Must be called after `wait`.
pub fn readAll(res: *Response, buffer: []u8) !usize {
var index: usize = 0;
while (index < buffer.len) {
@ -605,6 +610,7 @@ pub const Response = struct {
}
/// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.
/// Must be called after `start` and before `finish`.
pub fn write(res: *Response, bytes: []const u8) WriteError!usize {
switch (res.state) {
.responded => {},
@ -630,6 +636,8 @@ pub const Response = struct {
}
}
/// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.
/// Must be called after `start` and before `finish`.
pub fn writeAll(req: *Response, bytes: []const u8) WriteError!void {
var index: usize = 0;
while (index < bytes.len) {
@ -640,6 +648,7 @@ pub const Response = struct {
pub const FinishError = WriteError || error{MessageNotCompleted};
/// Finish the body of a request. This notifies the server that you have no more data to send.
/// Must be called after `start`.
pub fn finish(res: *Response) FinishError!void {
switch (res.state) {
.responded => res.state = .finished,
@ -654,6 +663,7 @@ pub const Response = struct {
}
};
/// Create a new HTTP server.
pub fn init(allocator: Allocator, options: net.StreamServer.Options) Server {
return .{
.allocator = allocator,
@ -661,6 +671,7 @@ pub fn init(allocator: Allocator, options: net.StreamServer.Options) Server {
};
}
/// Free all resources associated with this server.
pub fn deinit(server: *Server) void {
server.socket.deinit();
}
@ -756,13 +767,13 @@ test "HTTP server handles a chunked transfer coding request" {
defer _ = res.reset();
try res.wait();
try expect(res.request.transfer_encoding.? == .chunked);
try expect(res.request.transfer_encoding == .chunked);
const server_body: []const u8 = "message from server!\n";
res.transfer_encoding = .{ .content_length = server_body.len };
try res.headers.append("content-type", "text/plain");
try res.headers.append("connection", "close");
try res.do();
try res.send();
var buf: [128]u8 = undefined;
const n = try res.readAll(&buf);

View File

@ -529,7 +529,7 @@ pub const HeadersParser = struct {
try conn.fill();
const nread = @min(conn.peek().len, data_avail);
conn.drop(@as(u16, @intCast(nread)));
conn.drop(@intCast(nread));
r.next_chunk_length -= nread;
if (r.next_chunk_length == 0) r.done = true;
@ -553,7 +553,7 @@ pub const HeadersParser = struct {
try conn.fill();
const i = r.findChunkedLen(conn.peek());
conn.drop(@as(u16, @intCast(i)));
conn.drop(@intCast(i));
switch (r.state) {
.invalid => return error.HttpChunkInvalid,
@ -582,7 +582,7 @@ pub const HeadersParser = struct {
try conn.fill();
const nread = @min(conn.peek().len, data_avail);
conn.drop(@as(u16, @intCast(nread)));
conn.drop(@intCast(nread));
r.next_chunk_length -= nread;
} else if (out_avail > 0) {
const can_read: usize = @intCast(@min(data_avail, out_avail));

View File

@ -283,10 +283,15 @@ pub const options = struct {
else
false;
pub const http_connection_pool_size = if (@hasDecl(options_override, "http_connection_pool_size"))
options_override.http_connection_pool_size
/// By default, std.http.Client will support HTTPS connections. Set this option to `true` to
/// disable TLS support.
///
/// This will likely reduce the size of the binary, but it will also make it impossible to
/// make a HTTPS connection.
pub const http_disable_tls = if (@hasDecl(options_override, "http_disable_tls"))
options_override.http_disable_tls
else
http.Client.default_connection_pool_size;
false;
pub const side_channels_mitigations: crypto.SideChannelsMitigations = if (@hasDecl(options_override, "side_channels_mitigations"))
options_override.side_channels_mitigations

View File

@ -826,7 +826,7 @@ fn initResource(f: *Fetch, uri: std.Uri) RunError!Resource {
var h = std.http.Headers{ .allocator = gpa };
defer h.deinit();
var req = http_client.request(.GET, uri, h, .{}) catch |err| {
var req = http_client.open(.GET, uri, h, .{}) catch |err| {
return f.fail(f.location_tok, try eb.printString(
"unable to connect to server: {s}",
.{@errorName(err)},
@ -834,7 +834,7 @@ fn initResource(f: *Fetch, uri: std.Uri) RunError!Resource {
};
errdefer req.deinit(); // releases more than memory
req.start(.{}) catch |err| {
req.send(.{}) catch |err| {
return f.fail(f.location_tok, try eb.printString(
"HTTP request failed: {s}",
.{@errorName(err)},

View File

@ -518,11 +518,11 @@ pub const Session = struct {
defer headers.deinit();
try headers.append("Git-Protocol", "version=2");
var request = try session.transport.request(.GET, info_refs_uri, headers, .{
var request = try session.transport.open(.GET, info_refs_uri, headers, .{
.max_redirects = 3,
});
errdefer request.deinit();
try request.start(.{});
try request.send(.{});
try request.finish();
try request.wait();
@ -641,12 +641,12 @@ pub const Session = struct {
}
try Packet.write(.flush, body_writer);
var request = try session.transport.request(.POST, upload_pack_uri, headers, .{
var request = try session.transport.open(.POST, upload_pack_uri, headers, .{
.handle_redirects = false,
});
errdefer request.deinit();
request.transfer_encoding = .{ .content_length = body.items.len };
try request.start(.{});
try request.send(.{});
try request.writeAll(body.items);
try request.finish();
@ -740,12 +740,12 @@ pub const Session = struct {
try Packet.write(.{ .data = "done\n" }, body_writer);
try Packet.write(.flush, body_writer);
var request = try session.transport.request(.POST, upload_pack_uri, headers, .{
var request = try session.transport.open(.POST, upload_pack_uri, headers, .{
.handle_redirects = false,
});
errdefer request.deinit();
request.transfer_encoding = .{ .content_length = body.items.len };
try request.start(.{});
try request.send(.{});
try request.writeAll(body.items);
try request.finish();

View File

@ -5128,6 +5128,8 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
var http_client: std.http.Client = .{ .allocator = gpa };
defer http_client.deinit();
try http_client.loadDefaultProxies();
var progress: std.Progress = .{ .dont_print_on_dumb = true };
const root_prog_node = progress.start("Fetch Packages", 0);
defer root_prog_node.end();
@ -7039,6 +7041,8 @@ fn cmdFetch(
var http_client: std.http.Client = .{ .allocator = gpa };
defer http_client.deinit();
try http_client.loadDefaultProxies();
var progress: std.Progress = .{ .dont_print_on_dumb = true };
const root_prog_node = progress.start("Fetch", 0);
defer root_prog_node.end();

View File

@ -7,6 +7,10 @@ const Client = http.Client;
const mem = std.mem;
const testing = std.testing;
pub const std_options = struct {
pub const http_disable_tls = true;
};
const max_header_size = 8192;
var gpa_server = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){};
@ -25,11 +29,11 @@ fn handleRequest(res: *Server.Response) !void {
if (res.request.headers.contains("expect")) {
if (mem.eql(u8, res.request.headers.getFirstValue("expect").?, "100-continue")) {
res.status = .@"continue";
try res.do();
try res.send();
res.status = .ok;
} else {
res.status = .expectation_failed;
try res.do();
try res.send();
return;
}
}
@ -50,7 +54,7 @@ fn handleRequest(res: *Server.Response) !void {
try res.headers.append("content-type", "text/plain");
try res.do();
try res.send();
if (res.request.method != .HEAD) {
try res.writeAll("Hello, ");
try res.writeAll("World!\n");
@ -61,7 +65,7 @@ fn handleRequest(res: *Server.Response) !void {
} else if (mem.startsWith(u8, res.request.target, "/large")) {
res.transfer_encoding = .{ .content_length = 14 * 1024 + 14 * 10 };
try res.do();
try res.send();
var i: u32 = 0;
while (i < 5) : (i += 1) {
@ -88,14 +92,14 @@ fn handleRequest(res: *Server.Response) !void {
try testing.expectEqualStrings("14", res.request.headers.getFirstValue("content-length").?);
}
try res.do();
try res.send();
try res.writeAll("Hello, ");
try res.writeAll("World!\n");
try res.finish();
} else if (mem.eql(u8, res.request.target, "/trailer")) {
res.transfer_encoding = .chunked;
try res.do();
try res.send();
try res.writeAll("Hello, ");
try res.writeAll("World!\n");
// try res.finish();
@ -106,7 +110,7 @@ fn handleRequest(res: *Server.Response) !void {
res.status = .found;
try res.headers.append("location", "../../get");
try res.do();
try res.send();
try res.writeAll("Hello, ");
try res.writeAll("Redirected!\n");
try res.finish();
@ -116,7 +120,7 @@ fn handleRequest(res: *Server.Response) !void {
res.status = .found;
try res.headers.append("location", "/redirect/1");
try res.do();
try res.send();
try res.writeAll("Hello, ");
try res.writeAll("Redirected!\n");
try res.finish();
@ -129,7 +133,7 @@ fn handleRequest(res: *Server.Response) !void {
res.status = .found;
try res.headers.append("location", location);
try res.do();
try res.send();
try res.writeAll("Hello, ");
try res.writeAll("Redirected!\n");
try res.finish();
@ -139,7 +143,7 @@ fn handleRequest(res: *Server.Response) !void {
res.status = .found;
try res.headers.append("location", "/redirect/3");
try res.do();
try res.send();
try res.writeAll("Hello, ");
try res.writeAll("Redirected!\n");
try res.finish();
@ -150,11 +154,11 @@ fn handleRequest(res: *Server.Response) !void {
res.status = .found;
try res.headers.append("location", location);
try res.do();
try res.send();
try res.finish();
} else {
res.status = .not_found;
try res.do();
try res.send();
}
}
@ -226,8 +230,11 @@ pub fn main() !void {
const server_thread = try std.Thread.spawn(.{}, serverThread, .{&server});
var client = Client{ .allocator = calloc };
errdefer client.deinit();
// defer client.deinit(); handled below
try client.loadDefaultProxies();
{ // read content-length response
var h = http.Headers{ .allocator = calloc };
defer h.deinit();
@ -237,10 +244,10 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.GET, uri, h, .{});
var req = try client.open(.GET, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
try req.wait();
const body = try req.reader().readAllAlloc(calloc, 8192);
@ -251,7 +258,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // read large content-length response
var h = http.Headers{ .allocator = calloc };
@ -262,10 +269,10 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.GET, uri, h, .{});
var req = try client.open(.GET, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
try req.wait();
const body = try req.reader().readAllAlloc(calloc, 8192 * 1024);
@ -275,7 +282,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // send head request and not read chunked
var h = http.Headers{ .allocator = calloc };
@ -286,10 +293,10 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.HEAD, uri, h, .{});
var req = try client.open(.HEAD, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
try req.wait();
const body = try req.reader().readAllAlloc(calloc, 8192);
@ -301,7 +308,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // read chunked response
var h = http.Headers{ .allocator = calloc };
@ -312,10 +319,10 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.GET, uri, h, .{});
var req = try client.open(.GET, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
try req.wait();
const body = try req.reader().readAllAlloc(calloc, 8192);
@ -326,7 +333,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // send head request and not read chunked
var h = http.Headers{ .allocator = calloc };
@ -337,10 +344,10 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.HEAD, uri, h, .{});
var req = try client.open(.HEAD, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
try req.wait();
const body = try req.reader().readAllAlloc(calloc, 8192);
@ -352,7 +359,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // check trailing headers
var h = http.Headers{ .allocator = calloc };
@ -363,10 +370,10 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.GET, uri, h, .{});
var req = try client.open(.GET, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
try req.wait();
const body = try req.reader().readAllAlloc(calloc, 8192);
@ -377,7 +384,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // send content-length request
var h = http.Headers{ .allocator = calloc };
@ -390,12 +397,12 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.POST, uri, h, .{});
var req = try client.open(.POST, uri, h, .{});
defer req.deinit();
req.transfer_encoding = .{ .content_length = 14 };
try req.start(.{});
try req.send(.{});
try req.writeAll("Hello, ");
try req.writeAll("World!\n");
try req.finish();
@ -409,7 +416,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // read content-length response with connection close
var h = http.Headers{ .allocator = calloc };
@ -422,10 +429,10 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.GET, uri, h, .{});
var req = try client.open(.GET, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
try req.wait();
const body = try req.reader().readAllAlloc(calloc, 8192);
@ -449,12 +456,12 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.POST, uri, h, .{});
var req = try client.open(.POST, uri, h, .{});
defer req.deinit();
req.transfer_encoding = .chunked;
try req.start(.{});
try req.send(.{});
try req.writeAll("Hello, ");
try req.writeAll("World!\n");
try req.finish();
@ -468,7 +475,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // relative redirect
var h = http.Headers{ .allocator = calloc };
@ -479,10 +486,10 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.GET, uri, h, .{});
var req = try client.open(.GET, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
try req.wait();
const body = try req.reader().readAllAlloc(calloc, 8192);
@ -492,7 +499,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // redirect from root
var h = http.Headers{ .allocator = calloc };
@ -503,10 +510,10 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.GET, uri, h, .{});
var req = try client.open(.GET, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
try req.wait();
const body = try req.reader().readAllAlloc(calloc, 8192);
@ -516,7 +523,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // absolute redirect
var h = http.Headers{ .allocator = calloc };
@ -527,10 +534,10 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.GET, uri, h, .{});
var req = try client.open(.GET, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
try req.wait();
const body = try req.reader().readAllAlloc(calloc, 8192);
@ -540,7 +547,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // too many redirects
var h = http.Headers{ .allocator = calloc };
@ -551,10 +558,10 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.GET, uri, h, .{});
var req = try client.open(.GET, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
req.wait() catch |err| switch (err) {
error.TooManyHttpRedirects => {},
else => return err,
@ -562,7 +569,7 @@ pub fn main() !void {
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // check client without segfault by connection error after redirection
var h = http.Headers{ .allocator = calloc };
@ -573,17 +580,20 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.GET, uri, h, .{});
var req = try client.open(.GET, uri, h, .{});
defer req.deinit();
try req.start(.{});
try req.send(.{});
const result = req.wait();
try testing.expectError(error.ConnectionRefused, result); // expects not segfault but the regular error
// a proxy without an upstream is likely to return a 5xx status.
if (client.http_proxy == null) {
try testing.expectError(error.ConnectionRefused, result); // expects not segfault but the regular error
}
}
// connection has been kept alive
try testing.expect(client.connection_pool.free_len == 1);
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
{ // Client.fetch()
var h = http.Headers{ .allocator = calloc };
@ -618,15 +628,12 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.POST, uri, h, .{});
var req = try client.open(.POST, uri, h, .{});
defer req.deinit();
req.transfer_encoding = .chunked;
try req.start(.{});
try req.wait();
try testing.expectEqual(http.Status.@"continue", req.response.status);
try req.send(.{});
try req.writeAll("Hello, ");
try req.writeAll("World!\n");
try req.finish();
@ -652,12 +659,12 @@ pub fn main() !void {
const uri = try std.Uri.parse(location);
log.info("{s}", .{location});
var req = try client.request(.POST, uri, h, .{});
var req = try client.open(.POST, uri, h, .{});
defer req.deinit();
req.transfer_encoding = .chunked;
try req.start(.{});
try req.send(.{});
try req.wait();
try testing.expectEqual(http.Status.expectation_failed, req.response.status);
}
@ -672,9 +679,9 @@ pub fn main() !void {
defer calloc.free(requests);
for (0..total_connections) |i| {
var req = try client.request(.GET, uri, .{ .allocator = calloc }, .{});
var req = try client.open(.GET, uri, .{ .allocator = calloc }, .{});
req.response.parser.done = true;
req.connection.?.data.closing = false;
req.connection.?.closing = false;
requests[i] = req;
}