Está en la página 1de 5

Laboratorio de Sistemas Operativos Curso 2020/2021

LABORATORIO DE SISTEMAS OPERATIVOS


(Curso 2020/2021)

PRÁCTICA 2
Servidor web multiproceso – Ejemplo

Objetivos del documento


Este documento presenta un ejemplo, en el contexto de desarrollo planteado en el enunciado de la
práctica del servidor web multiproceso, de cómo implementar un servidor web sencillo capaz de atender
a una única petición HTTP efectuada a través de una conexión con un cliente.
El ejemplo se desarrolla mediante un proceso incremental en el que se parte de un programa mínimo que
se limita a sacar un mensaje por pantalla y al que en pasos sucesivos se le van añadiendo llamadas a
diversas funciones de la API de comunicaciones de la práctica con el objetivo de que el alumno vea un
ejemplo de uso de las mismas y comprenda el comportamiento básico de un programa encargado de
gestionar una conexión con un cliente. Solo el último paso realmente responde al cliente web, aunque a
una sola petición; en todos los demás pasos, el programa se limita a mostrar mensajes que pueden servir
para confirmar que hace lo que se esperaba.
Se recomienda que el alumno escriba, compile y ejecute cada uno de los pasos. En los ejemplos de
ejecución, se supondrá que el nombre del ejecutable es servidorWeb.

Paso 1: Leer los parámetros y mostrar información de depuración


#include <stdio.h>
#include <stdlib.h>
#include "param.h"
#include "http.h"

/* Variable global, excepcionalmente. */


static ParametrosServidor param;

int main(int argc, char** argv)


{

/* Procesar los parámetros de configuración */


analizar_linea_mandatos ( argc, argv, &param );

/* El siguiente mensaje saldrá solo si el usuario especificó la opción –g */


if ( param.nivel_depuracion > 0 )
fprintf ( stderr, "\nPuerto del Servidor:: %d\n", param.puerto_escucha );

/* Si quiere, puede imprimir otros campos de la estructura param para


comprobar que tienen los valores esperados. */

printf("FIN DEL PROGRAMA");


return(0);
}

Página 1 -
Laboratorio de Sistemas Operativos Curso 2020/2021

Como se observa, el programa solo saca mensajes informativos si se ejecuta con la opción -g y un valor
mayor que 0. Ejemplo: ./servidorWeb -p 50000 -g 1

Dar al usuario la opción de escoger el nivel de detalle de los mensajes informativos y de depuración con
un parámetro como -g es una buena práctica, porque las necesidades de detalle de un desarrollador y de
un usuario final son muy diferentes. La idea es que, en lugar de usar printf() sin más para mostrar
cualquier mensaje que le parezca interesante al desarrollador, se metan estos mensajes en el cuerpo de
una sentencia if que se ejecutará si el nivel de detalle especificado por el usuario supera un cierto
umbral. Así, el usuario final puede no especificar -g y no se verá abrumado con su pantalla llena de todo
tipo de mensajes que a él no le aportan nada; en cambio, el desarrollador puede poner un valor a -g que
haga que aparezcan los mensajes e incluso jugar con diferentes valores para que ciertos mensajes
aparezcan solo para ciertos valores mínimos de detalle (otra opción típica es que se considere el nivel de
detalle como una máscara de bits y que ciertos mensajes aparezcan solo si están activados ciertos bits en
el nivel de detalle). Como un refinamiento adicional, muchos programas, como el de este ejemplo, sacan
los mensajes de depuración por el canal de error estándar (stderr) y así se pueden separar de los
mensajes de salida estándar del programa.

Paso 2: Activar la escucha en el puerto indicado


#include <stdio.h>
#include <stdlib.h>

#include "param.h"
#include "http.h"

static ParametrosServidor param;

int main(int argc, char** argv)


{
GestorHTTP* gHttp;

/* Procesar los parámetros de configuración */


analizar_linea_mandatos ( argc, argv, &param );

/* Crear gestorHTTP */
if ( ( gHttp = http_crear ( param.puerto_escucha ) ) == NULL ){
fprintf( stderr, "\n....ERROR: No se ha podido crear el gestor" );
exit(1);
}

/* Escribe el descriptor asociado al puerto de escucha. Será un número mayor


o igual que cero. */
if ( param.nivel_depuracion > 0 )
fprintf ( stderr, "\nDescriptor del gestor :: %d\n",
http_obtener_descriptor_escucha ( gHttp ) );

/* destrucción del gestor */


http_destruir ( gHttp );
printf("FIN DEL PROGRAMA");
return(0);
}

Si vuelve a ejecutar el programa de la misma forma que en el paso anterior, obtendrá algún mensaje
adicional que indica si se ha podido crear el gestorHttp para activar la escucha en el puerto indicado.

Página 2 -
Laboratorio de Sistemas Operativos Curso 2020/2021

Paso 3: Aceptar conexión de cliente


#include <stdio.h>
#include <stdlib.h>

#include "param.h"
#include "http.h"

static ParametrosServidor param;

int main(int argc, char** argv)


