diff options
| author | Nameless <truemedian@gmail.com> | 2023-04-25 09:22:20 -0500 |
|---|---|---|
| committer | Nameless <truemedian@gmail.com> | 2023-05-06 21:35:15 -0500 |
| commit | 71c228fe6572b9f3b30e82035bf8fd7e2b1dd29d (patch) | |
| tree | bc260df4dd4ac6befa2f05d54a39dd8b43548907 /test | |
| parent | d71a43ec2c28a53a3e1d9bcb538707eca00a6fc0 (diff) | |
| download | zig-71c228fe6572b9f3b30e82035bf8fd7e2b1dd29d.tar.gz zig-71c228fe6572b9f3b30e82035bf8fd7e2b1dd29d.zip | |
std.http: add simple standalone http tests, add state check for http server
Diffstat (limited to 'test')
| -rw-r--r-- | test/standalone.zig | 4 | ||||
| -rw-r--r-- | test/standalone/http.zig | 432 |
2 files changed, 436 insertions, 0 deletions
diff --git a/test/standalone.zig b/test/standalone.zig index 1f4d7cfded..a055da9761 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -55,6 +55,10 @@ 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`. diff --git a/test/standalone/http.zig b/test/standalone/http.zig new file mode 100644 index 0000000000..1ed4b1b279 --- /dev/null +++ b/test/standalone/http.zig @@ -0,0 +1,432 @@ +const std = @import("std"); + +const http = std.http; +const Server = http.Server; +const Client = http.Client; + +const mem = std.mem; +const testing = std.testing; + +const max_header_size = 8192; + +var gpa_server = std.heap.GeneralPurposeAllocator(.{}){}; +var gpa_client = std.heap.GeneralPurposeAllocator(.{}){}; + +const salloc = gpa_server.allocator(); +const calloc = gpa_client.allocator(); + +fn handleRequest(res: *Server.Response) !void { + const log = std.log.scoped(.server); + + log.info("{s} {s} {s}", .{ @tagName(res.request.method), @tagName(res.request.version), res.request.target }); + + const body = try res.reader().readAllAlloc(salloc, 8192); + defer salloc.free(body); + + if (res.request.headers.contains("connection")) { + try res.headers.append("connection", "keep-alive"); + } + + if (mem.startsWith(u8, res.request.target, "/get")) { + if (std.mem.indexOf(u8, res.request.target, "?chunked") != null) { + res.transfer_encoding = .chunked; + } else { + res.transfer_encoding = .{ .content_length = 14 }; + } + + try res.headers.append("content-type", "text/plain"); + + try res.do(); + if (res.request.method != .HEAD) { + try res.writeAll("Hello, "); + try res.writeAll("World!\n"); + try res.finish(); + } + } else if (mem.eql(u8, res.request.target, "/echo-content")) { + try testing.expectEqualStrings("Hello, World!\n", body); + try testing.expectEqualStrings("text/plain", res.request.headers.getFirstValue("content-type").?); + + if (res.request.headers.contains("transfer-encoding")) { + try testing.expectEqualStrings("chunked", res.request.headers.getFirstValue("transfer-encoding").?); + res.transfer_encoding = .chunked; + } else { + res.transfer_encoding = .{ .content_length = 14 }; + try testing.expectEqualStrings("14", res.request.headers.getFirstValue("content-length").?); + } + + try res.do(); + 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.writeAll("Hello, "); + try res.writeAll("World!\n"); + // try res.finish(); + try res.connection.writeAll("0\r\nX-Checksum: aaaa\r\n\r\n"); + } else if (mem.eql(u8, res.request.target, "/redirect/1")) { + res.transfer_encoding = .chunked; + + res.status = .found; + try res.headers.append("location", "../../get"); + + try res.do(); + try res.writeAll("Hello, "); + try res.writeAll("Redirected!\n"); + try res.finish(); + } else if (mem.eql(u8, res.request.target, "/redirect/2")) { + res.transfer_encoding = .chunked; + + res.status = .found; + try res.headers.append("location", "/redirect/1"); + + try res.do(); + try res.writeAll("Hello, "); + try res.writeAll("Redirected!\n"); + try res.finish(); + } else if (mem.eql(u8, res.request.target, "/redirect/3")) { + res.transfer_encoding = .chunked; + + const location = try std.fmt.allocPrint(salloc, "http://127.0.0.1:{d}/redirect/2", .{res.server.socket.listen_address.getPort()}); + defer salloc.free(location); + + res.status = .found; + try res.headers.append("location", location); + + try res.do(); + try res.writeAll("Hello, "); + try res.writeAll("Redirected!\n"); + try res.finish(); + } else if (mem.eql(u8, res.request.target, "/redirect/4")) { + res.transfer_encoding = .chunked; + + res.status = .found; + try res.headers.append("location", "/redirect/3"); + + try res.do(); + try res.writeAll("Hello, "); + try res.writeAll("Redirected!\n"); + try res.finish(); + } else { + res.status = .not_found; + try res.do(); + } +} + +var handle_new_requests = true; + +fn runServer(srv: *Server) !void { + outer: while (handle_new_requests) { + var res = try srv.accept(.{ .dynamic = max_header_size }); + defer res.deinit(); + + while (res.reset()) { + res.wait() catch |err| switch (err) { + error.HttpHeadersInvalid => continue :outer, + error.EndOfStream => continue, + else => return err, + }; + + try handleRequest(&res); + } + } +} + +fn serverThread(srv: *Server) void { + defer srv.deinit(); + defer _ = gpa_server.deinit(); + + runServer(srv) catch |err| { + std.debug.print("server error: {}\n", .{err}); + + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + + _ = gpa_server.deinit(); + std.os.exit(1); + }; +} + +fn killServer(addr: std.net.Address) void { + handle_new_requests = false; + + const conn = std.net.tcpConnectToAddress(addr) catch return; + conn.close(); +} + +pub fn main() !void { + const log = std.log.scoped(.client); + + defer _ = gpa_client.deinit(); + + var server = Server.init(salloc, .{ .reuse_address = true }); + + const addr = std.net.Address.parseIp("127.0.0.1", 0) catch unreachable; + try server.listen(addr); + + const port = server.socket.listen_address.getPort(); + + const server_thread = try std.Thread.spawn(.{}, serverThread, .{&server}); + + var client = Client{ .allocator = calloc }; + + defer client.deinit(); + + { // read content-length response + var h = http.Headers{ .allocator = calloc }; + defer h.deinit(); + + 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 req = try client.request(.GET, uri, h, .{}); + defer req.deinit(); + + try req.start(); + 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.headers.getFirstValue("content-type").?); + } + + { // send head request and not read chunked + var h = http.Headers{ .allocator = calloc }; + defer h.deinit(); + + 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 req = try client.request(.HEAD, uri, h, .{}); + defer req.deinit(); + + try req.start(); + 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.headers.getFirstValue("content-type").?); + try testing.expectEqualStrings("14", req.response.headers.getFirstValue("content-length").?); + } + + { // read chunked response + var h = http.Headers{ .allocator = calloc }; + defer h.deinit(); + + 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 req = try client.request(.GET, uri, h, .{}); + defer req.deinit(); + + try req.start(); + 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.headers.getFirstValue("content-type").?); + } + + { // send head request and not read chunked + var h = http.Headers{ .allocator = calloc }; + defer h.deinit(); + + 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 req = try client.request(.HEAD, uri, h, .{}); + defer req.deinit(); + + try req.start(); + 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.headers.getFirstValue("content-type").?); + try testing.expectEqualStrings("chunked", req.response.headers.getFirstValue("transfer-encoding").?); + } + + { // check trailing headers + var h = http.Headers{ .allocator = calloc }; + defer h.deinit(); + + const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/trailer", .{port}); + defer calloc.free(location); + const uri = try std.Uri.parse(location); + + log.info("{s}", .{location}); + var req = try client.request(.GET, uri, h, .{}); + defer req.deinit(); + + try req.start(); + 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("aaaa", req.response.headers.getFirstValue("x-checksum").?); + } + + { // send content-length request + var h = http.Headers{ .allocator = calloc }; + defer h.deinit(); + + try h.append("content-type", "text/plain"); + + const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/echo-content", .{port}); + defer calloc.free(location); + const uri = try std.Uri.parse(location); + + log.info("{s}", .{location}); + var req = try client.request(.POST, uri, h, .{}); + defer req.deinit(); + + req.transfer_encoding = .{ .content_length = 14 }; + + try req.start(); + try req.writeAll("Hello, "); + try req.writeAll("World!\n"); + try req.finish(); + + try req.wait(); + + const body = try req.reader().readAllAlloc(calloc, 8192); + defer calloc.free(body); + + try testing.expectEqualStrings("Hello, World!\n", body); + } + + { // send chunked request + var h = http.Headers{ .allocator = calloc }; + defer h.deinit(); + + try h.append("content-type", "text/plain"); + + const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/echo-content", .{port}); + defer calloc.free(location); + const uri = try std.Uri.parse(location); + + log.info("{s}", .{location}); + var req = try client.request(.POST, uri, h, .{}); + defer req.deinit(); + + req.transfer_encoding = .chunked; + + try req.start(); + try req.writeAll("Hello, "); + try req.writeAll("World!\n"); + try req.finish(); + + try req.wait(); + + const body = try req.reader().readAllAlloc(calloc, 8192); + defer calloc.free(body); + + try testing.expectEqualStrings("Hello, World!\n", body); + } + + { // relative redirect + var h = http.Headers{ .allocator = calloc }; + defer h.deinit(); + + 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 req = try client.request(.GET, uri, h, .{}); + defer req.deinit(); + + try req.start(); + try req.wait(); + + const body = try req.reader().readAllAlloc(calloc, 8192); + defer calloc.free(body); + + try testing.expectEqualStrings("Hello, World!\n", body); + } + + { // redirect from root + var h = http.Headers{ .allocator = calloc }; + defer h.deinit(); + + 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 req = try client.request(.GET, uri, h, .{}); + defer req.deinit(); + + try req.start(); + try req.wait(); + + const body = try req.reader().readAllAlloc(calloc, 8192); + defer calloc.free(body); + + try testing.expectEqualStrings("Hello, World!\n", body); + } + + { // absolute redirect + var h = http.Headers{ .allocator = calloc }; + defer h.deinit(); + + 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 req = try client.request(.GET, uri, h, .{}); + defer req.deinit(); + + try req.start(); + try req.wait(); + + const body = try req.reader().readAllAlloc(calloc, 8192); + defer calloc.free(body); + + try testing.expectEqualStrings("Hello, World!\n", body); + } + + { // too many redirects + var h = http.Headers{ .allocator = calloc }; + defer h.deinit(); + + 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 req = try client.request(.GET, uri, h, .{}); + defer req.deinit(); + + try req.start(); + req.wait() catch |err| switch (err) { + error.TooManyHttpRedirects => {}, + else => return err, + }; + } + + killServer(server.socket.listen_address); + server_thread.join(); +} |
