Net-Base Revista

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.

Legado. Refactorización. Migración.

Modernizar software consolidado sin perder la sustancia funcional del sistema.

Legado Delphi Refactorización Puesta en producción

Mantener la lógica de negocio

La valiosa lógica de negocio permanece visible y no queda enterrada bajo una reescritura completa.

Desacoplar la base de código

Separar responsabilidades para que nuevas interfaces y nuevos servicios puedan acoplarse de forma controlada.

Hacer la migración planificable

Entregar, probar e implementar de forma incremental en lugar de poner en riesgo el entorno de producción con un corte abrupto.

24.10.2026

Del tema de la revista a la práctica del proyecto

Páginas de servicios y técnicas relacionadas

La escena es desagradable, pero típica: un trabajo nocturno descarga un gran archivo, la barra de progreso está en 93 %. Entonces un breve tirón del VPN, timeout, cancelación. Por la mañana hay un archivo en el directorio de destino, tamaño „parece correcto“, pero al descomprimir aparece solo „unexpected end of file“. Quien luego, por reflejo, añade la función „reanudar“, pronto se encuentra en el siguiente caso límite: la descarga se completa, pero está compuesta por dos versiones distintas.

Precisamente para eso sirve un HttpClient Download con reanudación — pero solo si lo tratas como una pequeña canalización de datos: con reglas claras sobre cuándo puedes anexar, cómo verificas la fidelidad de la versión (ETag/If-Range) y cómo gestionas los estados locales intermedios (archivos parciales) para que la operación y la depuración no se conviertan en un juego de adivinanzas.

El conflicto objetivo es claro: ahorrar ancho de banda y tiempo (continuar la descarga en lugar de reiniciar) se contrapone a integridad y mantenibilidad (no mezclar versiones en silencio, decisiones trazables, un patrón de fallo reproducible). Este artículo está estructurado como una carta de ponderación: beneficios, esfuerzo, riesgos, límites — y una línea pragmática sobre cuándo implementar realmente la reanudación y cuándo es mejor un buen reintento.

Carta de ponderación: dos objetivos, un compromiso duro

La reanudación no merece la pena por ser „moderna“, sino porque tu entorno la exige: archivos grandes, enlaces inestables, ventanas de tiempo en procesos por lotes, usuarios que interrumpen descargas, o servicios que deben continuar tras un reinicio donde lo dejaron. Al mismo tiempo, la reanudación es el punto donde se puede introducir corrupción de datos sin darse cuenta — y eso suele detectarse mucho más tarde (el import falla, la firma es inválida, el instalador no se ejecuta).

Situación Beneficio de reanudación Riesgo típico Decisión plausible
Archivos grandes sobre WAN/VPN o redes móviles alto (tiempo, ancho de banda) Se adjunta a ciegas „200 en lugar de 206“ Reanudación sí, pero defensiva (Status/Content-Range/Validator)
URLs „latest“ / artefactos sin vinculación de versión medio Mezcla de versiones tras una actualización intermedia Reanudación solo con un validador robusto, si no, reinicio
Almacenamiento interno con ETags estables alto bajo, si los encabezados son consistentes Reanudación muy adecuada, persistir metadatos
Archivos pequeños (unas pocas decenas de MB) bajo la complejidad pesa más Descarga completa + reintentos/con backoff suele ser mejor
Proxies/CDN cambiantes, situación de encabezados incierta incierto validadores inconsistentes, depuración difícil Solo con buen logging; de lo contrario, conscientemente conservador

Por qué Resume es más que «establecer Range»

HTTP conoce las solicitudes de rango de bytes a través del Range-Header. La frase importante, a menudo pasada por alto: el soporte de Range es opcional. Un servidor puede ignorar Range y responder como si fuera un GET normal — es decir, con 200 OK y el cuerpo completo. Para los clientes esto significa: la verdad está en la respuesta, no en la intención de tu petición.[Quelle]

Un cliente de Resume que confunde «Range enviado» con «llega contenido parcial» comete exactamente el error que viste al principio de la escena: una descarga completa se concatena a un archivo ya parcialmente cargado. Resultado: el archivo parece tener el tamaño correcto, pero está lógicamente corrupto.

Señal de éxito: 206 más un Content-Range plausible

