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