authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-04-16 16:26:25-05:00
committergravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-04-17 19:16:01-05:00
log85221b4e977756bf30f7d45a7eb8636ea0d5168a
treef69fa0d5707108924e1725187c8bde843a5b6ab0
parent134294230a08d531afd0a6d823ae3046b1699b0f
signature Commit is signed but in an unrecognized format.

std.http: curate some Server errors, fix reading chunked bodies


3 files changed, 140 insertions(+), 83 deletions(-)

lib/std/http/Client.zig+38-28
...@@ -193,7 +193,13 @@ pub const Connection = struct {...@@ -193,7 +193,13 @@ pub const Connection = struct {
193 };193 };
194 }194 }
195195
196 pub const ReadError = error{ TlsFailure, TlsAlert, ConnectionTimedOut, ConnectionResetByPeer, UnexpectedReadFailure };196 pub const ReadError = error{
197 TlsFailure,
198 TlsAlert,
199 ConnectionTimedOut,
200 ConnectionResetByPeer,
201 UnexpectedReadFailure,
202 };
197203
198 pub const Reader = std.io.Reader(*Connection, ReadError, read);204 pub const Reader = std.io.Reader(*Connection, ReadError, read);
199205
...@@ -518,7 +524,10 @@ pub const Request = struct {...@@ -518,7 +524,10 @@ pub const Request = struct {
518 req.* = undefined;524 req.* = undefined;
519 }525 }
520526
521 pub fn start(req: *Request, uri: Uri) !void {527 pub const StartError = BufferedConnection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
528
529 /// Send the request to the server.
530 pub fn start(req: *Request, uri: Uri) StartError!void {
522 var buffered = std.io.bufferedWriter(req.connection.data.buffered.writer());531 var buffered = std.io.bufferedWriter(req.connection.data.buffered.writer());
523 const w = buffered.writer();532 const w = buffered.writer();
524533
...@@ -575,7 +584,7 @@ pub const Request = struct {...@@ -575,7 +584,7 @@ pub const Request = struct {
575 }584 }
576 } else {585 } else {
577 if (has_content_length) {586 if (has_content_length) {
578 const content_length = try std.fmt.parseInt(u64, req.headers.getFirstValue("content-length").?, 10);587 const content_length = std.fmt.parseInt(u64, req.headers.getFirstValue("content-length").?, 10) catch return error.InvalidContentLength;
579588
580 req.transfer_encoding = .{ .content_length = content_length };589 req.transfer_encoding = .{ .content_length = content_length };
581 } else if (has_transfer_encoding) {590 } else if (has_transfer_encoding) {
...@@ -618,7 +627,7 @@ pub const Request = struct {...@@ -618,7 +627,7 @@ pub const Request = struct {
618 return index;627 return index;
619 }628 }
620629
621 pub const DoError = RequestError || TransferReadError || proto.HeadersParser.CheckCompleteHeadError || Response.ParseError || Uri.ParseError || error{ TooManyHttpRedirects, HttpRedirectMissingLocation, CompressionInitializationFailed };630 pub const DoError = RequestError || TransferReadError || proto.HeadersParser.CheckCompleteHeadError || Response.ParseError || Uri.ParseError || error{ TooManyHttpRedirects, HttpRedirectMissingLocation, CompressionInitializationFailed, CompressionNotSupported };
622631
623 /// Waits for a response from the server and parses any headers that are sent.632 /// Waits for a response from the server and parses any headers that are sent.
624 /// This function will block until the final response is received.633 /// This function will block until the final response is received.
...@@ -739,25 +748,23 @@ pub const Request = struct {...@@ -739,25 +748,23 @@ pub const Request = struct {
739748
740 /// Reads data from the response body. Must be called after `do`.749 /// Reads data from the response body. Must be called after `do`.
741 pub fn read(req: *Request, buffer: []u8) ReadError!usize {750 pub fn read(req: *Request, buffer: []u8) ReadError!usize {
742 while (true) {751 const out_index = switch (req.response.compression) {
743 const out_index = switch (req.response.compression) {752 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,
744 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,753 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
745 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,754 .zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
746 .zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,755 else => try req.transferRead(buffer),
747 else => try req.transferRead(buffer),756 };
748 };757
749758 if (out_index == 0) {
750 if (out_index == 0) {759 while (!req.response.parser.state.isContent()) { // read trailing headers
751 while (!req.response.parser.state.isContent()) { // read trailing headers760 try req.connection.data.buffered.fill();
752 try req.connection.data.buffered.fill();
753
754 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());
755 req.connection.data.buffered.clear(@intCast(u16, nchecked));
756 }
757 }
758761
759 return out_index;762 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());
763 req.connection.data.buffered.clear(@intCast(u16, nchecked));
764 }
760 }765 }
766
767 return out_index;
761 }768 }
762769
763 /// Reads data from the response body. Must be called after `do`.770 /// Reads data from the response body. Must be called after `do`.
...@@ -800,15 +807,19 @@ pub const Request = struct {...@@ -800,15 +807,19 @@ pub const Request = struct {
800 }807 }
801 }808 }
802809
810 pub fn writeAll(req: *Request, bytes: []const u8) WriteError!void {
811 var index: usize = 0;
812 while (index < bytes.len) {
813 index += try write(req, bytes[index..]);
814 }
815 }
816
803 pub const FinishError = WriteError || error{MessageNotCompleted};817 pub const FinishError = WriteError || error{MessageNotCompleted};
804818
805 /// Finish the body of a request. This notifies the server that you have no more data to send.819 /// Finish the body of a request. This notifies the server that you have no more data to send.
806 pub fn finish(req: *Request) FinishError!void {820 pub fn finish(req: *Request) FinishError!void {
807 switch (req.transfer_encoding) {821 switch (req.transfer_encoding) {
808 .chunked => req.connection.data.conn.writeAll("0\r\n\r\n") catch |err| {822 .chunked => try req.connection.data.conn.writeAll("0\r\n\r\n"),
809 req.client.last_error = .{ .write = err };
810 return error.WriteFailed;
811 },
812 .content_length => |len| if (len != 0) return error.MessageNotCompleted,823 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
813 .none => {},824 .none => {},
814 }825 }
...@@ -923,7 +934,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio...@@ -923,7 +934,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio
923 }934 }
924}935}
925936
926pub const RequestError = ConnectUnproxiedError || ConnectErrorPartial || std.fmt.ParseIntError || BufferedConnection.WriteError || error{937pub const RequestError = ConnectUnproxiedError || ConnectErrorPartial || Request.StartError || std.fmt.ParseIntError || BufferedConnection.WriteError || error{
927 UnsupportedUrlScheme,938 UnsupportedUrlScheme,
928 UriMissingHost,939 UriMissingHost,
929940
...@@ -998,6 +1009,7 @@ pub fn request(client: *Client, uri: Uri, headers: http.Headers, options: Option...@@ -998,6 +1009,7 @@ pub fn request(client: *Client, uri: Uri, headers: http.Headers, options: Option
998 .handle_redirects = options.handle_redirects,1009 .handle_redirects = options.handle_redirects,
999 .response = .{1010 .response = .{
1000 .status = undefined,1011 .status = undefined,
1012 .reason = undefined,
1001 .version = undefined,1013 .version = undefined,
1002 .headers = undefined,1014 .headers = undefined,
1003 .parser = switch (options.header_strategy) {1015 .parser = switch (options.header_strategy) {
...@@ -1011,8 +1023,6 @@ pub fn request(client: *Client, uri: Uri, headers: http.Headers, options: Option...@@ -1011,8 +1023,6 @@ pub fn request(client: *Client, uri: Uri, headers: http.Headers, options: Option
10111023
1012 req.arena = std.heap.ArenaAllocator.init(client.allocator);1024 req.arena = std.heap.ArenaAllocator.init(client.allocator);
10131025
1014 try req.start(uri);
1015
1016 return req;1026 return req;
1017}1027}
10181028
lib/std/http/Server.zig+100-55
...@@ -23,21 +23,33 @@ pub const Connection = struct {...@@ -23,21 +23,33 @@ pub const Connection = struct {
2323
24 pub const Protocol = enum { plain };24 pub const Protocol = enum { plain };
2525
26 pub fn read(conn: *Connection, buffer: []u8) !usize {26 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
27 switch (conn.protocol) {27 return switch (conn.protocol) {
28 .plain => return conn.stream.read(buffer),28 .plain => conn.stream.read(buffer),
29 // .tls => return conn.tls_client.read(conn.stream, buffer),29 // .tls => return conn.tls_client.read(conn.stream, buffer),
30 }30 } catch |err| switch (err) {
31 error.ConnectionTimedOut => return error.ConnectionTimedOut,
32 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
33 else => return error.UnexpectedReadFailure,
34 };
31 }35 }
3236
33 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) !usize {37 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
34 switch (conn.protocol) {38 return switch (conn.protocol) {
35 .plain => return conn.stream.readAtLeast(buffer, len),39 .plain => conn.stream.readAtLeast(buffer, len),
36 // .tls => return conn.tls_client.readAtLeast(conn.stream, buffer, len),40 // .tls => return conn.tls_client.readAtLeast(conn.stream, buffer, len),
37 }41 } catch |err| switch (err) {
42 error.ConnectionTimedOut => return error.ConnectionTimedOut,
43 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
44 else => return error.UnexpectedReadFailure,
45 };
38 }46 }
3947
40 pub const ReadError = net.Stream.ReadError;48 pub const ReadError = error{
49 ConnectionTimedOut,
50 ConnectionResetByPeer,
51 UnexpectedReadFailure,
52 };
4153
42 pub const Reader = std.io.Reader(*Connection, ReadError, read);54 pub const Reader = std.io.Reader(*Connection, ReadError, read);
4355
...@@ -45,21 +57,31 @@ pub const Connection = struct {...@@ -45,21 +57,31 @@ pub const Connection = struct {
45 return Reader{ .context = conn };57 return Reader{ .context = conn };
46 }58 }
4759
48 pub fn writeAll(conn: *Connection, buffer: []const u8) !void {60 pub fn writeAll(conn: *Connection, buffer: []const u8) WriteError!void {
49 switch (conn.protocol) {61 return switch (conn.protocol) {
50 .plain => return conn.stream.writeAll(buffer),62 .plain => conn.stream.writeAll(buffer),
51 // .tls => return conn.tls_client.writeAll(conn.stream, buffer),63 // .tls => return conn.tls_client.writeAll(conn.stream, buffer),
52 }64 } catch |err| switch (err) {
65 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
66 else => return error.UnexpectedWriteFailure,
67 };
53 }68 }
5469
55 pub fn write(conn: *Connection, buffer: []const u8) !usize {70 pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {
56 switch (conn.protocol) {71 return switch (conn.protocol) {
57 .plain => return conn.stream.write(buffer),72 .plain => conn.stream.write(buffer),
58 // .tls => return conn.tls_client.write(conn.stream, buffer),73 // .tls => return conn.tls_client.write(conn.stream, buffer),
59 }74 } catch |err| switch (err) {
75 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
76 else => return error.UnexpectedWriteFailure,
77 };
60 }78 }
6179
62 pub const WriteError = net.Stream.WriteError || error{};80 pub const WriteError = error{
81 ConnectionResetByPeer,
82 UnexpectedWriteFailure,
83 };
84
63 pub const Writer = std.io.Writer(*Connection, WriteError, write);85 pub const Writer = std.io.Writer(*Connection, WriteError, write);
6486
65 pub fn writer(conn: *Connection) Writer {87 pub fn writer(conn: *Connection) Writer {
...@@ -155,6 +177,25 @@ pub const BufferedConnection = struct {...@@ -155,6 +177,25 @@ pub const BufferedConnection = struct {
155 }177 }
156};178};
157179
180/// The mode of transport for responses.
181pub const ResponseTransfer = union(enum) {
182 content_length: u64,
183 chunked: void,
184 none: void,
185};
186
187/// The decompressor for request messages.
188pub const Compression = union(enum) {
189 pub const DeflateDecompressor = std.compress.zlib.ZlibStream(Response.TransferReader);
190 pub const GzipDecompressor = std.compress.gzip.Decompress(Response.TransferReader);
191 pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Response.TransferReader, .{});
192
193 deflate: DeflateDecompressor,
194 gzip: GzipDecompressor,
195 zstd: ZstdDecompressor,
196 none: void,
197};
198
158/// A HTTP request originating from a client.199/// A HTTP request originating from a client.
159pub const Request = struct {200pub const Request = struct {
160 pub const ParseError = Allocator.Error || error{201 pub const ParseError = Allocator.Error || error{
...@@ -165,10 +206,11 @@ pub const Request = struct {...@@ -165,10 +206,11 @@ pub const Request = struct {
165 HttpHeaderContinuationsUnsupported,206 HttpHeaderContinuationsUnsupported,
166 HttpTransferEncodingUnsupported,207 HttpTransferEncodingUnsupported,
167 HttpConnectionHeaderUnsupported,208 HttpConnectionHeaderUnsupported,
168 InvalidCharacter,209 InvalidContentLength,
210 CompressionNotSupported,
169 };211 };
170212
171 pub fn parse(req: *Request, bytes: []const u8) !void {213 pub fn parse(req: *Request, bytes: []const u8) ParseError!void {
172 var it = mem.tokenize(u8, bytes[0 .. bytes.len - 4], "\r\n");214 var it = mem.tokenize(u8, bytes[0 .. bytes.len - 4], "\r\n");
173215
174 const first_line = it.next() orelse return error.HttpHeadersInvalid;216 const first_line = it.next() orelse return error.HttpHeadersInvalid;
...@@ -211,7 +253,7 @@ pub const Request = struct {...@@ -211,7 +253,7 @@ pub const Request = struct {
211253
212 if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {254 if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {
213 if (req.content_length != null) return error.HttpHeadersInvalid;255 if (req.content_length != null) return error.HttpHeadersInvalid;
214 req.content_length = try std.fmt.parseInt(u64, header_value, 10);256 req.content_length = std.fmt.parseInt(u64, header_value, 10) catch return error.InvalidContentLength;
215 } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) {257 } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) {
216 // Transfer-Encoding: second, first258 // Transfer-Encoding: second, first
217 // Transfer-Encoding: deflate, chunked259 // Transfer-Encoding: deflate, chunked
...@@ -321,6 +363,8 @@ pub const Response = struct {...@@ -321,6 +363,8 @@ pub const Response = struct {
321 }363 }
322 }364 }
323365
366 pub const DoError = BufferedConnection.WriteError || error{ UnsupportedTransferEncoding, InvalidContentLength };
367
324 /// Send the response headers.368 /// Send the response headers.
325 pub fn do(res: *Response) !void {369 pub fn do(res: *Response) !void {
326 var buffered = std.io.bufferedWriter(res.connection.writer());370 var buffered = std.io.bufferedWriter(res.connection.writer());
...@@ -356,7 +400,7 @@ pub const Response = struct {...@@ -356,7 +400,7 @@ pub const Response = struct {
356 }400 }
357 } else {401 } else {
358 if (has_content_length) {402 if (has_content_length) {
359 const content_length = try std.fmt.parseInt(u64, res.headers.getFirstValue("content-length").?, 10);403 const content_length = std.fmt.parseInt(u64, res.headers.getFirstValue("content-length").?, 10) catch return error.InvalidContentLength;
360404
361 res.transfer_encoding = .{ .content_length = content_length };405 res.transfer_encoding = .{ .content_length = content_length };
362 } else if (has_transfer_encoding) {406 } else if (has_transfer_encoding) {
...@@ -386,23 +430,23 @@ pub const Response = struct {...@@ -386,23 +430,23 @@ pub const Response = struct {
386 return .{ .context = res };430 return .{ .context = res };
387 }431 }
388432
389 pub fn transferRead(res: *Response, buf: []u8) TransferReadError!usize {433 fn transferRead(res: *Response, buf: []u8) TransferReadError!usize {
390 if (res.request.parser.isComplete()) return 0;434 if (res.request.parser.done) return 0;
391435
392 var index: usize = 0;436 var index: usize = 0;
393 while (index == 0) {437 while (index == 0) {
394 const amt = try res.request.parser.read(&res.connection, buf[index..], false);438 const amt = try res.request.parser.read(&res.connection, buf[index..], false);
395 if (amt == 0 and res.request.parser.isComplete()) break;439 if (amt == 0 and res.request.parser.done) break;
396 index += amt;440 index += amt;
397 }441 }
398442
399 return index;443 return index;
400 }444 }
401445
402 pub const WaitForCompleteHeadError = BufferedConnection.ReadError || proto.HeadersParser.WaitForCompleteHeadError || Request.Headers.ParseError || error{ BadHeader, InvalidCompression, StreamTooLong, InvalidWindowSize } || error{CompressionNotSupported};446 pub const WaitError = BufferedConnection.ReadError || proto.HeadersParser.CheckCompleteHeadError || Request.ParseError || error{ CompressionInitializationFailed, CompressionNotSupported };
403447
404 /// Wait for the client to send a complete request head.448 /// Wait for the client to send a complete request head.
405 pub fn wait(res: *Response) !void {449 pub fn wait(res: *Response) WaitError!void {
406 while (true) {450 while (true) {
407 try res.connection.fill();451 try res.connection.fill();
408452
...@@ -445,10 +489,10 @@ pub const Response = struct {...@@ -445,10 +489,10 @@ pub const Response = struct {
445 if (res.request.transfer_compression) |tc| switch (tc) {489 if (res.request.transfer_compression) |tc| switch (tc) {
446 .compress => return error.CompressionNotSupported,490 .compress => return error.CompressionNotSupported,
447 .deflate => res.request.compression = .{491 .deflate => res.request.compression = .{
448 .deflate = try std.compress.zlib.zlibStream(res.server.allocator, res.transferReader()),492 .deflate = std.compress.zlib.zlibStream(res.server.allocator, res.transferReader()) catch return error.CompressionInitializationFailed,
449 },493 },
450 .gzip => res.request.compression = .{494 .gzip => res.request.compression = .{
451 .gzip = try std.compress.gzip.decompress(res.server.allocator, res.transferReader()),495 .gzip = std.compress.gzip.decompress(res.server.allocator, res.transferReader()) catch return error.CompressionInitializationFailed,
452 },496 },
453 .zstd => res.request.compression = .{497 .zstd => res.request.compression = .{
454 .zstd = std.compress.zstd.decompressStream(res.server.allocator, res.transferReader()),498 .zstd = std.compress.zstd.decompressStream(res.server.allocator, res.transferReader()),
...@@ -457,7 +501,7 @@ pub const Response = struct {...@@ -457,7 +501,7 @@ pub const Response = struct {
457 }501 }
458 }502 }
459503
460 pub const ReadError = Compression.DeflateDecompressor.Error || Compression.GzipDecompressor.Error || Compression.ZstdDecompressor.Error || WaitForCompleteHeadError;504 pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError || error{DecompressionFailure};
461505
462 pub const Reader = std.io.Reader(*Response, ReadError, read);506 pub const Reader = std.io.Reader(*Response, ReadError, read);
463507
...@@ -466,12 +510,23 @@ pub const Response = struct {...@@ -466,12 +510,23 @@ pub const Response = struct {
466 }510 }
467511
468 pub fn read(res: *Response, buffer: []u8) ReadError!usize {512 pub fn read(res: *Response, buffer: []u8) ReadError!usize {
469 return switch (res.request.compression) {513 const out_index = switch (res.request.compression) {
470 .deflate => |*deflate| try deflate.read(buffer),514 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,
471 .gzip => |*gzip| try gzip.read(buffer),515 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
472 .zstd => |*zstd| try zstd.read(buffer),516 .zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
473 else => try res.transferRead(buffer),517 else => try res.transferRead(buffer),
474 };518 };
519
520 if (out_index == 0) {
521 while (!res.request.parser.state.isContent()) { // read trailing headers
522 try res.connection.fill();
523
524 const nchecked = try res.request.parser.checkCompleteHead(res.server.allocator, res.connection.peek());
525 res.connection.clear(@intCast(u16, nchecked));
526 }
527 }
528
529 return out_index;
475 }530 }
476531
477 pub fn readAll(res: *Response, buffer: []u8) !usize {532 pub fn readAll(res: *Response, buffer: []u8) !usize {
...@@ -513,9 +568,18 @@ pub const Response = struct {...@@ -513,9 +568,18 @@ pub const Response = struct {
513 }568 }
514 }569 }
515570
571 pub fn writeAll(req: *Request, bytes: []const u8) WriteError!void {
572 var index: usize = 0;
573 while (index < bytes.len) {
574 index += try write(req, bytes[index..]);
575 }
576 }
577
578 pub const FinishError = WriteError || error{MessageNotCompleted};
579
516 /// Finish the body of a request. This notifies the server that you have no more data to send.580 /// Finish the body of a request. This notifies the server that you have no more data to send.
517 pub fn finish(res: *Response) !void {581 pub fn finish(res: *Response) FinishError!void {
518 switch (res.headers.transfer_encoding) {582 switch (res.transfer_encoding) {
519 .chunked => try res.connection.writeAll("0\r\n\r\n"),583 .chunked => try res.connection.writeAll("0\r\n\r\n"),
520 .content_length => |len| if (len != 0) return error.MessageNotCompleted,584 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
521 .none => {},585 .none => {},
...@@ -523,25 +587,6 @@ pub const Response = struct {...@@ -523,25 +587,6 @@ pub const Response = struct {
523 }587 }
524};588};
525589
526/// The mode of transport for responses.
527pub const ResponseTransfer = union(enum) {
528 content_length: u64,
529 chunked: void,
530 none: void,
531};
532
533/// The decompressor for request messages.
534pub const Compression = union(enum) {
535 pub const DeflateDecompressor = std.compress.zlib.ZlibStream(Response.TransferReader);
536 pub const GzipDecompressor = std.compress.gzip.Decompress(Response.TransferReader);
537 pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Response.TransferReader, .{});
538
539 deflate: DeflateDecompressor,
540 gzip: GzipDecompressor,
541 zstd: ZstdDecompressor,
542 none: void,
543};
544
545pub fn init(allocator: Allocator, options: net.StreamServer.Options) Server {590pub fn init(allocator: Allocator, options: net.StreamServer.Options) Server {
546 return .{591 return .{
547 .allocator = allocator,592 .allocator = allocator,
src/Package.zig+2
...@@ -485,6 +485,8 @@ fn fetchAndUnpack(...@@ -485,6 +485,8 @@ fn fetchAndUnpack(
485 var req = try http_client.request(uri, h, .{ .method = .GET });485 var req = try http_client.request(uri, h, .{ .method = .GET });
486 defer req.deinit();486 defer req.deinit();
487487
488 try req.start();
489
488 try req.do();490 try req.do();
489491
490 if (mem.endsWith(u8, uri.path, ".tar.gz")) {492 if (mem.endsWith(u8, uri.path, ".tar.gz")) {