Cuando Resume realmente funciona, típicamente verás:

  • Estado 206 Partial Content en lugar de 200 OK
  • Content-Range con Range-Unit bytes y el offset de inicio esperado
  • Validadores consistentes (p. ej., ETag), si los utilizas para comprobar que se trata de la misma versión

Estas comprobaciones no son „nice to have“. RFC 9110 establece explícitamente que los receptores no deben combinar una respuesta 206 con una unidad de Range desconocida con una representación almacenada. Consecuencia práctica: sin un Content-Range correctamente interpretado no debe realizarse el append.[Quelle]

If-Range und ETag: la vía limpia contra la mezcla de versiones

El problema central al reanudar no es la técnica de anexado, sino la pregunta: ¿sigues descargando el mismo recurso? HTTP proporciona validadores para eso. ETag (Entity Tag) es la palanca más precisa: un identificador del lado del servidor para una representación concreta. Last-Modified es una fecha, a menudo útil, pero menos precisa (resolución temporal, reempaquetado, distintas etapas intermedias a través de CDNs).

El mecanismo para «Range solo si realmente es la misma versión» se llama If-Range: envías Range más una condición. Si el validador coincide, se sirve el rango; si no, el servidor debe enviar el recurso completo. Para los ETag aplica: la condición se cumple solo con una coincidencia fuerte de ETag (comparación fuerte). Esto no es cuestión de interpretación, sino lo que prevé el estándar.[Quelle]

La realidad operativa sigue siendo: si el servidor no admite Range, debe ignorar If-Range. Suena académico, pero es un requisito directo para tu cliente: debes contemplar también un 200 OK y derivar de ello una decisión firme (reiniciar, no anexar).[Quelle]

Estrategia de archivo parcial: el análogo local a la semántica HTTP

Resume significa: gestionas un estado local. Y ese estado debe permanecer estable incluso tras un fallo, reinicio, inicio paralelo o acción del usuario („hacer clic de nuevo“). Una estrategia limpia de archivo parcial consta de tres artefactos:

  • Archivo parcial como único lugar de escritura durante la descarga (p. ej. foo.zip.part)
  • Metadatos almacenados por separado (p. ej. foo.zip.part.meta.json): URL, marca temporal, ETag, Last-Modified, longitud esperada (si se conoce), en su caso Content-Type, así como la longitud local en el último flush limpio
  • Finalización atómica: solo después de la descarga completa y de una verificación de plausibilidad se renombra/reemplaza por el nombre final

¿Por qué metadatos? Porque, de lo contrario, en el siguiente inicio solo conocerás la longitud del archivo, pero no si pertenece exactamente a esa versión del servidor. Precisamente esa información determina si puedes reanudar.

Descarga con HttpClient y reanudación: lógica de decisión que puedes probar

System.Net.HttpClient te proporciona Requests, Responses y Streams. La robustez proviene de tu matriz de decisiones. Si la formulas de forma clara, también podrás probarla correctamente (incluyendo „Server ignoriert Range“, „Validator mismatch“, „416“).

Proceso mínimo en 6 pasos

  1. Determinar el estado local: ¿Existe .part? Leer la longitud como offset. Cargar los metadatos.
  2. Decidir si es posible reanudar: Intentar reanudar solo si el offset > 0 y al menos un validador útil está presente en los metadatos (preferir ETag).
  3. Construir la solicitud: Establecer Range desde el offset. Además establecer If-Range (ETag, si no, Last-Modified).
  4. Clasificar la respuesta:
    • 206: parsear Content-Range, comprobar que la Range-Unit sea bytes, el Start-Offset debe coincidir exactamente. Solo entonces anexar.
    • 200: nunca anexar. Desechar o poner en cuarentena el estado parcial, volver a descargar.
    • 416 Range Not Satisfiable: el offset ya no concuerda (localmente o en el servidor). RESTablecer el estado y reiniciar.
    • en otro caso: ruta de error con diagnóstico claro (registrar código de estado y cabeceras).
  5. Escribir el stream: en bloques, hacer flush regularmente, actualizar metadatos (p. ej. «último flush en longitud X»).
  6. Finalización: comprobación de tamaño/plausibilidad (si es posible). Luego renombrar/reemplazar de forma atómica.

