Net-Base Magazyn

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.

Systemy dziedziczone. Refaktoryzacja. Migracja.

Modernizować rozbudowane oprogramowanie, nie tracąc logiki biznesowej systemu.

Systemy dziedziczone Delphi Refaktoryzacja Uruchomienie produkcyjne

Zachować logikę biznesową

Wartościowa logika biznesowa pozostaje widoczna i nie zostanie zakopana pod kompletną przebudową.

Rozplątanie bazy kodu

Rozdzielić odpowiedzialności, aby nowe interfejsy i nowe usługi mogły być podłączane w sposób kontrolowany.

Umożliwić planowanie migracji

Stopniowe dostarczanie, testowanie i wdrażanie zamiast narażać działanie środowiska produkcyjnego na ryzyko nagłym przełączeniem.

24.10.2026

Od tematu magazynowego do praktyki projektowej

Pasujące strony usługowe i techniczne do artykułu

Scena jest nieprzyjemna, ale typowa: nocne zadanie pobiera duże archiwum, pasek postępu stoi na 93 %. Potem krótki zryw VPN, timeout, przerwanie. Rano w katalogu docelowym leży plik, rozmiar „pasuje”, ale przy rozpakowywaniu pojawia się tylko „unexpected end of file”. Kto odruchowo dorzuca wznawianie, szybko trafia na następny przypadek brzegowy: pobieranie przebiega dalej, ale jest złożone z dwóch wersji.

Dokładnie do tego służy Pobieranie HttpClient z wznawianiem – ale tylko jeśli potraktujesz je jak mały potok danych: z jasnymi regułami, kiedy możesz dopisywać, jak sprawdzasz zgodność wersji (ETag/If-Range) i jak zarządzasz lokalnymi stanami pośrednimi (Partial Files), tak aby eksploatacja i debugowanie nie stały się zgadywanką.

Konflikt celów można jasno nazwać: oszczędność pasma i czasu (wznawianie zamiast restartu) stoi przeciwko integralności i utrzymywalności (brak cichego miksu wersji, przejrzyste decyzje, odtwarzalny obraz błędu). Ten tekst jest zbudowany jako mapa rozważań: korzyści, nakład, ryzyka, ograniczenia – oraz pragmatyczna linia wskazująca, kiedy faktycznie wdrożyć wznawianie, a kiedy lepsza będzie porządna ponowna próba.

Mapa rozważań: Dwa cele, twardy kompromis

Wznawianie opłaca się nie dlatego, że jest „nowoczesne”, lecz dlatego, że środowisko je wymusza: duże pliki, niestabilne łącza, okna czasowe w zadaniach wsadowych, użytkownicy przerywający pobieranie lub serwisy, które po restarcie powinny kontynuować od miejsca, w którym przerwano. Jednocześnie wznawianie to miejsce, gdzie łatwo wprowadzić niezauważalną korupcję danych — a ta ujawnia się często dopiero znacznie później (import się nie powiedzie, podpis jest nieprawidłowy, instalacja nie uruchamia się).

Sytuacja Korzyści ze wznawiania Typowe ryzyko Prawdopodobna decyzja
Duże pliki przez WAN/VPN lub sieć komórkową wysokie (czas, przepustowość) „200 zamiast 206” jest dołączane na ślepo Wznawianie tak, ale defensywnie (Status/Content-Range/Validator)
URL-e „latest” / artefakty bez powiązania z wersją średnie Mieszanka wersji po pośredniej aktualizacji Wznawianie tylko z silnym walidatorem, w przeciwnym razie restart
Wewnętrzne repozytorium/storage ze stabilnymi ETag wysokie niskie, jeśli nagłówki są spójne Wznawianie bardzo odpowiednie, metadane powinny być trwale przechowywane
Małe pliki (kilkadziesiąt MB) niskie Złożoność przeważa Pełne pobranie i ponowne próby z backoffem są zwykle lepsze
Zmienne proxy/CDN, niejasna sytuacja nagłówków niejasne Niespójne walidatory, trudne debugowanie Tylko przy dobrym logowaniu; w przeciwnym razie postępować konserwatywnie

