Desarrollo del servidor web

Introducción

Como se ha visto en el apartado Elección del lenguaje, el lenguaje que mejor rendimiento ofrece en el router es el Lua, por lo que el desarrollo de los scripts de configuración se hará en dicho lenguaje. Sin embargo, se ha querido dar un paso más y desarrollar un servidor (también en Lua) que ofrezca un interfaz web para facilitar la labor de configuración del router.

¿Qué es un servidor web?

Un servidor web es un programa que está diseñado para transferir hipertextos, páginas web o páginas HTML (HyperText Markup Language): textos complejos con enlaces, figuras, formularios, botones y objetos incrustados como animaciones o reproductores de música. El programa implementa el protocolo HTTP (HyperText Transfer Protocol) que pertenece a la capa de aplicación del modelo OSI. El término también se emplea para referirse al ordenador que ejecuta el programa.

El Servidor web se ejecuta continuamente en un ordenador, manteniéndose a la espera de peticiones por parte de un cliente (un navegador web) y que responde a estas peticiones adecuadamente, mediante una página web que se exhibirá en el navegador o mostrando el respectivo mensaje si se detectó algún error. A modo de ejemplo, al teclear www.wikipedia.org en nuestro navegador, éste realiza una petición HTTP al servidor de dicha dirección. El servidor responde al cliente enviando el código HTML de la página; el cliente, una vez recibido el código, lo interpreta y lo exhibe en pantalla. Como vemos con este ejemplo, el cliente es el encargado de interpretar el código HTML, es decir, de mostrar las fuentes, los colores y la disposición de los textos y objetos de la página; el servidor tan sólo se limita a transferir el código de la página sin llevar a cabo ninguna interpretación de la misma. 1)

¿Qué es el protocolo HTTP?

El protocolo de transferencia de hipertexto (HTTP, HyperText Transfer Protocol) es el protocolo usado en cada transacción de la Web (WWW). Es un protocolo orientado a transacciones y sigue el esquema petición-respuesta entre un cliente y un servidor. Al cliente que efectúa la petición (un navegador o un spider) se lo conoce como “user agent” (agente del usuario). A la información transmitida se la llama recurso y se la identifica mediante un URL. Los recursos pueden ser archivos, el resultado de la ejecución de un programa, una consulta a una base de datos, la traducción automática de un documento, etc.

HTTP es un protocolo sin estado, es decir, que no guarda ninguna información sobre conexiones anteriores. El desarrollo de aplicaciones web necesita frecuentemente mantener estado. Para esto se usan las cookies, que es información que un servidor puede almacenar en el sistema cliente. Esto le permite a las aplicaciones web instituir la noción de “sesión”, y también permite rastrear usuarios ya que las cookies pueden guardarse en el cliente por tiempo indeterminado. 2)

Ejemplo de un diálogo HTTP

Para obtener un recurso con el URL http://www.example.com/index.html

  1. Se abre una conexión al host www.example.com, puerto 80 que es el puerto por defecto para HTTP.
  2. Se envía un mensaje en el estilo siguiente:
    GET /index.html HTTP/1.1
    Host: www.example.com
    User-Agent: nombre-cliente
    [Línea en blanco]
    
  3. La respuesta del servidor está formada por encabezados seguidos del recurso solicitado, en el caso de una página web:
HTTP/1.1 200 OK
Date: Fri, 31 Dec 2003 23:59:59 GMT
Content-Type: text/html
Content-Length: 1221

<html>
<body>
<h1>Página principal de tuHost</h1>
(Contenido)
  .
  .
  .
</body>
</html>
  • En la petición del cliente, la primera línea indica el método, el recurso al que se quiere acceder y el protocolo. En el ejemplo anterior, estos son:
Método: GET
Recurso: /index.html
Protocolo: HTTP/1.1
  • En la respuesta del servidor al cliente, la primera línea indica el protocolo y el código del estado (en este caso 200 indica que no hay errores). Otra cabecera importante es Content-Type, que indica el tipo MIME del recurso que se está enviando, pudiendo ser una página web, un tipo de imagen, sonido, etc. Después de las cabeceras se envía una línea en blanco y a continuación el recurso.