RangeHeaderValue: no es un detalle, sino prevención de errores

.NET ofrece para el encabezado Range la clase tipada RangeHeaderValue. No es solo „código más bonito“: reduce errores sintácticos en los encabezados y hace que los logs sean más interpretables, porque genera valores consistentes. Microsoft también documenta la arista operativa relevante: los servidores no están obligados a soportar solicitudes Range; Accept-Ranges es una indicación, no una garantía.[Quelle]

Ejemplo construido: cuando «200 OK» rompe tu reanudación

Errores típicos y cómo reconoces su rastro en el log

Accept-Ranges: señal útil, pero no garantía

Muchos equipos usan Accept-Ranges como indicador de «el servidor soporta Resume». MDN describe el header precisamente así como indicador de soporte («todo menos none»). Eso es práctico para diagnóstico y telemetría. Aun así: nunca decidas únicamente en base a eso. Un proxy, una pasarela de autenticación o un CDN pueden cambiar el comportamiento. Por eso tu lógica debe basarse siempre en el código de estado (206 vs. 200) y en el Content-Range, no en una esperanza reflejada en Accept-Ranges.[Quelle]

ETags débiles (W/…) y transformaciones

Los ETags débiles (W/"…") aparecen con más frecuencia de la deseada. Para If-Range es decisivo un match de ETag fuerte. En la práctica eso significa: si solo recibes un ETag débil, la reanudación es menos fiable. Puedes seguir usándola, pero debes ser más conservador (p. ej. reiniciar ante las menores discrepancias) y, para archivos especialmente críticos, añadir una comprobación de integridad out-of-band (hash desde un manifiesto/API) si está disponible.

Longitud total desconocida (chunked) y falta de plausibilidad

Si no llega Content-Length (p. ej. transferencias chunked), pierdes la comprobación simple de finalización «longitud local == longitud esperada». Resume aún puede funcionar, pero necesitas validadores robustos y logging de calidad. Sin esos soportes, la investigación de fallos se vuelve difícil, porque ni siquiera puedes decir si faltan bytes o si hay bytes de más.

Paralelización: solo cuando la base esté asentada

Cargar varios rangos en paralelo es posible, pero multiplica los problemas de estado: gestión de segmentos, escrituras aleatorias, fusión, registro de errores, reintentos por segmento. Para muchas soluciones de software próximas al proceso es un exceso. La mejora estable suele venir ya con: descarga serial + Resume + una matriz de decisiones clara. La paralelización merece la pena solo cuando has comprobado soporte de Range estable y los datos de medida muestran que realmente estás limitado por la red o por el servidor.

Depuración que en 5 minutos aporta claridad

Los errores de reanudación rara vez son „solo un if incorrecto“. Por lo general se trata de una combinación de infraestructura (Proxy/CDN), configuración del servidor, tiempos de espera y estado local. Por eso merece la pena un registro estructurado por intento. No como un páramo de texto, sino como campos consistentes:

  • Request: URL (sin secretos), método, Range (inicio/fin), If-Range (ETag o fecha).
  • Response: código de estado, Accept-Ranges, ETag, Last-Modified, Content-Length, Content-Range (crudo y parseado).
  • Lokaler Zustand: ruta del .part, longitud de la parte antes de la solicitud, modo de escritura (Append/Overwrite), longitud después de la escritura.
  • Entscheidung: «Reanudación aceptada», «Reanudación rechazada (200) – reiniciar», «416 – reset», «Validador no coincide – reiniciar».

Con exactamente esos datos puedes probar hipótesis: «¿Ignora el servidor el Range?», «¿Cambia el ETag entre solicitudes?», «¿Se determina mal el offset?», «¿Está escribiendo un segundo proceso?» – sin tener que sacar Wireshark primero.

Cuándo no implementar la reanudación de forma deliberada

La decisión de arquitectura más limpia a veces es omitir la reanudación —no por comodidad, sino porque en tu escenario introduce más superficie de fallo que beneficio:

  • URL sin vinculación de versión: Si tras la misma URL hay contenidos nuevos con regularidad, reanudar sin un validador sólido es un riesgo de integridad.
  • Sin validadores útiles: Si ni ETag ni Last-Modified son consistentes, „reanudar“ es una apuesta. En importaciones críticas suele ser peor que un reinicio controlado.
  • Descarga pequeña y frecuente: Aquí las estrategias de retry (timeout, backoff, en su caso mirror-URL) son más mantenibles y más fáciles de justificar en operación.