{
GestorHTTP* gHttp;
ConexionHTTP* cliente;

/* Procesar los parámetros de configuración */


analizar_linea_mandatos ( argc, argv, &param );

/* Crear gestorHTTP */
if ( (gHttp = http_crear ( param.puerto_escucha ) ) == NULL){
fprintf( stderr, "\n....ERROR: No se ha podido crear el gestor");
exit(1);
}

/* Esperar conexión de cliente */


switch ( http_esperar_conexion ( gHttp, &cliente, 1 ) )
{
case HTTP_OK:
if ( param.nivel_depuracion > 0 )
fprintf ( stderr, "\nConexión de Cliente con IP %s establecida.\n",
http_obtener_ip_cliente ( cliente ) );

/* cerrar conexión */
http_cerrar_conexion ( cliente );
break;

case HTTP_SEGUIR_ESPERANDO:
case HTTP_ESPERA_INTERRUMPIDA:
/* En este ejemplo el programa no va a llegar nunca aquí, pero a
medida que avance la práctica, sí será necesario contemplar estos
casos. */
break;

case HTTP_ERROR:
fprintf ( stderr, "\n...ERROR: http_esperar_conexión" );
break;
}

/* destrucción del gestor */


http_destruir ( gHttp );
printf("FIN DEL PROGRAMA");
return(0);
}

Ejecute el programa con los parámetros del primer paso y observe que esta vez el programa se queda
parado en la función bloqueante http_esperar_conexion(). A continuación, desde un navegador, pruebe a
solicitar el recurso con URL http://IP_DEL_SERVIDOR:50000/index.html. Obviamente el
navegador web no recibirá nada e incluso se quejará de que el servidor ha cerrado la conexión
Página 3 -
Laboratorio de Sistemas Operativos Curso 2020/2021

inesperadamente o algo similar, pues el servidor aún no está programado para enviar recursos, pero al
menos debería ver el mensaje del servidor mostrando que acepta la conexión del cliente y cuál es su IP,
tras lo cual el programa finaliza.

Paso 4: Leer petición de cliente


Se muestra únicamente la función que lee la petición del cliente. Se deja que usted vea desde qué punto
del programa tiene que llamarla.
void atender_cliente ( ConexionHTTP* cliente )
{
Peticion* peticion;

/* leer peticion */
switch ( http_leer_peticion ( cliente, &peticion, 1 ) )
{
case HTTP_OK:
if ( param.nivel_depuracion > 0 )
fprintf ( stderr, "Petición: IP=%s, RECURSO: %s\n",
peticion->ip_cliente, peticion->ruta );

http_destruir_peticion ( peticion );
break;

case HTTP_CLIENTE_DESCONECTADO:
case HTTP_ESPERA_INTERRUMPIDA:
/* En este ejemplo el programa no va a llegar nunca aquí, pero a
medida que avance la práctica, sí será necesario contemplar
estos casos. */
break;

case HTTP_ERROR:
fprintf ( stderr, "\n...ERROR: Error http_leer_peticion \n");
break;
}
}

Ejecute el programa con los parámetros del primer paso, y al igual que en el paso anterior, el programa
se quedará a la escucha. Pruebe de nuevo desde un navegador con el mismo URL del paso anterior. Esta
vez deberá obtener, además de los mensajes de los pasos anteriores, un nuevo mensaje que indica el
recurso solicitado desde el cliente, finalizando seguidamente.

Página 4 -
Laboratorio de Sistemas Operativos Curso 2020/2021

Paso 5: Procesar petición de cliente


Se muestra únicamente la función que procesa la petición. Se deja que usted vea desde qué punto del
programa tiene que llamarla.
void atender_peticion ( ConexionHTTP* cliente, Peticion* peticion )
{
char nombre_fichero[MAXPATHLEN];

/* composición de la ruta completa del recurso solicitado como concatenación


del directorio_base y del nombre del recurso. NOTA: este uso de
strcpy/strcat es inseguro. ¿Sabe por qué? */
strcpy ( nombre_fichero, param.directorio_base );
strcat ( nombre_fichero, peticion->ruta );

if ( param.nivel_depuracion > 0 )
fprintf ( stderr, "... Recurso solicitado:: %s\n", nombre_fichero );

enviar_recurso ( cliente, peticion, nombre_fichero );


}

void enviar_recurso( ConexionHTTP* cliente, Peticion* peticion,


const char* nombre_fichero )
{
int fd;

fd = open ( nombre_fichero, O_RDONLY );

if ( fd >= 0 ){
http_enviar_respuesta ( cliente, peticion, fd, 1 );
/* http_enviar_respuesta puede que falle (lo indica mediante el valor
que devuelve). ¿Qué hacemos si ha fallado? */

if ( param.nivel_depuracion > 0 )
fprintf ( stderr, " Fichero %s enviado\n", nombre_fichero );

close ( fd );
}
else {
/* No se ha podido abrir el fichero correspondiente al recurso
solicitado, por lo que habría que consultar errno para saber qué ha
pasado y responder con el código HTTP correspondiente (403, 404...) */
http_enviar_codigo ( ... );
if ( param.nivel_depuracion > 0 )
fprintf ( stderr, " No se ha podido enviar el fichero %s\n",
nombre_fichero );
return;
}
}
Si ha situado correctamente la invocación a estas funciones, puede probar el programa añadiendo esta
vez el parámetro que indica donde están los recursos web. Puede probar con esta invocación:
./servidorWeb -p 50000 -g 1 -b /usr/share/doc/ntp
Si usa un navegador con el URL de los pasos anteriores, esta vez sí obtendrá la página index.html, tras lo
cual el programa finalizará, pues este programa de ejemplo sirve un único recurso del servidor.
Partiendo de esta base, podrá desarrollar la primera fase de la práctica con mayor facilidad.

Página 5 -

También podría gustarte