Servidor Web 1

12
 Práctica 5: Servi dor web concu rrente en Java Esta práctica pretende familiarizar al alumno con la programación de servidores que emplean sockets TCP. Para ello partiremos del servidor web  básico visto como ejemplo en las clases de teoría (incluido como anexo al final del capítulo), y lo modificaremos para obtener un servidor web concurrente, capaz de admitir conexiones simultáneamente con varios clientes.  Nuestro ser vidor seguirá la vers ión 1.0 del protocolo HTTP definida en el RFC 1945 ( http://www.ietf.org/rfc/rfc1945.txt ), aunque no permitirá todas sus posibilidades de intercambio de información, sino sólo un subconjunto reducido de las mismas. Como ya hemos estudiado, esta versión emplea conexiones no persistentes, es decir, el cliente realiza una conexión TCP distinta para cada uno de los objetos que componen la  página web. Por lo tanto, c ada petición se tratará de forma independiente y no es necesario mantener información de estado de una petición a la siguiente, lo que simplifica la implementación del servidor. Por otra parte, nuestro servidor debe ser capaz de atender varias peticiones concurren- temente, por lo que debe ser multihilo. Esto nos permitirá estudiar un ejemplo sencillo de servidor concurrente y ver las diferencias en la estructura con respecto al caso iterativo.  

Transcript of Servidor Web 1

Page 1: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 1/12

Práctica 5: Servidor web concurrenteen Java

Esta práctica pretende familiarizar al alumno con la programación de

servidores que emplean sockets TCP. Para ello partiremos del servidor webbásico visto como ejemplo en las clases de teoría (incluido como anexo al

final del capítulo), y lo modificaremos para obtener un servidor web

concurrente, capaz de admitir conexiones simultáneamente con varios

clientes.

Nuestro servidor seguirá la versión 1.0 del protocolo HTTP definida

en el RFC 1945 (http://www.ietf.org/rfc/rfc1945.txt), aunque

no permitirá todas sus posibilidades de intercambio de información, sino

sólo un subconjunto reducido de las mismas. Como ya hemos estudiado,

esta versión emplea conexiones no persistentes, es decir, el cliente realiza

una conexión TCP distinta para cada uno de los objetos que componen la

página web. Por lo tanto, cada petición se tratará de forma independiente y

no es necesario mantener información de estado de una petición a la

siguiente, lo que simplifica la implementación del servidor. Por otra parte,

nuestro servidor debe ser capaz de atender varias peticiones concurren-

temente, por lo que debe ser multihilo. Esto nos permitirá estudiar un

ejemplo sencillo de servidor concurrente y ver las diferencias en la

estructura con respecto al caso iterativo. 

Page 2: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 2/12

P5-2 Prácticas de Redes de Computadores

En los siguientes apartados abordaremos diferentes aspectos que

deben resolverse para la correcta implementación del servidor propuesto.

1. Clases para comunicaciones

Como sabemos el servicio de web utiliza para transferir sus mensajes

el protocolo de nivel de transporte TCP. Por ello, en este apartado veremos

las clases básicas para comunicar un cliente y un servidor mediante sockets 

TCP. La información que proporcionamos sobre las clases java que hay que

emplear y sus métodos es bastante limitada. Para ver los detalles es

aconsejable consultar la página web de Sun con información sobre java

(http://java.sun.com/javase/6/docs/api). También puede ser de

interés consultar el documento:

http://www.ulpgc.es/otros/tutoriales/java.

Como mínimo necesitaremos dos clases pertenecientes al paquete

java.net.*, las clases Socket y ServerSocket. 

1.  Socket permite establecer una conexión entre un cliente y un

servidor TCP. El cliente crea un socket  e inicia la conexión con el

servidor. Una vez conectado al otro extremo utiliza este socket  para el

envío y la recepción de información.

2.  ServerSocket permite a un servidor escuchar en un puerto a la

espera de una conexión TCP. Al establecerse ésta, en el programa

servidor se crea un objeto de la clase Socket que se emplea para el inter-

cambio de información posterior entre ambos extremos.

Nuestro servidor esperará las conexiones de los clientes escuchando

en un puerto previamente determinado. Como nuestro proceso no tiene

privilegios de administrador, el puerto con el que trabaje el servidor deberá

ser uno mayor que el 1023. Por ejemplo, podemos elegir el 8000.

Para esperar la conexiones hay que invocar el método accept() dela clase ServerSocket. Este método bloquea la ejecución del programa a

la espera de una conexión. Al recibir una petición, accept() crea un nuevo

objeto de la clase Socket, que se usará durante el resto de la comunicación

con el cliente.

El empleo del método accept() puede generar una excepción

IOException, por lo que, o bien habrá que capturarla mediante una cláu-

sula try, tal y como se hizo en la práctica anterior, o bien puede lanzarse

(throws) al programa o función que llamó al método que genera la

Page 3: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 3/12

Servidor web concurrente en Java P5-3

excepción. De esta manera no será necesario programar una cláusula try.

En nuestro caso, dado que es la máquina virtual Java quien llamó al método

 main, esta excepción se lanzará hacia ella.

2. Gestión de la entrada/salida

Una vez conectado con un cliente, con el fin de comprobar y depurar

el funcionamiento del servidor, éste VISUALIZARÁ POR PANTALLALAS CADENAS DE CARACTERES que reciba del cliente. Para manejar

la transferencia de información a través del socket , la clase Socket dispone

de dos métodos: getInputStream() y getOutputStream(), que pro-

porcionan un flujo de entrada y uno de salida, respectivamente. Como ya

vimos en una práctica anterior, es mejor no manejar estos flujos

directamente, sino anidados a través de otras clases como Scanner y

PrintWriter y, al igual que hicimos allí, serán las que empleemos.

A continuación se muestra, de forma breve, un ejemplo de uso de

estas clases y algunos de sus métodos:Scanner recibe = new Scanner (cliente.getInputStream());

System.out.println(recibe.nextLine());

PrintWriter envia = new

PrintWriter(cliente.getOutputStream());envia.println(“GET / HTTP/1.0”); 

donde cliente es un objeto de la clase Socket conectado con un cliente.

3. Descripción del protocolo e implementación del servidor

El servidor que vamos a realizar sigue la versión 1.0 del protocolo

HTTP. No obstante, nuestro servidor no implementará todo el protocolo,

sino únicamente un subconjunto de éste. En este apartado vamos a explicar

qué parte implementaremos. Para ello necesitamos conocer, qué peticiones

debe ser capaz de servir y cómo responderá a esas peticiones.

HTTP emplea dos tipos de mensajes: peticiones de los clientes a los

servidores y respuestas de los servidores a los clientes. Ambos poseen una

estructura similar formada por tres campos: una línea inicial que se incluye

siempre, a continuación puede haber una o más cabeceras (en la versión 1.0

ninguna es obligatoria), y por último, un cuerpo que no siempre estará

presente. Tras finalizar las cabeceras (o después de la línea inicial, en el

Page 4: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 4/12

P5-4 Prácticas de Redes de Computadores

caso de que no haya ninguna cabecera) debe haber una línea en blanco,

formada por los caracteres ASCII: CR y LF (retorno de carro y salto de

línea, respectivamente).

Mensaje de petición del cliente

En los mensajes de petición la línea inicial contiene la orden deseada

por el cliente. Aunque el estándar especifica varias órdenes posibles, la más

habitual es la orden GET, que será la única que implementaremos. Esta

orden permite al cliente solicitar objetos almacenados en el servidor.

Comienza con la palabra reservada GET, le sigue el objeto solicitado por elcliente y finaliza con la versión del protocolo empleado. En el caso de la

versión 1.0 tiene la forma:

GET objeto HTTP/1.0 CR LF CR LF 

Donde, como se ha indicado, CR LF son los caracteres de retorno de carro y

salto de línea, respectivamente. Por ejemplo, para poder obtener la página

web correspondiente a http://www.upv.es/index.html, un

navegador se conectará al puerto 80 del servidor www.upv.es y enviará la

orden: 

GET /index.html HTTP/1.0

← línea en blanco enviada también por elnavegador. 

Nuestro servidor sólo analizará la línea inicial de los mensajes de

petición y no se preocupará del resto. Para interpretar la línea de petición, el

servidor necesita analizar cada una de las palabras que la componen. Esta

tarea podemos resolverla fácilmente mediante la clase java Scanner que

nos permite leer el siguiente elemento (String) de un flujo de entrada,

mediante el método next(). Como ya hemos mencionado, la descripción

de dicha clase se puede consultar en la página web de Sun.

Como nosotros sólo vamos a implementar la orden GET, nuestro

servidor sólo tiene que comprobar si la primera palabra de la cadena es

“GET”, y en caso afirmativo identificar qué fichero ha solicitado el cliente.

Para comprobar que la petición es un GET podemos emplear cualquiera de

los métodos que tiene la clase String para comparar dos objetos de ese

tipo, como equals, equalsIgnoreCase o startsWith. Si el servidor

tiene el fichero solicitado (y el cliente dispone de los permisos necesarios

para acceder a él), el servidor debe enviárselo al cliente, y en caso contrario

responder con un mensaje de error.

Page 5: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 5/12

Servidor web concurrente en Java P5-5

Mensaje de respuesta del servidor

La respuesta a una petición GET incluye los tres campos que hemos

mencionado antes, y es necesario que nuestro servidor los emplee todos.

Veamos que información debemos poner en cada uno de ellos:

Campo 1: la línea inicial

En el caso de la respuesta de un servidor esta línea se denomina línea

de estado y emplea el formato siguiente:

versión num cadena

donde versión es la versión del protocolo utilizada, num es un códigonumérico de tres cifras que expresa el resultado de la petición realizada por

el cliente, y por último, cadena es una cadena de caracteres que también

indica el resultado de la operación pero ahora en un formato fácilmente

comprensible por los programadores. Algunos de los valores de los últimos

dos campos son:

La línea de estado típica de la versión 1.0 para indicar que se envía el objeto

pedido por el cliente es:

HTTP/1.0 200 OK

Si, por el contrario, el servidor no dispone del objeto solicitado responderá

con:

HTTP/1.0 404 Not Found

Estas son las dos líneas de estado que nuestro servidor debe implementar.

Campo 2: las líneas de cabecera

Aunque la versión 1.0 no establece ninguna cabecera obligatoria lo

habitual es que los servidores envíen varias líneas de cabecera en sus

respuestas. Estas líneas permiten a los servidores incluir información sobre

Num  Cadena 

200 OK

304 Not Modified

400 Bad Request

401 Unauthorized

404 Not Found

501 Not Implemented

Page 6: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 6/12

P5-6 Prácticas de Redes de Computadores

el objeto que van a enviar en el cuerpo del mensaje, así como avisar al

cliente en el caso de vayan a cerrar la conexión una vez finalizado el envío

del mensaje al cliente. El orden de las distintas cabeceras es irrelevante.

El formato de las líneas de cabecera es:

Etiqueta: valor

donde la Etiqueta indica el tipo de información que se envía y valor su valor

asociado. Las líneas siguientes muestran algunas líneas típicas de cabecera: 

Content-Type: text/html

Content-Length: 3453Content-Encoding: x-gzipDate: Tue, 15 Nov 2004 08:12:31 GMTExpires: Tue, 12 Oct 2005 06:22:41 GMT 

Nuestro servidor deberá enviar, al menos, las líneas de cabecera del

Content-Type y el Content-Length. La primera de ellas indica el tipo

MIME del objeto que se va a enviar. Por ejemplo, un fichero *.htm  tiene

como tipo MIME text/html. Algunos de los tipos MIME más usados

pueden encontrarse en la siguiente tabla:

Extensión del fichero Tipo MIME

.htm .html text/html

.txt text/plain

.gif image/gif

.jpg .jpeg image/jpeg

.pdf application/pdf

* application/octet-stream

La clase String posee el método endsWith(String) que podemos

utilizar para comprobar la extensión del fichero. En el caso en el que elservidor no pueda asociar la extensión del fichero con una de las específicas

que tiene definidas, empleará como tipo por defecto el último mostrado en

la tabla: application/octet-stream. En nuestro caso, hay que implementar

como mínimo los tipos: text/html, image/gif y application/octet-stream.

Por otra parte, la etiqueta Content-Length hace referencia al tamaño

del objeto que se va a enviar. Para calcular ese tamaño se puede utilizar la

Page 7: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 7/12

Servidor web concurrente en Java P5-7

clase File  (perteneciente al paquete java.io.*) ya que uno de sus

métodos (length()) permite obtener este dato.

Por último, las líneas de la cabecera y el cuerpo están separados mediante

una línea en blanco, que en nuestro caso se puede generar con el método

 println() de la clase PrintWriter.

Campo 3: el cuerpo de mensaje

El cuerpo es el objeto que había solicitado el cliente. El servidor tiene

que buscarlo en su sistema de ficheros local, para lo que puede utilizar la

clase FileInputStream . Al constructor de esta clase se le pasa como

parámetro el nombre del fichero (o bien un objeto de la clase File) y, si no

existe generará la excepción FileNotFoundException. Capturando dicha

excepción podemos averiguar si existe o no el fichero.

Como lo habitual es que las peticiones vengan de un navegador, éste

intentará visualizar cualquier respuesta del servidor. Por ello, incluso

aunque no haya encontrado el objeto solicitado y devuelva un mensaje de

error, el servidor suele incluir una página html informando del suceso.

Nuestro servidor también debe seguir ese comportamiento habitual, y

aunque el fichero no exista devolverá una respuesta completa: línea deestado, cabecera y cuerpo. En este caso la cabecera identificará al cuerpo

como text/html y el cuerpo será un mensaje de error en formato html como

el siguiente: 

<html><body><h1>404 Not Found</h1></body></html>

Ejercicio 1:

Completa el servidor web del anexo para que se ajuste a la versión 1.0 del

protocolo HTTP conforme a lo descrito en este apartado. Respecto a los tipos

MIME, sólo es necesario implementar los tipos text/html, image/gif  y

application/octet-stream. En caso de recibir una orden diferente a GET, el

servidor debe contestar con la línea de estado ”HTTP/1.0 501 Not Implemented”.Por lo tanto, los cambios que hay que realizar con respecto al servidor del anexo

son:

1.  Comprobar que la petición es un “GET”.

2.  Añadir la línea de estado (HTTP/1.0 …).

3.  Comprobar la extensión del fichero y añadir las cabeceras correspondientes,

relacionadas con su extensión y su longitud (Content-Length y Content-

Type).

Page 8: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 8/12

P5-8 Prácticas de Redes de Computadores

Recuerda que el servidor debe visualizar por pantalla todo lo que reciba del

cliente. 

4. Comprobación del funcionamiento del servidor

Una vez que hayamos programado el servidor web necesitamos una

página de muestra para comprobar que nuestro servidor atiende

correctamente las peticiones de sus clientes. Para ello podemos utilizar la

web de la asignatura de redes, por ejemplo. La copiamos a nuestro

ordenador local mediante la instrucciónwget –r –nH  http://www.redes.upv.es/redes/

desde el directorio donde esté nuestro programa.

A continuación, lanzaremos el servidor en nuestro ordenador local y

nos conectaremos a él mediante el navegador, usando el URL siguiente:http://localhost:8000/redes/index.html

Se supone que, como se ha recomendado en el apartado 1, el servidor

escucha en el puerto 8000. En otro caso, habría que sustituir el 8000 que

aparece en el URL por el número de puerto correspondiente. Si el proceso

servidor pudiera lanzarse con privilegios de administrador y escuchar en el

puerto 80 (puerto estándar para el servicio web) no sería necesario indicar elnúmero de puerto en el URL del navegador.

Si nuestro servidor no funciona correctamente al primer intento, puede

resultar útil para realizar la depuración emplear el programa telnet. Si

tecleamos:

  telnet localhost 8000

  GET /redes/index.html http/1.0

  CR LF

Veremos en pantalla, a continuación, la respuesta del servidor y podremoscomprobar si es la esperada.

5. Implementación de la concurrencia

Dado que el servidor debe ser capaz de atender varias peticiones

simultáneamente, vamos a convertir el servidor iterativo en un servidor

concurrente usando varios hilos de ejecución. En el hilo principal, el

servidor permanecerá a la espera de que se conecte algún cliente. Cuando se

Page 9: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 9/12

Servidor web concurrente en Java P5-9

reciba una petición de conexión TCP, el servidor aceptará la conexión y se

la pasará a un nuevo hilo para ser atendida mientras el hilo principal del

servidor queda a la espera de nuevos clientes.

El hilo principal tendrá un aspecto parecido al siguiente: 

 public class ServidorWebConcurrente {

 public static void main(String argv[]) throws

UnknownHostException, IOException {

int puerto=8000;

ServerSocket servidor=new ServerSocket(puerto);

while (true) {

//Espero un cliente

Socket cliente=servidor.accept();

// Código para lanzar un hilo que atienda la petición

// Hay que pasarle el socket “cliente” al hilo 

}

}

Java ofrece varias posibilidades para crear un hilo de ejecución. La

más sencilla es declarar una subclase de la clase Thread. Cada objeto deesta subclase permite lanzar un nuevo hilo de ejecución. La sintaxis para

crear una nueva subclase a partir de la clase Thread es:

class PeticionHTTP extends Thread

La clase Thread (y por extensión la subclase creada a partir de ella)

siempre incluye un método run() que contiene el código que debe ejecu-

tarse concurrentemente con el resto del programa.

Poniendo juntos todos estos detalles, nuestra nueva clase tendrá un

aspecto parecido a: 

class PeticionHTTP extends Thread {

//atributos...

 public PeticionHTTP(Socket s) {

// Código a ejecutar durante la creación del hilo

}

 public void run() {

// Código del nuevo hilo que atiende al cliente http

Page 10: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 10/12

P5-10 Prácticas de Redes de Computadores

}

}

Después de haber visto cómo se declara una subclase de la clase

Thread, vamos a ver cómo utilizarla. El primer paso es crear un objeto de

la subclase nueva, en nuestro ejemplo, la clase PeticionHTTP. A conti-

nuación hay que arrancar el hilo. Esto se hace invocando el método

start(), que a su vez llamará al método run().

PeticionHTTP pethttp=new PeticionHTTP(); pethttp.start(); 

En nuestro caso, cada objeto de la clase PeticionHTTP deberáatender una petición HTTP a través de la conexión TCP que se acaba de

realizar tras la ejecución del método accept(). El socket devuelto por el

método accept()se crea en el hilo principal, pero debe ser accesible desde

los nuevos hilos que se van lanzando. Esto se puede hacer a través del

constructor de la clase PeticionHTTP, pasándole como parámetro el

socket conectado con el cliente. 

El hilo principal creará un nuevo objeto de la clase PeticionHTTP,le pasará como parámetro el socket  conectado con el cliente, y después

invocará el método start():

PeticionHTTP pethttp=new PeticionHTTP(cliente); pethttp.start(); 

Ejercicio 2:

Copia el servidor web iterativo del ejercicio anterior a un fichero llamado

”ServidorWebConcurrente.java”. Modifica este nuevo programa para

convertir el servidor iterativo en servidor concurrente.

6. Anexo

En este anexo se incluye el código del micro-servidor web iterativo

visto en clase. Las principales diferencias entre este servidor y el que hay

que realizar en esta práctica son dos. La primera es que el servidor mostrado

como ejemplo más abajo no sigue el estándar HTTP/1.0, ya que no devuelve

siempre la línea de estado ni la cabecera de respuesta. La segunda diferencia

es que no es concurrente.

import java.net.*;

import java.util.Scanner;

Page 11: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 11/12

Servidor web concurrente en Java P5-11

import java.io.*;

class ServidorWebIterativo { 

 public static void main(String args[]) throws UnknownHostException, IOException {

byte[] buffer = new byte[1024];

int bytes;

int Puerto=8000;

ServerSocket servidor=new ServerSocket(puerto);

while(true) { 

// espero a que venga un clienteSocket cliente=servidor.accept();

// nos aseguramos de que el fin de línea se ajuste al estándar

System.setProperty("line.separator","\r\n");

Scanner lee=new Scanner (cliente.getInputStream());

PrintWriter escribe=new

PrintWriter(cliente.getOutputStream(),true);

// esto debe ser el "GET"

lee.next();

// esto es el fichero

String fichero = "." + lee.next();

// comprobamos si existe

FileInputStream fis = null;

boolean existe = true;

try { 

fis = new FileInputStream(fichero);

catch (FileNotFoundException e) {

existe = false;

}

if (existe && fichero.length()>2)

while((bytes = fis.read(buffer)) != -1 ) // enviar fichero

cliente.getOutputStream().write(buffer, 0, bytes);

else {escribe.println("HTTP/1.0 404 Not Found");

escribe.println();

cliente.close();

}

}

}

Page 12: Servidor Web 1

5/10/2018 Servidor Web 1 - slidepdf.com

http://slidepdf.com/reader/full/servidor-web-1 12/12

P5-12 Prácticas de Redes de Computadores