Con lo que sabemos hasta ahora, es fácil hacer un programa que descargue un recurso de internet, sin embargo, no sabemos como
trocear nuestro recurso para ir descargando por separado cada una de las partes. Para ello tenemos que utilizar una nueva característica de HTTP, introducida en la versión 1.1 del protocolo.
Esta característica consiste en informar al servidor de que no queremos descargar el recurso completo, sino un rango de byte de él. De este modo, conseguimos ir descargando el recurso
por rangos. Por ejemplo, imaginemos un archivo que ocupa 2 KB. (2048 bytes). Podemos dividirlo en los siguientes 4 segmentos:
- Segmento 1: desde el byte 0 al 511 (ambos inclusive).
- Segmento 2: desde el byte 512 al 1023 (ambos inclusive)
- Segmento 3: desde el byte 1024 al 1535 (ambos inclusive)
- Segmento 4: desde el byte 1536 al 2047 (ambos inclusive)
Como vemos, hemos dividido el tamaño total en cuatro partes iguales, de 512 bytes cada una de ellas, marcando las fronteras de bytes entre unas y otras partes. El segmento final no llega hasta el byte 2048, sino uno menos, ya que como empezamos a contar en el byte 0, el último byte será el tamaño menos 1 (n-1).
El modo de informar al servidor del rango que queremos descargar, es a través de una cabecera adicional al comando GET, que como sabemos, podemos enviar a través de la función HttpAddRequestHeaders.
Esta petición, con la cabecera, tiene el siguiente aspecto:
|| GET /recurso.zip HTTP/1.1 ||
|| Accept: text/html ||
|| Range: bytes=512-1023 ||
|| ||
Enviando esta cabecera, indicamos al servidor que queremos descargar los bytes comprendidos entre el 512 y el 1023.
Adicionalmente, podemos indicar que queremos descargar los bytes desde una posición determinada, hasta el final del recurso, utilizando una cabecera como la siguiente:
|| GET /recurso.zip HTTP/1.1 ||
|| Accept: text/html ||
|| Range: bytes=1536- ||
|| ||
De este modo, indicamos que descargaremos desde el byte 1536, hasta el final del recurso. Esta misma sintaxis, nos permite definir un único rango, para descargar el recurso por completo, como hemos hecho hasta ahora:
|| GET /recurso.zip HTTP/1.1 ||
|| Accept: text/html ||
|| Range: bytes=0- ||
|| ||
Esto indicará que se descargue desde el byte 0 (el primero) hasta el final del recurso, o dicho de otro modo: se descargará el recurso completo, en un único segmento.
Si nos fijamos en la línea de la petición GET de todos estos ejemplos, veremos que la versión que se utiliza del protocolo es la 1.1. Esto es debido a que, como ya he dicho, los rangos de descarga sólo se soportan a partir de esta versión. En el mundo WinInet, si utilizamos el método directo (con la función InternetOpenUrl), no será necesario indicar la versión del protocolo. Sin embargo, si utilizamos el método detallado, os recuerdo que indicamos la versión a utilizar del protocolo a través del parámetro lpszVersion de la función HttpOpenRequest. Si olvidamos indicar la versión 1.1, e intentamos utilizar este tipo de cabecera, recibiremos un error de tipo ERROR_HTTP_HEADER_NOT_FOUND.
Y ahora que sabemos cómo se forma la cabecera que definen nuestros rangos, lo único que tenemos que hacer es añadir esta cabecera a nuestra petición, utilizando los mecanismos que nos ofrece WinInet para ello:
- Con el parámetro "lpszCabeceras" de la función InternetOpenUrl: consiste en enviar las cabeceras adicionales a través de la función utilizada en el método directo: InternetOpenUrl. No es necesario indicar la versión del protoco, ya que se seleccionará automaticamente.
{
se abre la instancia de internet
HINTERNET hInet = ::InternetOpen(...);
almacenamos en variables los límites del rango
DWORD byteIni = 512;
DWORD byteFin = 1023;
se compone la cabecera con su sintaxis
char* cabecera;
wsprintf(cabecera, "Range: bytes=%d-%d", byteIni, byteFin);
se abre la URL, enviando también las cabeceras adicionales
HINTERNET hReq = ::InternetOpenUrl(hInet,
"http://www.servidor.com/recurso.zip",
cabecera, strlen(cabecera),
INTERNET_FLAG_RELOAD, );
... lectura del recurso con InternetReadFile
y cierre de descriptores
}
- Con el parámetro "lpszCabeceras" de la función HttpSendRequest: utilizando el método detallado, podemos enviar cabeceras adicionales de la petición a través del segundo parámetro en la función HttpSendRequest. En este caso, debemos indicar la versión del protocolo (1.1) en la llamada a la función HttpOpenRequest:
{
se abre la instancia de internet
HINTERNET hInet = ::InternetOpen(...);
se conecta al servidor
HINTERNET hConn = ::InternetConnect(hInet, "servidor.com",
INTERNET_DEFAULT_HTTP_PORT, NULL, NULL,
INTERNET_SERVICE_HTTP, , );
se crea la petición GET. Ojo a la versión
HINTERNET hReq = ::HttpOpenRequest(hConn, "GET", "/recurso.zip",
"HTTP/1.1", NULL, NULL, , );
almacenamos en variables los límites del rango
DWORD byteIni = 512;
DWORD byteFin = 1023;
se compone la cabecera con su sintaxis
char* cabecera;
wsprintf(cabecera, "Range: bytes=%d-%d", byteIni, byteFin);
se manda la URL, enviando también las cabeceras adicionales
int len = sizeof(cabecera);
if ( !::HttpSendRequest(hReq, cabecera, len, NULL, 0) ) {
Se ha producido un error.
Si no se admite la cabecera, al llamar al GetLastError
obtendremos el error ERROR_HTTP_HEADER_NOT_FOUND.
}
... lectura del recurso con InternetReadFile
y cierre de descriptores
}
- Con la función HttpAddRequestHeaders: otra opción si utilizamos el método detallado es añadir la cabecera a la petición, utilizando HttpAddRequestHeaders. Esto nos permite, no solo añadir, sino eliminar, cambiar o reemplazar las cabeceras a enviar junto con la petición.
{
se abre la instancia de internet
HINTERNET hInet = ::InternetOpen(...);
se conecta al servidor
HINTERNET hConn = ::InternetConnect(...);
se crea la petición GET. Ojo a la versión
HINTERNET hReq = ::HttpOpenRequest(...);
almacenamos en variables los límites del rango
DWORD byteIni = 512;
DWORD byteFin = 1023;
se compone la cabecera con su sintaxis
char* cabecera;
wsprintf(cabecera, "Range: bytes=%d-%d", byteIni, byteFin);
se añade la nueva cabecera a la petición
if ( !::HttpAddRequestHeaders(hReq, cabecera, sizeof(cabecera),
HTTP_ADDREQ_FLAG_ADD) )
{
Se ha producido un error.
}
se manda la petción
if ( !::HttpSendRequest(hReq, NULL, 0, NULL, 0) ) {
Se ha producido un error.
}
... lectura del recurso con InternetReadFile
y cierre de descriptores
}