De qué vamos a hablar
Durante los cuatro últimos artículos hemos ido tratando
un tema muy básico dentro de cualquier arquitectura: la memoria.
Hemos profundizado en aspectos internos, sobre cómo gestionarla
en esta plataforma, así como las funciones más útiles
en cada una de las tareas.
En esta ocasión vamos a "poner los pies en la tierra",
y durante los próximos artículos vamos a tratar un aspecto
con aplicaciones mucho más inmediatas, y no tan teórico
como han podido ser los anteriores artículos: el acceso a recursos
en internet.
No vamos a tratar los conceptos más internos de comunicaciones
en internet, ni de los protocolos de transporte TCP/IP, ni siquiera vamos
a tratar la programación de sockets, sino que nuestro objetivo
se centra en un API muy concreto, desarrollado por Microsoft allá
cuando salió Internet Explorer 3: el WinInet.
Todos estamos viendo como se está desarrollando la industria del
software, y podemos percibir como cada vez toma más importancia
en una aplicación el acceso a internet, la recuperación
de datos y contenidos de la red y la información on-line. Ya pasaron
los tiempos de aplicaciones cliente-servidor normales, de bases de datos
en redes locales, de aplicaciones de escritorio simples, etc. Ahora lo
más "in" son las aplicaciones que están
constantemente conectadas a la red para recuperar información.
Para cumplir este objetivo tenemos muchas alternativas: desde los nuevos
protocolos de intercambio de ficheros peer-to-peer (como Napster,
eDonkey, eMule, etc.), hasta ajustarse a protocolos más estándar
como pueden ser los archiconocidos HTTP y FTP.
Esta serie de artículos que comenzamos se limita a utilizar protocolos
y modos de acceso estándar, a través de un API desarrollado
por Microsoft para estas tareas. Posiblemente, este modo de acceso sea
el más adecuado para la mayoría de nuestras necesidades, en que
sólo necesitamos acceder a contenidos en modo texto o binario.
Para que os hagáis una idea de la importancia de esta tecnología,
Internet Explorer, el componente COM WebBrowser e incluso el propio Explorador
de Windows, utilizan Wininet para acceder a los contenidos en red. De
hecho, los planes de Microsoft en esta línea son muy claros: integrar
los accesos a internet dentro del núcleo de Win32, de modo que
el acceso a un recurso local se diferencie lo más mínimo
del acceso a recursos en red (todo ello, en la plataforma .NET,
por supuesto).
Introducción a WinInet
Básicamente, WinInet es un conjunto de funciones de alto nivel,
para al acceso a contenidos en internet.
Estas funciones, están disponibles dentro de la librería
"wininet.dll", que podréis encontrar en la carpeta de
sistema. Esta librería se distribuye desde Windows 95, o con cualquier
instalación del Internet Explorer a partir de la versión
3.0, así que no tendremos problemas en este aspecto. De todas formas,
ciertas funciones sólo están disponibles a partir de la
versión 4 del explorador de Microsoft, y otras más evolucionadas
a partir de la versión 5.
La principal ventaja de su uso es que se nos ocultan los detalles de
implementación de los distintos protocolos que podemos utilizar.
De este modo, no necesitamos conocer la estructura de peticiones y respuestas
de cada protocolo, sino que será la propia librería la encargada
de convertir nuestras llamadas en peticiones en el lenguaje del protocolo
adecuado.
Además de esto, cuando cambien los detalles internos del protocolo
(por ejemplo si se establece una nueva versión), nuestras aplicaciones
seguirán funcionando, ya que será la librería la
encargada de modificar sus llamadas.
Otra de las ventajas que ofrece Wininet es que nos abstrae sobre el tipo
de conexión que estamos realizando. Es decir, para acceder a los
contenidos actuaremos de una forma similar estemos en una LAN o en internet,
ya sea a través de proxy, conexión por modem o cualquier
otro tipo. Además, podemos aprovecharnos de la configuración
que haya establecido el usuario en las opciones de conexión a internet
(en el Panel de control - Opciones de internet - Conexiones).
Por si fuera poco, con el API Wininet se nos permite acceder a programas
y scripts desarrollados en el lado del servidor, ya sean CGI, ISAPI, ASP,
PHP, J2EE, etc. Por ejemplo, supongamos que ya tenemos desarrolladas una
serie de funciones, en PHP, para la validación de usuarios, y estas
funciones están siendo usadas desde el sistema de login
de nuestra página web. Desde nuestras aplicaciones de escritorio,
podremos beneficiarnos de estos desarrollos, accediendo a través
de Wininet a los scripts que ya están funcionando en el
servidor. En una posible modificación de estos scripts del
servidor, será beneficiado tanto el acceso desde la página
web como desde la aplicación de escritorio.
Además, también nos permite gestionar las cookies
del sistema, añadiendo, eliminando o manipulando nuestras propias
galletas.
Y por último, una característica muy interesante: Wininet
nos permite acceder de forma transparente al caché de datos que
gestiona Internet Explorer. De este modo, si cierta página o archivo
ha sido descargada previamente, desde nuestras aplicaciones podemos beneficiarnos
de este caché, y recuperar los datos del disco duro en vez de acceder
a internet.
Protocolos
Wininet, en su versión actual, soporta el acceso a contenidos
en internet a través de tres protocolos: HTTP, FTP y Gopher.
No vamos a profundizar en los detalles de cada uno de ellos, porque no
es nuestro objetivo, aunque sí vamos a dar una pequeña descripción
de su principal objetivo:
HTTP (Hyper Text Transfer Protocol): a todo el mundo le sonará
este protocolo, el más utilizado de internet. Su principal uso
es el envío y recepción de información en formato
texto, como pueden ser páginas HTML. Además, Wininet soporta
la extensión HTTPS, para conexiones seguras.
FTP (File Transfer Protocol): este protocolo también
es bastante utilizado, y está orientado a la transmisión
de archivos binarios de gran tamaño, como pueden ser ejecutables,
archivos comprimidos, imágenes, etc.
Gopher (Go-for...): Este protocolo actualmente está
en desuso, aunque en los inicios de internet tuvo un papel muy importante.
Su principal función fue la de catalogar y buscar contenidos
a través de la red, almacenando índices con todos los
recursos disponibles. Hoy en día, gracias a motores de búsqueda
basados en HTTP, como Google o Yahoo, este servicio ya no es necesario.
En esta nueva serie de artículos, vamos a profundizar en los dos
primeros protocolos, HTTP y FTP, ya que no creo que el protocolo Gopher
vaya a ser de mucha utilidad para alguno de vosotros (para mi no lo es).
La tecnología Wininet
Wininet nació con la idea de abstraer al programador de la implementación
de los protocolos de aplicación más utilizados en internet.
Gracias a esta idea, podemos acceder a internet sin saber, ni siquiera,
qué es un protocolo o cómo implementarlo. Para ello, se
ha creado como una capa de abstracción sobre el API Winsock.
Winsock es la tecnología utilizada por Win32 para comunicar máquinas
a través de los protocolos de transporte TCP/IP y UPD. Estos protocolos
sólo se encargan del nivel de transporte en el modelo OSI. Por
encima de esto, se encuentran los protocolos de aplicación, que
son los que nos ocupan en nuestro caso: HTTP, FTP, SMTP, NNTP, etc.
Antes de la aparición del API Wininet, era obligatorio utilizar
Winsock para acceder a algún recurso en internet. Eso requería
un entendimiento bastante profundo del protocolo a utilizar, además
de conceptos como socket, puerto, etc. Actualmente, sólo
será necesario utilizar Winsock para hacer uso de protocolos no
soportados por Wininet, como puede ser envío y recepción
de correos electrónicos (protocolos SMTP y POP3), acceso a servidores
de noticias (protocolo NNTP), etc. Tampoco podremos utilizar Wininet cuando
desarrollemos una aplicación que utilice un protocolo no estándar,
como pueden ser las aplicaciones de intercambio de ficheros, mensajería
instantánea, etc.
Parece que la cosa se está liando, así que vamos a zanjar
el tema aquí. Dejaremos los detalles sobre el protocolo TCP/IP
y el API WinSock para otra ocasión. Símplemente nos basta
con tener claro que nos estamos moviendo en niveles muy altos de abstracción
y que los detalles internos quedan ocultos para nosotros, siendo manejados
por el propio API.
En la siguiente figura podemos ver cómo viaja la información
desde nuestro API Wininet hasta el servidor destino.
Nosotros tan sólo vamos a ocuparnos del primer punto, es decir:
qué llamadas debemos hacer desde nuestras aplicaciones al API Wininet.
Funciones generales
Como ya hemos dicho, Microsoft ha definido un grupo de funciones de uso
general, para tratar situaciones comunes a cualquier tipo de conexión,
independientemente del protocolo que utilicemos.
Estas funciones se encargan, entre otras cosas, de establecer la conexión,
manipular el marcado del modem, tratar cadenas de URL, mostrar diálogos
de error, manipular cookies, etc.
Por cierto, se me olvidaba comentar que para utilizar este API nos bastará con hacer el
include del archivo cabecera "wininet.h" y linkar
nuestra aplicación junto con la librería "wininet.lib".
Bueno, como todo esto puede ser bastante extenso, vamos a ir por partes.
InternetCanonicalizeUrl
Esta función nos permite convertir una cadena con una URL a su
equivalente pero con caracteres seguros. Las cadenas URL no pueden contener
cualquier tipo de carácter, y existe un método para buscar
qué caracteres no son seguros y sustituirlos por la secuencia equivalente.
El ejemplo que todos conocemos es el carácter espacio y su secuencia
"%20".
Hay que tener cuidado de no utilizar esta función con una cadena
previamente codificada, porque la función no se realiza ningún
tipo de comprobación y la re-codificará de nuevo.
BOOL InternetCanonicalizeUrl(
LPCSTR lpszUrl,
LPSTR lpszBuffer,
LPDWORD lpdwLongitudBuffer,
DWORD dwOpciones
);
-
lpszUrl: un puntero a una cadena con la URL a codificar.
-
lpszBuffer: un puntero a una cadena donde se almacenará
la URL resultante. Este puntero debe tener memoria previamente reservada.
-
lpdwLongitudBuffer: un puntero a un valor que indica la longitud
de caracteres que se han reservado para lpszBuffer. Cuando
la función retorna, copiará en este valor el número
de caracteres copiados en lpszBuffer si la función se ejecutó
correctamente, o el número de bytes necesitados si la función
retornó error.
dwOpciones: una serie de banderas que configuran el comportamiento
de la función:
- ICU_BROWSER_MODE: indica que se comportará como la codificación
que hace el navegador, es decir, después del carácter
"?" ó "#" no codificará nada.
- ICU_NO_ENCODE: No codifica nada.
- ICU_DECODE: convierte todas las secuencias codificadas (%XX) en
su correspondiente cadena decodificada. Para usar esta bandera debe
hacerse en combinación con ICU_NO_ENCONDE ya que en caso contrario, no
tendrá efecto.
- ICU_ENCODE_PERCENT: Codifica los signos de porcentaje, que por
defecto no son codificados. Sólo está disponible a
partir de IE 5.
- ICU_ENCODE_SPACES_ONLY: Sólo codifica los espacios.
La función retorna TRUE o FALSE dependiendo de su éxito.
Si utilizamos esta función a partir de IE4, la bandera ICU_BROWSER_MODE
se utilizará siempre, por lo que si queremos codificar la cadena
completa, debemos recurrir a otras funciones, como UrlCanonicalize de
la librería shlwapi.dll
InternetCreateUrl
Esta función hace la operación inversa a la anterior, es
decir: nos compone una cadena de URL a partir de sus componentes individuales.
BOOL InternetCreateUrl(
LPURL_COMPONENTS lpComponentesUrl,
DWORD dwOpciones,
LPTSTR lpszUrl,
LPDWORD lpdwLongitudUrl
);
No voy a entrar en más detalles, porque los parámetros
se usan del mismo modo que con InternetCrackUrl, pero de forma inversa.
Sólo hay que decir, que dentro de la estructura URL_COMPONENTS,
se ignorarán aquellos componentes cuyo puntero se haya establecido
a NULL. Hay que ser cuidadoso también con la manipulación
de los punteros a carácter, ya que es muy normal equivocarse.
La función retornará FALSE en caso de error y GetLastError
nos retorna ERROR_INSUFFICIENT_BUFFER si el buffer lpszUrl es demasiado
pequeño, o si hemos cometido algún error manipulando los
punteros de caracteres.
Funciones de estado de la conexión
Uno de los principales problemas a la hora de conectarnos a internet,
suele ser los distintos tipos de configuración y la comprobación
de la conexión. Hasta ahora, podíamos apoyarnos en el API
RAS (Remote Access Service) para consultar la configuración del
módem, aunque a partir de Wininet, se nos ofrece una serie de funciones
para estas tareas.
Antes de empezar, hay que dejar claro que sólo hay una manera
de comprobar al 100% la existencia una conexión: intentando acceder
a un recurso remoto (por ejemplo con un ping), pero esto suele
ser una tarea lenta, en la que tenemos que dejar que se agote un tiempo
de espera (timeout), para asegurarnos de que no hay nadie al otro
lado escuchando nuestras peticiones. Para evitar este tiempo de espera,
existen otras funciones que intentan averiguar el estado de conexión
o la posibilidad de acceso a internet, sin intentar realizar una petición
a la red. Concretamente se considera que un equipo no tiene conexión
a internet cuando no tiene ni tarjeta de red ni módem configurado.
Estas son las posibilidades, según la gente de Microsoft:
IsNetworkAlive / IsDestinationReachable: estas funciones
sólo están disponibles si tenemos instalada cualquier
versión de Internet Explorer a partir de la 5. Nos permiten
comprobar una tipo de conexión o incluso hacer un ping a una
URL determinada para garantizar la conexión. El principal inconveniente
es que estas funciones no permiten la existencia de firewalls
en la conexión, por lo que muchas redes de empresa no permitirían
su uso.
RasEnumConnections: para conexión vía módem,
podemos utilizar esta función del API RAS que nos permite consultar
la configuración de los módems que tengamos instalados
en nuestro sistema.
InternetAttemptConnect: esta función nos permite hacer
un primer intento de conexión a internet. Si el usuario configuró
su sistema como con "auto-marcado" (Panel de control - Opciones
de internet - Conexiones - Marcar siempre la conexión predeterminada),
esta función mostrará la ventana de marcado del módem.
Si este intento falla, se activará la bandera global de conexión
"Trabajar sin conexión".
InternetCheckConnection: según la documentación
oficial, esta función intenta acceder a una URL dada haciendo
un ping. La práctica (y sus propios autores) nos dice
que no funciona, y que debe evitarse su uso (un buen trabajo de Microsoft).
InetIsOffline: esta función está exportada
en la librería URL.DLL y según la documentación,
nos indica que estamos conectados si la conexión está
activa o si todavía no ha habido ningún
intento de conexión. Por si fuera poco, la función nos
informa de que estamos conectados símplemente si hay una tarjeta
de red configurada en el sistema, aunque el cable esté desconectado.
Por todo esto, se desaconseja el uso de esta función.
InternetGetConnectedState: a partir de IE 4 se nos ofrece
esta función que nos permite distinguir configuraciones típicas
como conexión por módem o conexión vía
LAN, aunque no gestiona bien configuraciones complejas como LAN +
router con auto-marcado. Además, esta función es capaz
de retornarnos el valor de la bandera "Trabajar sin conexión".
La bandera "Trabajar sin conexión": también
a partir de IE 4, se gestiona una variable global (almacenada en el
registro) que nos indica si debemos trabajar con o sin conexión
a internet. Esta bandera suele actuar de un modo complementario con
el resto de funciones y su valor puede consultarse o modificarse a
través de la función InternetQueryOption. Para asegurarnos
que pasamos a un estado "Trabajar con conexión",
debemos llamar a la función InternetGoOnline. Esta bandera,
junto con el uso de la función InternetGetConnectedState, parece
ser el método más fiable de comprobar la conexión
(si intentar un acceso a la red), de hecho, es el método que
utiliza Microsoft para sus aplicaciones (IE, Outlook, etc.).
Ahora vamos a describir con más profundidad la sintaxis
de algunas de estas funciones:
IsNetworkAlive
Esta función sólo la tendremos disponible a partir de IE
5, por lo que debemos asegurarnos de su existencia antes de usarla. Para
ellos, lo mejor que podemos hacer es una carga dinámica de la librería
Wininet.dll y de esta función. Para aprender más sobre la
carga de librerías, podéis consultar el artículo
de C++Builder sobre "Librerías de enlace dinámico",
aparecido en el número 4 de la revista Síntesis.
BOOL IsNetworkAlive(
LPDWORD dwTipoConexión
);
Esta función retornará TRUE o FALSE dependiendo si considera
al sistema preparado para establecer una conexión a internet, según
el método indicado en el parámetro. Hay que recordar que
esta función no detectará una conexión correcta detrás
de firewalls corporativos. Los valores permitidos para dwTipoconexión
son:
- NETWORK_ALIVE_LAN: el sistema cuenta con tarjeta de red y conexión
a red local.
- NETWORK_ALIVE_WAN: el sistema cuenta con una conexión remota
activa.
InternetAttemptConnect
Esta función se usa para hacer el primer intento de conexión,
y permitir al sistema que marque el módem si así está
configurado. La función es muy sencilla:
DWORD InternetAttemptConnect(
DWORD dwReserved
);
Como parámetro debemos pasar siempre un 0, y la función
nos retornará el valor ERROR_SUCCESS si todo ha ido bien, o un
código de error en caso contrario.
Hay que tener en cuenta que esta función nos abrirá la
ventana de marcado del módem, si hemos configurado la opción
de configuración del acceso telefónico a redes (en "Panel
de control - Opciones de internet - Conexiones"), como "Marcar
siempre la conexión predeterminada". Si en dicha ventana
se pulsa el botón cancelar o "Trabajar sin conexión",
se activará esta bandera global.
InternetGetConnectedState
Esta función nos consultará el estado de conexión
de nuestro sistema.
BOOL InternetGetConnectedState(
LPDWORD lpdwTipoConexion,
DWORD dwReservado
);
El segundo argumento es un valor de 32 bits que debe valer siempre 0,
y el primer es un puntero a otro valor de 32 bits que contendrá
el tipo de conexión. Los distintos valores combinados que se retornarán,
son:
- INTERNET_CONNECTION_CONFIGURED: indica que hay algún enlace
a internet configurado, pero no es seguro que esté activo en
este momento.
- INTERNET_CONNECTION_LAN: se utiliza una red local para acceder a internet.
- INTERNET_CONNECTION_MODEM: se utiliza un módem para acceder
a internet.
- INTERNET_CONNECTION_OFFLINE: la bandera de "Trabajar sin conexión"
está activada, valor que puede haber adquirido desde cualquier
otra aplicación que acceda a ella.
- INTERNET_CONNECTION_PROXY: se utiliza un proxy para acceder a internet.
- INTERNET_RAS_INSTALLED: El sistema tiene algún sistema de acceso
remoto configurado.
La función retornará TRUE o FALSE, dependiendo del éxito
en su ejecución.
Como siempre, se retornará TRUE o FALSE dependiendo de si el usuario
ha pulsado en el botón "Conectar" o "Seguir desconectado"
de la ventana.
El segundo parámetro será el descriptor de la ventana padre,
al igual que en la función InternetGoOnline. El primer parámetro
será un valor dentro de los siguientes posibles:
Esta función, como todas las demás, retorna un valor boolean
indicando el éxito de la ejecución.
InternetDial
Dado el carácter limitado de las funciones anteriores, se puede
establecer el marcado del módem con esta otra función, que
además nos permite utilizar distintos módems o configurar el comportamiento.
DWORD InternetDial(
HWND hwndPadre,
LPTSTR lpszIdConexión,
DWORD dwOpciones,
LPDWORD lpdwConexión,
DWORD dwReservado
);
Los parámetros tienen los siguientes significados:
- hwndPadre: el descriptor de la ventana padre, al igual que en InternetGoOnline.
- lpszIdConexión: una cadena con el nombre de la conexión
a marcar. Se puede pasar NULL para utilizar la conexión predeterminada,
o bien utilizar la función RasEnumConnections para obtener los
nombres de las conexiones disponibles.
- dwOpciones: son las opciones de marcado. Puede utilizarse cualquiera
de los siguientes valores:
- INTERNET_AUTODIAL_FORCE_ONLINE: Fuerza a desactivar la bandera
"Trabajar sin conexión".
- INTERNET_AUTODIAL_FORCE_UNATTENDED: Intenta realizar el marcado
sin la intervención del usuario. En caso de que sea necesaria
esta intervención, la función fallará.
- INTERNET_DIAL_FORCE_PROMPT: Ignora la configuración de
"Marcar automáticamente" y muestra siempre la ventana
de marcado.
- INTERNET_DIAL_UNATTENDED: Intenta realizar el marcado sin mostrar
ningún tipo de interfaz. Si esto no es posible, mostrará
algún tipo de ventana para que el usuario configure la conexión.
- INTERNET_DIAL_SHOW_OFFLINE: Muestra el botón "Trabajar
sin conexión" en vez del botón "Cancelar".
- lpdwConexión: es un puntero a una variable donde se almacenará
el número asignado a la conexión. Este valor debe conservarse
hasta la llamada a InternetHangup.
- dwReservado: Debe ser 0.
La función retorna ERROR_SUCCESS si todo ha ido correctamente,
o uno de los siguientes errores:
- ERROR_INVALID_PARAMETER: Alguno de los parámetros es incorrecto.
- ERROR_NO_CONNECTION: Ha habido un problema.
- ERROR_USER_DISCONNECTION: el usuario ha pulsado en el botón
"Cancelar" o "Trabajar sin conexión".
Funciones para comer galletas
Todos sabemos lo que son estas pequeñas galletas (cookies)
que últimamente las vemos aparecer por cualquier lado. Para los
olvidadizos, diremos que una cookie es un pequeño archivo
almacenado por una página web en nuestro disco duro, para grabar en
él datos de configuración, preferencias de usuario, contraseñas,
etc. De este modo, la página en cuestión es capaz de reconocernos
al entrar en la página, o saber si ha habido cambios desde nuestra
última visita.
Desde el propio API Wininet también es posible gestionar estos
pequeños archivos, sin tener que recurrir al navegador, Javascript,
CGI o cualquier otro sistema de programación web.
Las cookies están asociadas a una URL concreta, así
que por cada URL podremos tener un sólo archivo de configuración,
y dentro de él, uno o varios pares de datos "variable-valor".
Existen dos tipos de cookies, las que tienen fecha de caducidad,
y las que se eliminan al terminar una sesión de internet.
Las primeras (llamadas cookies persistentes) deben crearse indicando
la fecha a partir de la cual dejan de ser válidas.
Las segundas (llamadas cookies de sesión) se crean sin
ninguna fecha, y no se almacenarán en el disco duro, sino que permanecen
en memoria hasta el final del proceso que las creó.
Las funciones para manipular las cookies son sencillas: