Net-Base Rivista

System.Net.HttpClient: Download mit Resume – Range-Requests, ETag-Prüfung und saubere Partial-File-Strategie

Wenn Downloads abbrechen, ist Resume verlockend – und riskant. So kombinierst du Range-Requests, If-Range/ETag und eine robuste .part-Strategie, ohne stille Dateikorruption zu bauen.

Legacy. Refactoring. Migrazione.

Modernizzare software consolidato, senza perdere la logica di dominio del sistema.

Legacy Delphi Refactoring Messa in produzione

Mantenere la logica di dominio

La logica di business di valore resta visibile e non viene sepolta sotto una riscrittura completa.

Disaccoppiare la base di codice

Separare le responsabilità affinché nuove interfacce e nuovi servizi possano agganciarsi in modo controllato.

Rendere pianificabile la migrazione

Rilasciare, testare e introdurre per fasi invece di mettere a rischio l'operatività con un cut netto.

24.10.2026

Dal tema della rivista alla pratica di progetto

Pagine di servizi e tecniche correlate all'articolo

La scena è spiacevole, ma tipica: un job notturno carica un grande archivio, la barra di progresso è al 93 %. Poi una breve fluttuazione della VPN, timeout, interruzione. Al mattino c’è un file nella directory di destinazione, la dimensione „sembra“ corretta, ma durante l’estrazione compare solo „unexpected end of file“. Chi poi per riflesso aggiunge „ripresa“ si trova rapidamente nel caso limite successivo: il download va a buon fine, ma è composto da due versioni diverse.

Proprio per questo è pensato un download HttpClient con ripresa – ma solo se lo tratti come una piccola pipeline di dati: con regole chiare su quando puoi appendere, come verifichi la fedeltà della versione (ETag/If-Range) e come gestisci gli stati intermedi locali (file parziali) in modo che esercizio e debug non diventino un gioco d’azzardo.

Il conflitto di obiettivi è facilmente identificabile: risparmiare banda e tempo (continuare il download invece di ricominciare) contro integrità e manutenibilità (nessun mix silenzioso di versioni, decisioni tracciabili, quadro d’errore riproducibile). Questo articolo è costruito come una mappa di valutazione: benefici, sforzo, rischi, limiti – e una linea pragmatica su quando implementare effettivamente la ripresa e quando è meglio un buon retry.

Mappa di valutazione: due obiettivi, un trade-off netto

La ripresa vale la pena non perché sia „moderna“, ma perché il tuo contesto la impone: file grandi, linee instabili, finestre temporali nei batch, utenti che interrompono i download o servizi che devono riprendere dopo un riavvio da dove avevano lasciato. Allo stesso tempo la ripresa è il punto in cui si possono introdurre inconsapevolmente corruzioni dei dati – e queste emergono spesso molto dopo (import fallito, firma non valida, setup non eseguibile).

Situazione Beneficio della ripresa Rischio tipico Decisione plausibile
File grandi su WAN/VPN o reti mobili alto (tempo, banda) „200 invece di 206“ viene allegato alla cieca Ripresa sì, ma difensiva (Status/Content-Range/Validator)
URL „latest“ / artefatti senza vincolo di versione medio mix di versioni dopo un aggiornamento intermedio Ripresa solo con validatore forte, altrimenti riavvio
Storage interno con ETag stabili alto basso, se gli header sono consistenti Ripresa molto adatta, persistere i metadati
File piccoli (alcune decine di MB) basso la complessità prevale Download completo + retry/backoff è in genere meglio
Proxy/CDN variabili, situazione header poco chiara incerto validator incoerenti, debug difficile Solo con buon logging; altrimenti intenzionalmente conservativo

Perché Resume è più di „impostare Range“

HTTP conosce le richieste Byte-Range tramite l’header Range. La frase importante, spesso trascurata: il supporto di Range è opzionale. Un server può ignorare Range e rispondere come se fosse una normale GET – quindi con 200 OK e tutto il body. Per i client questo significa: la verità sta nella Response, non nell’intento della tua Request.[Quelle]

Un Resume-Client che confonde „Range inviato“ con „arriva contenuto parziale“ commette esattamente l’errore che hai visto all’inizio della scena: un download completo viene aggiunto a un file già parzialmente scaricato. Risultato: il file sembra avere la dimensione corretta, ma è logicamente corrotto.

