authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-22 19:19:12-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 02:37:11-07:00
log483b63d301315d6ca2c1c8660f5ec636c7358dc6
treedb3e13650ac772202e2553d4dfee0a571cedf0b1
parent10beb19ce7804f9669f25a6b5d80e7eef3fdc14f

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.

4 files changed, 486 insertions(+), 527 deletions(-)

lib/std/http/Client.zig+4
......@@ -1676,3 +1676,7 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult {
16761676 .status = req.response.status,
16771677 };
16781678}
1679
1680test {
1681 _ = &initDefaultProxies;
1682}
lib/std/http/test.zig+482-13
......@@ -1,5 +1,7 @@
11const builtin = @import("builtin");
22const std = @import("std");
3const http = std.http;
4const mem = std.mem;
35const native_endian = builtin.cpu.arch.endian();
46const expect = std.testing.expect;
57const expectEqual = std.testing.expectEqual;
......@@ -15,7 +17,7 @@ test "trailers" {
1517 const conn = try net_server.accept();
1618 defer conn.stream.close();
1719
18 var server = std.http.Server.init(conn, &header_buffer);
20 var server = http.Server.init(conn, &header_buffer);
1921
2022 try expectEqual(.ready, server.state);
2123 var request = try server.receiveHead();
......@@ -24,7 +26,7 @@ test "trailers" {
2426 }
2527 }
2628
27 fn serve(request: *std.http.Server.Request) !void {
29 fn serve(request: *http.Server.Request) !void {
2830 try expectEqualStrings(request.head.target, "/trailer");
2931
3032 var send_buffer: [1024]u8 = undefined;
......@@ -46,7 +48,7 @@ test "trailers" {
4648
4749 const gpa = std.testing.allocator;
4850
49 var client: std.http.Client = .{ .allocator = gpa };
51 var client: http.Client = .{ .allocator = gpa };
5052 defer client.deinit();
5153
5254 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" {
103105 const conn = try net_server.accept();
104106 defer conn.stream.close();
105107
106 var server = std.http.Server.init(conn, &header_buffer);
108 var server = http.Server.init(conn, &header_buffer);
107109 var request = try server.receiveHead();
108110
109111 try expect(request.head.transfer_encoding == .chunked);
110112
111113 var buf: [128]u8 = undefined;
112114 const n = try (try request.reader()).readAll(&buf);
113 try expect(std.mem.eql(u8, buf[0..n], "ABCD"));
115 try expect(mem.eql(u8, buf[0..n], "ABCD"));
114116
115117 try request.respond("message from server!\n", .{
116118 .extra_headers = &.{
......@@ -151,18 +153,18 @@ test "echo content server" {
151153 const conn = try net_server.accept();
152154 defer conn.stream.close();
153155
154 var http_server = std.http.Server.init(conn, &read_buffer);
156 var http_server = http.Server.init(conn, &read_buffer);
155157
156158 while (http_server.state == .ready) {
157159 var request = http_server.receiveHead() catch |err| switch (err) {
158160 error.HttpConnectionClosing => continue :accept,
159161 else => |e| return e,
160162 };
161 if (std.mem.eql(u8, request.head.target, "/end")) {
163 if (mem.eql(u8, request.head.target, "/end")) {
162164 return request.respond("", .{ .keep_alive = false });
163165 }
164166 if (request.head.expect) |expect_header_value| {
165 if (std.mem.eql(u8, expect_header_value, "garbage")) {
167 if (mem.eql(u8, expect_header_value, "garbage")) {
166168 try expectError(error.HttpExpectationFailed, request.reader());
167169 try request.respond("", .{ .keep_alive = false });
168170 continue;
......@@ -178,7 +180,7 @@ test "echo content server" {
178180 }
179181 }
180182
181 fn handleRequest(request: *std.http.Server.Request) !void {
183 fn handleRequest(request: *http.Server.Request) !void {
182184 //std.debug.print("server received {s} {s} {s}\n", .{
183185 // @tagName(request.head.method),
184186 // @tagName(request.head.version),
......@@ -188,7 +190,7 @@ test "echo content server" {
188190 const body = try (try request.reader()).readAllAlloc(std.testing.allocator, 8192);
189191 defer std.testing.allocator.free(body);
190192
191 try expect(std.mem.startsWith(u8, request.head.target, "/echo-content"));
193 try expect(mem.startsWith(u8, request.head.target, "/echo-content"));
192194 try expectEqualStrings("Hello, World!\n", body);
193195 try expectEqualStrings("text/plain", request.head.content_type.?);
194196
......@@ -215,7 +217,7 @@ test "echo content server" {
215217 defer test_server.destroy();
216218
217219 {
218 var client: std.http.Client = .{ .allocator = std.testing.allocator };
220 var client: http.Client = .{ .allocator = std.testing.allocator };
219221 defer client.deinit();
220222
221223 try echoTests(&client, test_server.port());
......@@ -233,7 +235,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
233235 const conn = try net_server.accept();
234236 defer conn.stream.close();
235237
236 var server = std.http.Server.init(conn, &header_buffer);
238 var server = http.Server.init(conn, &header_buffer);
237239
238240 try expectEqual(.ready, server.state);
239241 var request = try server.receiveHead();
......@@ -288,7 +290,474 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
288290 try expectEqualStrings(expected_response.items, response);
289291}
290292
291fn echoTests(client: *std.http.Client, port: u16) !void {
293test "general client/server API coverage" {
294 const global = struct {
295 var handle_new_requests = true;
296 };
297 const test_server = try createTestServer(struct {
298 fn run(net_server: *std.net.Server) anyerror!void {
299 var client_header_buffer: [1024]u8 = undefined;
300 outer: while (global.handle_new_requests) {
301 var connection = try net_server.accept();
302 defer connection.stream.close();
303
304 var http_server = http.Server.init(connection, &client_header_buffer);
305
306 while (http_server.state == .ready) {
307 var request = http_server.receiveHead() catch |err| switch (err) {
308 error.HttpConnectionClosing => continue :outer,
309 else => |e| return e,
310 };
311
312 try handleRequest(&request, net_server.listen_address.getPort());
313 }
314 }
315 }
316
317 fn handleRequest(request: *http.Server.Request, listen_port: u16) !void {
318 const log = std.log.scoped(.server);
319
320 log.info("{} {s} {s}", .{
321 request.head.method,
322 @tagName(request.head.version),
323 request.head.target,
324 });
325
326 const gpa = std.testing.allocator;
327 const body = try (try request.reader()).readAllAlloc(gpa, 8192);
328 defer gpa.free(body);
329
330 var send_buffer: [100]u8 = undefined;
331
332 if (mem.startsWith(u8, request.head.target, "/get")) {
333 var response = request.respondStreaming(.{
334 .send_buffer = &send_buffer,
335 .content_length = if (mem.indexOf(u8, request.head.target, "?chunked") == null)
336 14
337 else
338 null,
339 .respond_options = .{
340 .extra_headers = &.{
341 .{ .name = "content-type", .value = "text/plain" },
342 },
343 },
344 });
345 const w = response.writer();
346 try w.writeAll("Hello, ");
347 try w.writeAll("World!\n");
348 try response.end();
349 // Writing again would cause an assertion failure.
350 } else if (mem.startsWith(u8, request.head.target, "/large")) {
351 var response = request.respondStreaming(.{
352 .send_buffer = &send_buffer,
353 .content_length = 14 * 1024 + 14 * 10,
354 });
355
356 try response.flush(); // Test an early flush to send the HTTP headers before the body.
357
358 const w = response.writer();
359
360 var i: u32 = 0;
361 while (i < 5) : (i += 1) {
362 try w.writeAll("Hello, World!\n");
363 }
364
365 try w.writeAll("Hello, World!\n" ** 1024);
366
367 i = 0;
368 while (i < 5) : (i += 1) {
369 try w.writeAll("Hello, World!\n");
370 }
371
372 try response.end();
373 } else if (mem.eql(u8, request.head.target, "/redirect/1")) {
374 var response = request.respondStreaming(.{
375 .send_buffer = &send_buffer,
376 .respond_options = .{
377 .status = .found,
378 .extra_headers = &.{
379 .{ .name = "location", .value = "../../get" },
380 },
381 },
382 });
383
384 const w = response.writer();
385 try w.writeAll("Hello, ");
386 try w.writeAll("Redirected!\n");
387 try response.end();
388 } else if (mem.eql(u8, request.head.target, "/redirect/2")) {
389 try request.respond("Hello, Redirected!\n", .{
390 .status = .found,
391 .extra_headers = &.{
392 .{ .name = "location", .value = "/redirect/1" },
393 },
394 });
395 } else if (mem.eql(u8, request.head.target, "/redirect/3")) {
396 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/2", .{
397 listen_port,
398 });
399 defer gpa.free(location);
400
401 try request.respond("Hello, Redirected!\n", .{
402 .status = .found,
403 .extra_headers = &.{
404 .{ .name = "location", .value = location },
405 },
406 });
407 } else if (mem.eql(u8, request.head.target, "/redirect/4")) {
408 try request.respond("Hello, Redirected!\n", .{
409 .status = .found,
410 .extra_headers = &.{
411 .{ .name = "location", .value = "/redirect/3" },
412 },
413 });
414 } else if (mem.eql(u8, request.head.target, "/redirect/invalid")) {
415 const invalid_port = try getUnusedTcpPort();
416 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}", .{invalid_port});
417 defer gpa.free(location);
418
419 try request.respond("", .{
420 .status = .found,
421 .extra_headers = &.{
422 .{ .name = "location", .value = location },
423 },
424 });
425 } else {
426 try request.respond("", .{ .status = .not_found });
427 }
428 }
429
430 fn getUnusedTcpPort() !u16 {
431 const addr = try std.net.Address.parseIp("127.0.0.1", 0);
432 var s = try addr.listen(.{});
433 defer s.deinit();
434 return s.listen_address.in.getPort();
435 }
436 });
437 defer test_server.destroy();
438
439 const log = std.log.scoped(.client);
440
441 const gpa = std.testing.allocator;
442 var client: http.Client = .{ .allocator = gpa };
443 errdefer client.deinit();
444 // defer client.deinit(); handled below
445
446 const port = test_server.port();
447
448 { // read content-length response
449 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get", .{port});
450 defer gpa.free(location);
451 const uri = try std.Uri.parse(location);
452
453 log.info("{s}", .{location});
454 var server_header_buffer: [1024]u8 = undefined;
455 var req = try client.open(.GET, uri, .{
456 .server_header_buffer = &server_header_buffer,
457 });
458 defer req.deinit();
459
460 try req.send(.{});
461 try req.wait();
462
463 const body = try req.reader().readAllAlloc(gpa, 8192);
464 defer gpa.free(body);
465
466 try expectEqualStrings("Hello, World!\n", body);
467 try expectEqualStrings("text/plain", req.response.content_type.?);
468 }
469
470 // connection has been kept alive
471 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
472
473 { // read large content-length response
474 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/large", .{port});
475 defer gpa.free(location);
476 const uri = try std.Uri.parse(location);
477
478 log.info("{s}", .{location});
479 var server_header_buffer: [1024]u8 = undefined;
480 var req = try client.open(.GET, uri, .{
481 .server_header_buffer = &server_header_buffer,
482 });
483 defer req.deinit();
484
485 try req.send(.{});
486 try req.wait();
487
488 const body = try req.reader().readAllAlloc(gpa, 8192 * 1024);
489 defer gpa.free(body);
490
491 try expectEqual(@as(usize, 14 * 1024 + 14 * 10), body.len);
492 }
493
494 // connection has been kept alive
495 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
496
497 { // send head request and not read chunked
498 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get", .{port});
499 defer gpa.free(location);
500 const uri = try std.Uri.parse(location);
501
502 log.info("{s}", .{location});
503 var server_header_buffer: [1024]u8 = undefined;
504 var req = try client.open(.HEAD, uri, .{
505 .server_header_buffer = &server_header_buffer,
506 });
507 defer req.deinit();
508
509 try req.send(.{});
510 try req.wait();
511
512 const body = try req.reader().readAllAlloc(gpa, 8192);
513 defer gpa.free(body);
514
515 try expectEqualStrings("", body);
516 try expectEqualStrings("text/plain", req.response.content_type.?);
517 try expectEqual(14, req.response.content_length.?);
518 }
519
520 // connection has been kept alive
521 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
522
523 { // read chunked response
524 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get?chunked", .{port});
525 defer gpa.free(location);
526 const uri = try std.Uri.parse(location);
527
528 log.info("{s}", .{location});
529 var server_header_buffer: [1024]u8 = undefined;
530 var req = try client.open(.GET, uri, .{
531 .server_header_buffer = &server_header_buffer,
532 });
533 defer req.deinit();
534
535 try req.send(.{});
536 try req.wait();
537
538 const body = try req.reader().readAllAlloc(gpa, 8192);
539 defer gpa.free(body);
540
541 try expectEqualStrings("Hello, World!\n", body);
542 try expectEqualStrings("text/plain", req.response.content_type.?);
543 }
544
545 // connection has been kept alive
546 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
547
548 { // send head request and not read chunked
549 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get?chunked", .{port});
550 defer gpa.free(location);
551 const uri = try std.Uri.parse(location);
552
553 log.info("{s}", .{location});
554 var server_header_buffer: [1024]u8 = undefined;
555 var req = try client.open(.HEAD, uri, .{
556 .server_header_buffer = &server_header_buffer,
557 });
558 defer req.deinit();
559
560 try req.send(.{});
561 try req.wait();
562
563 const body = try req.reader().readAllAlloc(gpa, 8192);
564 defer gpa.free(body);
565
566 try expectEqualStrings("", body);
567 try expectEqualStrings("text/plain", req.response.content_type.?);
568 try expect(req.response.transfer_encoding == .chunked);
569 }
570
571 // connection has been kept alive
572 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
573
574 { // read content-length response with connection close
575 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get", .{port});
576 defer gpa.free(location);
577 const uri = try std.Uri.parse(location);
578
579 log.info("{s}", .{location});
580 var server_header_buffer: [1024]u8 = undefined;
581 var req = try client.open(.GET, uri, .{
582 .server_header_buffer = &server_header_buffer,
583 .keep_alive = false,
584 });
585 defer req.deinit();
586
587 try req.send(.{});
588 try req.wait();
589
590 const body = try req.reader().readAllAlloc(gpa, 8192);
591 defer gpa.free(body);
592
593 try expectEqualStrings("Hello, World!\n", body);
594 try expectEqualStrings("text/plain", req.response.content_type.?);
595 }
596
597 // connection has been closed
598 try expect(client.connection_pool.free_len == 0);
599
600 { // relative redirect
601 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/1", .{port});
602 defer gpa.free(location);
603 const uri = try std.Uri.parse(location);
604
605 log.info("{s}", .{location});
606 var server_header_buffer: [1024]u8 = undefined;
607 var req = try client.open(.GET, uri, .{
608 .server_header_buffer = &server_header_buffer,
609 });
610 defer req.deinit();
611
612 try req.send(.{});
613 try req.wait();
614
615 const body = try req.reader().readAllAlloc(gpa, 8192);
616 defer gpa.free(body);
617
618 try expectEqualStrings("Hello, World!\n", body);
619 }
620
621 // connection has been kept alive
622 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
623
624 { // redirect from root
625 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/2", .{port});
626 defer gpa.free(location);
627 const uri = try std.Uri.parse(location);
628
629 log.info("{s}", .{location});
630 var server_header_buffer: [1024]u8 = undefined;
631 var req = try client.open(.GET, uri, .{
632 .server_header_buffer = &server_header_buffer,
633 });
634 defer req.deinit();
635
636 try req.send(.{});
637 try req.wait();
638
639 const body = try req.reader().readAllAlloc(gpa, 8192);
640 defer gpa.free(body);
641
642 try expectEqualStrings("Hello, World!\n", body);
643 }
644
645 // connection has been kept alive
646 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
647
648 { // absolute redirect
649 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/3", .{port});
650 defer gpa.free(location);
651 const uri = try std.Uri.parse(location);
652
653 log.info("{s}", .{location});
654 var server_header_buffer: [1024]u8 = undefined;
655 var req = try client.open(.GET, uri, .{
656 .server_header_buffer = &server_header_buffer,
657 });
658 defer req.deinit();
659
660 try req.send(.{});
661 try req.wait();
662
663 const body = try req.reader().readAllAlloc(gpa, 8192);
664 defer gpa.free(body);
665
666 try expectEqualStrings("Hello, World!\n", body);
667 }
668
669 // connection has been kept alive
670 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
671
672 { // too many redirects
673 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/4", .{port});
674 defer gpa.free(location);
675 const uri = try std.Uri.parse(location);
676
677 log.info("{s}", .{location});
678 var server_header_buffer: [1024]u8 = undefined;
679 var req = try client.open(.GET, uri, .{
680 .server_header_buffer = &server_header_buffer,
681 });
682 defer req.deinit();
683
684 try req.send(.{});
685 req.wait() catch |err| switch (err) {
686 error.TooManyHttpRedirects => {},
687 else => return err,
688 };
689 }
690
691 // connection has been kept alive
692 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
693
694 { // check client without segfault by connection error after redirection
695 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/invalid", .{port});
696 defer gpa.free(location);
697 const uri = try std.Uri.parse(location);
698
699 log.info("{s}", .{location});
700 var server_header_buffer: [1024]u8 = undefined;
701 var req = try client.open(.GET, uri, .{
702 .server_header_buffer = &server_header_buffer,
703 });
704 defer req.deinit();
705
706 try req.send(.{});
707 const result = req.wait();
708
709 // a proxy without an upstream is likely to return a 5xx status.
710 if (client.http_proxy == null) {
711 try expectError(error.ConnectionRefused, result); // expects not segfault but the regular error
712 }
713 }
714
715 // connection has been kept alive
716 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
717
718 { // issue 16282 *** This test leaves the client in an invalid state, it must be last ***
719 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get", .{port});
720 defer gpa.free(location);
721 const uri = try std.Uri.parse(location);
722
723 const total_connections = client.connection_pool.free_size + 64;
724 var requests = try gpa.alloc(http.Client.Request, total_connections);
725 defer gpa.free(requests);
726
727 var header_bufs = std.ArrayList([]u8).init(gpa);
728 defer header_bufs.deinit();
729 defer for (header_bufs.items) |item| gpa.free(item);
730
731 for (0..total_connections) |i| {
732 const headers_buf = try gpa.alloc(u8, 1024);
733 try header_bufs.append(headers_buf);
734 var req = try client.open(.GET, uri, .{
735 .server_header_buffer = headers_buf,
736 });
737 req.response.parser.done = true;
738 req.connection.?.closing = false;
739 requests[i] = req;
740 }
741
742 for (0..total_connections) |i| {
743 requests[i].deinit();
744 }
745
746 // free connections should be full now
747 try expect(client.connection_pool.free_len == client.connection_pool.free_size);
748 }
749
750 client.deinit();
751
752 {
753 global.handle_new_requests = false;
754
755 const conn = try std.net.tcpConnectToAddress(test_server.net_server.listen_address);
756 conn.close();
757 }
758}
759
760fn echoTests(client: *http.Client, port: u16) !void {
292761 const gpa = std.testing.allocator;
293762 var location_buffer: [100]u8 = undefined;
294763
test/standalone.zig-4
......@@ -55,10 +55,6 @@ pub const simple_cases = [_]SimpleCase{
5555 .os_filter = .windows,
5656 .link_libc = true,
5757 },
58 .{
59 .src_path = "test/standalone/http.zig",
60 .all_modes = true,
61 },
6258
6359 // Ensure the development tools are buildable. Alphabetically sorted.
6460 // No need to build `tools/spirv/grammar.zig`.
test/standalone/http.zig deleted-510
......@@ -1,510 +0,0 @@
1const std = @import("std");
2
3const http = std.http;
4
5const mem = std.mem;
6const testing = std.testing;
7
8pub const std_options = .{
9 .http_disable_tls = true,
10};
11
12const max_header_size = 8192;
13
14var gpa_server = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){};
15var gpa_client = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){};
16
17const salloc = gpa_server.allocator();
18const calloc = gpa_client.allocator();
19
20fn handleRequest(request: *http.Server.Request, listen_port: u16) !void {
21 const log = std.log.scoped(.server);
22
23 log.info("{} {s} {s}", .{
24 request.head.method,
25 @tagName(request.head.version),
26 request.head.target,
27 });
28
29 const body = try (try request.reader()).readAllAlloc(salloc, 8192);
30 defer salloc.free(body);
31
32 var send_buffer: [100]u8 = undefined;
33
34 if (mem.startsWith(u8, request.head.target, "/get")) {
35 var response = request.respondStreaming(.{
36 .send_buffer = &send_buffer,
37 .content_length = if (std.mem.indexOf(u8, request.head.target, "?chunked") == null)
38 14
39 else
40 null,
41 .respond_options = .{
42 .extra_headers = &.{
43 .{ .name = "content-type", .value = "text/plain" },
44 },
45 },
46 });
47 const w = response.writer();
48 try w.writeAll("Hello, ");
49 try w.writeAll("World!\n");
50 try response.end();
51 // Writing again would cause an assertion failure.
52 } else if (mem.startsWith(u8, request.head.target, "/large")) {
53 var response = request.respondStreaming(.{
54 .send_buffer = &send_buffer,
55 .content_length = 14 * 1024 + 14 * 10,
56 });
57
58 try response.flush(); // Test an early flush to send the HTTP headers before the body.
59
60 const w = response.writer();
61
62 var i: u32 = 0;
63 while (i < 5) : (i += 1) {
64 try w.writeAll("Hello, World!\n");
65 }
66
67 try w.writeAll("Hello, World!\n" ** 1024);
68
69 i = 0;
70 while (i < 5) : (i += 1) {
71 try w.writeAll("Hello, World!\n");
72 }
73
74 try response.end();
75 } else if (mem.eql(u8, request.head.target, "/redirect/1")) {
76 var response = request.respondStreaming(.{
77 .send_buffer = &send_buffer,
78 .respond_options = .{
79 .status = .found,
80 .extra_headers = &.{
81 .{ .name = "location", .value = "../../get" },
82 },
83 },
84 });
85
86 const w = response.writer();
87 try w.writeAll("Hello, ");
88 try w.writeAll("Redirected!\n");
89 try response.end();
90 } else if (mem.eql(u8, request.head.target, "/redirect/2")) {
91 try request.respond("Hello, Redirected!\n", .{
92 .status = .found,
93 .extra_headers = &.{
94 .{ .name = "location", .value = "/redirect/1" },
95 },
96 });
97 } else if (mem.eql(u8, request.head.target, "/redirect/3")) {
98 const location = try std.fmt.allocPrint(salloc, "http://127.0.0.1:{d}/redirect/2", .{
99 listen_port,
100 });
101 defer salloc.free(location);
102
103 try request.respond("Hello, Redirected!\n", .{
104 .status = .found,
105 .extra_headers = &.{
106 .{ .name = "location", .value = location },
107 },
108 });
109 } else if (mem.eql(u8, request.head.target, "/redirect/4")) {
110 try request.respond("Hello, Redirected!\n", .{
111 .status = .found,
112 .extra_headers = &.{
113 .{ .name = "location", .value = "/redirect/3" },
114 },
115 });
116 } else if (mem.eql(u8, request.head.target, "/redirect/invalid")) {
117 const invalid_port = try getUnusedTcpPort();
118 const location = try std.fmt.allocPrint(salloc, "http://127.0.0.1:{d}", .{invalid_port});
119 defer salloc.free(location);
120
121 try request.respond("", .{
122 .status = .found,
123 .extra_headers = &.{
124 .{ .name = "location", .value = location },
125 },
126 });
127 } else {
128 try request.respond("", .{ .status = .not_found });
129 }
130}
131
132var handle_new_requests = true;
133
134fn runServer(server: *std.net.Server) !void {
135 var client_header_buffer: [1024]u8 = undefined;
136 outer: while (handle_new_requests) {
137 var connection = try server.accept();
138 defer connection.stream.close();
139
140 var http_server = http.Server.init(connection, &client_header_buffer);
141
142 while (http_server.state == .ready) {
143 var request = http_server.receiveHead() catch |err| switch (err) {
144 error.HttpConnectionClosing => continue :outer,
145 else => |e| return e,
146 };
147
148 try handleRequest(&request, server.listen_address.getPort());
149 }
150 }
151}
152
153fn serverThread(server: *std.net.Server) void {
154 defer _ = gpa_server.deinit();
155
156 runServer(server) catch |err| {
157 std.debug.print("server error: {}\n", .{err});
158
159 if (@errorReturnTrace()) |trace| {
160 std.debug.dumpStackTrace(trace.*);
161 }
162
163 _ = gpa_server.deinit();
164 std.os.exit(1);
165 };
166}
167
168fn getUnusedTcpPort() !u16 {
169 const addr = try std.net.Address.parseIp("127.0.0.1", 0);
170 var s = try addr.listen(.{});
171 defer s.deinit();
172 return s.listen_address.in.getPort();
173}
174
175pub fn main() !void {
176 const log = std.log.scoped(.client);
177
178 defer _ = gpa_client.deinit();
179
180 const addr = std.net.Address.parseIp("127.0.0.1", 0) catch unreachable;
181 var server = try addr.listen(.{ .reuse_address = true });
182 defer server.deinit();
183
184 const port = server.listen_address.getPort();
185
186 const server_thread = try std.Thread.spawn(.{}, serverThread, .{&server});
187
188 var client: http.Client = .{ .allocator = calloc };
189 errdefer client.deinit();
190 // defer client.deinit(); handled below
191
192 var arena_instance = std.heap.ArenaAllocator.init(calloc);
193 defer arena_instance.deinit();
194 const arena = arena_instance.allocator();
195
196 try client.initDefaultProxies(arena);
197
198 { // read content-length response
199 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get", .{port});
200 defer calloc.free(location);
201 const uri = try std.Uri.parse(location);
202
203 log.info("{s}", .{location});
204 var server_header_buffer: [1024]u8 = undefined;
205 var req = try client.open(.GET, uri, .{
206 .server_header_buffer = &server_header_buffer,
207 });
208 defer req.deinit();
209
210 try req.send(.{});
211 try req.wait();
212
213 const body = try req.reader().readAllAlloc(calloc, 8192);
214 defer calloc.free(body);
215
216 try testing.expectEqualStrings("Hello, World!\n", body);
217 try testing.expectEqualStrings("text/plain", req.response.content_type.?);
218 }
219
220 // connection has been kept alive
221 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
222
223 { // read large content-length response
224 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/large", .{port});
225 defer calloc.free(location);
226 const uri = try std.Uri.parse(location);
227
228 log.info("{s}", .{location});
229 var server_header_buffer: [1024]u8 = undefined;
230 var req = try client.open(.GET, uri, .{
231 .server_header_buffer = &server_header_buffer,
232 });
233 defer req.deinit();
234
235 try req.send(.{});
236 try req.wait();
237
238 const body = try req.reader().readAllAlloc(calloc, 8192 * 1024);
239 defer calloc.free(body);
240
241 try testing.expectEqual(@as(usize, 14 * 1024 + 14 * 10), body.len);
242 }
243
244 // connection has been kept alive
245 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
246
247 { // send head request and not read chunked
248 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get", .{port});
249 defer calloc.free(location);
250 const uri = try std.Uri.parse(location);
251
252 log.info("{s}", .{location});
253 var server_header_buffer: [1024]u8 = undefined;
254 var req = try client.open(.HEAD, uri, .{
255 .server_header_buffer = &server_header_buffer,
256 });
257 defer req.deinit();
258
259 try req.send(.{});
260 try req.wait();
261
262 const body = try req.reader().readAllAlloc(calloc, 8192);
263 defer calloc.free(body);
264
265 try testing.expectEqualStrings("", body);
266 try testing.expectEqualStrings("text/plain", req.response.content_type.?);
267 try testing.expectEqual(14, req.response.content_length.?);
268 }
269
270 // connection has been kept alive
271 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
272
273 { // read chunked response
274 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get?chunked", .{port});
275 defer calloc.free(location);
276 const uri = try std.Uri.parse(location);
277
278 log.info("{s}", .{location});
279 var server_header_buffer: [1024]u8 = undefined;
280 var req = try client.open(.GET, uri, .{
281 .server_header_buffer = &server_header_buffer,
282 });
283 defer req.deinit();
284
285 try req.send(.{});
286 try req.wait();
287
288 const body = try req.reader().readAllAlloc(calloc, 8192);
289 defer calloc.free(body);
290
291 try testing.expectEqualStrings("Hello, World!\n", body);
292 try testing.expectEqualStrings("text/plain", req.response.content_type.?);
293 }
294
295 // connection has been kept alive
296 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
297
298 { // send head request and not read chunked
299 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get?chunked", .{port});
300 defer calloc.free(location);
301 const uri = try std.Uri.parse(location);
302
303 log.info("{s}", .{location});
304 var server_header_buffer: [1024]u8 = undefined;
305 var req = try client.open(.HEAD, uri, .{
306 .server_header_buffer = &server_header_buffer,
307 });
308 defer req.deinit();
309
310 try req.send(.{});
311 try req.wait();
312
313 const body = try req.reader().readAllAlloc(calloc, 8192);
314 defer calloc.free(body);
315
316 try testing.expectEqualStrings("", body);
317 try testing.expectEqualStrings("text/plain", req.response.content_type.?);
318 try testing.expect(req.response.transfer_encoding == .chunked);
319 }
320
321 // connection has been kept alive
322 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
323
324 { // read content-length response with connection close
325 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get", .{port});
326 defer calloc.free(location);
327 const uri = try std.Uri.parse(location);
328
329 log.info("{s}", .{location});
330 var server_header_buffer: [1024]u8 = undefined;
331 var req = try client.open(.GET, uri, .{
332 .server_header_buffer = &server_header_buffer,
333 .keep_alive = false,
334 });
335 defer req.deinit();
336
337 try req.send(.{});
338 try req.wait();
339
340 const body = try req.reader().readAllAlloc(calloc, 8192);
341 defer calloc.free(body);
342
343 try testing.expectEqualStrings("Hello, World!\n", body);
344 try testing.expectEqualStrings("text/plain", req.response.content_type.?);
345 }
346
347 // connection has been closed
348 try testing.expect(client.connection_pool.free_len == 0);
349
350 { // relative redirect
351 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/redirect/1", .{port});
352 defer calloc.free(location);
353 const uri = try std.Uri.parse(location);
354
355 log.info("{s}", .{location});
356 var server_header_buffer: [1024]u8 = undefined;
357 var req = try client.open(.GET, uri, .{
358 .server_header_buffer = &server_header_buffer,
359 });
360 defer req.deinit();
361
362 try req.send(.{});
363 try req.wait();
364
365 const body = try req.reader().readAllAlloc(calloc, 8192);
366 defer calloc.free(body);
367
368 try testing.expectEqualStrings("Hello, World!\n", body);
369 }
370
371 // connection has been kept alive
372 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
373
374 { // redirect from root
375 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/redirect/2", .{port});
376 defer calloc.free(location);
377 const uri = try std.Uri.parse(location);
378
379 log.info("{s}", .{location});
380 var server_header_buffer: [1024]u8 = undefined;
381 var req = try client.open(.GET, uri, .{
382 .server_header_buffer = &server_header_buffer,
383 });
384 defer req.deinit();
385
386 try req.send(.{});
387 try req.wait();
388
389 const body = try req.reader().readAllAlloc(calloc, 8192);
390 defer calloc.free(body);
391
392 try testing.expectEqualStrings("Hello, World!\n", body);
393 }
394
395 // connection has been kept alive
396 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
397
398 { // absolute redirect
399 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/redirect/3", .{port});
400 defer calloc.free(location);
401 const uri = try std.Uri.parse(location);
402
403 log.info("{s}", .{location});
404 var server_header_buffer: [1024]u8 = undefined;
405 var req = try client.open(.GET, uri, .{
406 .server_header_buffer = &server_header_buffer,
407 });
408 defer req.deinit();
409
410 try req.send(.{});
411 try req.wait();
412
413 const body = try req.reader().readAllAlloc(calloc, 8192);
414 defer calloc.free(body);
415
416 try testing.expectEqualStrings("Hello, World!\n", body);
417 }
418
419 // connection has been kept alive
420 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
421
422 { // too many redirects
423 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/redirect/4", .{port});
424 defer calloc.free(location);
425 const uri = try std.Uri.parse(location);
426
427 log.info("{s}", .{location});
428 var server_header_buffer: [1024]u8 = undefined;
429 var req = try client.open(.GET, uri, .{
430 .server_header_buffer = &server_header_buffer,
431 });
432 defer req.deinit();
433
434 try req.send(.{});
435 req.wait() catch |err| switch (err) {
436 error.TooManyHttpRedirects => {},
437 else => return err,
438 };
439 }
440
441 // connection has been kept alive
442 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
443
444 { // check client without segfault by connection error after redirection
445 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/redirect/invalid", .{port});
446 defer calloc.free(location);
447 const uri = try std.Uri.parse(location);
448
449 log.info("{s}", .{location});
450 var server_header_buffer: [1024]u8 = undefined;
451 var req = try client.open(.GET, uri, .{
452 .server_header_buffer = &server_header_buffer,
453 });
454 defer req.deinit();
455
456 try req.send(.{});
457 const result = req.wait();
458
459 // a proxy without an upstream is likely to return a 5xx status.
460 if (client.http_proxy == null) {
461 try testing.expectError(error.ConnectionRefused, result); // expects not segfault but the regular error
462 }
463 }
464
465 // connection has been kept alive
466 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
467
468 { // issue 16282 *** This test leaves the client in an invalid state, it must be last ***
469 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get", .{port});
470 defer calloc.free(location);
471 const uri = try std.Uri.parse(location);
472
473 const total_connections = client.connection_pool.free_size + 64;
474 var requests = try calloc.alloc(http.Client.Request, total_connections);
475 defer calloc.free(requests);
476
477 var header_bufs = std.ArrayList([]u8).init(calloc);
478 defer header_bufs.deinit();
479 defer for (header_bufs.items) |item| calloc.free(item);
480
481 for (0..total_connections) |i| {
482 const headers_buf = try calloc.alloc(u8, 1024);
483 try header_bufs.append(headers_buf);
484 var req = try client.open(.GET, uri, .{
485 .server_header_buffer = headers_buf,
486 });
487 req.response.parser.done = true;
488 req.connection.?.closing = false;
489 requests[i] = req;
490 }
491
492 for (0..total_connections) |i| {
493 requests[i].deinit();
494 }
495
496 // free connections should be full now
497 try testing.expect(client.connection_pool.free_len == client.connection_pool.free_size);
498 }
499
500 client.deinit();
501
502 {
503 handle_new_requests = false;
504
505 const conn = std.net.tcpConnectToAddress(server.listen_address) catch return;
506 conn.close();
507 }
508
509 server_thread.join();
510}