Files
MasterHttpRelayVPN-RUST/src
dazzling-no-more aa16abcafc fix(relay): stream range-parallel downloads larger than Apps Script's 50 MiB cap (#1042, #1085)
Fixes #1042 — range-capable downloads larger than ~50 MiB through the
Apps Script relay returned `504 Relay timeout — Apps Script unresponsive`
instead of the file. The 104 MiB v2rayN DMG in the reported logs was the
canonical repro (also matches @Paymanonline's report in #1077, closed
prior as "architectural limit, use mirrors" — this PR makes it actually
work via streaming).

## Root cause

`relay_parallel_range` capped the stitched response at 64 MiB and fell
back to a single `relay()` for anything larger. Single-GET routes through
Apps Script's ~50 MiB response ceiling, so Apps Script killed the script
mid-execution and we hung for the full 25s relay timeout before returning
504.

## Fix

Convert `relay_parallel_range` into a writer-based API that streams large
files chunk-by-chunk to the client socket. Each chunk is still one
≤256 KiB Apps Script call (well under the 50 MiB cap); only the host-side
buffering changes. Backward-compatible `Vec<u8>` wrapper preserves the
pre-1.9.23 API surface for external library consumers.

Three-way dispatch via `RangeDispatch { Buffered, Stream, FallbackSingleGet,
RejectTooLarge }` and the pure `dispatch_range_response(total,
streaming_allowed)` predicate:

- **`Buffered`** — `total ≤ APPS_SCRIPT_BODY_MAX_BYTES` (40 MiB) on either
  surface. Existing stitch + single-GET fallback path; fully recovers on
  chunk failure.
- **`Stream`** — writer API above 40 MiB. Streams; chunk failure flushes
  the committed prefix and returns `Err` so the `Content-Length`
  mismatch tells download clients to resume via `Range`.
- **`FallbackSingleGet`** — wrapper above 64 MiB. Matches pre-1.9.23 cliff
  for external library consumers stuck on the old API.
- **`RejectTooLarge`** — writer API above 16 GiB. Refuses with 502;
  bounds worst-case Apps Script quota drain from a hostile origin
  advertising an absurd `Content-Range` total.

## Memory bounds

Lazy `plan_remaining_ranges` (via `std::iter::from_fn` + `saturating_*`):
range planning is `O(1)` memory regardless of advertised total. Even a
`u64::MAX` total no longer drives a ~6 GB `Vec<(u64, u64)>` allocation.

## CORS interaction

MITM HTTPS and plain-HTTP call sites updated to use `relay_parallel_range_to`
with a CORS-aware `transform_head` closure. Extracted `inject_cors_into_head`
(head-only variant of `inject_cors_response_headers`) so the streaming
path can rewrite ACL headers before the body has been assembled.

## Verified locally on top of v1.9.22

- `cargo test --lib --release`: 227/227  (was 209; +18 new — 15 stated
  in PR body + 3 incidental from the helper extractions)
- `cargo build --release --features ui --bin mhrv-rs-ui`: clean 

Manual repro of the 104 MiB v2rayN DMG download is unchecked in the PR
test plan — the unit tests cover the dispatch + streaming + flush
contracts thoroughly. The architectural reasoning is sound and the new
test count (+18) is concrete.

Reviewed via Anthropic Claude.

Co-Authored-By: dazzling-no-more <noreply@github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:54:17 +03:00
..