mirror of
https://github.com/ziglang/zig.git
synced 2026-01-20 14:25:16 +00:00
std.http: migrate remaining test/standalone/http.zig to std lib
These tests were not being actually run. Now they are run along with testing the standard library.
This commit is contained in:
parent
10beb19ce7
commit
483b63d301
@ -1676,3 +1676,7 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult {
|
||||
.status = req.response.status,
|
||||
};
|
||||
}
|
||||
|
||||
test {
|
||||
_ = &initDefaultProxies;
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
const http = std.http;
|
||||
const mem = std.mem;
|
||||
const native_endian = builtin.cpu.arch.endian();
|
||||
const expect = std.testing.expect;
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
@ -15,7 +17,7 @@ test "trailers" {
|
||||
const conn = try net_server.accept();
|
||||
defer conn.stream.close();
|
||||
|
||||
var server = std.http.Server.init(conn, &header_buffer);
|
||||
var server = http.Server.init(conn, &header_buffer);
|
||||
|
||||
try expectEqual(.ready, server.state);
|
||||
var request = try server.receiveHead();
|
||||
@ -24,7 +26,7 @@ test "trailers" {
|
||||
}
|
||||
}
|
||||
|
||||
fn serve(request: *std.http.Server.Request) !void {
|
||||
fn serve(request: *http.Server.Request) !void {
|
||||
try expectEqualStrings(request.head.target, "/trailer");
|
||||
|
||||
var send_buffer: [1024]u8 = undefined;
|
||||
@ -46,7 +48,7 @@ test "trailers" {
|
||||
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
var client: std.http.Client = .{ .allocator = gpa };
|
||||
var client: http.Client = .{ .allocator = gpa };
|
||||
defer client.deinit();
|
||||
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{
|
||||
@ -103,14 +105,14 @@ test "HTTP server handles a chunked transfer coding request" {
|
||||
const conn = try net_server.accept();
|
||||
defer conn.stream.close();
|
||||
|
||||
var server = std.http.Server.init(conn, &header_buffer);
|
||||
var server = http.Server.init(conn, &header_buffer);
|
||||
var request = try server.receiveHead();
|
||||
|
||||
try expect(request.head.transfer_encoding == .chunked);
|
||||
|
||||
var buf: [128]u8 = undefined;
|
||||
const n = try (try request.reader()).readAll(&buf);
|
||||
try expect(std.mem.eql(u8, buf[0..n], "ABCD"));
|
||||
try expect(mem.eql(u8, buf[0..n], "ABCD"));
|
||||
|
||||
try request.respond("message from server!\n", .{
|
||||
.extra_headers = &.{
|
||||
@ -151,18 +153,18 @@ test "echo content server" {
|
||||
const conn = try net_server.accept();
|
||||
defer conn.stream.close();
|
||||
|
||||
var http_server = std.http.Server.init(conn, &read_buffer);
|
||||
var http_server = http.Server.init(conn, &read_buffer);
|
||||
|
||||
while (http_server.state == .ready) {
|
||||
var request = http_server.receiveHead() catch |err| switch (err) {
|
||||
error.HttpConnectionClosing => continue :accept,
|
||||
else => |e| return e,
|
||||
};
|
||||
if (std.mem.eql(u8, request.head.target, "/end")) {
|
||||
if (mem.eql(u8, request.head.target, "/end")) {
|
||||
return request.respond("", .{ .keep_alive = false });
|
||||
}
|
||||
if (request.head.expect) |expect_header_value| {
|
||||
if (std.mem.eql(u8, expect_header_value, "garbage")) {
|
||||
if (mem.eql(u8, expect_header_value, "garbage")) {
|
||||
try expectError(error.HttpExpectationFailed, request.reader());
|
||||
try request.respond("", .{ .keep_alive = false });
|
||||
continue;
|
||||
@ -178,7 +180,7 @@ test "echo content server" {
|
||||
}
|
||||
}
|
||||
|
||||
fn handleRequest(request: *std.http.Server.Request) !void {
|
||||
fn handleRequest(request: *http.Server.Request) !void {
|
||||
//std.debug.print("server received {s} {s} {s}\n", .{
|
||||
// @tagName(request.head.method),
|
||||
// @tagName(request.head.version),
|
||||
@ -188,7 +190,7 @@ test "echo content server" {
|
||||
const body = try (try request.reader()).readAllAlloc(std.testing.allocator, 8192);
|
||||
defer std.testing.allocator.free(body);
|
||||
|
||||
try expect(std.mem.startsWith(u8, request.head.target, "/echo-content"));
|
||||
try expect(mem.startsWith(u8, request.head.target, "/echo-content"));
|
||||
try expectEqualStrings("Hello, World!\n", body);
|
||||
try expectEqualStrings("text/plain", request.head.content_type.?);
|
||||
|
||||
@ -215,7 +217,7 @@ test "echo content server" {
|
||||
defer test_server.destroy();
|
||||
|
||||
{
|
||||
var client: std.http.Client = .{ .allocator = std.testing.allocator };
|
||||
var client: http.Client = .{ .allocator = std.testing.allocator };
|
||||
defer client.deinit();
|
||||
|
||||
try echoTests(&client, test_server.port());
|
||||
@ -233,7 +235,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
|
||||
const conn = try net_server.accept();
|
||||
defer conn.stream.close();
|
||||
|
||||
var server = std.http.Server.init(conn, &header_buffer);
|
||||
var server = http.Server.init(conn, &header_buffer);
|
||||
|
||||
try expectEqual(.ready, server.state);
|
||||
var request = try server.receiveHead();
|
||||
@ -288,7 +290,474 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
|
||||
try expectEqualStrings(expected_response.items, response);
|
||||
}
|
||||
|
||||
fn echoTests(client: *std.http.Client, port: u16) !void {
|
||||
test "general client/server API coverage" {
|
||||
const global = struct {
|
||||
var handle_new_requests = true;
|
||||
};
|
||||
const test_server = try createTestServer(struct {
|
||||
fn run(net_server: *std.net.Server) anyerror!void {
|
||||
var client_header_buffer: [1024]u8 = undefined;
|
||||
outer: while (global.handle_new_requests) {
|
||||
var connection = try net_server.accept();
|
||||
defer connection.stream.close();
|
||||
|
||||
var http_server = http.Server.init(connection, &client_header_buffer);
|
||||
|
||||
while (http_server.state == .ready) {
|
||||
var request = http_server.receiveHead() catch |err| switch (err) {
|
||||
error.HttpConnectionClosing => continue :outer,
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
try handleRequest(&request, net_server.listen_address.getPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handleRequest(request: *http.Server.Request, listen_port: u16) !void {
|
||||
const log = std.log.scoped(.server);
|
||||
|
||||
log.info("{} {s} {s}", .{
|
||||
request.head.method,
|
||||
@tagName(request.head.version),
|
||||
request.head.target,
|
||||
});
|
||||
|
||||
const gpa = std.testing.allocator;
|
||||
const body = try (try request.reader()).readAllAlloc(gpa, 8192);
|
||||
defer gpa.free(body);
|
||||
|
||||
var send_buffer: [100]u8 = undefined;
|
||||
|
||||
if (mem.startsWith(u8, request.head.target, "/get")) {
|
||||
var response = request.respondStreaming(.{
|
||||
.send_buffer = &send_buffer,
|
||||
.content_length = if (mem.indexOf(u8, request.head.target, "?chunked") == null)
|
||||
14
|
||||
else
|
||||
null,
|
||||
.respond_options = .{
|
||||
.extra_headers = &.{
|
||||
.{ .name = "content-type", .value = "text/plain" },
|
||||
},
|
||||
},
|
||||
});
|
||||
const w = response.writer();
|
||||
try w.writeAll("Hello, ");
|
||||
try w.writeAll("World!\n");
|
||||
try response.end();
|
||||
// Writing again would cause an assertion failure.
|
||||
} else if (mem.startsWith(u8, request.head.target, "/large")) {
|
||||
var response = request.respondStreaming(.{
|
||||
.send_buffer = &send_buffer,
|
||||
.content_length = 14 * 1024 + 14 * 10,
|
||||
});
|
||||
|
||||
try response.flush(); // Test an early flush to send the HTTP headers before the body.
|
||||
|
||||
const w = response.writer();
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < 5) : (i += 1) {
|
||||
try w.writeAll("Hello, World!\n");
|
||||
}
|
||||
|
||||
try w.writeAll("Hello, World!\n" ** 1024);
|
||||
|
||||
i = 0;
|
||||
while (i < 5) : (i += 1) {
|
||||
try w.writeAll("Hello, World!\n");
|
||||
}
|
||||
|
||||
try response.end();
|
||||
} else if (mem.eql(u8, request.head.target, "/redirect/1")) {
|
||||
var response = request.respondStreaming(.{
|
||||
.send_buffer = &send_buffer,
|
||||
.respond_options = .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "location", .value = "../../get" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const w = response.writer();
|
||||
try w.writeAll("Hello, ");
|
||||
try w.writeAll("Redirected!\n");
|
||||
try response.end();
|
||||
} else if (mem.eql(u8, request.head.target, "/redirect/2")) {
|
||||
try request.respond("Hello, Redirected!\n", .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "location", .value = "/redirect/1" },
|
||||
},
|
||||
});
|
||||
} else if (mem.eql(u8, request.head.target, "/redirect/3")) {
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/2", .{
|
||||
listen_port,
|
||||
});
|
||||
defer gpa.free(location);
|
||||
|
||||
try request.respond("Hello, Redirected!\n", .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "location", .value = location },
|
||||
},
|
||||
});
|
||||
} else if (mem.eql(u8, request.head.target, "/redirect/4")) {
|
||||
try request.respond("Hello, Redirected!\n", .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "location", .value = "/redirect/3" },
|
||||
},
|
||||
});
|
||||
} else if (mem.eql(u8, request.head.target, "/redirect/invalid")) {
|
||||
const invalid_port = try getUnusedTcpPort();
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}", .{invalid_port});
|
||||
defer gpa.free(location);
|
||||
|
||||
try request.respond("", .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "location", .value = location },
|
||||
},
|
||||
});
|
||||
} else {
|
||||
try request.respond("", .{ .status = .not_found });
|
||||
}
|
||||
}
|
||||
|
||||
fn getUnusedTcpPort() !u16 {
|
||||
const addr = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var s = try addr.listen(.{});
|
||||
defer s.deinit();
|
||||
return s.listen_address.in.getPort();
|
||||
}
|
||||
});
|
||||
defer test_server.destroy();
|
||||
|
||||
const log = std.log.scoped(.client);
|
||||
|
||||
const gpa = std.testing.allocator;
|
||||
var client: http.Client = .{ .allocator = gpa };
|
||||
errdefer client.deinit();
|
||||
// defer client.deinit(); handled below
|
||||
|
||||
const port = test_server.port();
|
||||
|
||||
{ // read content-length response
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(gpa, 8192);
|
||||
defer gpa.free(body);
|
||||
|
||||
try expectEqualStrings("Hello, World!\n", body);
|
||||
try expectEqualStrings("text/plain", req.response.content_type.?);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // read large content-length response
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/large", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(gpa, 8192 * 1024);
|
||||
defer gpa.free(body);
|
||||
|
||||
try expectEqual(@as(usize, 14 * 1024 + 14 * 10), body.len);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // send head request and not read chunked
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.HEAD, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(gpa, 8192);
|
||||
defer gpa.free(body);
|
||||
|
||||
try expectEqualStrings("", body);
|
||||
try expectEqualStrings("text/plain", req.response.content_type.?);
|
||||
try expectEqual(14, req.response.content_length.?);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // read chunked response
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get?chunked", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(gpa, 8192);
|
||||
defer gpa.free(body);
|
||||
|
||||
try expectEqualStrings("Hello, World!\n", body);
|
||||
try expectEqualStrings("text/plain", req.response.content_type.?);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // send head request and not read chunked
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get?chunked", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.HEAD, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(gpa, 8192);
|
||||
defer gpa.free(body);
|
||||
|
||||
try expectEqualStrings("", body);
|
||||
try expectEqualStrings("text/plain", req.response.content_type.?);
|
||||
try expect(req.response.transfer_encoding == .chunked);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // read content-length response with connection close
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
.keep_alive = false,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(gpa, 8192);
|
||||
defer gpa.free(body);
|
||||
|
||||
try expectEqualStrings("Hello, World!\n", body);
|
||||
try expectEqualStrings("text/plain", req.response.content_type.?);
|
||||
}
|
||||
|
||||
// connection has been closed
|
||||
try expect(client.connection_pool.free_len == 0);
|
||||
|
||||
{ // relative redirect
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/1", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(gpa, 8192);
|
||||
defer gpa.free(body);
|
||||
|
||||
try expectEqualStrings("Hello, World!\n", body);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // redirect from root
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/2", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(gpa, 8192);
|
||||
defer gpa.free(body);
|
||||
|
||||
try expectEqualStrings("Hello, World!\n", body);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // absolute redirect
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/3", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(gpa, 8192);
|
||||
defer gpa.free(body);
|
||||
|
||||
try expectEqualStrings("Hello, World!\n", body);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // too many redirects
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/4", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
req.wait() catch |err| switch (err) {
|
||||
error.TooManyHttpRedirects => {},
|
||||
else => return err,
|
||||
};
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // check client without segfault by connection error after redirection
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/invalid", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
const result = req.wait();
|
||||
|
||||
// a proxy without an upstream is likely to return a 5xx status.
|
||||
if (client.http_proxy == null) {
|
||||
try expectError(error.ConnectionRefused, result); // expects not segfault but the regular error
|
||||
}
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // issue 16282 *** This test leaves the client in an invalid state, it must be last ***
|
||||
const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get", .{port});
|
||||
defer gpa.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
const total_connections = client.connection_pool.free_size + 64;
|
||||
var requests = try gpa.alloc(http.Client.Request, total_connections);
|
||||
defer gpa.free(requests);
|
||||
|
||||
var header_bufs = std.ArrayList([]u8).init(gpa);
|
||||
defer header_bufs.deinit();
|
||||
defer for (header_bufs.items) |item| gpa.free(item);
|
||||
|
||||
for (0..total_connections) |i| {
|
||||
const headers_buf = try gpa.alloc(u8, 1024);
|
||||
try header_bufs.append(headers_buf);
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = headers_buf,
|
||||
});
|
||||
req.response.parser.done = true;
|
||||
req.connection.?.closing = false;
|
||||
requests[i] = req;
|
||||
}
|
||||
|
||||
for (0..total_connections) |i| {
|
||||
requests[i].deinit();
|
||||
}
|
||||
|
||||
// free connections should be full now
|
||||
try expect(client.connection_pool.free_len == client.connection_pool.free_size);
|
||||
}
|
||||
|
||||
client.deinit();
|
||||
|
||||
{
|
||||
global.handle_new_requests = false;
|
||||
|
||||
const conn = try std.net.tcpConnectToAddress(test_server.net_server.listen_address);
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
|
||||
fn echoTests(client: *http.Client, port: u16) !void {
|
||||
const gpa = std.testing.allocator;
|
||||
var location_buffer: [100]u8 = undefined;
|
||||
|
||||
|
||||
@ -55,10 +55,6 @@ pub const simple_cases = [_]SimpleCase{
|
||||
.os_filter = .windows,
|
||||
.link_libc = true,
|
||||
},
|
||||
.{
|
||||
.src_path = "test/standalone/http.zig",
|
||||
.all_modes = true,
|
||||
},
|
||||
|
||||
// Ensure the development tools are buildable. Alphabetically sorted.
|
||||
// No need to build `tools/spirv/grammar.zig`.
|
||||
|
||||
@ -1,510 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const http = std.http;
|
||||
|
||||
const mem = std.mem;
|
||||
const testing = std.testing;
|
||||
|
||||
pub const std_options = .{
|
||||
.http_disable_tls = true,
|
||||
};
|
||||
|
||||
const max_header_size = 8192;
|
||||
|
||||
var gpa_server = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){};
|
||||
var gpa_client = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){};
|
||||
|
||||
const salloc = gpa_server.allocator();
|
||||
const calloc = gpa_client.allocator();
|
||||
|
||||
fn handleRequest(request: *http.Server.Request, listen_port: u16) !void {
|
||||
const log = std.log.scoped(.server);
|
||||
|
||||
log.info("{} {s} {s}", .{
|
||||
request.head.method,
|
||||
@tagName(request.head.version),
|
||||
request.head.target,
|
||||
});
|
||||
|
||||
const body = try (try request.reader()).readAllAlloc(salloc, 8192);
|
||||
defer salloc.free(body);
|
||||
|
||||
var send_buffer: [100]u8 = undefined;
|
||||
|
||||
if (mem.startsWith(u8, request.head.target, "/get")) {
|
||||
var response = request.respondStreaming(.{
|
||||
.send_buffer = &send_buffer,
|
||||
.content_length = if (std.mem.indexOf(u8, request.head.target, "?chunked") == null)
|
||||
14
|
||||
else
|
||||
null,
|
||||
.respond_options = .{
|
||||
.extra_headers = &.{
|
||||
.{ .name = "content-type", .value = "text/plain" },
|
||||
},
|
||||
},
|
||||
});
|
||||
const w = response.writer();
|
||||
try w.writeAll("Hello, ");
|
||||
try w.writeAll("World!\n");
|
||||
try response.end();
|
||||
// Writing again would cause an assertion failure.
|
||||
} else if (mem.startsWith(u8, request.head.target, "/large")) {
|
||||
var response = request.respondStreaming(.{
|
||||
.send_buffer = &send_buffer,
|
||||
.content_length = 14 * 1024 + 14 * 10,
|
||||
});
|
||||
|
||||
try response.flush(); // Test an early flush to send the HTTP headers before the body.
|
||||
|
||||
const w = response.writer();
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < 5) : (i += 1) {
|
||||
try w.writeAll("Hello, World!\n");
|
||||
}
|
||||
|
||||
try w.writeAll("Hello, World!\n" ** 1024);
|
||||
|
||||
i = 0;
|
||||
while (i < 5) : (i += 1) {
|
||||
try w.writeAll("Hello, World!\n");
|
||||
}
|
||||
|
||||
try response.end();
|
||||
} else if (mem.eql(u8, request.head.target, "/redirect/1")) {
|
||||
var response = request.respondStreaming(.{
|
||||
.send_buffer = &send_buffer,
|
||||
.respond_options = .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "location", .value = "../../get" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const w = response.writer();
|
||||
try w.writeAll("Hello, ");
|
||||
try w.writeAll("Redirected!\n");
|
||||
try response.end();
|
||||
} else if (mem.eql(u8, request.head.target, "/redirect/2")) {
|
||||
try request.respond("Hello, Redirected!\n", .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "location", .value = "/redirect/1" },
|
||||
},
|
||||
});
|
||||
} else if (mem.eql(u8, request.head.target, "/redirect/3")) {
|
||||
const location = try std.fmt.allocPrint(salloc, "http://127.0.0.1:{d}/redirect/2", .{
|
||||
listen_port,
|
||||
});
|
||||
defer salloc.free(location);
|
||||
|
||||
try request.respond("Hello, Redirected!\n", .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "location", .value = location },
|
||||
},
|
||||
});
|
||||
} else if (mem.eql(u8, request.head.target, "/redirect/4")) {
|
||||
try request.respond("Hello, Redirected!\n", .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "location", .value = "/redirect/3" },
|
||||
},
|
||||
});
|
||||
} else if (mem.eql(u8, request.head.target, "/redirect/invalid")) {
|
||||
const invalid_port = try getUnusedTcpPort();
|
||||
const location = try std.fmt.allocPrint(salloc, "http://127.0.0.1:{d}", .{invalid_port});
|
||||
defer salloc.free(location);
|
||||
|
||||
try request.respond("", .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "location", .value = location },
|
||||
},
|
||||
});
|
||||
} else {
|
||||
try request.respond("", .{ .status = .not_found });
|
||||
}
|
||||
}
|
||||
|
||||
var handle_new_requests = true;
|
||||
|
||||
fn runServer(server: *std.net.Server) !void {
|
||||
var client_header_buffer: [1024]u8 = undefined;
|
||||
outer: while (handle_new_requests) {
|
||||
var connection = try server.accept();
|
||||
defer connection.stream.close();
|
||||
|
||||
var http_server = http.Server.init(connection, &client_header_buffer);
|
||||
|
||||
while (http_server.state == .ready) {
|
||||
var request = http_server.receiveHead() catch |err| switch (err) {
|
||||
error.HttpConnectionClosing => continue :outer,
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
try handleRequest(&request, server.listen_address.getPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serverThread(server: *std.net.Server) void {
|
||||
defer _ = gpa_server.deinit();
|
||||
|
||||
runServer(server) catch |err| {
|
||||
std.debug.print("server error: {}\n", .{err});
|
||||
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
|
||||
_ = gpa_server.deinit();
|
||||
std.os.exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
fn getUnusedTcpPort() !u16 {
|
||||
const addr = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var s = try addr.listen(.{});
|
||||
defer s.deinit();
|
||||
return s.listen_address.in.getPort();
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
const log = std.log.scoped(.client);
|
||||
|
||||
defer _ = gpa_client.deinit();
|
||||
|
||||
const addr = std.net.Address.parseIp("127.0.0.1", 0) catch unreachable;
|
||||
var server = try addr.listen(.{ .reuse_address = true });
|
||||
defer server.deinit();
|
||||
|
||||
const port = server.listen_address.getPort();
|
||||
|
||||
const server_thread = try std.Thread.spawn(.{}, serverThread, .{&server});
|
||||
|
||||
var client: http.Client = .{ .allocator = calloc };
|
||||
errdefer client.deinit();
|
||||
// defer client.deinit(); handled below
|
||||
|
||||
var arena_instance = std.heap.ArenaAllocator.init(calloc);
|
||||
defer arena_instance.deinit();
|
||||
const arena = arena_instance.allocator();
|
||||
|
||||
try client.initDefaultProxies(arena);
|
||||
|
||||
{ // read content-length response
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(calloc, 8192);
|
||||
defer calloc.free(body);
|
||||
|
||||
try testing.expectEqualStrings("Hello, World!\n", body);
|
||||
try testing.expectEqualStrings("text/plain", req.response.content_type.?);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // read large content-length response
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/large", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(calloc, 8192 * 1024);
|
||||
defer calloc.free(body);
|
||||
|
||||
try testing.expectEqual(@as(usize, 14 * 1024 + 14 * 10), body.len);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // send head request and not read chunked
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.HEAD, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(calloc, 8192);
|
||||
defer calloc.free(body);
|
||||
|
||||
try testing.expectEqualStrings("", body);
|
||||
try testing.expectEqualStrings("text/plain", req.response.content_type.?);
|
||||
try testing.expectEqual(14, req.response.content_length.?);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // read chunked response
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get?chunked", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(calloc, 8192);
|
||||
defer calloc.free(body);
|
||||
|
||||
try testing.expectEqualStrings("Hello, World!\n", body);
|
||||
try testing.expectEqualStrings("text/plain", req.response.content_type.?);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // send head request and not read chunked
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get?chunked", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.HEAD, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(calloc, 8192);
|
||||
defer calloc.free(body);
|
||||
|
||||
try testing.expectEqualStrings("", body);
|
||||
try testing.expectEqualStrings("text/plain", req.response.content_type.?);
|
||||
try testing.expect(req.response.transfer_encoding == .chunked);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // read content-length response with connection close
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
.keep_alive = false,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(calloc, 8192);
|
||||
defer calloc.free(body);
|
||||
|
||||
try testing.expectEqualStrings("Hello, World!\n", body);
|
||||
try testing.expectEqualStrings("text/plain", req.response.content_type.?);
|
||||
}
|
||||
|
||||
// connection has been closed
|
||||
try testing.expect(client.connection_pool.free_len == 0);
|
||||
|
||||
{ // relative redirect
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/redirect/1", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(calloc, 8192);
|
||||
defer calloc.free(body);
|
||||
|
||||
try testing.expectEqualStrings("Hello, World!\n", body);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // redirect from root
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/redirect/2", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(calloc, 8192);
|
||||
defer calloc.free(body);
|
||||
|
||||
try testing.expectEqualStrings("Hello, World!\n", body);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // absolute redirect
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/redirect/3", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
try req.wait();
|
||||
|
||||
const body = try req.reader().readAllAlloc(calloc, 8192);
|
||||
defer calloc.free(body);
|
||||
|
||||
try testing.expectEqualStrings("Hello, World!\n", body);
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // too many redirects
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/redirect/4", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
req.wait() catch |err| switch (err) {
|
||||
error.TooManyHttpRedirects => {},
|
||||
else => return err,
|
||||
};
|
||||
}
|
||||
|
||||
// connection has been kept alive
|
||||
try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // check client without segfault by connection error after redirection
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/redirect/invalid", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
log.info("{s}", .{location});
|
||||
var server_header_buffer: [1024]u8 = undefined;
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
const result = req.wait();
|
||||
|
||||
// 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.http_proxy != null or client.connection_pool.free_len == 1);
|
||||
|
||||
{ // issue 16282 *** This test leaves the client in an invalid state, it must be last ***
|
||||
const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get", .{port});
|
||||
defer calloc.free(location);
|
||||
const uri = try std.Uri.parse(location);
|
||||
|
||||
const total_connections = client.connection_pool.free_size + 64;
|
||||
var requests = try calloc.alloc(http.Client.Request, total_connections);
|
||||
defer calloc.free(requests);
|
||||
|
||||
var header_bufs = std.ArrayList([]u8).init(calloc);
|
||||
defer header_bufs.deinit();
|
||||
defer for (header_bufs.items) |item| calloc.free(item);
|
||||
|
||||
for (0..total_connections) |i| {
|
||||
const headers_buf = try calloc.alloc(u8, 1024);
|
||||
try header_bufs.append(headers_buf);
|
||||
var req = try client.open(.GET, uri, .{
|
||||
.server_header_buffer = headers_buf,
|
||||
});
|
||||
req.response.parser.done = true;
|
||||
req.connection.?.closing = false;
|
||||
requests[i] = req;
|
||||
}
|
||||
|
||||
for (0..total_connections) |i| {
|
||||
requests[i].deinit();
|
||||
}
|
||||
|
||||
// free connections should be full now
|
||||
try testing.expect(client.connection_pool.free_len == client.connection_pool.free_size);
|
||||
}
|
||||
|
||||
client.deinit();
|
||||
|
||||
{
|
||||
handle_new_requests = false;
|
||||
|
||||
const conn = std.net.tcpConnectToAddress(server.listen_address) catch return;
|
||||
conn.close();
|
||||
}
|
||||
|
||||
server_thread.join();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user