authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-02-26 17:04:28+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-26 20:11:43-08:00
log4e2570baafb587c679ee0fc5e113ddeb36522a5d
treee74f607290693900c9e863d427e9a78ee5f03826
parent8775d8bbcef347640c6ae7e73da02ca6eff1d669

http: fix fetching a github release

* 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 stored367/// 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`.
369pub fn resolve_inplace(base: Uri, new: []const u8, aux_buf: []u8) ResolveInplaceError!Uri {369pub 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];
373373
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,
432432
433 keep_alive: bool = false,433 keep_alive: bool,
434434
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 };
480484
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();
685689
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 client27 /// 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};
3131
...@@ -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 };
203206
...@@ -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 it333 // 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;
341344
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 }
343349
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 it486 // 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 }
491501
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 }
696706
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);
7474
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);
145139
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}
157151
...@@ -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();
287281
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");
289283
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();
350344
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);
701696
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);