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