Net-Base Magasin

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. Refaktorering. Migrering.

Modernisere veletableret software uden at miste systemets faglige substans.

Legacy-systemer Delphi Refaktorering Idriftsættelse

Bevar forretningslogikken

Værdifuld forretningslogik forbliver synlig og bliver ikke begravet under en komplet nyopbygning.

Afkobling af kodebasen

Adskil ansvarsområder, så nye grænseflader og nye tjenester kan tilsluttes kontrolleret.

Gør migration planlægbar

Levere, teste og udrulle trinvis i stedet for at risikere driften med et hårdt cut.

24.10.2026

Fra magasinets tema til projektpraksis

Passende service- og tekniske sider til artiklen

Scenen er ubehagelig, men typisk: Et natligt job henter et stort arkiv, fremdriftsbjælken står på 93 %. Så et kort VPN-ryk, timeout, afbrudt. Om morgenen ligger der en fil i målmappe, størrelsen „passer“, men ved udpakket kommer kun „unexpected end of file“. Den, der reflexmæssigt eftermonterer „Genoptag“, ender hurtigt i næste kanttilfælde: Downloadet fuldføres, men er sammensat af to versioner.

Netop til det formål er en HttpClient-download med genoptagelse tænkt – men kun hvis du behandler den som en lille datapipeline: med klare regler for, hvornår du må føje til, hvordan du kontrollerer versionsægtheden (ETag/If-Range) og hvordan du håndterer lokale mellemstande (Partial Files), så drift og fejlsøgning ikke bliver et gætteri.

Konflikten kan navngives præcist: Sparring af båndbredde og tid (genoptage i stedet for genstart) står over for integritet og vedligeholdbarhed (ingen stille versionsblanding, efterviselige beslutninger, reproducerbart fejlscenarie). Denne artikel er bygget som et afvejningskort: nytte, indsats, risici, begrænsninger – og en pragmatisk linje for, hvornår du bør implementere genoptagelse, og hvornår et godt retry er bedre.

Afvejningkort: To mål, en hård trade-off

Genoptagelse betaler sig ikke, fordi det er „moderne“, men fordi dit miljø kræver det: store filer, ustabile forbindelser, tidsvinduer i batchkørsler, brugere der afbryder downloads, eller services der efter genstart skal fortsætte, hvor de slap. Samtidig er genoptagelse det sted, hvor man uforvarende introducerer datakorruption – og det opdages ofte først langt senere (import fejler, signatur ugyldig, setup kan ikke køres).

Situation Genoptagelsesfordel Typisk risiko Plausibel beslutning
Store filer over WAN/VPN eller mobilnet høj (tid, båndbredde) „200 i stedet for 206“ bliver blindt føjet til Genoptagelse ja, men defensivt (Status/Content-Range/Validator)
„latest“-URL’er / artefakter uden versionsbinding moderat Versionsblanding efter mellemliggende opdatering Genoptagelse kun med stærk validator, ellers genstart
Intern storage med stabile ETags høj lav, hvis headers er konsistente Genoptagelse meget velegnet, gem metadata permanent
Små filer (et par dusin MB) lav Kompleksiteten opvejer fordelen Fuld-download + retry/backoff er som regel bedre
Skiftende proxyer/CDN’er, uklar header-situation uklar Inkonsistente validatorer, svært at debugge Kun med godt logging; ellers bevidst konservativ

Hvorfor genoptagelse er mere end blot at sætte „Range“

HTTP håndterer byte-range-requests via Range-headeren. Den vigtige, ofte oversete pointe: understøttelse af Range er valgfri. En server må ignorere Range og dermed svare, som var det et normalt GET – altså med 200 OK og fuld body. For klienter betyder det: sandheden står i responsen, ikke i din request-intention.[Kilde]

En genoptagelsesklient, der forveksler „Range sendt“ med „delindhold kommer“, begår præcis den fejl, du så i begyndelsen af scenen: Et komplet download bliver føjet til en allerede delvist hentet fil. Resultat: filen ser ud til at have korrekt størrelse, men er logisk beskadiget.

Succesindikator: 206 og plausibelt Content-Range

