| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | const builtin = @import("builtin"); | 1 | const builtin = @import("builtin"); |
| 2 | const std = @import("std"); | 2 | const std = @import("std"); |
| | 3 | const http = std.http; |
| | 4 | const mem = std.mem; |
| 3 | const native_endian = builtin.cpu.arch.endian(); | 5 | const native_endian = builtin.cpu.arch.endian(); |
| 4 | const expect = std.testing.expect; | 6 | const expect = std.testing.expect; |
| 5 | const expectEqual = std.testing.expectEqual; | 7 | const expectEqual = std.testing.expectEqual; |
| ... | @@ -15,7 +17,7 @@ test "trailers" { | ... | @@ -15,7 +17,7 @@ test "trailers" { |
| 15 | const conn = try net_server.accept(); | 17 | const conn = try net_server.accept(); |
| 16 | defer conn.stream.close(); | 18 | defer conn.stream.close(); |
| 17 | | 19 | |
| 18 | var server = std.http.Server.init(conn, &header_buffer); | 20 | var server = http.Server.init(conn, &header_buffer); |
| 19 | | 21 | |
| 20 | try expectEqual(.ready, server.state); | 22 | try expectEqual(.ready, server.state); |
| 21 | var request = try server.receiveHead(); | 23 | var request = try server.receiveHead(); |
| ... | @@ -24,7 +26,7 @@ test "trailers" { | ... | @@ -24,7 +26,7 @@ test "trailers" { |
| 24 | } | 26 | } |
| 25 | } | 27 | } |
| 26 | | 28 | |
| 27 | fn serve(request: *std.http.Server.Request) !void { | 29 | fn serve(request: *http.Server.Request) !void { |
| 28 | try expectEqualStrings(request.head.target, "/trailer"); | 30 | try expectEqualStrings(request.head.target, "/trailer"); |
| 29 | | 31 | |
| 30 | var send_buffer: [1024]u8 = undefined; | 32 | var send_buffer: [1024]u8 = undefined; |
| ... | @@ -46,7 +48,7 @@ test "trailers" { | ... | @@ -46,7 +48,7 @@ test "trailers" { |
| 46 | | 48 | |
| 47 | const gpa = std.testing.allocator; | 49 | const gpa = std.testing.allocator; |
| 48 | | 50 | |
| 49 | var client: std.http.Client = .{ .allocator = gpa }; | 51 | var client: http.Client = .{ .allocator = gpa }; |
| 50 | defer client.deinit(); | 52 | defer client.deinit(); |
| 51 | | 53 | |
| 52 | const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{ | 54 | 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" { | ... | @@ -103,14 +105,14 @@ test "HTTP server handles a chunked transfer coding request" { |
| 103 | const conn = try net_server.accept(); | 105 | const conn = try net_server.accept(); |
| 104 | defer conn.stream.close(); | 106 | defer conn.stream.close(); |
| 105 | | 107 | |
| 106 | var server = std.http.Server.init(conn, &header_buffer); | 108 | var server = http.Server.init(conn, &header_buffer); |
| 107 | var request = try server.receiveHead(); | 109 | var request = try server.receiveHead(); |
| 108 | | 110 | |
| 109 | try expect(request.head.transfer_encoding == .chunked); | 111 | try expect(request.head.transfer_encoding == .chunked); |
| 110 | | 112 | |
| 111 | var buf: [128]u8 = undefined; | 113 | var buf: [128]u8 = undefined; |
| 112 | const n = try (try request.reader()).readAll(&buf); | 114 | 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")); |
| 114 | | 116 | |
| 115 | try request.respond("message from server!\n", .{ | 117 | try request.respond("message from server!\n", .{ |
| 116 | .extra_headers = &.{ | 118 | .extra_headers = &.{ |
| ... | @@ -151,18 +153,18 @@ test "echo content server" { | ... | @@ -151,18 +153,18 @@ test "echo content server" { |
| 151 | const conn = try net_server.accept(); | 153 | const conn = try net_server.accept(); |
| 152 | defer conn.stream.close(); | 154 | defer conn.stream.close(); |
| 153 | | 155 | |
| 154 | var http_server = std.http.Server.init(conn, &read_buffer); | 156 | var http_server = http.Server.init(conn, &read_buffer); |
| 155 | | 157 | |
| 156 | while (http_server.state == .ready) { | 158 | while (http_server.state == .ready) { |
| 157 | var request = http_server.receiveHead() catch |err| switch (err) { | 159 | var request = http_server.receiveHead() catch |err| switch (err) { |
| 158 | error.HttpConnectionClosing => continue :accept, | 160 | error.HttpConnectionClosing => continue :accept, |
| 159 | else => |e| return e, | 161 | else => |e| return e, |
| 160 | }; | 162 | }; |
| 161 | if (std.mem.eql(u8, request.head.target, "/end")) { | 163 | if (mem.eql(u8, request.head.target, "/end")) { |
| 162 | return request.respond("", .{ .keep_alive = false }); | 164 | return request.respond("", .{ .keep_alive = false }); |
| 163 | } | 165 | } |
| 164 | if (request.head.expect) |expect_header_value| { | 166 | 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")) { |
| 166 | try expectError(error.HttpExpectationFailed, request.reader()); | 168 | try expectError(error.HttpExpectationFailed, request.reader()); |
| 167 | try request.respond("", .{ .keep_alive = false }); | 169 | try request.respond("", .{ .keep_alive = false }); |
| 168 | continue; | 170 | continue; |
| ... | @@ -178,7 +180,7 @@ test "echo content server" { | ... | @@ -178,7 +180,7 @@ test "echo content server" { |
| 178 | } | 180 | } |
| 179 | } | 181 | } |
| 180 | | 182 | |
| 181 | fn handleRequest(request: *std.http.Server.Request) !void { | 183 | fn handleRequest(request: *http.Server.Request) !void { |
| 182 | //std.debug.print("server received {s} {s} {s}\n", .{ | 184 | //std.debug.print("server received {s} {s} {s}\n", .{ |
| 183 | // @tagName(request.head.method), | 185 | // @tagName(request.head.method), |
| 184 | // @tagName(request.head.version), | 186 | // @tagName(request.head.version), |
| ... | @@ -188,7 +190,7 @@ test "echo content server" { | ... | @@ -188,7 +190,7 @@ test "echo content server" { |
| 188 | const body = try (try request.reader()).readAllAlloc(std.testing.allocator, 8192); | 190 | const body = try (try request.reader()).readAllAlloc(std.testing.allocator, 8192); |
| 189 | defer std.testing.allocator.free(body); | 191 | defer std.testing.allocator.free(body); |
| 190 | | 192 | |
| 191 | try expect(std.mem.startsWith(u8, request.head.target, "/echo-content")); | 193 | try expect(mem.startsWith(u8, request.head.target, "/echo-content")); |
| 192 | try expectEqualStrings("Hello, World!\n", body); | 194 | try expectEqualStrings("Hello, World!\n", body); |
| 193 | try expectEqualStrings("text/plain", request.head.content_type.?); | 195 | try expectEqualStrings("text/plain", request.head.content_type.?); |
| 194 | | 196 | |
| ... | @@ -215,7 +217,7 @@ test "echo content server" { | ... | @@ -215,7 +217,7 @@ test "echo content server" { |
| 215 | defer test_server.destroy(); | 217 | defer test_server.destroy(); |
| 216 | | 218 | |
| 217 | { | 219 | { |
| 218 | var client: std.http.Client = .{ .allocator = std.testing.allocator }; | 220 | var client: http.Client = .{ .allocator = std.testing.allocator }; |
| 219 | defer client.deinit(); | 221 | defer client.deinit(); |
| 220 | | 222 | |
| 221 | try echoTests(&client, test_server.port()); | 223 | try echoTests(&client, test_server.port()); |
| ... | @@ -233,7 +235,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { | ... | @@ -233,7 +235,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { |
| 233 | const conn = try net_server.accept(); | 235 | const conn = try net_server.accept(); |
| 234 | defer conn.stream.close(); | 236 | defer conn.stream.close(); |
| 235 | | 237 | |
| 236 | var server = std.http.Server.init(conn, &header_buffer); | 238 | var server = http.Server.init(conn, &header_buffer); |
| 237 | | 239 | |
| 238 | try expectEqual(.ready, server.state); | 240 | try expectEqual(.ready, server.state); |
| 239 | var request = try server.receiveHead(); | 241 | var request = try server.receiveHead(); |
| ... | @@ -288,7 +290,474 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { | ... | @@ -288,7 +290,474 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { |
| 288 | try expectEqualStrings(expected_response.items, response); | 290 | try expectEqualStrings(expected_response.items, response); |
| 289 | } | 291 | } |
| 290 | | 292 | |
| 291 | fn echoTests(client: *std.http.Client, port: u16) !void { | 293 | test "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 | |
| | 760 | fn echoTests(client: *http.Client, port: u16) !void { |
| 292 | const gpa = std.testing.allocator; | 761 | const gpa = std.testing.allocator; |
| 293 | var location_buffer: [100]u8 = undefined; | 762 | var location_buffer: [100]u8 = undefined; |
| 294 | | 763 | |