Segnale di successo: 206 più un plausibile Content-Range

Se il Resume funziona veramente, tipicamente vedi:

  • Stato 206 Partial Content invece di 200 OK
  • Content-Range con Range-Unit bytes e l’offset iniziale previsto
  • validator coerenti (ad es. ETag), se li usi per mantenere la coerenza di versione

Questi controlli non sono „nice to have“. L’RFC 9110 formula esplicitamente che i ricevitori non devono ricomporre una 206 con una Range-Unit sconosciuta con una rappresentazione memorizzata. Conseguenza pratica: senza un Content-Range correttamente interpretato, niente append.[Quelle]

If-Range und ETag: Il modo corretto per evitare il mescolamento di versioni

Il problema centrale nel riprendere un download non è la tecnica di aggiunta in coda, ma la domanda: stai ancora scaricando la stessa risorsa? HTTP fornisce validator per questo. ETag (Entity Tag) è la leva più precisa: un identificatore lato server per una rappresentazione concreta. Last-Modified è una data, spesso utilizzabile, ma meno accurata (risoluzione temporale, repackaging, diverse fasi intermedie tramite CDN).

Il meccanismo per „Range solo se è davvero la stessa versione“ si chiama If-Range: mandi Range più una condizione. Se il validator corrisponde, il server restituisce il Range; se non corrisponde, il server deve inviare l’intera risorsa. Per gli ETag vale: la condizione è soddisfatta solo in caso di confronto strong (strong comparison). Questo non è materia di interpretazione, è previsto dallo standard.[Quelle]

La realtà operativa resta: se il server non supporta Range, deve ignorare If-Range. Suona accademico, ma è una richiesta diretta al tuo client: devi prevedere anche 200 OK e trarne una decisione netta (ricominciare da capo, non aggiungere in coda).[Quelle]

Strategia del file parziale: il corrispettivo locale della semantica HTTP

Resume significa: gestire lo stato locale. E questo stato deve rimanere consistente anche dopo crash, riavvio, avvii paralleli o azioni dell’utente („cliccare di nuovo“). Una buona strategia per i file parziali è composta da tre artefatti:

  • File parziale come unico punto di scrittura durante il download (ad es. foo.zip.part)
  • Metadaten memorizzati separatamente (p. es. foo.zip.part.meta.json): URL, Zeitpunkt, ETag, Last-Modified, lunghezza prevista (se nota), eventualmente Content-Type, nonché la lunghezza locale al momento dell’ultimo flush pulito
  • Chiusura atomica: solo dopo il download completo e un controllo di plausibilità viene rinominato/sostituito con il nome finale

Perché i metadati? Perché altrimenti al prossimo avvio conosci solo la lunghezza del file – ma non sai se corrisponde esattamente a quella versione del server. Proprio questa informazione determina se puoi riprendere.

HttpClient Download mit Resume: Entscheidungslogik, die du testen kannst

System.Net.HttpClient liefert dir Requests, Responses und Streams. La robustezza deriva dalla tua matrice decisionale. Se la formuli in modo chiaro, puoi testarla in modo efficace (inclusi „Server ignoriert Range“, „Validator mismatch“, „416“).

Flusso minimale in 6 passaggi

  1. Determinare lo stato locale: Esiste .part? Leggere la lunghezza come offset. Caricare i metadati.
  2. Valutare la possibilità di Resume: Tentare il Resume solo se Offset > 0 e nei metadati è presente almeno un validator utilizzabile (preferibilmente ETag).
  3. Costruire la request: Impostare Range a partire dall’offset. Impostare inoltre If-Range (ETag, altrimenti Last-Modified).
  4. Classificare la Response:
    • 206: analizzare Content-Range, verificare che la Range-Unit sia bytes, l’offset iniziale deve corrispondere esattamente. Solo allora append.
    • 200: mai appendare. Scartare o mettere in quarantena lo stato parziale, riscaricare.
    • 416 Range Not Satisfiable: l’offset non è più valido (locale o lato server). Resettare lo stato e ricominciare.
    • altrimenti: percorso di errore con diagnosi chiara (registrare status code e header nei log).
  5. Scrivere sullo stream: a blocchi, effettuare flush regolari, aggiornare i metadati (p. es. „ultimo flush alla lunghezza X“).
  6. Chiusura: Controllo di dimensione/plausibilità (se possibile). Quindi rinominare/sostituire in modo atomico.

