Run a download that has the right length and wrong bytes
Keep six bytes from version A, then request the remaining bytes after the file changes to version B. A client that appends the returned suffix can produce a 16-byte file that matches neither version. Checking the final length will accept it.
You can reproduce that failure with the Python HTTP lab. Save it in an empty writable directory and run python3 experiment.py. It needs Python 3.7 or later and permission to open a loopback socket. Our execution used Python 3.14.7. No package installation or service credentials are required.
The script starts a temporary HTTP server on 127.0.0.1, makes eight sequential GET requests and stops the server. It writes results.json in the current directory. The requests use real HTTP connections between the local client and the authored fixture server. The fixture seeds the saved prefix directly, so it does not simulate a socket interruption.
Start with two deliberately small bodies: A is AAAAAA1111111111 and B is BBBBBB2222222222. Both contain 16 ASCII bytes. The saved prefix is AAAAAA. Without a version condition, Range: bytes=6- retrieves ten 2 bytes from the changed route. Blindly appending them creates AAAAAA2222222222. Its length is correct. Its identity is wrong.
For a SaaS export, the practical question is which generated artifact the partial bytes belong to. A stable download URL can later identify a different generated result. Keep that artifact identity beside the partial file before implementing a retry button.
Send the validator, then handle the full response
Use the strong ETag saved with the original representation when constructing the resume request. The fixture assigns A the tag "version-a" and B the tag "version-b". These are controlled labels for the sample bodies, not computed content hashes.
GET /changed HTTP/1.1
Range: bytes=6-
If-Range: "version-a"
Accept-Encoding: identityIf-Range makes the range request conditional. When the validator no longer matches, the server can return the complete representation with 200 OK. Your response handler must replace the old partial content in that branch. Appending a full body to an existing prefix defeats the protection supplied by the request header.
Our changed route returns B's full 16 bytes when it receives A's validator. Blind append now produces 22 bytes: six from A plus all 16 from B. The corrected branch discards the old prefix and retains exactly B. The lab computes SHA-256 values for the assembled outputs and compares the actual bytes with the known fixture bodies.
The unchanged route supplies the control: the same conditional request returns 206, the suffix starts at byte six and appending produces A exactly. A separate route intentionally ignores Range and returns the full A body with 200. The replacement branch handles that response too. A client cannot assume that sending Range guarantees a partial response; the MDN range guide describes both response paths.
If-Match serves a different response contract. RFC 9110 distinguishes If-Range by its full-representation fallback rather than a failed-precondition response. The separate ETag write-concurrency example uses If-Match to protect competing edits. Copying its error handler into a download client would miss today's replacement branch.
Validate the partial response before appending
In this lab, a valid unchanged response carries Content-Range: bytes 6-15/16, a ten-byte body and the saved strong ETag. The Content-Range reference identifies the start, inclusive end and full size. Calculate the expected body length as end - start + 1.
The consumer checks that the range starts at the saved offset, reaches the declared end of this fixture's representation and agrees with the saved total. It also checks the body length and validator. Only then does it assemble the prefix and suffix. The two malformed routes isolate different mistakes: one starts a byte too early, while the other advertises the expected interval but supplies a body one byte shorter.
This is a deliberately narrow client policy: one open-ended byte range, a known total, an explicit matching ETag and identity coding. A response that does not fit that policy is rejected. The code does not implement every valid HTTP range form or multipart response. A general downloader needs a wider parser and an explicit policy for each supported form.
200: validate the full response, replace partial content
206: validate the selected range, then append
416: stop; do not declare completion from length alone
other status: keep response content out of the partial fileOur rejected cases return no assembled output. In an application, stage a replacement response separately so that a failed transfer does not leave a file marked complete. Publish the completed file only after the required validation succeeds. The miniature consumer holds bytes in memory and does not test file writes or crash durability.
Inspect all eight HTTP outcomes
Read the response headers and the consumer action together. The unprotected changed request intentionally omits If-Range as a negative control. Its strict consumer rejects the different ETag; the separate blind-append comparison demonstrates what a client that ignores that evidence would write.
| Fixture case | HTTP response | Consumer action |
|---|---|---|
| Same version, conditional | 206; bytes 6-15/16 | Append; exact A |
| Changed version, conditional | 200; 16-byte B | Replace; exact B |
| Changed version, no condition | 206; different ETag | Reject validator |
| Range ignored | 200; 16-byte A | Replace; exact A |
| Offset at current end | 416; bytes */16 | Reject status |
| Partial starts too early | 206; bytes 5-15/16 | Reject range |
| Partial body too short | 206; nine bytes for ten-byte interval | Reject length |
| Download denied | 403; denial body | Reject status |
The 416 route uses offset 16 against a 16-byte representation. Its unsatisfied-range header describes the current size. The client does not infer that a local file of that length contains the same bytes. The first counterexample already demonstrates why equal length cannot establish identity.
All eight response/action assertions passed. Three additional assembly comparisons preserve the two incorrect outputs and the exact B replacement. A weak-tag preflight check also passed: W/"version-a" is refused before constructing a conditional resume request. That check makes no ninth HTTP request. RFC 9110 forbids weak entity tags in If-Range; the fixture's tag parser accepts only its small ASCII subset.
An independent run from another temporary directory produced byte-identical results. The JSON excludes the ephemeral port and generated server date. These are deterministic contract checks with synthetic content, not download failure rates or throughput measurements.
Persist the representation you actually saved
Store a small manifest alongside the unfinished content: resource or artifact identity, its strong validator, the representation's expected size and the contiguous byte count durably saved. Keep the offset consistent with the file after a crash. A claimed offset ahead of the persisted bytes can turn a valid suffix into an incomplete artifact.
Weak ETags cannot authorize this resume strategy. A Last-Modified date has stricter conditions for acting as a strong validator, so this sample avoids that alternative. If the serving contract supplies no usable validator, choose a complete restart or a separately guaranteed immutable artifact rather than silently resuming by URL and size.
Preserve representation selection as well. RFC 9110 defines byte ranges over encoded bytes. An offset measured after decompression is not interchangeable with an offset into compressed content. The fixture requests identity coding, serves uncompressed bodies and rejects a nonidentity coding in its consumer. It contains no compression test, and browser applications must account for their own request and decoding constraints.
ETag continuity also does not establish download permission. If a new request requires fresh authorization, apply it on that serving path. The presigned-download revocation article covers that separate boundary. Our denied route checks only that a 403 body is never appended; it implements no authentication system.
Carry the changed-file case through your delivery path
Before shipping a resumable SaaS export, generate artifact A and retain a partial copy through the real client. Make the same application entry point select B, then retry with A's validator. Inspect the received response and the resulting file. Verify that a full B response replaces the partial A bytes and that the file matches the intended B artifact.
Repeat with an unchanged artifact and a serving path that returns a full response despite the range request. Retain the malformed-range and denied-response cases as client tests. If the product promises a fixed export snapshot, also verify that the snapshot remains available for the supported retry window.
Those are proposed integration checks. The executed lab uses Python's test HTTP server and HTTP client, with no CDN, browser download manager or cross-origin JavaScript request. Validate those actual boundaries in the product test suite. The acceptance result is the completed artifact's bytes, not a progress bar reaching its advertised length.
Sources
Documentation checked .

Continue the conversation
Comments (0)