Hvis genoptagelse virkelig virker, ser du typisk:

  • Status 206 Partial Content i stedet for 200 OK
  • Content-Range med Range-enhed bytes og det forventede start-offset
  • konsistente validatorer (f.eks. ETag), hvis du bruger dem til versionsintegritet

Disse kontroller er ikke blot „nice to have“. RFC 9110 fastslår eksplicit, at modtagere ikke må sammensætte en 206-respons med ukendt Range-enhed med en lagret repræsentation. Praktisk konsekvens: Uden korrekt forstået Content-Range intet append.[Kilde]

If-Range und ETag: Der saubere Weg gegen Versionsmix

Kerneproblemet ved at fortsætte er ikke teknikken ved at appende, men spørgsmålet: Henter du stadig den samme ressource? HTTP leverer validatorer til det. ETag (Entity Tag) er det mest præcise redskab: en server-side identifikator for en konkret repræsentation. Last-Modified er en dato, ofte brugbar, men mindre præcis (tidsopløsning, repackaging, forskellige mellemtrin via CDN’er).

Mekanismen for „Range kun hvis det virkelig er samme version“ hedder If-Range: Du sender Range plus en betingelse. Hvis validatoren passer, returneres Range; hvis den ikke passer, skal serveren sende hele ressourcen. For ETags gælder, at betingelsen kun er opfyldt ved et stærkt ETag-match (strong comparison). Det er ikke et spørgsmål om fortolkning, men fastlagt i standarden.[Kilde]

Driftsrealiteten er: Hvis serveren ikke understøtter Range, skal den ignorere If-Range. Det lyder akademisk, men det er et direkte krav til din klient: Du skal også regne med 200 OK og træffe en hård beslutning ud fra det (genstart, ikke appende).[Kilde]

Partial-File-Strategie: Das lokale Gegenstück zur HTTP-Semantik

Genoptagelse betyder: Du administrerer lokal tilstand. Og denne tilstand skal forblive stabil efter nedbrud, genstart, parallel opstart eller brugerhandling („klikke igen“). En ren partial-file-strategi består af tre artefakter:

  • Partial-fil som eneste skriveplacering under downloadet (f.eks. foo.zip.part)
  • Metadata opbevaret separat (fx foo.zip.part.meta.json): URL, tidspunkt, ETag, Last-Modified, forventet længde (hvis kendt), eventuelt Content-Type, samt den lokale længde ved sidste rene Flush
  • Atomisk afslutning: bliver først omdøbt/erstattet til det endelige navn efter fuldført download og plausibilitetskontrol

Hvorfor metadata? Fordi du ellers ved næste opstart kun kender filens længde – men ikke, om den hører til netop denne serverversion. Netop denne information afgør, om du kan genoptage.

HttpClient Download med Resume: Beslutningslogik, som du kan teste

System.Net.HttpClient leverer dig Requests, Responses og Streams. Robustheden kommer fra din beslutningsmatrix. Hvis du formulerer den klart, kan du også teste den grundigt (inklusive „Server ignoriert Range“, „Validator mismatch“, „416“).

Minimalt forløb i 6 trin

  1. Bestem lokal tilstand: Findes .part? Læs længden som Offset. Indlæs metadata.
  2. Vurdér Resume-egnethed: Forsøg kun Resume, hvis Offset > 0 og mindst én brugbar validator findes i metadata (ETag foretrækkes).
  3. Opbyg Request: Sæt Range fra Offset. Sæt derudover If-Range (ETag, ellers Last-Modified).
  4. Klassificer Response:
    • 206: Parse Content-Range, kontroller Range-Unit bytes, start-Offset skal passe nøjagtigt. Kun da Append.
    • 200: aldrig Append. Forkast eller karantænér den delvise filtilstand, hent igen.
    • 416 Range Not Satisfiable: Offset passer ikke længere (lokalt eller på serversiden). Nulstil tilstand og genstart.
    • ellers: fejlvej med klar diagnose (Statuscode + Header logge).
  5. Skriv Stream: i blokke, flush regelmæssigt, opdater metadata (f.eks. „sidste Flush ved længde X“).
  6. Afslutning: Størrelses-/plausibilitetskontrol (hvis muligt). Derefter atomisk omdøb/erstat.

RangeHeaderValue: ikke et detalje, men fejlforebyggelse