RangeHeaderValue: non un dettaglio, ma prevenzione degli errori

.NET offre per l’header Range la classe tipizzata RangeHeaderValue. Non è solo „bel codice“: riduce gli errori sintattici negli header e rende i log più interpretabili, perché produci valori coerenti. Microsoft documenta inoltre il vincolo operativo rilevante: i server non sono obbligati a supportare le richieste Range; Accept-Ranges è un’indicazione, non una garanzia.[Quelle]

Esempio costruito: quando „200 OK“ compromette il tuo Resume

Insidie tipiche e come riconoscerle nei log

Accept-Ranges: segnale utile, ma non una garanzia

Molti team usano Accept-Ranges come indicazione che il server supporta la ripresa. MDN descrive l’header esattamente così come indicatore di supporto („tutto tranne none„). Questo è pratico per diagnostica e telemetria. Tuttavia: non decidere mai solo in base a quello. Un proxy, un auth-gateway o un CDN possono modificare il comportamento. La tua logica deve quindi basarsi sempre sul codice di stato (206 vs. 200) e sul Content-Range, non sulla flebile speranza rappresentata da Accept-Ranges.[Fonte]

ETag deboli (W/…) e trasformazioni

Gli ETag deboli (W/"…") compaiono più frequentemente di quanto si desideri. Per If-Range, però, è decisivo un confronto ETag forte. In pratica questo significa: se ricevi solo ETag weak, la ripresa è meno affidabile. Puoi continuare a usarla, ma dovresti comportarti in modo più conservativo (p. es. riavviare al minimo disallineamento) e per file particolarmente critici aggiungere una verifica di integrità out-of-band (hash da manifest/API), se disponibile.

Lunghezza totale sconosciuta (chunked) e mancanza di plausibilità

Se non arriva Content-Length (p. es. transfer chunked), perdi il semplice controllo di completamento „lunghezza locale == lunghezza attesa“. La ripresa può comunque funzionare, ma servono validator puliti e logging efficace. Senza questi supporti la ricerca degli errori diventa ostinata, perché non puoi nemmeno dire se ti mancano byte o se ce ne sono di troppo.

Parallelizzazione: solo quando la base è solida

Scaricare più range in parallelo è possibile, ma moltiplica i problemi di stato: gestione dei segmenti, scrittura casuale, fusione, log degli errori, retry per segmento. Per molte soluzioni software a contatto con il processo è eccessivo. Il guadagno stabile spesso si ottiene già con: download seriale + ripresa + una matrice decisionale chiara. La parallelizzazione conviene solo quando hai dimostrato un supporto ai range stabile e misurazioni concrete mostrano che sei realmente limitato dalla rete o dal server.

Debugging che in 5 minuti fornisce chiarezza

I bug di ripresa raramente sono „solo un if sbagliato“. Di solito è una combinazione di infrastruttura (proxy/CDN), configurazione del server, timeout e stato locale. Per questo vale la pena registrare in modo strutturato ogni tentativo. Non come una massa di testo, ma con campi coerenti:

  • Richiesta: URL (senza segreti), metodo, Range (inizio/fine), If-Range (ETag o data).
  • Risposta: codice di stato, Accept-Ranges, ETag, Last-Modified, Content-Length, Content-Range (grezzo e analizzato).
  • Stato locale: percorso del file .part, lunghezza del .part prima della richiesta, modalità di scrittura (Append/Overwrite), lunghezza dopo la scrittura.
  • Decisione: „Resume accepted“, „Resume rejected (200) – RESTart“, „416 – reset“, „Validator mismatch – RESTart“.

Con esattamente questi dati puoi testare ipotesi: «Il server ignora il Range?», «L’ETag cambia tra le richieste?», «L’offset è calcolato in modo errato?», «Un secondo processo scrive sul file?» — senza dover prima tirare fuori Wireshark.

Quando decidere consapevolmente di non implementare la ripresa

La decisione architetturale più pulita a volte è non implementare la ripresa — non per comodità, ma perché nel tuo scenario aumenta la superficie di errore più del vantaggio:

  • URL senza vincolo di versione: Se dietro la stessa URL compaiono regolarmente contenuti nuovi, la ripresa senza un validator forte è un rischio per l’integrità.
  • Assenza di validator utili: Se né ETag né Last-Modified sono consistenti, il «continuare» è una scommessa. Negli import critici questo è spesso peggiore di un riavvio controllato.
  • Download piccoli e frequenti: Qui le strategie di retry (timeout, backoff, eventualmente mirror-URL) sono più manutenibili e più facili da giustificare in esercizio.