Métodos

HTTP define 8 métodos: HEAD, GET, POST, PUT, DELETE, TRACE, OPTIONS y CONNECT. Sin embargo, en el servidor a desarrollar sólo se implementarán GET y POST, ya que son los más utilizados.

Herramientas de apoyo

Lua es un lenguaje muy rápido y potente, sin embargo, para desarrollar el servidor web es necesario hacer uso de multitud de llamadas al sistema de muy bajo nivel, de las cuales Lua carece. Por ello se ha investigado en varias ramas para poder ampliar la funcionalidad de este lenguaje.

La primera aproximación fue añadir funcionalidades desarrolladas por terceros, como es el caso de LuaSocket y ex API, descritas ya en Elección del lenguaje. Sin embargo, con estas extensiones no se cubrían todas las necesidades, por lo que sería necesario continuar añadiendo librerías desarrolladas por terceros, con el inconveniente de incrementar cada vez más las dependencias de la aplicación con aplicaciones externas, por esto fue descartada esta opción.

Otra posibilidad consistía en hacer una biblioteca wrapper (envoltura), de forma que se puedan integrar las llamadas al sistema en C con el lenguaje Lua. Lua cuenta con una interfaz que facilita esta labor, por lo que se vió como una posible solución. Sin embargo, esta solución tiene un lado negativo: la biblioteca interfaz entre Lua y C tiene que ser previamente compilada, por lo que por cada funcionalidad nueva que se quiera añadir, sería necesario recompilarla, perdiendo de algún modo los beneficios de trabajar con lenguajes de scripting.

Posteriormente se investigó una tercera opción: desarrollar una biblioteca interfaz entre Lua y C (mismo principio del caso anterior), pero en lugar de disponer de una envoltura por cada función, se desarrollaría una envoltura genérica capaz de ofrecer una interfaz hacia Lua e invocar a cualquier funcion en C. Esta solución (la cual fue llamada LuaFlexible) está explicada con más detalle en LuaFlexible.

Sin embargo, al finalizar el desarrollo de LuaFlexible se decidió compartirla con la comunidad Lua. En el momento en el que se disponía a colgar dicha biblioteca en el repositorio, se comprobó que ya existía una solución muy parecida, C/Invoke, con algunos aspectos mejorados respecto a la desarrollada en este proyecto. Se decidió por tanto abandonar LuaFlexible e incoporar C/Invoke, que proporcionaba toda la potencia necesitada para el servidor web.

C/Invoke

Como se ha comentado anteriormente, C/Invoke es una biblioteca capaz de proporcionar una interfaz común desde Lua a cualquier función de C de forma dinámica. Dispone de un manual de refencia en su página web, así que no se describirá su utilización en este apartado, sin embargo se mostrarán los pasos necesarios para su compilación:

Ha sido necesario cambiar algunos ficheros y crear dos nuevos para la arquitectura PowerPC del router. Estas modificaciones se pueden descargar aquí.

wget http://download.savannah.nongnu.org/releases/cinvoke/cinvoke-1.0.tgz
tar xvpf cinvoke-1.0.tgz
cd cinvoke-1.0
mkdir local
wget http://laurel.datsi.fi.upm.es/_media/proyectos/teldatsi/cinvoke.tar
tar -xvpf cinvoke.tar
./configure.pl --prefix=/Scripting/lua/cinvoke-1.0/local/
CC="powerpc-linux-gcc -Wa,-mregnames" make
cd bindings/lua
CC="powerpc-linux-gcc" make
sudo cp cinvoke_lua.so /NFSroot/usr/local/lib/lua/5.1/

Servidor Web

El servidor web se ha desarrollado completamente en Lua, haciendo uso únicamente de la biblioteca C/Invoke para ampliar las funcionalidades del lenguaje.

El servidor web se ha divido en varios módulos según sus funcionalidades o servicios que ofrecen. Se puede ver cómo se relacionan los módulos en el siguiente gráfico:

luaCWrapper

