Skip to content

fix(install): retry truncated downloads in HttpClient::get_bytes#1940

Merged
fengmk2 merged 2 commits into
mainfrom
06-25-fix_install_retry_truncated_downloads_in_httpclient_get_bytes
Jun 25, 2026
Merged

fix(install): retry truncated downloads in HttpClient::get_bytes#1940
fengmk2 merged 2 commits into
mainfrom
06-25-fix_install_retry_truncated_downloads_in_httpclient_get_bytes

Conversation

@shulaoda

Copy link
Copy Markdown
Member

Problem

HttpClient::get_bytes read the response body (response.bytes().await) outside the retry. The retry only wrapped request setup (send().await?.error_for_status()), which resolves as soon as the response headers arrive — so a connection dropped mid-body (a response truncated relative to its advertised Content-Length) surfaced as a hard error that was never retried. download_file already handles this by streaming the body inside its retry and checking Content-Length.

get_bytes is the download path for the platform tarball in:

  • vp upgradecrates/vite_global_cli/src/commands/upgrade
  • the standalone installer — crates/vite_installer

So a transient mid-body drop (flaky wifi, a proxy/CDN closing the connection early) failed the upgrade/install outright with … end of file before message length reached, even though a single retry would usually succeed. Node-runtime and package-manager tarball downloads (which go through download_file) already retried.

Fix

Read the body inside the retried closure — one send+body unit, mirroring download_file — so a mid-body drop is retried. The now-unused private get / get_with_accept helpers are removed (get_with_accept was only ever called with accept = None).

Test

test_get_bytes_retries_on_truncated_body: a raw tokio TCP server truncates the first response (advertises the full Content-Length, sends half the body, drops the connection) and serves the full body on the retry. Against the old code it fails with hyper IncompleteBody after 1 attempt; with the fix it succeeds after 2.

Reproduce end-to-end with vp upgrade

A mock registry truncates the platform tarball on every request. The fixed get_bytes retries (4 tarball hits); the old one gives up after 1.

# build vp from this branch
cargo build -p vite_global_cli

# terminal A — truncating mock registry (script below)
node mock-registry.js

# terminal B — a real upgrade against it
# NO_PROXY is needed if you run a local/system HTTP proxy, so loopback isn't intercepted
NO_PROXY=127.0.0.1,localhost target/debug/vp upgrade --registry http://127.0.0.1:8911 --force

Watch terminal A's [tarball] hit #N:

build terminal A result
this branch (fixed) hit #1 #2 #3 #4 — retried Failed to download platform package: … end of file before message length reached
before the fix hit #1 — no retry same error

Both end with the same error (the mock always truncates); the hit count is the difference — on a real flaky network the retry usually succeeds on attempt 2–4.

mock-registry.js
// Truncates the platform tarball on every request (advertises a full
// Content-Length, sends a few bytes, drops the connection). Fixed get_bytes
// retries -> 4 tarball hits; the buggy one does not -> 1 hit.
const http = require('http');

const PORT = Number(process.env.PORT || 8911);
const VERSION = '9.9.9';
const FULL = 4096; // advertised Content-Length
const SENT = 100; // bytes actually sent before the drop

let tarballHits = 0;

function meta(tarballPath) {
  return JSON.stringify({
    version: VERSION,
    dist: {
      tarball: `http://127.0.0.1:${PORT}${tarballPath}`,
      integrity: 'sha512-AAAA', // never reached: the download always fails first
    },
  });
}

const server = http.createServer((req, res) => {
  const url = req.url;

  // Platform tarball: truncate every time.
  if (url === '/platform.tgz') {
    tarballHits += 1;
    console.log(`[tarball] hit #${tarballHits}  ->  send ${SENT}/${FULL} bytes then drop the connection`);
    const sock = res.socket;
    sock.write(
      'HTTP/1.1 200 OK\r\n' +
        'Content-Type: application/octet-stream\r\n' +
        `Content-Length: ${FULL}\r\n` +
        'Connection: close\r\n' +
        '\r\n',
    );
    sock.write(Buffer.alloc(SENT, 0x41));
    setTimeout(() => sock.destroy(), 80); // flush, then mid-body connection drop
    return;
  }

  console.log(`[meta] ${req.method} ${url}`);

  // Platform package metadata: /@voidzero-dev/vite-plus-cli-<suffix>/<version>
  if (url.includes('/vite-plus-cli-')) {
    res.writeHead(200, { 'content-type': 'application/json' });
    res.end(meta('/platform.tgz'));
    return;
  }

  // Main package metadata: /vite-plus/<tag-or-version>
  if (url.startsWith('/vite-plus/')) {
    res.writeHead(200, { 'content-type': 'application/json' });
    res.end(meta('/main.tgz'));
    return;
  }

  res.writeHead(404, { 'content-type': 'text/plain' });
  res.end('not found');
});

server.listen(PORT, '127.0.0.1', () => {
  console.log(`mock registry listening on http://127.0.0.1:${PORT}`);
});

🤖 Generated with Claude Code

@netlify

netlify Bot commented Jun 25, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-preview canceled.

Name Link
🔨 Latest commit e8beddb
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a3c7f77b997480008751dd7

@shulaoda shulaoda force-pushed the 06-25-fix_install_retry_truncated_downloads_in_httpclient_get_bytes branch from 83fb362 to 6eb6a31 Compare June 25, 2026 00:51
@fengmk2

fengmk2 commented Jun 25, 2026

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 6eb6a31a72

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@fengmk2 fengmk2 added test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: create-e2e Run `vp create` e2e tests test: sfw labels Jun 25, 2026
@fengmk2

fengmk2 commented Jun 25, 2026

Copy link
Copy Markdown
Member

@shulaoda Nice job!

@fengmk2 fengmk2 merged commit 1bbf3f1 into main Jun 25, 2026
94 checks passed
@fengmk2 fengmk2 deleted the 06-25-fix_install_retry_truncated_downloads_in_httpclient_get_bytes branch June 25, 2026 01:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test: create-e2e Run `vp create` e2e tests test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: sfw

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants