viernes, 18 de diciembre de 2009

Subir archivos con Java mediante HTTP y tratamiento con PHP

Más de una vez me ha surgido la necesidad de subir varios archivos al servidor asíncronamente y de tal forma que muestre al detalle el total de archivos subidos, progreso, tamaño total ,etc.
Pues bien, en uno de mis experimentos, me decidí a desarrollar mi propio uploader en Java, que es lo que voy a tratar de explicar aquí. Para ello analizaremos los paquetes que son enviados al servidor mediante el protocolo HTTP, oséase al servidor web y así poder implementar dicho protocolo en Java.

La subida de archivos por el protocolo HTTP mediante PHP y un servidor web tal como APACHE, para un programador experimentado es bien conocida, no obstante, vamos a repasar algunos conceptos de cómo se hace con este método tan rudimentario y tan usado por muchos.

upload.html

Tenemos preparado el documento html para la entrada de datos, una vez que se pulsa el botón "Subir!", la petición se redirige al script especificado en el atributo "action", en este caso vamos a parar a "tratar-upload.php".

tratar-upload.php
Bien, ya tenemos todo listo, ahora nos toca analizar cómo hace intrínsicamente la operación de subir archivos. Para ello nos valemos de un analizador de protocolos de red tal como wireshark. Ponemos a la escucha en el interfaz de red pertinente, seleccionamos archivos en el documento html y pulsamos el botón "Subir!".

Análisis de subida de archivos por el protocolo HTTP usando el método POST.

Petición HTTP
Quiero hacer una objeción respecto a -RETORNO DE CARRO + SALTO DE LINEA-. Indica que existen 2 bytes no visibles adicionales para el salto a la siguiente línea. Véase más sobre secuencias de escape, pero para el ejemplo sólo usaremos dos:

Retorno de carro:\r
Salto de línea: \n

Donde se indica "2 x", es que existen dos saltos.
Pasemos a analizar paso a paso cada campo de la cebecera de la petición.

Realizamos petición para "mandar, fijar, poner" en el script "tratar-upload.php" los datos de la petición.
POST tratar-upload.php HTTP/1.1
Dirección completa del servidor desde donde realizamos la petición.
Host: localhost
Agente-navegador usado desde donde realizamos la petición.
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; es-ES; rv:1.9.0.16)
Tipos de datos que acepta el agente-navegador para procesar la respuesta una vez concluida la petición.
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Lenguajes aceptados para la respuesta por parte del navegador.
Accept-Language: es-es,es;q=0.8,en-us;q=0.5,en;q=0.3
Tipos de codificación aceptados para la respuesta por parte del navegador.
Accept-Encoding: gzip,deflate
Conjunto de carácteres aceptados para la respuesta por parte del navegador.
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Representa el número máximo de segundos entre envío y respuesta de paquetes sobre una conexión persistente.
Keep-Alive: 300
Indica que el navegador soporta conexiones persistentes sobre una misma conexión TCP.
Connection: keep-alive
Indica desde donde procede la petición.
Referer: http://localhost/upload.html
Muy Importante.
Establece que la petición puede estar formada por diferentes partes de datos y se indica el separador (límite) entre las partes involucradas. Oséase, que cada archivo debe estar separado convenientemente por el límite establecido boundary. La serie numérica que le sigue está formada aleatoriamente y no sigue ningún tipo de lógica.
Content-Type: multipart/form-data; boundary=---------------------------265001916915724
Tamaño en bytes del conjunto de datos que vamos a enviar excluyendo la cabecera.
Content-Length: total_bytes_de_los_ficheros_incluyendo_los_delimitadores<2 x RETORNO DE CARRO + SALTO DE LINEA>

A continuación voy a explicar las partes de cada archivo que enviamos al servidor.

Indica el comienzo de una parte, osea, los datos de un archivo
-----------------------------265001916915724
Aquí se establece el nombre del campo del fichero especificado en el form y el nombre del archivo que vamos a enviar.
Content-Disposition: form-data; name="f1"; filename="archivo1.ext"
Tipos de datos que contiene el archivo. Normalmente binarios.
Content-Type: application/binary<2 x RETORNO DE CARRO + SALTO DE LINEA>
Los datos en bruto del fichero son colocados aquí, acabamos con un salto de línea.


Para terminar la retransmisión de datos hacia el servidor, debemos indicarlo con el siguiente campo.
-----------------------------265001916915724--
(nótese que acaba con dos guiones -- más retorno de carro y el salto de línea correspondiente.)

Ya creo que estamos listos para ver el código fuente del programa que desarrollé en Java para la subida de ilimitado número de archivos. El código se corresponde con la clase más significativa del programa, el uploader. He suprimido algunas partes del código para exponer más claramente lo que estamos tratando aquí.

Uploader.java

Si estás interesado en todo el desarrollo del applet, puedes ponerte en contacto conmigo.
Para poder usarlo correctamente, el applet está firmado para poder explorar el disco duro del cliente con los archivos que desea subir al servidor.

martes, 1 de diciembre de 2009

Implementando cliente DNS para resolver servidores MX (MAIL EXCHANGE) con C

A continuación listo el codigo fuente totalmente comentado para resolver servidores de correo saliente a partir de un email, un servidor DNS y el puerto usado por el servidor (normalmente el 53 por UDP).

Uso del programa:

DNS-MX.exe <Email> <Servidor DNS> <Puerto servidor DNS>

DNS-MX.c

Analizando la respuesta DNS

Procederemos a analizar la respuesta que nos devuelve el servidor DNS.



Voy a pasar a comentar sólo algunos campos que nos resultan de interés para la práctica que estamos realizando.
  • Transaction ID: Como vemos, coincide el ID de la consulta con la de la respuesta.
  • Answer RRs: Nos indica que el número de servidores tipo MX resueltos.
  • Additional RRs: Son campos adicionales donde se muestran las direcciones IP de cada servidor MX resuelto.

Ahora vamos a desglosar los campos Answers donde se encuentran los servidores MX resueltos por el servidor DNS.



Datos de la estructura Answers:
  • Name: gmail.com = 0xc00c. c0 indica que hay compresión y el siguiente byte establece el byte de desplazamiento donde se encuentra la información, osea: 0c, que en decimal se traduce a 12. En la posición 12 de la respuesta se encuentra lo que buscamos. Los primeros 12 bytes de la respuesta corresponden a parámetros de la consulta, el byte siguiente, osea, el 13, nos deja justo en el campo Name de la estructura Queries, que es nada más ni nada menos que: gmail.com. Todo este procedimiento se traduce como compresión.
  • Type: MX = 0x000f. Indica el tipo de servidor (servidor de correo saliente)
  • Class: IN = 0x0001. Indica la clase de servidor (servidor de internet)
  • Time to live: 47 minutes, 52 secons = 0x00000b38. Marca de tiempo que establece el tiempo que puede ser el servidor cacheado hasta vovler a interrogar para resolverlos de nuevo.
  • Data length: 27 = 0x000b. Indica el tamaño de la cadena en bytes del servidor resuelto.
  • Preference: 5 = 0x0005. Indica preferencia de uso del servidor entre los resueltos.
  • Mail exchange: gmail-smtp-in.l.google.com

Nos detenemos en el campo Mail exchange ya que merece la pena para analizar la compresión.



Este tipo de procedimiento se repite una y otra vez mientras se pueda hacer uso de la compresión.
Como ya dije, se hace uso de la compresión para dar una rápida respuesta y demorar lo menos posible en resolver las consultas de los clientes. Es un buen mecanismo pero un tanto engorroso, se deben de tener bien claros los conceptos porque en el capítulo siguiente veremos la implementación de un cliente DNS para resolver servidores tipo Mail exchange.

Analizando la consulta DNS

Hace algún tiempo, desarrollé un cliente DNS para resolver consultas tipo MX(MAIL EXCHANGE), que no son más que consultas para resolver  servidores de correo saliente a partir de un nombre de dominio.

Para obtener más información sobre DNS RFC 1035

La siguiente captura está realizada con wireshark para ilustrar un poco que campos intervienen a la hora de realizar una consulta DNS.



Voy a explicar un poco que significan los siguientes campos.
  • Transaction ID (2 bytes): Identifica la consulta del cliente que debe conincidir con la respuesta del servidor.
  • Flags (2 bytes): Son parámetros para la consulta a realizar, si es una consulta inversa, el mensaje está truncado, etc. De momento nos basta saber que la consulta a que hemos realizado es recursiva, es decir, si el servidor DNS al que hemos realizado la consulta no contiene la información que necesitamos, éste acude a otros servidores para obtener la respuesta.
  • Questions (2 bytes): Número de consultas a realizar, por defecto sólo una.
  • Answer RRs (2 bytes): Número de entradas que aparecen en la sección de respuesta, siempre a cero en la consulta.
  • Authority RRs (2 bytes): Número de entradas que aparecen en la sección de autoridad, siempre a cero en la consulta.
  • Additional RRs (2 bytes): Número de entradas que aparecen en la sección adicional, siempre a cero en la consulta.
  • Queries
    Este campo especifica las consultas a realizar.
    En este caso es de 15 bytes
  • Name (11 bytes): Nombre del dominio a consultar para obtener los servidores de correo saliente para enviar el mensaje
  • Type (2 bytes): Tipo de consulta a realizar. En este caso es 0x000f para especificar que es tipo MX.
  • Class (2 bytes): El tipo de consulta pertenece a internet, la más usada.

A excepción del campo Queries, la cabecera de la consulta DNS tiene siempre una constante de 12 bytes.
Vamos a ver al detalle el campo Name que contiene el dominio a consultar ya que resulta un tanto interesante en la forma en la que lo hace.

1 2 3 4 5 6 7 8 9 10 11
05 g m a i l 03 c o m 00

El dominio es gmail.com, y como vemos en la anterior tabla, los puntos '.' los sustituye por un campo numérico que indica la longitud del siguiente campo. El final de la cadena siempre acaba en cero.

Parece fácil, no?

En el siguiente capítulo veremos la respuesta que nos devuelve el servidor DNS y veremos también la compresión de los mensajes que es utilizada para ahorrar tamaño en el paquete y por consiguiente demorar lo menos posible para obtener un respuesta rápida.