Este módulo no es más que una interfaz para utilizar de forma cómoda la biblioteca C/Invoke, exporta las siguientes funcionalidades, muy similares a desarrolladas para LuaFlexible:

  • CWrapper.newLib(nombre, ruta) Crea una nueva referencia para una biblioteca. El parámetro nombre utilizado, será necesario para acceder a los servicios de esta biblioteca en adelante. Así, por ejemplo, si se escribe
    CWrapper.newLib("libc","/lib/libc.so.0")

    la forma de registrar una función de esta biblioteca sería:

    CWrapper.libc:registerFunction(Cint, "fork")
  • CWrapper.lib:registerFunction(ret, simbolo, [tipos…]) Registra una función, donde lib corresponde al primer argumento de una llamada a newLib anterior. ret es el tipo de valor devuelto, simbolo el nombre de la funcion y tipos son los tipos de los argumentos que recibe la función. Tanto ret como tipos deben ser tipos soportados por C/Invoke.
  • CWrapper.libc:registerValue(tipo, simbolo) Registra una variable, donde lib corresponde al primer argumento de una llamada a newLib anterior. tipo es el tipo de la variable y simbolo el nombre de la misma. Tanto tipo debe ser un tipo soportados por C/Invoke.

A continuación se muestra un ejemplo de cómo se fija una variable de entorno haciendo uso de la llamada al sistema setenv.

require("luaCWrapper")
 
CWrapper.newLib("libc","/lib/libc.so.0");
 
CWrapper.libc:registerFunction(Cint, "setenv", Cstring, Cstring, Cint);
 
CWrapper.libc.setenv("SERVER_PROTOCOL","HTTP/1.1",1);

LuaWeb

Este módulo está pensado para ser incorporado en las páginas webs que se desarrollen en Lua, proporciona un grado de abstracción sobre el protocolo HTTP/1.1.

Incorpora los siguientes servicios:

  • POST, GET y Cookie: Variables globales de tabla que almacenan los datos devueltos por ExtractPOST, ExtractGET y ExtractCookie respectivamente.
  • Header: Variable global en forma de tabla utilizada en la función SetHeader. Puede completarse para enviar más cabeceras HTTP al cliente, de forma Header[k]=v, donde k es la clave y v el valor. Por ejemplo:
    Header["Cache-Control"]="max-age=3600"
  • ResponseCookies: Variable global en forma de tabla utilizada en la función SetHeader, mediante la cual se pueden añadir cookies que se enviarán al cliente. Por ejemplo:
    ResponseCookies["CookieDePrueba"] = "ValorDeLaCookie"
  • EndResponse(): Finaliza la comunicación con el cliente: envía los últimos datos al cliente y cierra la entrada y salida estándar del proceso. Es recomendable utilizarla al finalizar una página web.
  • ExtractPOST(), ExtractGET(), ExtractCookie(): Que extraen la información referente al método POST, GET, y a las cookies, respectivamente, de la variable de entorno oportuna y la almacena en forma de tabla para facilitar su tratamiento.
  • SetHeader(Mime): Envía una cabecera HTTP/1.1 con el tipo mime pasado como argumento. Además, incorpora a la cabecera todas las cabeceras y cookies contenidas en las variables globales de tabla Header y ResponseCookies respectivamente.
  • Además, implementa funciones auxiliares como son:
    • EscapeChars(s): Devuelve una copia del string s, con los caracteres escapados según las reglas de Lua (ver Patterns de Lua)
    • CheckIP(ip): Comprueba que ip sea una IP correcta.
    • CheckString(s): Se utiliza para evitar errores en el manejo de strings. Si s es un string, lo devuelve sin cambios, pero si es nil devuelve un string vacío (””).

A la hora de incorporar este módulo a una web, se puede especificar que automáticamente extraiga la información del POST y/o que se habilite el manejo de sessions con las variables __EnablePOST y __EnableSession respectivamente. Por ejemplo:

__EnableSession=true;
__EnablePOST=true;
require("LuaWeb");
 
...

ModGlobal

Este módulo, como su nombre indica, es un módulo global, común a todos los demás módulos que implementa funciones que pueden ser utilizadas por éstos, tales como codificación y decodificación de URLs, funciones de manejo de rutas, entre otras.

