| author | |
| committer | |
| log | 1884be4ecd2bf5458afedbc73eb3286a8b1b2d18 |
| tree | 27aa7f425b40a8f7b8e9392e19dec3c91d8b75a1 |
| parent | b95cdf0aeb4d4d31c0b6a54302ef61baec8f6773 |
| parent | afebef2465a828132f87cd2aeff0c5873ca10de5 |
| signature |
4 files changed, 86 insertions(+), 8 deletions(-)
lib/std/http.zig+1| ... | @@ -275,4 +275,5 @@ test { | ... | @@ -275,4 +275,5 @@ test { |
| 275 | _ = Client; | 275 | _ = Client; |
| 276 | _ = Method; | 276 | _ = Method; |
| 277 | _ = Status; | 277 | _ = Status; |
| 278 | _ = @import("http/test.zig"); | ||
| 278 | } | 279 | } |
lib/std/http/Client.zig+6-6| ... | @@ -797,18 +797,18 @@ pub const Request = struct { | ... | @@ -797,18 +797,18 @@ pub const Request = struct { |
| 797 | 797 | ||
| 798 | /// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent. | 798 | /// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent. |
| 799 | pub fn write(req: *Request, bytes: []const u8) WriteError!usize { | 799 | pub fn write(req: *Request, bytes: []const u8) WriteError!usize { |
| 800 | switch (req.headers.transfer_encoding) { | 800 | switch (req.transfer_encoding) { |
| 801 | .chunked => { | 801 | .chunked => { |
| 802 | try req.connection.data.conn.writer().print("{x}\r\n", .{bytes.len}); | 802 | try req.connection.data.buffered.writer().print("{x}\r\n", .{bytes.len}); |
| 803 | try req.connection.data.conn.writeAll(bytes); | 803 | try req.connection.data.buffered.writeAll(bytes); |
| 804 | try req.connection.data.conn.writeAll("\r\n"); | 804 | try req.connection.data.buffered.writeAll("\r\n"); |
| 805 | 805 | ||
| 806 | return bytes.len; | 806 | return bytes.len; |
| 807 | }, | 807 | }, |
| 808 | .content_length => |*len| { | 808 | .content_length => |*len| { |
| 809 | if (len.* < bytes.len) return error.MessageTooLong; | 809 | if (len.* < bytes.len) return error.MessageTooLong; |
| 810 | 810 | ||
| 811 | const amt = try req.connection.data.conn.write(bytes); | 811 | const amt = try req.connection.data.buffered.write(bytes); |
| 812 | len.* -= amt; | 812 | len.* -= amt; |
| 813 | return amt; | 813 | return amt; |
| 814 | }, | 814 | }, |
| ... | @@ -828,7 +828,7 @@ pub const Request = struct { | ... | @@ -828,7 +828,7 @@ pub const Request = struct { |
| 828 | /// Finish the body of a request. This notifies the server that you have no more data to send. | 828 | /// Finish the body of a request. This notifies the server that you have no more data to send. |
| 829 | pub fn finish(req: *Request) FinishError!void { | 829 | pub fn finish(req: *Request) FinishError!void { |
| 830 | switch (req.transfer_encoding) { | 830 | switch (req.transfer_encoding) { |
| 831 | .chunked => try req.connection.data.conn.writeAll("0\r\n\r\n"), | 831 | .chunked => try req.connection.data.buffered.writeAll("0\r\n\r\n"), |
| 832 | .content_length => |len| if (len != 0) return error.MessageNotCompleted, | 832 | .content_length => |len| if (len != 0) return error.MessageNotCompleted, |
| 833 | .none => {}, | 833 | .none => {}, |
| 834 | } | 834 | } |
lib/std/http/Server.zig+7-2| ... | @@ -336,8 +336,15 @@ pub const Response = struct { | ... | @@ -336,8 +336,15 @@ pub const Response = struct { |
| 336 | headers: http.Headers, | 336 | headers: http.Headers, |
| 337 | request: Request, | 337 | request: Request, |
| 338 | 338 | ||
| 339 | pub fn deinit(res: *Response) void { | ||
| 340 | res.server.allocator.destroy(res); | ||
| 341 | } | ||
| 342 | |||
| 339 | /// Reset this response to its initial state. This must be called before handling a second request on the same connection. | 343 | /// Reset this response to its initial state. This must be called before handling a second request on the same connection. |
| 340 | pub fn reset(res: *Response) void { | 344 | pub fn reset(res: *Response) void { |
| 345 | res.request.headers.deinit(); | ||
| 346 | res.headers.deinit(); | ||
| 347 | |||
| 341 | switch (res.request.compression) { | 348 | switch (res.request.compression) { |
| 342 | .none => {}, | 349 | .none => {}, |
| 343 | .deflate => |*deflate| deflate.deinit(), | 350 | .deflate => |*deflate| deflate.deinit(), |
| ... | @@ -356,8 +363,6 @@ pub const Response = struct { | ... | @@ -356,8 +363,6 @@ pub const Response = struct { |
| 356 | if (res.request.parser.header_bytes_owned) { | 363 | if (res.request.parser.header_bytes_owned) { |
| 357 | res.request.parser.header_bytes.deinit(res.server.allocator); | 364 | res.request.parser.header_bytes.deinit(res.server.allocator); |
| 358 | } | 365 | } |
| 359 | |||
| 360 | res.* = undefined; | ||
| 361 | } else { | 366 | } else { |
| 362 | res.request.parser.reset(); | 367 | res.request.parser.reset(); |
| 363 | } | 368 | } |
lib/std/http/test.zig created+72| ... | @@ -0,0 +1,72 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const expect = std.testing.expect; | ||
| 3 | |||
| 4 | test "client requests server" { | ||
| 5 | const builtin = @import("builtin"); | ||
| 6 | |||
| 7 | // This test requires spawning threads. | ||
| 8 | if (builtin.single_threaded) { | ||
| 9 | return error.SkipZigTest; | ||
| 10 | } | ||
| 11 | |||
| 12 | const native_endian = comptime builtin.cpu.arch.endian(); | ||
| 13 | if (builtin.zig_backend == .stage2_llvm and native_endian == .Big) { | ||
| 14 | // https://github.com/ziglang/zig/issues/13782 | ||
| 15 | return error.SkipZigTest; | ||
| 16 | } | ||
| 17 | |||
| 18 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | ||
| 19 | |||
| 20 | const allocator = std.testing.allocator; | ||
| 21 | |||
| 22 | const max_header_size = 8192; | ||
| 23 | var server = std.http.Server.init(allocator, .{ .reuse_address = true }); | ||
| 24 | defer server.deinit(); | ||
| 25 | |||
| 26 | const address = try std.net.Address.parseIp("127.0.0.1", 0); | ||
| 27 | try server.listen(address); | ||
| 28 | const server_port = server.socket.listen_address.in.getPort(); | ||
| 29 | |||
| 30 | const server_thread = try std.Thread.spawn(.{}, (struct { | ||
| 31 | fn apply(s: *std.http.Server) !void { | ||
| 32 | const res = try s.accept(.{ .dynamic = max_header_size }); | ||
| 33 | defer res.deinit(); | ||
| 34 | defer res.reset(); | ||
| 35 | try res.wait(); | ||
| 36 | |||
| 37 | const server_body: []const u8 = "message from server!\n"; | ||
| 38 | res.transfer_encoding = .{ .content_length = server_body.len }; | ||
| 39 | try res.headers.append("content-type", "text/plain"); | ||
| 40 | try res.headers.append("connection", "close"); | ||
| 41 | try res.do(); | ||
| 42 | |||
| 43 | var buf: [128]u8 = undefined; | ||
| 44 | const n = try res.readAll(&buf); | ||
| 45 | try expect(std.mem.eql(u8, buf[0..n], "Hello, World!\n")); | ||
| 46 | _ = try res.writer().writeAll(server_body); | ||
| 47 | try res.finish(); | ||
| 48 | } | ||
| 49 | }).apply, .{&server}); | ||
| 50 | |||
| 51 | var uri_buf: [22]u8 = undefined; | ||
| 52 | const uri = try std.Uri.parse(try std.fmt.bufPrint(&uri_buf, "http://127.0.0.1:{d}", .{server_port})); | ||
| 53 | var client = std.http.Client{ .allocator = allocator }; | ||
| 54 | defer client.deinit(); | ||
| 55 | var client_headers = std.http.Headers{ .allocator = allocator }; | ||
| 56 | defer client_headers.deinit(); | ||
| 57 | var client_req = try client.request(.POST, uri, client_headers, .{}); | ||
| 58 | defer client_req.deinit(); | ||
| 59 | |||
| 60 | client_req.transfer_encoding = .{ .content_length = 14 }; // this will be checked to ensure you sent exactly 14 bytes | ||
| 61 | try client_req.start(); // this sends the request | ||
| 62 | try client_req.writeAll("Hello, "); | ||
| 63 | try client_req.writeAll("World!\n"); | ||
| 64 | try client_req.finish(); | ||
| 65 | try client_req.do(); // this waits for a response | ||
| 66 | |||
| 67 | const body = try client_req.reader().readAllAlloc(allocator, 8192 * 1024); | ||
| 68 | defer allocator.free(body); | ||
| 69 | try expect(std.mem.eql(u8, body, "message from server!\n")); | ||
| 70 | |||
| 71 | server_thread.join(); | ||
| 72 | } | ||