Reti di sicurezza pragmatiche che ripagano

Quarantena invece di cancellare

Se un tentativo di ripresa fallisce, non cancellare reflexivamente .part. Rinominala (es. .part.bad + timestamp). Questo rende visibili problemi del server/proxy perché poi puoi confrontare header di risposta e stato del file. E impedisce che un problema raro scompaia immediatamente dall’analisi a causa della «pulizia».

Locking contro avvii simultanei

Se due processi scrivono simultaneamente sulla stessa .part, tutti i validator HTTP diventano inutili. Un lock esclusivo (file-lock o mutex di sistema) non è quindi un lusso, ma una protezione di base. I metadati possono includere anche un’informazione sull’owner (ID istanza), così puoi riconoscere e ripulire i download «orfani».

Regole di riferimento rapido di cui hai davvero bisogno

  • Append solo per 206 – e solo con Content-Range verificato (unità bytes, offset di inizio coerente).
  • 200 significa: riavviare (overwrite), mai aggiungere a un file parziale.
  • If-Range riduce il mix di versioni, ma non sostituisce l’obbligo di valutare lo status/il Content-Range.
  • 416 è un segnale di reset: l’offset locale non è più valido, scartare/quarantinare lo stato.

Conclusione: prima l’integrità, poi l’ottimizzazione

Un download HttpClient con ripresa è valido quando è noioso: invia l’header Range solo se lo stato locale è affidabile, riprende il download solo se la risposta è 206 e il Content-Range è stato verificato, e previene mix di versioni tramite If-Range e validatori. Tutto il resto è una funzionalità che «sembra veloce» e che ti farà perdere tempo in seguito, perché gli errori sono rari e allora difficili da riprodurre.

Se per updater, pipeline di importazione o servizi devi impostare correttamente una pipeline di download robusta (ripresa, logging, strategia per file parziali, aspetti operativi), spesso può essere chiarito con un breve check architetturale — compresa la valutazione se la complessità sia giustificata nel tuo contesto. Contatta e discuti il progetto con Net-Base.

Fonti e informazioni aggiuntive

Le affermazioni tecniche principali sono state contestualizzate editorialmente sulla base delle seguenti fonti esterne.

  1. RFC 9110: HTTP Semantics (www.rfc-editor.org)
    Le richieste Range HTTP sono opzionali; i server possono ignorare Range e rispondere come a una normale GET, quindi il client deve valutare lo status code e gli header invece di inviare Range alla cieca.
  2. HTTP range requests – HTTP | MDN (developer.mozilla.org)
    Accept-Ranges è un segnale per il supporto di Range, ma i client devono verificare in modo robusto il comportamento della risposta (206/Content-Range).
  3. RangeHeaderValue Class (System.Net.Http.Headers) | Microsoft Learn (learn.microsoft.com)
    .NET fornisce con RangeHeaderValue una rappresentazione tipizzata dell’header Range; i server non sono tenuti a supportare le richieste Range.

Discutere un progetto o un intervento di modernizzazione con Net-Base.

Passo successivo

Quando un tema diventa un progetto reale, architettura, sistemi esistenti e gestione operativa dovrebbero essere considerati insieme fin dalle fasi iniziali.

Non forniamo solo supporto per questioni isolate, ma anche quando da frammenti di codice sorgente, tematiche legacy o idee di portale deve nascere un progetto aziendale solido.

  • Stato attuale, stato obiettivo e rischi tecnici vengono valutati insieme.
  • REST, l'accesso ai dati, i portali e il rollout non vengono rinviati a fasi successive.
  • Vede in anticipo quale percorso è economicamente e operativamente sostenibile.

Condividi il post

Condividi direttamente questo articolo

LinkedIn, X, XING, Facebook, WhatsApp e e-mail sono immediatamente disponibili. Per Instagram prepariamo direttamente il link e un breve testo.

E-mail

Instagram si apre in una nuova scheda. Il link e il breve testo vengono copiati prima negli appunti.

Scrivi un commento

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert