Depto. Automática y computación
Universidad Pública de Navarra
Daniel Morató
daniel.morato@unavarra.es

Laboratorio de Interfaces de Redes

Práctica 4 - CGIs

1. Objetivos

En esta práctica veremos cómo configurar el Apache para que ejecute CGIs y crearemos varios en diferentes lenguajes.

2. Configuración del Apache para permitir la ejecución de CGIs

En la siguiente sección del manual del Apache tiene un tutorial sobre cómo configurar que el servidor ejecute CGIs:
http://www.tlm.unavarra.es/~daniel/docencia/lir/lir03_04/manuales/httpd/howto/cgi.html

Hay varias formas de hacer esto. Como podrá ver si consulta su fichero de configuración httpd.conf ya tiene activado que se ejecuten CGIs siempre que estos se encuentren en el directorio cgi-bin dentro del directorio de instalación del Apache. Puede cambiar el directorio de CGIs con la directriz ScriptAlias (vea el howto anterior). En el directorio por defecto de CGIs de la instalación de Apache vienen ya un par de CGIs de ejemplo. Uno de ellos es un script de Perl y el otro un script de la Shell sh. Tenga en cuenta que los scripts en la primera línea indican el programa para el cual son un script mediante algo como #!/bin/sh y que éste debe ser el path correcto para ese ejecutable. Además necesitan permisos de lectura y ejecución. Pruebe esos dos script de ejemplo que no hacen más que sacar las variables de entorno típicas que prepara el servidor web para el CGI (pruébelos tanto desde un terminal como pidiéndoselos al servidor con un navegador).

Otra alternativa es indicar que se ejcutarán CGIs que se encuentren en un directorio. Para ello indicamos un directorio con <Directory> y activamos esta capacidad con Options +ExecCGI (vea en la documentación qué tipo de opciones se pueden indicar con la directriz Options). De esta forma podemos marcar muchos directorios donde se aceptarán CGIs. ¿Cómo sabe el servidor si un fichero de ese directorio es un CGI y por lo tanto debe ejecutarlo en un proceso nuevo o es un fichero HTML? Normalmente por la extensión. Se puede indicar qué extensiones hacen referencias a CGIs mediante la directriz AddHandler cgi-script. Con la configuración mediante ScriptAlias no necesitábamos extensiones especiales porque entiende que todos los ficheros en ese directorio son CGIs.

Finalmente, podemos activar la opción ExecCGI para un directorio desde un fichero .htaccess para lo cual necesitamos haberle dado permiso al fichero .htaccess de ese directorio para hacer esto (vea de nuevo las opciones de la directriz AllowOverride).

Creen un directorio de nombre miscgis dentro del directorio en su HOME desde donde se sirve su web personal. Configure mediante un fichero .htaccess que se puedan ejecutar CGIs que haya en ese directorio. Coloque ahí un CGI como el siguiente (no es más que un script de shell):

#!/bin/sh

echo "Content-Type: text/html"
echo ""
echo "<html><head><title>CGI 1</title></head>"
echo "<body>"
echo "<p>Hooooola"
echo "</body></html>"

Checkpoint
Muestren al profesor de prácticas que les funciona ese CGI

3. CGIs

Los CGIs se pueden escribir en cualquier lenguaje siempre que podamos obtener un ejecutable que sea capaz de leer de la entrada estándar y de variables de entorno y escribir por la salida estándar. Hasta ahora hemos visto ejemplos de CGIs con scripts de una shell o de Perl. Seguiremos empleando scripts de shell por su comodidad pero también veremos algunos CGIs en lenguaje C.

A. Viendo las variables de entorno

El código que tiene a continuación vuelca por la salida estándar la cabecera HTTP y el contenido de todas las variables de entorno. Compile ese programa y pruébelo desde el terminal. A continuación colóquelo en su directorio de CGIs y pruebe a solicitárselo a su servidor.

#include <stdio.h>

main (int argc, char *argv[], char *env[])
{
    char **p=env;

    printf("Content-Type: text/html\r\n\r\n");
    printf("<html><body>\n");
    while (*p!=NULL)
    {
	printf("%s<br>\n", *p);
	p++;
    }

    
    printf("</body></html>\n");
}

B. Un contador en texto

Podemos hacer un sencillo contador de accesos. Para ello el programa va a hacer lo siguiente:

  1. Leer de un fichero el número de accesos hasta el momento
  2. Incrementarlo en 1 y sacar el contador en una web
  3. Guardar la nueva cuenta en el fichero

