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

41311 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.

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).

#!/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.

#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.

#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?

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);
    }
}

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

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

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

4. Server-Side Includes (SSI) [opcional]

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.

<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>

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

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

B. Más publicidad en el mundo [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.

Checkpoint opcional
Muéstrnselo al profesor de prácticas y expliquen cómo lo han resuelto

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