Taller: Cliente HTTP en Java
Objetivos
•
Desarrollar un cliente básico, en Java, del protocolo web que permita enviar una solicitud
HTTP a un servidor web y procesar la respuesta.
Recursos requeridos para el taller
Antes de iniciar el taller debe leer los siguientes documentos disponibles en la página del curso
(ver panel lateral):
1. Guía HTTP: https://goo.gl/0ALs9U
2. Uso de telnet para HTTP: https://goo.gl/zlujpr
El proyecto base para el taller se encuentra disponible en la página del curso (ver panel a la
izquierda), o en la siguiente URL de forma directa: https://goo.gl/zLSL0N
Requerimientos de la aplicación:
Usted debe implementar in cliente básico HTTP, extendiendo el proyecto base proporcionado.
Su cliente básico HTTP debe:
- Obtener la información de una URL que se usará para hacer la petición web. La URL es
ingresada por el usuario a través de una GUI en Java.
- Abrir una conexión al servidor (dominio) ingresado por el usuario, por medio de un
socket HTTP.
- Escribir en el socket (enviar al servidor) un mensaje de petición HTTP por medio de un
PrintWriter del OutputStream sobre el socket
- Leer el mensaje de respuesta, realizar un procesamiento mínimo de los encabezados y
descargar en un archivo el cuerpo de la respuesta
Diagrama de clases
Como propuesta de solución se ha definido un conjunto de clases para procesar las peticiones.
HttpClient
Es la clase que coordina (controla) el funcionamiento de la
aplicación. Básicamente, esta clase realiza la conexión por un socket
al servidor web, invoca al RequestProcessor para enviar el mensaje
de solicitud al servidor e invoca al ResponseProcessor para obtener
y procesar el mensaje de respuesta.
RequestProcessor
Esta clase envía el mensaje de solicitud al servidor web. El método
sendRequestMessage recibe como parámetro un OutputStream
que es usado para enviar la solicitud.
ResponseProcessor Esta clase obtiene y procesa el mensaje de respuesta. El método
processResponse recibe como parámetro un InputStream que es
usado para leer el mensaje.
Instrucciones Paso a paso
1. El cliente se debe conectar al servidor por medio de un socket.
Cliente
Socket
Servidor
2. El usuario debe indicar la petición HTTP que el cliente envía al servidor.
Cliente
Servidor
Petición
HTTP
3. El cliente retorna como respuesta un mensaje HTTP con las siguiente estructura:
HTTP/1.1 200 OK
Content-Type: image/jpeg
.
.
.
.
1..n campos de encabezado
<CRLF>
<Cuerpo del mensaje>
4. El programa debe leer el mensaje de respuesta, byte por byte, usando un buffer de ntrada.
Cada byte del buffer representa un carácter. Como el objetivo es descargar únicamente el
cuerpo del mensaje de respuesta, el programa puede importar todos los encabezados de la
respuesta. El programa puede buscar el renglón vacío para reconocer el final de los
encabezados.
a. Los caracteres de fin de línea (es decir, los caracteres <CR><LF>) pueden leerse
en java usando el texto “\r\n”. Para identificar el fin de los encabezados, el
programa puede buscar el punto en donde esta secuencia se encuentre dos veces
(es decir, el final de los encabezados del mensaje y el renglón vacío). A partir de
ese punto, el texto corresponde con el cuerpo del mensaje que se debe guardar.
5. El programa debe guardar el cuerpo del mensaje en un archivo local, con el nombre que
indique el usuario.
Creación del proyecto
Estructura general del proyecto
El proyecto base se entrega con la siguiente estructura:
HttpClient
|
---src
|
|
|
------client
|
|
|
|
|
-------HttpClient.java
|
|
|
|
|
-------RequestProcessor.java
|
|
|
|
|
-------ResponseProcessor.java
|
|
|
---------ui
|
|
|
|
|
-------ClientUI.java
|
---JRE System Library
|
---descarga
|
---respuestas-guia-telnet
La clase RequestProcessor.java
La responsabilidad de esta clase es enviar, por medio del socket, la petición al servidor web. Ésta
clase es un Singleton, tiene un atributo donde vamos a guardar el histórico de peticiones realizadas
desde el cliente web. Para enviar la petición contamos con el método sendRequestMessage que
recibe como parámetros el OutputStream del socket, el mensaje y el host. Complete el método
con las siguientes instrucciones:
public void sendRequestMessage(OutputStream output, String message, String host)
throws IOException{
output.write((message+"\r\n").getBytes());
output.write(("Host:"+host+"\r\n").getBytes());
output.write("\r\n".getBytes());
output.write("\r\n".getBytes());
output.flush();
historic.add(host + " - " + message);
}
La clase ResponseProcessor.java
La responsabilidad de esta clase es procesar la respuesta del servidor, la clase es un Singleton y
procesa la respuesta por medio del método RequestProcessor que recibe como parámetros el
InputStream y el nombre del archivo donde se quiere guardar el cuerpo del mensaje.
La idea del proceso es leer el archivo Byte por Byte, debe buscar la secuencia “\r\n\r\n” que indica
el fin del encabezado del mensaje y el inicio del cuerpo; el cuerpo es el recurso solicitado, que
debe guardar en un archivo local con el nombre ingresado por el usuario. El archivo debe ser
ubicado en la carpeta “descarga” en la raíz del proyecto. Complete el método con las siguientes
instrucciones:
public File processResponse(InputStream input, String fileName) throws IOException{
byte[] buffer = new byte[1024];
File file = new File("descarga/"+fileName);
BufferedOutputStream bOutput
= new BufferedOutputStream(new FileOutputStream(file));
int tReaded = 0;
int tArchivo = 0;
int tReadedi = 0;
int tPatterns = 0;
int tWaitedChar = '\r';
boolean inContent = false;
//En cada paso del ciclo lee la entrada y lo guarda en el array buffer,
//el ciclo sigue si el numero de bytes es mayor que 0
while((tReadedi = input.read(buffer))>0){
if(inContent){
// A este bloque entra una vez ha encontrado
// el inicio del mensaje.
bOutput.write(buffer,0,tReadedi);
tArchivo += tReadedi;
}
else {
//buscamos en el buffer el patron '\r\n\r\n'
// y guardamos en caso de encontrarlo
for(int i=0; i<tReadedi; i++){
int chari = buffer[i];
System.out.println((int) chari);
if(chari==tWaitedChar){
if(chari=='\r'){
tWaitedChar='\n';
}
else if(chari=='\n'){
tPatterns++;
tWaitedChar='\r';
}
inContent = tPatterns==2;
if(inContent){
//Entrar a esta parte significa
// que encontramos '\r\n\r\n'
//Guardamos el buffer desde la posición i+1
bOutput.write(buffer,i+1,tReadedi-(i+1));
bOutput.flush();
tArchivo+=tReadedi-(i+1);
break;
}
}
else {
tPatterns = 0;
tWaitedChar = '\r';
}
}
}
tReaded += tReadedi;
buffer = new byte[1024];
if(input.available()<=0) break;
try{
Thread.sleep(10);
}
catch(Exception ex){
ex.printStackTrace();
}
}
bOutput.close();
return file;
}
La clase HttpClient
La responsabilidad de esta clase es gestionar la conexión con el servidor y coordinar el envío del
mensaje y la recepción de la respuesta. En su constructor recibe el host y el puerto de conexión
para iniciarla.
El método processRequest recibe como parámetros el mensaje de la petición, el host y el nombre
del archivo, gestiona las clases RequestProcessor y ResponseProcessor para guardar el cuerpo
de la petición y retorna como respuesta el archivo guardado. Usted debe completar el método
HttpClient.processRequest. Recuerde, que los flujos (OutputStream e InputStream)
requeridos por las clases RequestProcessor y ResponseProcessor , se obtienen del socket creado
con los datos ingresados por el usuario1.
La clase ClientUI2.java
Agregue al Proyecto una nueva clase de interfaz que (i) recibe una URL completa; (ii)
automáticamente extrae el host, puerto, y recurso de la URL; (iii) construye la petición y el
mensaje con la información extraída de la URL. Reuse todo lo que pueda de la clase
ClientUI.java
Instrucciones para la entrega
Genere un zip de la carpeta raiz del Proyecto. El zip debe ser envíado via SICUA a través del
enlace correspondiente en la carpeta “Tareas”. Este Zip debe incluir también sus respuestas para
la guía de cliente telnet.
Bonos
Se darán puntos adicionales por las siguientes funcionalidades:
-
Visor de historial
Implementar el cliente pero usando un Apache HTTPClient en vez de un socket.
En el caso de implementar alguna de las funcionalidades adicionales, por favor incluir in archivo
README indicando las funcionalidades implementadas y como se ejecutan.
1
Ver: http://docs.oracle.com/javase/tutorial/networking/sockets/readingWriting.html