Universidad de Costa Rica
Escuela de Ciencias de la Computación e Informática
CI-2413 Desarrollo de aplicaciones web
Profesor: Jeisson Hidalgo Céspedes
02-Noviembre-2011
Este laboratorio guía autodidácticamente al estudiante en el aprendizaje del servidor web y la imprlementación de programas en el lado del servidor que responden a clientes web a través de CGI y FastCGI.
Un servidor web es un programa que responde peticiones HTTP en algún puerto TCP, normalmente el 80. Existe en la actualidad varias implementaciones populares, entre las que sobresalen Apache HTTP Server, Microsoft Internet Information Services (IIS), y nginx. Este laboratorio pretende que el estudiante instale, configure y utilice uno de estos servidores para construir un pequeño sitio web dinámico utilizando FastCGI. En este texto se utilizará nginx.
Ejercicio 0. Repase la sección Arquitectura web del material de apoyo del curso, en especial los temas "El servidor web" y "El protocolo HTTP".
La instalación de un servidor web es un proceso muy dependiente de la implementación escogida y de las necesidades del autor. En el caso de los servidores web libres, el método ideal es obtener el código fuente, instalarlar sus pre-requisitos, configurarlo, compilarlo, e instalarlo. Este procedimiento tipicamente se documenta con detalle en el sitio del servidor web. Por otra parte, tanto los servidores web libres como propietarios distribuyen binarios que son relativamente fáciles de instalar, pero los módulos que traen son los escogidos por quien generó el ejecutable.
Ejercicio 1. Instale nginx en su máquina local. Para propósitos de este laboratorio una versión binaria será suficiente. Si trabaja con Linux, utilice el administrador de paquetes de su distribución, por ejemplo, en Debian/Ubuntu bastará con el comando:
sudo apt-get install nginx
Si utiliza Windows, descargue una versión estable del sitio oficial de nginx. Descomprímala en cualquier lugar de su disco duro.
Una vez instalado el servidor web es necesario ponerlo en marcha. En el caso de nginx basta con invocar su ejecutable. El usuario notará que al hacerlo el programa termina inmediatamente dando la sensación de que nada ocurrió, sin embargo, nginx habrá iniciado al menos dos procesos homónimos en segundo plano (background), los cuales estarán esperando solicitudes HTTP en el puerto 80. Esto se puede probar con un navegador que acceda a la dirección http://localhost.
Ejercicio 2. Ponga en marcha su servidor web. En el caso de Windows corra con permisos de administración el ejecutable que descargó. Si no tiene permisos de administración, edite el archivo conf/nginx.conf
y cambie la directiva listen 80
a un puerto superior a 1024, por ejemplo listen 8080
; luego corra el ejecutable normalmente. En el caso de Linux puede ocurrir que su administrador de paquetes ya lo haya puesto en ejecución. De lo contrario invóquelo con permisos de administración:
sudo nginx
Si el ejecutable nginx
no está en el $PATH, deberá localizar su ruta completa con el administrador de paquetes, por ejemplo en Debian se obtiene con dpkg -L nginx-full
. Sin embargo, los administradores de paquetes asumen que el servidor debe iniciarse cada vez que arranca la computadora, por lo que estará en algún "init level", y como cualquier otro demonio se puede arrancar con:
sudo /etc/init.d/nginx start
Finalmente pruebe con un navegador que su servidor web responda en http://localhost.
Desde el punto de vista de la interacción con el usuario hay dos tipos de programas. Los programas en primer plano (foreground) cuando son ejecutados mantienen un diálogo con el usuario. La mayoría de personas creen que todos los programas son así. Sin embargo, en ese mismo instante el sistema operativo estará ejecutando un número, probablemente mayor, de programas en segundo plano (background) que no esperan una interacción directa con el usuario, como por ejemplo, el núcleo del sistema operativo, servicios de acceso a la red, demonios que ejecutan tareas programadas, servidores de correo, FTP, etc. El servidor web cabe en esta segunda categoría.
Cuando nginx es ejecutado arranca al menos dos procesos. El primero se llama nginx master process y debe ser ejecutado con permisos de administración, ya que por política de Unix, sólo procesos iniciados por el superusuario pueden abrir sockets en cualquier puerto, mientras que los iniciados por usuarios normales, sólo pueden escuchar en puertos superiores a 1024. El segundo y demás procesos reciben el nombre de nginx working processes, son procesos iniciados por nginx a nombre de un usuario cualquiera (especificado en la configuración de nginx) que atienden un cliente web en cualquier otro puerto, con el fin de liberar el puerto 80 para que esté disponible y poder aceptar más conexiones.
Si se invoca el ejecutable de nginx
sin parámetros, tratará de iniciar el servidor web. Si se invoca por segunda vez sin parámetros, fallará ya que el puerto 80 estará en uso. Si se invoca con el parámetro nginx -h
brindará ayuda. La opción nginx -V
permite ver la versión y los módulos con que fue compilado el ejecutable. La opción nginx -s
sirve para controlar al servidor en ejecución. Otras opciones se discutirán más adelante.
Ejercicio 3. Utilizando parámetros del ejecutable nginx
, detenga la ejecución del servidor y pruebe su efecto en el navegador; determine la versión que tiene instalada en el sistema y los módulos que puede habilitar. Compare contra la lista de módulos disponibles si existe alguno que necesite o esté interesado(a). Si el módulo no está en la lista de instalados, deberá compilar nginx desde el código fuente.
Una vez instalado el servidor web, el autor querrá configurarlo para hacer funcionar su sitio. Tareas como indicarle al servidor web dónde están los documentos que conforman el sitio, si debe o no habilitar PHP, y cuánto es el número máximo de conexiones (visitantes) que debe permitir simultáneamente, son las que se establecen en la configuración.
La mayoría de servidores web se configuran con directivas en archivos de texto. Desdichadamente cada uno tiene sus propias convenciones, en lugar de existir un estándar. En el caso de nginx, el archivo principal de configuración se llama nginx.conf
y su ubicación depende de los parámetros con que se compiló el código fuente, o de las convenciones de la distribución de Linux. Por ejemplo, en Ubuntu puede ubicarse bajo la carpeta /etc/nginx/
. La nomenclatura de las directivas de configuración de nginx puede visualizarse con el siguiente pseudocódigo:
# Esto es un comentario nombre_directiva valores; # Un modulo se configura con un bloque de directivas nombre_modulo { directiva2 valor; directiva3 valor1 valor2; # ... }
Las directivas constan de un término que significa alguna característica para nginx, seguido por uno o más valores. El término y los valores se separan por espacios en blanco. Como es de esperar, nginx está dividido en muchos módulos: core, webdav, fastcgi, mail, etc., y cada uno tiene directivas para configurarlos. Las directivas de cada módulo se escriben en el archivo de configuración agrupadas dentro de llaves { }
, lo que forma un bloque de directivas (directive block), y son precedidos por el nombre del módulo. Esta práctica mantiene el orden e incrementa la legibilidad. El siguiente listado muestra un fragmento de un archivo de configuración de nginx.
user www-data; worker_processes 4; pid /var/run/nginx.pid; events { worker_connections 768; # multi_accept on; } http { # Basic settings include /etc/nginx/mime.types; default_type application/octet-stream; # Logs access_log /var/log/nginx/access.log; error_log "/var/log/nginx/error.log"; # Virtual host configs include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; }
Del listado anterior se puede leer lo siguiente. La directiva en la línea 7 sólo afecta al módulo de eventos (Events Module) de nginx. Las directivas que no están dentro de llaves (líneas 1 a 3) se dice que están en el bloque principal (main block) y tienen un efecto global en el servidor web. Algunas directivas esperan valores numéricos como worker_processes
en la línea 2, otras cadenas de caracteres como access_log
y error_log
en las líneas 18 y 19. Las cadenas se pueden escribir literalmente, pero si tienen espacios en blanco, puntos y comas, u otros caracteres especiales, deberán encerrarse entre comillas dobles o simples.
Los módulos de nginx son opcionales, es decir, se pueden instalar o no, y si están instalados se pueden habilitar o no; a excepción de tres módulos: Core module
, Events module
y Configuration module
, los cuales siempre están instalados y siempre habilitados, ya que proveen la funcionalidad mínima de nginx.
include
Una instalación de un servidor web puede utilizarse para uno o más sitios web. No es aconsejable que la configuración de todos esos sitios se haga en el mismo archivo, puesto que se hará voluminoso y difícil de mantener. En su lugar, se recomienda distribuir la configuración en varios archivos que contengan directivas coherentes, al menos un archivo por cada sitio web que deba servir la instalación de nginx.
La directiva include
recibe un nombre de un archivo (el cual debe existir) y le indica a nginx que debe insertar el contenido de dicho archivo en el lugar de la directiva include
. La línea 14 del listado anterior muestra un ejemplo. El nombre del archivo puede ser un patrón con los caracteres comodines *
y ?
(filename globbing), que indica a nginx incluir, no un archivo, sino todos aquellos que cumplan el patrón. Por ejemplo, la línea 22 del listado anterior indica a nginx importar en ese lugar todos los archivos que terminan en *.conf
que se encuentran en la carpeta /etc/nginx/conf.d/
.
El módulo HTTP de nginx es opcional, por lo que puede deshabilitarse, sin embargo, es quizá el más requerido por el autor y el que querrá configurar primero para hacer funcionar su sitio web. Es también el módulo más extenso de nginx y su configuración se hace con tres bloques: http
, server
y location
. Tómese el siguiente ejemplo:
http { gzip on; server { server_name miempresa.com www.miempresa.com; listen 80; root /home/sites/miempresa.com; location /download/ { gzip off; } } server { server_name portal.miempresa.com; root /home/sites/portal.miempresa.com; index index.php index.html; } server { server_name svn.miempresa.com; root /home/svn; } }
Las directivas que se escriban directamente en el bloque http { ... }
afectan a todo el módulo HTTP, como la línea 3 del listado anterior, que indica a nginx comprimir los recursos antes de enviarlos al cliente.
Un servidor web puede alojar uno o varios sitios web, los cuales se acceden con diferentes dominios. Por ejemplo, una misma instalación de nginx podría servir la página pública de la empresa (www.miempresa.com
), un sitio para empleados, clientes y proveedores de la empresa (portal.empresa.com
), y un sitio con repositorios de Subversion (svn.miempresa.com
). Es deseable poder configurar cada uno de esos sitios independiente de los demás. Por ejemplo, cada uno debe tener un diferente directorio físico donde se encuentran sus recursos, para los dos primeros podría ser importante tener PHP habilitado, mientras que para el segundo el módulo WebDAV, y así por el estilo.
nginx, y en general todos los servidores web, representan cada sitio web como un servidor virtual, es decir, como un pequeño servidor web que corre bajo el auspicio del servidor web real, con su propia configuración independiente de los demás servidores virtuales. Si sólo se quiere montar un único sitio web en un servidor web, lo normal es crear un servidor virtual para dicho sitio, con el bloque de directivas server { ... }
dentro del bloque http { ... }
.
El listado anterior define tres servidores virtuales y por ende tres sitios web. La directiva listen
le indica a nginx en cuál puerto TCP, o dirección IP, espera conexiones para ese sitio web, de esta forma se puede tener dos sitios web con igual dominio pero diferenciados por el puerto. Si se omite, se asume el puerto 80.
La directiva server_name
hace que el servidor virtual responda a uno o más nombres de dominio. Cuando nginx recibe un mensaje de solicitud, revisa el atributo Host:
en el encabezado HTTP, y compara su valor contra todos los servidores virtuales para determinar cuál debe responder. En nuestro ejemplo, si nginx recibe el siguiente mensaje HTTP (recuerde que los cambios de línea son importantes):
GET /login.php?remember_user=yes Host: portal.miempresa.com:80 User-Agent: Opera/11.01 (iOS)
no podrá utilizar el puerto o la dirección IP como criterio para seleccionar el servidor virtual, pero sí el dominio. En este caso, sólo el segundo servidor virtual atiende el dominio portal.miempresa.com
en el puerto 80, por lo que nginx cargará el recurso /home/sites/portal.miempresa.com/login.php?remember_user=yes
y lo retornará en un mensaje de respuesta HTTP. La directiva server_name
acepta comodines, por lo que un servidor virtual podría antender todos los dominios que terminen en o tengan una cadena particular, como:
server_name *.miempresa.com; # or server_name *miempresa.*; # or server_name *miempresa*;
La directiva root
le indica a nginx dónde se encuentran físicamente los recursos que deben ser servidos a través de un servidor virtual. Es seguido por una ruta, normalmente absoluta, de lo contrario se tomará como relativa al directorio donde está instalado nginx.
La directiva index
le indica a nginx cuál recurso o archivo escoger para retornar al cliente cuando se solicita un directorio. Si se especifican varios archivos, se retornará el primero que se encuentre. A este recurso se le suele llamar index page. Por ejemplo, si el servidor web recibe la siguiente solicitud HTTP
GET /admin/ Host: portal.miempresa.com:80 User-Agent: Opera/11.01 (iOS)
nginx intentará encontrar el archivo /home/sites/portal.miempresa.com/admin/index.php
, si no lo encuentra, intentará con /home/sites/portal.miempresa.com/admin/index.html
, y si tampoco existe, responderá con un código de estado de error, posiblemente 404 NOT FOUND
. Si en un servidor virtual no se especifica la directiva index
, se asumirá index.html
.
location
A veces dentro de un servidor virtual, es deseable aplicar una configuración diferente a ciertos recursos del sitio. Los bloques location pattern { ... }
permiten escoger un subconjunto de recursos que coincidan con el patrón pattern
y aplicarles la configuración que aparece dentro de llaves. El patrón es sensitivo a mayúsculas/minúsculas si el sistema de archivos del sistema operativo lo es también.
Cuando el cliente solicita un recurso, el servidor web lo localiza o genera, y debe retornarlo en un mensaje de respuesta HTTP, en el cual debe indicarle al cliente de qué tipo de datos es dicho recurso para que lo pueda interpretar adecuadamente, utilizando una notación estándar llamada extensiones multipropósito de correo de internet (MIME, Multipurpose Internet Mail Extensions), ideadas para el intercambio de archivos por Internet. Por ejemplo, text/html
indica un documento HTML, image/png
una imagen codificada con el formato PNG, y application/xhtml+xml
un documento XHTML.
Póngase en los zapatos del servidor web. Dado un archivo que debe retornar al cliente ¿cómo sabe cuál tipo MIME asignarle en el mensaje de respuesta? nginx se guía por la extensión del archivo, y si no la tiene, asume un tipo MIME por defecto. nginx le permite configurar las asociaciones entre extensiones y los tipos MIME utilizando el bloque types { ... }
, como en el siguiente ejemplo:
http { types { text/html html htm shtml; text/css css; text/xml xml rss; application/xhtml+xml xhtml; application/x-javascript js; image/gif gif; image/jpeg jpeg jpg; # ... } default_type application/octet-stream; }
La directiva default_type
le indica a nginx cuál MIME type asumir cuando la extensión no se encuentra en el bloque types { ... }
o cuando el recurso simplemente no tiene extensión.
Ejercicio 4. Compruebe el efecto de servir un documento XHTML con el MIME adecuado (application/xhtml+xml
). Escriba en un documento web con extensión .xthml
que tenga alguna diferencia con HTML, por ejemplo, una sección CDATA cuyo contenido es código (X)HTML:
<body> <h1>MIME application/xhtml+xml</h1> <p>Si este <strong>documento XHTML</strong> es servido con el MIME <code>application/xhtml+xml</code>, abajo se debe ver <em>el código fuente</em> que generó este párrafo y no un texto con formato.</p> <pre><![CDATA[ <p>Si este <strong>documento XHTML</strong> es servido con el MIME <code>application/xhtml+xml</code>, abajo se debe ver <em>el código fuente</em> que generó este párrafo y no un texto con formato.</p> ]]></pre> </body>
application/xhtml+xml
. Correr este ejemplo.Ejercicio 5. Monte su sitio web personal (creado en el proyecto 01) en su instalación local de nginx. Cree un servidor virtual o utilice el que nginx trae por defecto. Ya que su sitio web se compone únicamente de documentos XHTML pero con extensión .html
, registre el MIME type application/xhtml+xml
a la extensión html
en su servidor virtual.
nginx -y en general todos los servidores web- pueden extender su funcionalidad con módulos, escritos por el equipo oficial, terceros, o incluso usted. En esta y siguientes secciones se explorarán algunos de ellos.
El módulo Server Side Includes (SSI) le indica al servidor web que antes de servir un recurso al cliente, analice (parse) dicho recurso en búsqueda de comandos para el servidor web, los cuales tienen la notación:
<!--# command parameter1="value1" parameter2="value2" ... parameterN="valueN" -->
Por ejemplo, al servir el siguiente documento
<html> <head> <title>Mi empresa</title> </head> <body> <div id="whole"> <!--# include file="header.html" --> <!--# include file="menu.html" --> <div id="content"> <p>Bienvenidos al ...</p> </div> <!--# include file="footer.html" --> </div> </body> </html>
el servidor web localizará todos los comentarios que inicien con <--#
y tratará de ejecutar el comando que sigue. El comando include
le indica al servidor cargar el archivo que se encuentra en el atributo file="url"
, o si es un programa, ejecutarlo. El contenido del archivo o el resultado del programa será insertado en el documento servido, reemplazando el comando dentro del comentario.
El autor que quiera utilizar Server Side Includes, deberá habilitar el módulo ssi
en la configuración de nginx, e indicarle en cuáles tipos de recursos (MIME types) quiere que el servidor procese esos comandos, de lo contrario se asumirán sólo los text/html
. Por ejemplo:
server { server_name miempresa.com www.miempresa.com; root /home/sites/miempresa.com; ssi on; ssi_types text/html application/xhtml+xml application/x-javascript; }
Aunque SSI es muy eficiente y se puede habilitar para todo un sitio web, es mejor evitarlo para archivos que no lo requieren. Una práctica común es nombrar los archivos que tienen Server Side Includes con la extensión .shtml
(contracción de server html), y habilitar el módulo ssi
sólo para ellos, por ejemplo:
server { server_name miempresa.com www.miempresa.com; root /home/sites/miempresa.com; index index.shtml index.php index.html; # Habilita Server Side Includes en todos los archivos con extensión .shtml location ~* \.shtml$ { ssi on; } }
Ejercicio 6. Habilite el módulo SSI en su instalación de nginx; y cambie todos los elementos object
de su sitio web personal por comandos include
de SSI. Modifique los documentos web referidos en esos comandos para quitar el encabezado (head
), cuerpo (body
) y otros elementos ahora innecesarios.
La publicación de páginas web estáticas se está convirtiendo en una práctica en desuso. La mayoría de sitios web pretenden hacer tan efectiva la comunicación con el lector para que éste se sienta a gusto y ambas partes se beneficien. En este movimiento no basta sólo con programación del lado del cliente con JavaScript, el servidor web también debe ajustarse a las necesidades del visitante y hacer que las páginas que sirva sean dinámicas. Es decir, la fuente de los recursos con los que responde el servidor web no son archivos estáticos, sino el resultado de ejecutar programas de aplicación escritos en cualquier lenguaje de programación. Es evidente entonces que debe existir un conjunto de convenciones que permitan a una aplicación cualquiera -ocupada de la lógica del negocio- comunicarse con un servidor web cualquiera -ocupado de los asuntos de red-, para que así ambos colaboren y respondan al visitante adecuadamente. Este conjunto de convenciones las establece el estándar Common Gateway Interface (CGI) surgido en 1993.
Cuando el usuario solicita un recurso, el servidor web sabe -de acuerdo a su configuración- si el recurso es estático o dinámico. Si es estático lo carga desde la memoria secundaria y retorna una copia al cliente. Si es dinámico, deberá interactuar con una aplicación y es ahí donde entra en juego CGI. CGI es protocolo que impone la forma en que la información se transfiere entre el servidor web y una aplicación cualquiera (llamada gateway application) y se resume en lo siguiente. El servidor web ejecuta el programa de aplicación pasándole por parámetro o en la línea de entrada un conjunto de variables. La aplicación se carga, realiza su lógica del dominio en función de esas variables, consultando bases de datos o cualquier otro recurso, construye una página web, la imprime en la salida estándar y termina su ejecución. El servidor web captura esa salida y la envía al cliente que solicitó el recurso.
El procotolo CGI tiene varios inconvenientes, en especial para sitios web mundialmente concurridos. Por esto a mediados de los 90 se desarrolló el estándar FastCGI, el cual introduce las siguientes diferencias sobre CGI:
De lo anterior se infiere que la aplicación, indeferentemente del lenguaje de programación en que esté hecha (C, C++, Java, PHP, Ruby, etc.), debe implementar un servidor TCP/IP esperando conexiones en algún puerto, configurar nginx para que cuando se solicita un recurso con una extensión particular o una ruta particular, se contacte la aplicación en dicho puerto TCP. En lo siguiente este proceso se explicará para PHP.
Los programas para PHP no son ejecutables, es decir, no son compilados. Un archivo de texto con extensión .php
es interpretado por el ejecutable de PHP (/usr/bin/php
ó php.exe
), de la siguiente forma:
php /path/to/myfile.php
Lo anterior claramente no es un servidor TCP/IP esperando conexiones en algún puerto, como exige el protocolo FastCGI. El proyecto PHP-FPM (PHP FastCGI Process Manager) es una alteración de PHP para Unix que ejecuta un demonio normalmente en el puerto 9000, el cual espera solicitudes de ejecutar un archivo .php
particular con sus respectivos parámetros. nginx puede comunicarse con PHP-FPM a través de FastCGI, lo cual se considera una implementación muy eficiente, ideada para sitios altamente concurridos.
Para instalar PHP-FPM se puede configurar y compilar su código fuente, o utilizar el administrador de paquetes de su distribución de Linux. Por ejemplo, los siguientes comandos instalan y ponen en ejecución un servidor de PHP-FPM en Debian/Ubuntu:
sudo apt-get install php5-fpm sudo /etc/init.d/php5-fpm start
Ahora debe decirle a nginx que cada vez que un visitante solicita un archivo .php, debe contactar al servidor de PHP a través de FastCGI en el puerto 9000, para que lo ejecute, y el resultado que se genere de su invocación, enviarlo al visitante. Para nuestros efectos, el archivo .php puede estar en cualquier lugar del sitio web, su único distintivo con los demás archivos del sitio es su extensión .php. La siguiente configuración mínima hace este trabajo. Nota: es probable que las siguientes líneas ya estén presentes en la configuración por defecto, basta con quitarles los comentarios.
server { server_name miempresa.com www.miempresa.com; root /home/sites/miempresa.com; index index.php index.html; # Ejecuta todos los archivos con extensión .php antes de enviarlos al cliente location ~* \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_index index.php; } }
El bloque location
anterior le indica a nginx que antes de servir un archivo con extensión .php
, debe contactar a través de FastCGI al programa que está corriendo en el puerto 9000, e indicarle que ejecute el archivo .php
. Esto último se indica a través de parámetros con la directiva fastcgi_param NOMBRE_PARAMETRO valor_parametro
. Por ejemplo, la directiva en la línea 11 le indica a nginx enviar a PHP-FPM la ruta absoluta del archivo .php
en el parámetro SCRIPT_FILENAME
. Los valores iniciados con un símbolo de dólar, como $document_root
son variables de nginx, las cuales se reemplazan por sus valores respectivos, en el caso de $document_root
por el directorio donde están los recursos del sitio web (/home/sites/miempresa.com
en este ejemplo), y $fastcgi_script_name
por la ruta relativa dentro del sitio del archivo .php
solicitado por el visitante. Otros parámetros son necesarios, el archivo fastcgi_params
que trae la instalación de nginx se encarga de ellos y funciona para la mayoría de sitios.
Para Microsoft Windows no existe una implementación de PHP-FPM, por lo que el desarrollador puede emularlo a través de Cygwin, o utilizar algún paquete "todo en uno", como Easy WEMP (acrónimo de Windows, nginx, MySQL y PHP).
Ejercicio 7. Pruebe que su instalación de nginx-PHP funcione. Cree un archivo info.php
con el siguiente contenido en algún lugar de su sitio web:
<?php phpinfo(); ?>
Solicite el recurso anterior a través de un navegador, por ejemplo, dirigiéndolo a http://localhost/info.php. Si la instalación fue exitosa, verá una extensa página con detalles sobre su configuración de PHP.
Comprima su nuevo sitio web (con "server side includes" en lugar de elementos object
) y la configuración de nginx que haya creado durante el laboratorio. Súbalos a la plataforma educativa (Moodle) de la ECCI, en la asignación con título Tarea11
.