Dlaczego Resume to więcej niż „ustawienie Range”

HTTP obsługuje żądania zakresu bajtów za pomocą nagłówka Range. Ważne, często pomijane stwierdzenie: obsługa Range jest opcjonalna. Serwer może zignorować Range i odpowiedzieć, jakby to było zwykłe GET — czyli 200 OK i pełne ciało odpowiedzi. Dla klientów oznacza to: prawda znajduje się w odpowiedzi, nie w intencji twojego żądania.[Quelle]

Klient Resume, który myli „Range wysłany” z „przychodzi część zawartości”, popełnia dokładnie ten błąd, który widziałeś na początku sceny: pełne pobranie zostaje dopisane do już częściowo załadowanego pliku. Efekt: plik ma pozornie odpowiedni rozmiar, ale jest logicznie uszkodzony.

Sygnał sukcesu: 206 plus wiarygodny Content-Range

Gdy Resume rzeczywiście działa, zazwyczaj widzisz:

  • Status 206 Partial Content zamiast 200 OK
  • Content-Range z jednostką zakresu bytes i oczekiwanym startowym offsetem
  • spójne walidatory (np. ETag), jeśli używasz ich do weryfikacji wersji

Te kontrole nie są „miłe do posiadania”. RFC 9110 wyraźnie stwierdza, że odbiorca nie może składać zapisanej reprezentacji z odpowiedzi 206 zawierającej nieznaną jednostkę zakresu. Konsekwencja praktyczna: bez poprawnie zrozumianego Content-Range nie wolno dopisywać danych.[Quelle]

If-Range und ETag: Der saubere Weg gegen Versionsmix

Główny problem przy wznawianiu pobierania nie leży w samej technice dopisywania, lecz w pytaniu: czy pobierasz nadal tę samą zasób? HTTP dostarcza do tego walidatory. ETag (Entity Tag) jest najprecyzyjniejszym narzędziem: identyfikatorem po stronie serwera dla konkretnej reprezentacji. Last-Modified to znacznik czasu, często wystarczający, ale mniej ostry (rozdzielczość czasu, repakowanie, różne pośrednie stany przez CDN).

Mechanizm „Range tylko wtedy, gdy to rzeczywiście ta sama wersja” nazywa się If-Range: wysyłasz Range razem z warunkiem. Jeśli walidator pasuje, serwer zwraca żądany zakres; jeśli nie, serwer powinien zwrócić całą zasób. Dla ETagów zasada brzmi: warunek jest spełniony tylko przy starkem ETag‑Match (strong comparison). To nie jest kwestia interpretacji — tak jest określone w standardzie.[Quelle]

W praktyce operacyjnej pozostaje fakt: jeśli serwer nie obsługuje Range, musi zignorować If-Range. Brzmi akademicko, ale to bezpośrednie wymaganie wobec twojego klienta: musisz spodziewać się także 200 OK i na tej podstawie podjąć twardą decyzję (zacząć od nowa, a nie dopisywać).[Quelle]

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

Resume oznacza: zarządzasz stanem lokalnym. Ten stan musi pozostać stabilny także po awarii, restarcie, równoległym uruchomieniu lub akcji użytkownika („ponowne kliknięcie”). Czysta strategia pliku częściowego składa się z trzech artefaktów:

  • Plik częściowy jako jedyne miejsce zapisu podczas pobierania (np. foo.zip.part)
  • Metadane przechowywane oddzielnie (np. foo.zip.part.meta.json): URL, znacznik czasu, ETag, Last-Modified, oczekiwana długość (jeśli znana), ewentualnie Content-Type, oraz lokalna długość przy ostatnim poprawnym Flush
  • Atomowe zakończenie: dopiero po pełnym pobraniu i kontroli spójności plik jest zmieniany na nazwę docelową/zastępowany

