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 Contenten lugar de200 OK Content-Rangecon Range-Unitbytesy 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
- Determinar el estado local: ¿Existe
.part? Leer la longitud como offset. Cargar los metadatos. - 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).
- Construir la solicitud: Establecer Range desde el offset. Además establecer
If-Range(ETag, si no, Last-Modified). - Clasificar la respuesta:
206: parsearContent-Range, comprobar que la Range-Unit seabytes, 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).
- Escribir el stream: en bloques, hacer flush regularmente, actualizar metadatos (p. ej. «último flush en longitud X»).
- 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 conContent-Rangeverificado (unidadbytes, offset de inicio coincide). 200significa: reiniciar (overwrite), nunca anexar a un archivo parcial.If-Rangereduce la mezcla de versiones, pero no sustituye la obligación de evaluar el Status/Content-Range.416es 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.
- 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. - 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). - 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.