A continuación tiene el código C de un programa muy simple para hacer lo que acabamos de comentar. Péguelo en un fichero de texto, cambie el define para que apunte a un lugar donde su servidor web pueda guardar ficheros, compílelo y pruébelo.

#include <stdio.h>

#define	NOM_FICHERO	"/opt3/lir/lir/contador.dato"

int cuenta()
{
    FILE	*fichero;
    int		actual;
    
    fichero=fopen(NOM_FICHERO, "r");
    if (fichero==NULL) actual=0;
    else
    {
	fscanf(fichero, "%i\n", &actual);
	fclose(fichero);
    }

    actual++;
    fichero=fopen(NOM_FICHERO, "w");
    if (fichero==NULL) return -1;
    fprintf(fichero, "%i", actual);
    fclose(fichero);
    return actual;
}

main()
{
    int	valor;
    
    printf("Content-Type: text/html\r\n\r\n");
    valor=cuenta();

    printf("<html><head><title>Los accesos a esta pagina se contabilizan</title></head>\n");
    printf("<body><h1>Esta p&aacute;gina es resultado de un CGI</h1>\n");
    printf("<p>Se han realizado %i accesos a esta p&aacute;gina\n", valor);
    printf("</body></html>\n");

}

Cuestiones:
En determinadas circunstancias este mecanismo de contador puede llevar mal la cuenta de accesos. ¿Cuándo puede fallar? (Pista: sección crítica). ¿Cómo lo arreglaría?

Cree un script de shell que haga lo mismo que ese programa C.

C. Minicontador con una imagen