Po co metadane? Bo inaczej przy następnym uruchomieniu będziesz znać tylko długość pliku – ale nie będziesz wiedzieć, czy odpowiada ona tej konkretnej wersji serwera. To właśnie ta informacja decyduje, czy możesz kontynuować.

HttpClient Download mit Resume: logika decyzyjna, którą możesz przetestować

System.Net.HttpClient dostarcza Requests, Responses i Streams. Odporność wynika z twojej matrycy decyzyjnej. Jeśli ją jasno sformułujesz, możesz ją też dobrze przetestować (włącznie z „Server ignoriert Range“, „Validator mismatch“, „416“).

Minimalny przebieg w 6 krokach

  1. Ustalenie stanu lokalnego: Czy istnieje .part? Odczytać długość jako offset. Załadować metadane.
  2. Ocena możliwości wznowienia: Próbować wznowić tylko, jeśli Offset > 0 i w metadanych znajduje się co najmniej jeden użyteczny walidator (preferowany ETag).
  3. Zbudować żądanie: Ustawić Range zaczynając od offsetu. Dodatkowo ustawić If-Range (ETag, w przeciwnym razie Last-Modified).
  4. Skalsyfikować odpowiedź:
    • 206: sparsować Content-Range, sprawdzić Range-Unit bytes, startowy offset musi zgadzać się dokładnie. Tylko wtedy dopisać.
    • 200: nigdy nie dopisywać. Stan częściowy odrzucić lub poddać kwarantannie, pobrać ponownie.
    • 416 Range Not Satisfiable: offset już nie pasuje (lokalnie lub po stronie serwera). Zresetować stan i rozpocząć od nowa.
    • inne: ścieżka błędu z jasną diagnozą (logować kod statusu + nagłówki).
  5. Zapisywanie strumienia: w blokach, regularnie flushować, aktualizować metadane (np. „ostatni Flush przy długości X”).
  6. Zakończenie: kontrola rozmiaru/spójności (jeśli to możliwe). Następnie atomowo zmienić nazwę/zastąpić.

RangeHeaderValue: nie drobny szczegół, lecz zapobieganie błędom

.NET udostępnia dla nagłówka Range typowaną klasę RangeHeaderValue. To nie tylko „ładniejszy kod”: redukuje syntaktyczne błędy w nagłówkach i sprawia, że logi są bardziej zrozumiałe, ponieważ generujesz spójne wartości. Microsoft dokumentuje też istotną z operacyjnego punktu widzenia granicę: serwery nie muszą obsługiwać żądań Range; Accept-Ranges jest wskazówką, nie gwarancją.[Quelle]

Przykład skonstruowany: gdy „200 OK” psuje twoje wznowienie

Typowe pułapki i jak je rozpoznasz w logu

Accept-Ranges: przydatny sygnał, ale brak gwarancji

Wiele zespołów używa nagłówka Accept-Ranges jako wskazówki „serwer obsługuje Resume”. MDN opisuje ten nagłówek dokładnie tak jako wskaźnik wsparcia („wszystko poza none”). To jest praktyczne dla diagnostyki i telemetrii. Mimo to: nigdy nie decyduj wyłącznie na jego podstawie. Proxy, bramka uwierzytelniająca lub CDN mogą zmienić zachowanie. Twoja logika musi więc zawsze opierać się na kodzie statusu (206 vs. 200) i na Content-Range, nie na nikłym światełku nadziei w Accept-Ranges.[Quelle]

Weak ETags (W/…) i transformacje

Słabe ETagi (W/"…") pojawiają się częściej, niż byśmy chcieli. Dla If-Range decydujące jest jednak pełne (silne) dopasowanie ETag. W praktyce oznacza to: jeśli otrzymujesz tylko słaby ETag, wznawianie jest mniej niezawodne. Możesz go dalej używać, ale powinieneś być bardziej konserwatywny (np. restartować przy najmniejszych niezgodnościach) i dla szczególnie krytycznych plików dodatkowo stosować poza‑kanałową kontrolę integralności (hash z manifestu/API), jeśli jest dostępna.