Servidor

Este es el módulo principal, junto con el explicado a continuación. Implementa el servicio de escucha por un puerto (en nuestro caso el 80), y acepta las conexiones entrantes. Por cada una de las peticiones que llegan al servidor, éste crea un hijo que pasa a ejecutar el código del módulo ServidorHijo.

A continuación se muestra un diagrama de los distintos pasos que realiza el servidor para atender las peticiones de los clientes:

Diagrama Cliente-Servidor

  • Pasos 1-10: Se crea un proceso servidor que va a actuar como demonio. El servidor crea un socket TCP que tiene como descriptor sd. A continuación se asocia a un puerto TCP (en el ejemplo es el puerto 7, sin embargo, al tratarse de un servidor web se ha elegido el 80). Por último se dimensiona el número máximo de clientes que se encolan mediante listen y se queda esperando conexiones.
  • Pasos 11-17: Sea crea un proceso cliente que desea hacer una petición al servidor. Este proceso crea un socket TCP que tiene como descriptor cd y porteriormente se conecta al servidor mediante connect, especificando la dirección del servidor y el puerto. La llamada a connect busca un puerto TCP libre en el cliente, que será el utilizado en la conexión (opcionalmente se puede utilizar bind también en el cliente para especificar el puerto explícitamente).
  • Pasos 18-21: La petición de conexión llega al servidor, que obtiene un nuevo descriptor, cd, para la conexión. En este punto la conexión queda establecida como una quíntupla: (Protocolo, MáquinaCliente, PuertoCliente, MáquinaServidor, PuertoServidor).
  • Pasos 22-24: El cliente envía los datos al servidor y se queda esperando a su respuesta.
  • Pasos 25-32: El servidor crea un hijo que va a actuar como servidor dedicado en esa conexión. Mediante una llamada fork se crea el hijo, el cual hereda todos los descriptores del padre; acto seguido ambos procesos cierran los descriptores que no son necesarios. El demonio servidor vuelve a quedar a la espera de una nueva conexión.
  • Pasos 33-38: El servidor dedicado recibe la petición, la procesa y devuelve el resultado al cliente. Una vez se ha acabado con el servicio, ambos procesos cierran su extremo del socket y se finaliza la comunicación.

ServidorHijo

Este módulo atiende una petición de un cliente de la siguiente forma:

  1. Lee de la entrada estándar las cabeceras que envía el cliente, donde se encuentra, entre otras cosas, el recurso solicitado.
  2. Identifica las cabeceras que corresponden al método, al recurso solicitado, información que envía el cliente a través de la URL (si el método es GET), longitud de los datos enviados (si el método es POST), y otras cabeceras importantes.
  3. Comprueba que el recurso solicitado existe y que se poseen permisos para el mismo.
  4. Sirve el recurso:
    • Si el recurso es un ejecutable, pasa a ejecutar su código.
    • Si el recurso es otro fichero (texto, imagen…), se lo envía al cliente.

Session

Este módulo implementa una versión mínima de manejo de sesiones (sessions), utilizada para poder identificar las solicitudes provenientes de la misma sesión.

Al implementar este módulo se decidió guardar las sesiones en ficheros de texto en lugar de en memoria o utilizando algún otro mecanismo por simplicidad; estos ficheros se almacenarán en el directorio sessions de la aplicación.

Las funciones que implementa son:

  • __SessionId: Es una variable global que guarda el identificador de la sesión actual.
  • Session: Variable global de tabla que almacena los pares de clave-valor.
  • Session:New(): Crea una nueva sesión y actualiza __SessionId con el nuevo identificador.
  • Session:Load(): Carga una sesión con id __SessionId desde fichero.
  • Session:Save(): Guarda la sesión __SessionId en fichero.
  • Session:Add(k,v): Añade una nueva variable a la sesión, donde k es la clave y v el valor.
  • Session:Remove(k): Elimina la clave k de la sesión.
  • Session[k]: Recupera una variable de la sesión.
 
proyectos/teldatsi/servidor_web.txt · Última modificación: 2012/10/08 17:58 (editor externo)
 
Recent changes RSS feed Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki