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 Contenti stedet for200 OK Content-Rangemed Range-enhedbytesog 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
- Bestem lokal tilstand: Findes
.part? Læs længden som Offset. Indlæs metadata. - Vurdér Resume-egnethed: Forsøg kun Resume, hvis Offset > 0 og mindst én brugbar validator findes i metadata (ETag foretrækkes).
- Opbyg Request: Sæt Range fra Offset. Sæt derudover
If-Range(ETag, ellers Last-Modified). - Klassificer Response:
206: ParseContent-Range, kontroller Range-Unitbytes, 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).
- Skriv Stream: i blokke, flush regelmæssigt, opdater metadata (f.eks. „sidste Flush ved længde X“).
- 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 verificeretContent-Range(unitbytes, start-offset passer). 200betyder: genstart (overskriv), aldrig tilføje til en partial-fil.If-Rangereducerer versionsblanding, men erstatter ikke pligten til at vurdere status/Content-Range.416er 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.
- 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. - 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). - 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.