Medidas pragmáticas de protección que compensan

Cuarentena en lugar de borrar

Si un intento de reanudación falla, no borres reflexivamente .part. Renómbralo (p. ej. .part.bad + sello temporal). Eso hace visibles problemas de servidor/proxy, porque después puedes confrontar headers de respuesta y el estado del archivo. Y evita que un problema poco frecuente desaparezca de inmediato del análisis por una „limpieza“.

Bloqueo contra arranques duplicados

Si dos procesos escriben simultáneamente la misma .part, todos los validadores HTTP quedan inútiles. Un lock exclusivo (bloqueo de archivo o mutex a nivel de sistema) no es un lujo, sino protección básica. Los metadatos pueden llevar además información del propietario (ID de instancia), para que puedas detectar y limpiar descargas „huérfanas“ de forma ordenada.

Reglas de referencia rápida que realmente necesitas

  • Añadir (Append) solo con 206 – y únicamente con Content-Range verificado (unidad bytes, offset de inicio coincide).
  • 200 significa: reiniciar (overwrite), nunca anexar a un archivo parcial.
  • If-Range reduce la mezcla de versiones, pero no sustituye la obligación de evaluar el Status/Content-Range.
  • 416 es una señal de reset: el offset local ya no coincide, descartar/poner en cuarentena el estado.

Conclusión: primero integridad, luego optimización

Una descarga HttpClient con reanudación es buena cuando es aburrida: solo establece Range cuando el estado local es fiable, solo se engancha ante 206 más un verificado Content-Range, y evita mezclas de versiones mediante If-Range y validadores. Todo lo demás es una característica de «parece rápida» que te consumirá tiempo más adelante, porque los errores son raros y entonces apenas reproducibles.

Si quieres montar una canalización de descargas robusta (reanudación, registro, estrategia de archivos parciales, aspectos operativos) para actualizadores, procesos de importación o servicios, esto suele aclararse en una breve comprobación de arquitectura —incluida la cuestión de si la complejidad merece la pena en tu entorno. Ponte en contacto y discute el proyecto con Net-Base.

Fuentes e información adicional

Las afirmaciones técnicas clave se han contextualizado editorialmente a partir de las siguientes fuentes externas.

  1. RFC 9110: HTTP Semantics (www.rfc-editor.org)
    Las solicitudes Range de HTTP son opcionales; los servidores pueden ignorar Range y responder como en un GET normal, por lo que el cliente debe evaluar el código de estado y los encabezados en lugar de anexar Range a ciegas.
  2. HTTP range requests – HTTP | MDN (developer.mozilla.org)
    Accept-Ranges es una señal de soporte de Range, pero los clientes deben comprobar de forma robusta el comportamiento de la respuesta (206/Content-Range).
  3. RangeHeaderValue Class (System.Net.Http.Headers) | Microsoft Learn (learn.microsoft.com)
    .NET proporciona con RangeHeaderValue una representación tipada del encabezado Range; los servidores no están obligados a soportar solicitudes Range.

Discutir un proyecto o una iniciativa de modernización con Net-Base.

siguiente paso

Cuando un tema se convierte en un proyecto real, arquitectura, entorno existente y operación deben considerarse conjuntamente desde el inicio.

No solo apoyamos en consultas puntuales, sino también cuando, a partir de fragmentos de código fuente, temas heredados o ideas de portales, debe consolidarse un proyecto empresarial robusto.

  • La situación actual, el estado objetivo y los riesgos técnicos se evalúan conjuntamente.
  • REST, el acceso a datos, los portales y el despliegue no se relegan a fases posteriores.
  • Usted detecta con antelación qué camino es viable, tanto económica como operativamente.

Compartir entrada

Compartir esta publicación directamente

LinkedIn, X, XING, Facebook, WhatsApp y correo electrónico están disponibles de inmediato. Para Instagram preparamos el enlace y el texto breve directamente.

Correo electrónico

Instagram se abre en una nueva pestaña. El enlace y el texto breve se copian previamente en el portapapeles.

Escribir un comentario

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