A continuación vamos a ver que el resultado de un CGI no tiene porqué ser un fichero HTML sino que puede ser un documento de cualquier otro tipo. El programa siguiente es parecido al contador pero va a mandarle al navegador tan solo el último dígito de la cuenta de accesos y lo va a hacer como una imagen en formato GIF. Para que funcione consiga un conjunto de ficheros GIF con los dígitos decimales y guárdelos en un directorio con nombres 0.gif, 1.gif, 2.gif ... (google es un gran amigo, si no pruebe:
http://www.uni-ulm.de/graphics/icons/Numbers/index.html) Coloque el path a ese directorio en DIR_IMAGENES y modifique también NOM_FICHERO. Compile y pruebe el CGI.

#include <stdio.h>

#define	NOM_FICHERO	"/opt3/lir/lir/contador.dato"
#define DIR_IMAGENES	"/opt3/lir/lir/numeros"

int cuenta()
{
    FILE	*fichero;
    int		actual;
    
    fichero=fopen(NOM_FICHERO, "r");
    if (fichero==NULL) actual=0;
    else
    {
	fscanf(fichero, "%i\n", &actual);
	fclose(fichero);
    }

    actual++;
    fichero=fopen(NOM_FICHERO, "w");
    if (fichero==NULL) return -1;
    fprintf(fichero, "%i", actual);
    fclose(fichero);
    return actual;
}

main()
{
    int	valor;
    char nombre[200];
    
    valor=cuenta();
    if (valor==-1)
    {
	printf("Content-Type: text/html\r\n\r\n");
	printf("<html><head><title>Error</title></head><body></body></html>\n");
    }
    else
    {
	FILE	*fich;
	char	buf[1000];
	int	leido;
	printf("Content-Type: image/gif\r\n\r\n");
	fflush(stdout);
	sprintf(nombre, "%s/%i.gif", DIR_IMAGENES, valor%10);
	execl("/bin/cat", "cat", nombre, NULL);
    }
}

Modifique el script de shell del apartado anterior para que haga lo mismo que este programa en C.

Nota: Una forma de calcular en un script cuál es el último dígito del número que hay en la variable cuenta es con:

digito=`echo ${cuenta}%10 | bc`

Cuestiones:
¿Cómo calcula ese comando el último dígito?

Checkpoint
Muestren al profesor de prácticas que les funciona ese CGI en Shell

D. Imagen como resultado de un CGI

Hasta ahora hemos visto CGIs que se ejecutan al solicitar el usuario un URI directamente con el navegador. Pero si la página web que el navegador carga contiene imágenes y tiene configurado cargarlas automáticamente (si no habrá alguna forma de indicarle que las cargue) entonces el navegador solicitará al servidor web esos ficheros sin que el usuario tenga que hacer nada. Lo que podemos hacer es substituir el URI de un fichero de imagen por un CGI. Como resultado, cuando nuestro navegador cree que está solicitando una imagen en realidad está solicitando un CGI. Ahora nuestra tarea es hacer que ese CGI mande como resultado una imagen. Por ejemplo, en una página web podemos tener:

<img src="/cgi-bin/sacauna.cgi">

Y el CGI puede ser algo tan simple como:

#!/bin/csh
echo "Content-Type: image/gif"
echo ""
cat miimagen.gif

Compruebe que funciona e intente crear un script (o un programa) que mire en un directorio en concreto que tiene configurado y entregue uno de los GIFs que encuentre ahí al azar (podrían ser por ejemplo banners o anuncios) (No hace falta que la generación del "azar" sea perfecta).

Checkpoint
Muestren ese CGI al profesor de prácticas y expliquen cómo lo han hecho

4. Server-Side Includes (SSI)

Si quisiéramos por ejemplo mostrar la fecha en todas nuestras páginas HTML podríamos crear un CGI para cada documento que creara la página (tal vez sacándola de otro fichero) y le añadiera la fecha por ejemplo al final de la página. Claramente esta no es una solución práctica.

Para resolver este tipo de problemas podemos emplear lo que se llaman Server-Side Includes. Estos nos permiten marcar un punto del documento HTML con un tag especial y hacer que un CGI sustituya ese tag por su resultado (que en nuestro ejemplo sería la fecha).

Sin embargo los SSI hacen que el tiempo de respuesta del servidor sea mayor porque tiene que revisar cada documento buscando SSIs y procesándolos.

En la siguiente página tiene un tutorial sobre SSI y cómo configurar Apache para poder emplearlos:
http://www.tlm.unavarra.es/~daniel/docencia/lir/lir03_04/manuales/httpd/howto/ssi.html

Como puede ver en el tutorial hay dos formas principales de indicarle al servidor que un documento HTML debe ser procesado en busca de SSIs:

El formato de los SSIs es:

<!--#comando tag1="valor1" tag2="valor2" -->
Donde <!-- es el indicador de comienzo de un comentario en HTML y --> el final del mismo. De aquí los pares tag, valor son parámetros para el comando. El comando puede tomar varios valores diferentes, entre ellos:

A. Contador con SSI

Vamos a crear el contador anterior como un SSI. Active que el servidor Web reconozca los ficheros a procesar con la extensión .shtml y actívelo en un directorio solo con un fichero .htaccess adecuado. En ese directorio coloque una página web como esta:

<html>
<head><title>Pagina con contador</title></head>
<body>
Aqui va el contenido de la pagina
<p>
<hr>
Acceso numero <!--#exec cmd="progcuenta" -->
</body>
</html>

Modifique el programa en C de contador que hicimos antes para que sea el programa que ejecutamos en ese SSI. Ahora ya no hace falta que indiquemos un Content-Type.

Mediante un SSI haga que salga una imagen aleatoria en el comienzo de una página Web.

Cuestiones:
Vea el código HTML que recibe el navegador. ¿Queda algún indicador de que se ha ejecutado un CGI?

Checkpoint
Muestren esa página con una imagen aleatoria mediante un SSI al profesor de prácticas y expliquen cómo lo han resuelto

B. Trabajo opcional

Creen una página web donde mediante un SSI aparezca una imagen aleatoria de entre las existentes en un directorio y que además al hacer click sobre ella sea un enlace a una página web diferente según cuál sea la imagen seleccionada (por ejemplo en un fichero se puede tener guardado el enlace que corresponde a cada imagen). El objetivo es que sea sencillo incluir un banner aleatorio en cualquier página que se cree.

Si lo terminan muéstrenselo al profesor de prácticas y puede que obtengan un checkpoint adicional.

5. Conclusiones

Hemos visto cómo configurar Apache para permitir la ejecución de CGIs y hemos creado varios, empleando diferentes lenguajes con diferentes tipos de salida. Hemos visto también cómo insertar CGIs en una página. Más adelante veremos que PHP es una evolución del conceptor de los SSIs. Sin embargo, todos los CGIs que hemos creado carecían de información de entrada por parte del usuario. En otras prácticas veremos cómo puede el CGI acceder a información indicada por el usuario.


Depto. Automática y computación
Universidad Pública de Navarra
Daniel Morató
daniel.morato@unavarra.es