Nieznana całkowita długość (chunked) i brak plausybilności

Gdy nie ma Content-Length (np. transfer chunked), tracisz prosty test zakończenia „lokalna długość == oczekiwana długość”. Wznawianie może mimo to działać, ale potrzebujesz solidnych walidatorów i dobrego logowania. Bez tych podpór debugowanie staje się mozolne, bo nawet nie możesz powiedzieć, czy brakuje Ci bajtów, czy jest ich za dużo.

Paralelizacja: dopiero, gdy podstawa jest stabilna

Pobieranie kilku zakresów równolegle jest możliwe, ale mnoży problemy ze stanem: zarządzanie segmentami, zapis losowy (random-write), scalanie, logowanie błędów, ponawianie dla każdego segmentu. Dla wielu rozwiązań bliskich procesowi to przesada. Stabilny zysk często uzyskasz już dzięki: seryjnemu pobieraniu + Resume + jasnej macierzy decyzji. Równoległość ma sens dopiero wtedy, gdy udowodnisz stabilne wsparcie Range i konkretne pomiary pokażą, że naprawdę utknąłeś na limicie sieciowym lub serwera.

Debugowanie, które w 5 minut daje jasność

Błędy wznowienia rzadko są „tylko złym if-em”. Zazwyczaj to kombinacja infrastruktury (Proxy/CDN), konfiguracji serwera, timeoutów i lokalnego stanu. Dlatego opłaca się strukturalne logowanie dla każdej próby. Nie jako tekstowy chaos, lecz jako spójne pola:

  • Request: URL (bez sekretów), metoda, Range (start/koniec), If-Range (ETag lub data).
  • Response: kod statusu, Accept-Ranges, ETag, Last-Modified, Content-Length, Content-Range (surowy i sparsowany).
  • Lokalny stan: ścieżka partu, długość partu przed żądaniem, tryb zapisu (Append/Overwrite), długość po zapisie.
  • Decyzja: „Wznowienie zaakceptowane“, „Wznowienie odrzucone (200) – RESTart“, „416 – reset“, „Niezgodność walidatora – RESTart“.

Dysponując dokładnie tymi danymi możesz testować hipotezy: „Czy serwer ignoruje Range?“, „Czy ETag zmienia się między requestami?“, „Czy offset został błędnie ustalony?“, „Czy pisze drugi proces?“ – bez konieczności wyciągania Wiresharka.

Kiedy celowo nie wdrażać wznowienia

Najczystsza decyzja architektoniczna bywa czasem rezygnacja z wznowienia – nie z wygody, lecz dlatego, że w danym scenariuszu zwiększa się powierzchnia błędów bardziej niż korzyść:

  • URL bez powiązania z wersją: Jeśli pod tą samą URL regularnie pojawiają się nowe treści, wznowienie bez silnego walidatora stanowi ryzyko integralności.
  • Brak użytecznych walidatorów: Jeżeli ani ETag, ani Last-Modified nie są spójne, „wznawianie” to zakład. Przy krytycznych importach często lepszy jest kontrolowany RESTart.
  • Pobranie małe i częste: Tutaj strategie retry (timeout, backoff, ew. mirror-URL) są bardziej utrzymywalne i łatwiejsze do wyjaśnienia w eksploatacji.

Pragmatyczne zabezpieczenia, które się opłacają

Kwarantanna zamiast usuwania

Gdy próba wznowienia się nie powiedzie, nie usuwaj .part odruchowo. Zmień nazwę (np. .part.bad + znacznik czasu). To uwidacznia problemy serwera/proxy, ponieważ później możesz skonfrontować nagłówki odpowiedzi z zawartością pliku. I zapobiega sytuacji, w której rzadki problem znika z analizy przez „sprzątanie”.

Blokada przed równoczesnymi startami

