| author | |
| committer | |
| log | 4e2570baafb587c679ee0fc5e113ddeb36522a5d |
| tree | e74f607290693900c9e863d427e9a78ee5f03826 |
| parent | 8775d8bbcef347640c6ae7e73da02ca6eff1d669 |
* Support different keep alive defaults with different http versions.
* Fix incorrect usage of `copyBackwards`, which copies in a backwards
direction allowing data to be moved forward in a buffer, not
backwards in a buffer.4 files changed, 32 insertions(+), 27 deletions(-)
lib/std/Uri.zig+1-1| ... | ... | @@ -367,7 +367,7 @@ pub const ResolveInplaceError = ParseError || error{OutOfMemory}; |
| 367 | 367 | /// If a merge needs to take place, the newly constructed path will be stored |
| 368 | 368 | /// in `aux_buf` just after the copied `new`. |
| 369 | 369 | pub fn resolve_inplace(base: Uri, new: []const u8, aux_buf: []u8) ResolveInplaceError!Uri { |
| 370 | std.mem.copyBackwards(u8, aux_buf, new); | |
| 370 | std.mem.copyForwards(u8, aux_buf, new); | |
| 371 | 371 | // At this point, new is an invalid pointer. |
| 372 | 372 | const new_mut = aux_buf[0..new.len]; |
| 373 | 373 |
lib/std/http/Client.zig+9-3| ... | ... | @@ -430,7 +430,7 @@ pub const Response = struct { |
| 430 | 430 | /// Points into the user-provided `server_header_buffer`. |
| 431 | 431 | content_disposition: ?[]const u8 = null, |
| 432 | 432 | |
| 433 | keep_alive: bool = false, | |
| 433 | keep_alive: bool, | |
| 434 | 434 | |
| 435 | 435 | /// If present, the number of bytes in the response body. |
| 436 | 436 | content_length: ?u64 = null, |
| ... | ... | @@ -477,6 +477,10 @@ pub const Response = struct { |
| 477 | 477 | res.version = version; |
| 478 | 478 | res.status = status; |
| 479 | 479 | res.reason = reason; |
| 480 | res.keep_alive = switch (version) { | |
| 481 | .@"HTTP/1.0" => false, | |
| 482 | .@"HTTP/1.1" => true, | |
| 483 | }; | |
| 480 | 484 | |
| 481 | 485 | while (it.next()) |line| { |
| 482 | 486 | if (line.len == 0) return; |
| ... | ... | @@ -684,9 +688,10 @@ pub const Request = struct { |
| 684 | 688 | req.response.parser.reset(); |
| 685 | 689 | |
| 686 | 690 | req.response = .{ |
| 691 | .version = undefined, | |
| 687 | 692 | .status = undefined, |
| 688 | 693 | .reason = undefined, |
| 689 | .version = undefined, | |
| 694 | .keep_alive = undefined, | |
| 690 | 695 | .parser = req.response.parser, |
| 691 | 696 | }; |
| 692 | 697 | } |
| ... | ... | @@ -1564,9 +1569,10 @@ pub fn open( |
| 1564 | 1569 | .redirect_behavior = options.redirect_behavior, |
| 1565 | 1570 | .handle_continue = options.handle_continue, |
| 1566 | 1571 | .response = .{ |
| 1572 | .version = undefined, | |
| 1567 | 1573 | .status = undefined, |
| 1568 | 1574 | .reason = undefined, |
| 1569 | .version = undefined, | |
| 1575 | .keep_alive = undefined, | |
| 1570 | 1576 | .parser = proto.HeadersParser.init(options.server_header_buffer), |
| 1571 | 1577 | }, |
| 1572 | 1578 | .headers = options.headers, |
lib/std/http/Server.zig+17-7| ... | ... | @@ -25,7 +25,7 @@ pub const State = enum { |
| 25 | 25 | /// The client is uploading something to this Server. |
| 26 | 26 | receiving_body, |
| 27 | 27 | /// The connection is eligible for another HTTP request, however the client |
| 28 | /// and server did not negotiate connection: keep-alive. | |
| 28 | /// and server did not negotiate a persistent connection. | |
| 29 | 29 | closing, |
| 30 | 30 | }; |
| 31 | 31 | |
| ... | ... | @@ -197,7 +197,10 @@ pub const Request = struct { |
| 197 | 197 | .content_length = null, |
| 198 | 198 | .transfer_encoding = .none, |
| 199 | 199 | .transfer_compression = .identity, |
| 200 | .keep_alive = false, | |
| 200 | .keep_alive = switch (version) { | |
| 201 | .@"HTTP/1.0" => false, | |
| 202 | .@"HTTP/1.1" => true, | |
| 203 | }, | |
| 201 | 204 | .compression = .none, |
| 202 | 205 | }; |
| 203 | 206 | |
| ... | ... | @@ -330,7 +333,7 @@ pub const Request = struct { |
| 330 | 333 | // reader() and hence discardBody() above sets expect to null if it |
| 331 | 334 | // is handled. So the fact that it is not null here means unhandled. |
| 332 | 335 | h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n"); |
| 333 | if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"); | |
| 336 | if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n"); | |
| 334 | 337 | h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n"); |
| 335 | 338 | try request.server.connection.stream.writeAll(h.items); |
| 336 | 339 | return; |
| ... | ... | @@ -339,7 +342,10 @@ pub const Request = struct { |
| 339 | 342 | @tagName(options.version), @intFromEnum(options.status), phrase, |
| 340 | 343 | }) catch unreachable; |
| 341 | 344 | |
| 342 | if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"); | |
| 345 | switch (options.version) { | |
| 346 | .@"HTTP/1.0" => if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"), | |
| 347 | .@"HTTP/1.1" => if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n"), | |
| 348 | } | |
| 343 | 349 | |
| 344 | 350 | if (options.transfer_encoding) |transfer_encoding| switch (transfer_encoding) { |
| 345 | 351 | .none => {}, |
| ... | ... | @@ -480,14 +486,18 @@ pub const Request = struct { |
| 480 | 486 | // reader() and hence discardBody() above sets expect to null if it |
| 481 | 487 | // is handled. So the fact that it is not null here means unhandled. |
| 482 | 488 | h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n"); |
| 483 | if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"); | |
| 489 | if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n"); | |
| 484 | 490 | h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n"); |
| 485 | 491 | break :eb true; |
| 486 | 492 | } else eb: { |
| 487 | 493 | h.fixedWriter().print("{s} {d} {s}\r\n", .{ |
| 488 | 494 | @tagName(o.version), @intFromEnum(o.status), phrase, |
| 489 | 495 | }) catch unreachable; |
| 490 | if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"); | |
| 496 | ||
| 497 | switch (o.version) { | |
| 498 | .@"HTTP/1.0" => if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"), | |
| 499 | .@"HTTP/1.1" => if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n"), | |
| 500 | } | |
| 491 | 501 | |
| 492 | 502 | if (o.transfer_encoding) |transfer_encoding| switch (transfer_encoding) { |
| 493 | 503 | .chunked => h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n"), |
| ... | ... | @@ -694,7 +704,7 @@ pub const Request = struct { |
| 694 | 704 | } |
| 695 | 705 | } |
| 696 | 706 | |
| 697 | /// Returns whether the connection: keep-alive header should be sent to the client. | |
| 707 | /// Returns whether the connection should remain persistent. | |
| 698 | 708 | /// If it would fail, it instead sets the Server state to `receiving_body` |
| 699 | 709 | /// and returns false. |
| 700 | 710 | fn discardBody(request: *Request, keep_alive: bool) bool { |
lib/std/http/test.zig+5-16| ... | ... | @@ -73,12 +73,6 @@ test "trailers" { |
| 73 | 73 | try expectEqualStrings("Hello, World!\n", body); |
| 74 | 74 | |
| 75 | 75 | var it = req.response.iterateHeaders(); |
| 76 | { | |
| 77 | const header = it.next().?; | |
| 78 | try expect(!it.is_trailer); | |
| 79 | try expectEqualStrings("connection", header.name); | |
| 80 | try expectEqualStrings("keep-alive", header.value); | |
| 81 | } | |
| 82 | 76 | { |
| 83 | 77 | const header = it.next().?; |
| 84 | 78 | try expect(!it.is_trailer); |
| ... | ... | @@ -143,15 +137,15 @@ test "HTTP server handles a chunked transfer coding request" { |
| 143 | 137 | defer stream.close(); |
| 144 | 138 | try stream.writeAll(request_bytes); |
| 145 | 139 | |
| 146 | const response = try stream.reader().readAllAlloc(gpa, 100); | |
| 147 | defer gpa.free(response); | |
| 148 | ||
| 149 | 140 | const expected_response = |
| 150 | 141 | "HTTP/1.1 200 OK\r\n" ++ |
| 142 | "connection: close\r\n" ++ | |
| 151 | 143 | "content-length: 21\r\n" ++ |
| 152 | 144 | "content-type: text/plain\r\n" ++ |
| 153 | 145 | "\r\n" ++ |
| 154 | 146 | "message from server!\n"; |
| 147 | const response = try stream.reader().readAllAlloc(gpa, expected_response.len); | |
| 148 | defer gpa.free(response); | |
| 155 | 149 | try expectEqualStrings(expected_response, response); |
| 156 | 150 | } |
| 157 | 151 | |
| ... | ... | @@ -285,7 +279,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { |
| 285 | 279 | var expected_response = std.ArrayList(u8).init(gpa); |
| 286 | 280 | defer expected_response.deinit(); |
| 287 | 281 | |
| 288 | try expected_response.appendSlice("HTTP/1.1 200 OK\r\n\r\n"); | |
| 282 | try expected_response.appendSlice("HTTP/1.1 200 OK\r\nconnection: close\r\n\r\n"); | |
| 289 | 283 | |
| 290 | 284 | { |
| 291 | 285 | var total: usize = 0; |
| ... | ... | @@ -349,6 +343,7 @@ test "receiving arbitrary http headers from the client" { |
| 349 | 343 | defer expected_response.deinit(); |
| 350 | 344 | |
| 351 | 345 | try expected_response.appendSlice("HTTP/1.1 200 OK\r\n"); |
| 346 | try expected_response.appendSlice("connection: close\r\n"); | |
| 352 | 347 | try expected_response.appendSlice("content-length: 0\r\n\r\n"); |
| 353 | 348 | try expectEqualStrings(expected_response.items, response); |
| 354 | 349 | } |
| ... | ... | @@ -700,12 +695,6 @@ test "general client/server API coverage" { |
| 700 | 695 | try expectEqualStrings("", body); |
| 701 | 696 | |
| 702 | 697 | var it = req.response.iterateHeaders(); |
| 703 | { | |
| 704 | const header = it.next().?; | |
| 705 | try expect(!it.is_trailer); | |
| 706 | try expectEqualStrings("connection", header.name); | |
| 707 | try expectEqualStrings("keep-alive", header.value); | |
| 708 | } | |
| 709 | 698 | { |
| 710 | 699 | const header = it.next().?; |
| 711 | 700 | try expect(!it.is_trailer); |