.NET tilbyder for Range-headeren den typede klasse RangeHeaderValue. Det er ikke kun „pæn kode“: Det reducerer syntaktiske headerfejl og gør Logs mere tolkelige, fordi du genererer konsistente værdier. Microsoft dokumenterer også den operationelt relevante kant: Servere behøver ikke understøtte Range-Requests; Accept-Ranges er en indikation, ikke et løfte.[Quelle]

Konstrueret eksempel: Når „200 OK“ ødelægger dit Resume

Typiske faldgruber og hvordan du genkender dem i loggen

Accept-Ranges: nyttigt signal, men ingen garanti

Mange teams bruger Accept-Ranges som „serveren kan genoptage“. MDN beskriver headeren præcis sådan som en supportindikator („alt andet end none„). Det er praktisk til diagnostik og telemetri. Alligevel: beslut aldrig udelukkende ud fra det. En proxy, et auth-gateway eller et CDN kan ændre adfærden. Din logik skal derfor altid basere sig på statuskoden (206 vs. 200) og på Content-Range, ikke på håbet i Accept-Ranges.[Kilde]

Weak ETags (W/…) og transformationer

Weak ETags (W/\"…\") dukker op oftere, end man ønsker. For If-Range er et stærkt ETag-match dog afgørende. I praksis betyder det: Hvis du kun får weak ETag, er genoptagelse mindre robust. Du kan stadig bruge den, men du bør være mere konservativ (fx genstarte ved de mindste uoverensstemmelser) og for særligt kritiske filer supplere med en out-of-band-integritetskontrol (hash fra manifest/API), hvis det er tilgængeligt.

Ukendt samlet længde (chunked) og manglende plausibilitet

Hvis der ikke kommer nogen Content-Length (fx chunked transfer), mister du den simple slutkontrol „lokal længde == forventet længde“. Genoptagelse kan stadig fungere, men du har brug for solide validatorer og god logging. Uden disse støtter bliver fejlsøgning langsommelig, fordi du ikke engang kan afgøre, om der mangler bytes eller om der er for mange.

Parallelisering: først når grundlaget er på plads

Det er muligt at downloade flere ranges parallelt, men det multiplicerer tilstandsproblemer: segmenthåndtering, random-write, merge, fejllogning, retry per segment. For mange procesnære softwareløsninger er det overkill. Den stabile gevinst opnås ofte allerede ved: seriel download + genoptagelse + klar beslutningsmatrix. Parallelisering giver først mening, når du har dokumenteret stabil Range-support og konkrete målinger viser, at du faktisk er begrænset af netværk eller server.

Debugging, der på 5 minutter giver klarhed

Genoptagelsesfejl er sjældent „bare et forkert if“. Som regel er det en kombination af infrastruktur (Proxy/CDN), serverkonfiguration, timeouts og lokalt state. Derfor betaler det sig at føre struktureret protokollering pr. forsøg. Ikke som en tekstørken, men som konsistente felter:

  • Anmodning: URL (uden Secrets), metode, Range (start/slut), If-Range (ETag eller dato).
  • Respons: Statuskode, Accept-Ranges, ETag, Last-Modified, Content-Length, Content-Range (rå og parset).
  • Lokal tilstand: Part-Pfad, part-længde før request, skrivemodus (Append/Overwrite), længde efter skrivning.
  • Beslutning: „Genoptagelse accepteret“, „Genoptagelse afvist (200) – genstart“, „416 – nulstil“, „Validator-uoverensstemmelse – genstart“.

Med netop disse data kan du teste hypoteser: „Ignorerer serveren Range?“, „Skifter ETag mellem requests?“, „Er offset forkert bestemt?“, „Skriver en anden proces samtidigt?“ – uden at du først behøver at hive Wireshark frem.

Hvornår du bevidst ikke implementerer genoptagelse

Den reneste arkitektur‑beslutning er nogle gange at udelade genoptagelse – ikke af dovenskab, men fordi det i dit scenarie giver større fejlflade end nytte:

  • URL uden versionsbinding: Hvis der bag den samme URL løbende ligger nyt indhold, er genoptagelse uden en stærk validator et integritetsrisiko.
  • Ingen brugbare validatorer: Hvis hverken ETag eller Last-Modified er konsistente, er „fortsæt“ et væddemål. Ved kritiske importer er det ofte værre end en kontrolleret genstart.
  • Download lille og hyppig: Her er retry-strategier (timeout, backoff, evt. Mirror-URL) mere vedligeholdelsesvenlige og nemmere at forklare i drift.

Pragmatiske sikkerhedsnet, der betaler sig

Karantæne i stedet for sletning

Hvis et genoptagelsesforsøg fejler, slet .part ikke reflexmæssigt. Omdøb i stedet (f.eks. .part.bad + tidsstempel). Det gør server-/proxy-problemer synlige, fordi du senere kan sammenligne response-headers og filtilstand. Og det forhindrer, at et sjældent problem forsvinder fra analysen ved et tilfældigt „oprydning“.

Låsning mod dobbeltstarts

Hvis to processer samtidig skriver til samme .part, er alle HTTP-validatorer værdiløse. En eksklusiv lock (fil-lock eller systemomfattende mutex) er derfor ikke luksus, men grundbeskyttelse. Metadaten kan derudover bære owner-information (instans-id), så du kan identificere og rydde op i „forældede“ downloads.

Huskelister, du virkelig har brug for

  • Append kun ved 206 – og kun med verificeret Content-Range (unit bytes, start-offset passer).
  • 200 betyder: genstart (overskriv), aldrig tilføje til en partial-fil.
  • If-Range reducerer versionsblanding, men erstatter ikke pligten til at vurdere status/Content-Range.
  • 416 er et reset-signal: lokal offset passer ikke længere, forkast/quarantænér tilstanden.

Afslutning: Først integritet, så optimering

En HttpClient-download med resume er god, når den er kedelig: Den sender Range kun, når den lokale tilstand er pålidelig, den genoptager kun ved 206 med verificeret Content-Range, og den forhindrer versionsmix via If-Range og validatorer. Alt andet er en „ser hurtigt ud“-funktion, der senere koster dig tid, fordi fejl er sjældne og vanskelige at reproducere.

Hvis du for Updater, Importstrecken eller Services vil sætte en robust download-pipeline op (Resume, Logging, Partial-File-Strategie, driftsgrænser), kan det ofte afklares i et kort arkitektur-Check – inklusive spørgsmålet, om kompleksiteten overhovedet er berettiget i dit miljø. Kontakt aufnehmen und Vorhaben mit Net-Base besprechen.

Kilder og yderligere oplysninger

De faglige kerneudsagn er redaktionelt sat i kontekst på baggrund af følgende eksterne kilder.

  1. RFC 9110: HTTP Semantics (www.rfc-editor.org)
    HTTP Range-forespørgsler er valgfrie; servere kan ignorere Range og svare som ved et almindeligt GET, derfor skal klienten evaluere statuskode og headers i stedet for blindt at antage genoptagelse.
  2. HTTP range requests – HTTP | MDN (developer.mozilla.org)
    Accept-Ranges er et signal for Range-understøttelse, men klienter skal robust kontrollere responsadfærd (206/Content-Range).
  3. RangeHeaderValue Class (System.Net.Http.Headers) | Microsoft Learn (learn.microsoft.com)
    .NET tilbyder med RangeHeaderValue en typet repræsentation af Range-headeren; servere er ikke forpligtet til at understøtte Range-forespørgsler.

Projekt oder Modernisierungsvorhaben mit Net-Base besprechen.

Næste trin

Når emnet bliver til et reelt projekt, bør arkitektur, eksisterende systemer og drift tidligt vurderes samlet.

Vi støtter ikke kun ved enkeltspørsmål, men også når kildekodeudsnit, legacy-komponenter eller portalidéer skal udvikles til et robust virksomhedsprojekt.

  • Eksisterende tilstand, målbillede og tekniske risici vurderes samlet.
  • REST, dataadgang, portaler og udrulning bliver ikke udskudt som efterfølgende opgaver.
  • De ser tidligt, hvilken vej der er økonomisk og driftsmæssigt bæredygtig.

Del indlæg

Del dette indlæg direkte

LinkedIn, X, XING, Facebook, WhatsApp og e-mail er straks tilgængelige. Til Instagram forbereder vi link og kort tekst.

E-mail

Instagram åbner i en ny fane. Linket og kortteksten kopieres på forhånd til udklipsholderen.

Skriv kommentar

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