Jeżeli dwa procesy jednocześnie zapisują tę samą .part, wszystkie HTTP-walidatory tracą sens. Ekskluzywny lock (blokada pliku lub systemowy mutex) to nie luksus, lecz podstawowa ochrona. Metadane mogą dodatkowo zawierać informację o właścicielu (ID instancji), dzięki czemu łatwo zidentyfikujesz i posprzątasz „sierotne” pobrania.

Krótkie zasady, których naprawdę potrzebujesz

  • Dopisuj tylko przy 206 – i tylko z zweryfikowanym Content-Range (jednostka bytes, offset początkowy pasuje).
  • 200 oznacza: rozpocząć od nowa (overwrite), nigdy nie dopisywać do pliku częściowego.
  • If-Range redukuje mieszanie wersji, ale nie zastępuje obowiązku oceny statusu/Content-Range.
  • 416 to sygnał resetu: lokalny offset już nie pasuje, stan odrzucić/umieścić w kwarantannie.

Wniosek: najpierw integralność, potem optymalizacja

Pobieranie przez HttpClient z wznawianiem jest dobre wtedy, gdy jest nudne: Ustawia nagłówek Range tylko, gdy lokalny stan jest miarodajny, wznawia pobieranie tylko przy odpowiedzi 206 i po zweryfikowaniu Content-Range, oraz zapobiega mieszaniu wersji za pomocą If-Range i walidatorów. Wszystko inne to funkcja „wygląda na szybkie”, która później pochłonie twój czas, ponieważ błędy zdarzają się rzadko i są trudne do zreprodukowania.

Jeśli chcesz dla updaterów, ścieżek importu lub serwisów solidnie skonfigurować niezawodną pipeline pobierania (Resume, Logging, strategia plików częściowych, warunki eksploatacyjne), często można to wyjaśnić w krótkim przeglądzie architektury – włącznie z pytaniem, czy taka złożoność w twoim środowisku w ogóle ma sens. Skontaktuj się i omów przedsięwzięcie z Net-Base.

Źródła i dalsze informacje

Główne tezy merytoryczne zostały redakcyjnie skontekstualizowane na podstawie następujących zewnętrznych źródeł.

  1. RFC 9110: HTTP Semantics (www.rfc-editor.org)
    Żądania Range w HTTP są opcjonalne; serwery mogą zignorować nagłówek Range i odpowiedzieć jak przy zwykłym GET, więc klient musi analizować kod statusu i nagłówki zamiast ślepo dołączać.
  2. HTTP range requests – HTTP | MDN (developer.mozilla.org)
    Accept-Ranges jest sygnałem obsługi Range, ale klienci muszą rzetelnie sprawdzać zachowanie odpowiedzi (206/Content-Range).
  3. RangeHeaderValue Class (System.Net.Http.Headers) | Microsoft Learn (learn.microsoft.com)
    .NET dostarcza za pomocą RangeHeaderValue typowaną reprezentację nagłówka Range; serwery nie muszą obsługiwać żądań Range.

Omów projekt lub przedsięwzięcie modernizacyjne z Net-Base.

Następny krok

Jeżeli temat stanie się rzeczywistym projektem, architektura, stan istniejący i eksploatacja powinny być rozpatrywane razem na wczesnym etapie.

Wspieramy nie tylko w pojedynczych zagadnieniach, lecz także wtedy, gdy z fragmentów kodu źródłowego, kwestii związanych z systemami legacy lub koncepcji portalu ma powstać solidny projekt dla przedsiębiorstwa.

  • Stan istniejący, obraz docelowy i ryzyka techniczne są oceniane łącznie.
  • REST, dostęp do danych, portale i Rollout nie będą przesuwane na później.
  • Wcześnie widzą Państwo, która droga jest ekonomicznie i operacyjnie wykonalna.

Udostępnij wpis

Udostępnij ten wpis bezpośrednio

LinkedIn, X, XING, Facebook, WhatsApp i e-mail są natychmiast dostępne. Dla Instagrama przygotowujemy bezpośrednio link i krótki tekst.

E-mail

Instagram otwiera się w nowej karcie. Link i krótki tekst są wcześniej kopiowane do schowka.

Napisz komentarz

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