Comunicacrión Applet-Servlet y tunneling
sábado, septiembre 1, 2001 at 2:00AM
Comunicacion Applet-Servlet y tunneling
Todos sabemos como hacer una aplicación web en los que el usuario manda un formulario HTML y el servidor lo procesa por medio de Servlets o páginas JSP (sí!, ya sé que son "lo mismo"), pero quizás alguno ya haya tenido el problema de que eso no es siempre suficiente, no es lo suficentemente interactivo para algunas aplicaciones que requieren más dinamismo y funcionalidad en el lado del cliente, como por ejemplo un chat u otro tipo de aplicaciones, digámoslo así, distribuídas.
Si hablamos de aplicaciones distribuídas con clientes "ricos" (en el sentido de la funcionalidad) todos pensamos en RMI o quizás EJB, en comunicar una aplicación Java con el servidor por medio del API que ofrece Java para proceso distribuído, que aunque sea disfrazado de EJB (con mucha funcionalidad añadida), es RMI. Pero, ¿qué ocurre? Que ningún administrador de una red medianamente grande te va a abrir un puerto en su cortafuegos. La gente está demasiado histérica (seguramente con razón) con la seguridad, así que en algunas ocasiones esta solución no es posible.
Así que, si necesitamos una interface rica en el lado del cliente y usar el único puerto que los administradores de red suelen dejar "tranquilo" (el de HTTP, normalmente el 80), ¿qué nos queda? Pues sí, APPLETS!. Son "famosos" los applets que ejercen la labor de "menú" de la página web, sin ir más lejos, en un tiempo muy muy lejano, antes de ser la web preciosa que es ahora, javaHispano tuvo uno. Así que ya sabemos que un applet se puede comunicar con un servidor web para hacer peticiones de páginas HTML. Y si podemos mostrar páginas web.... ¿por qué no hacer peticiones a un servlet?.
Además, usando un applet evitamos una de las limitaciones de un formulario HTML, que es que solo pueden hacer peticiones a un servidor y a un recurso Web. Desde un applet podremos hacer, con los mismos datos, peticiones a varios servlets distintos de nuestro servidor, que a su vez se podrán comunicar con otros servidores o servlets, de forma que podemos, con una sola peticion, enviar los datos a cientos de sitios
El applet puede ser todo lo rico o pobre que necesitemos, manejar toda la información que necesitemos, y realizar todos los cálculos que queramos, exactamente igual que cualquier aplicación Java. Lo único que tenemos que aprender es cómo hacer que el applet transmita las peticiones al servidor, y por supuesto cómo hacer que recoja las respuestas.
Antes de empezar, un aviso. Como es lógico, todas estas operaciones que tratan con conexiones son susceptibles de lanzar excepciones. La mayoria de las líneas de código mostradas aquí deberían ir encerradas en una sentencia try/catch, pero las he omitido todas para no hacerlo más largo de lo necesario. En realidad vosotros tendréis que poner unas cuantas de ellas :-).
Aunque se explican mínimante algunos conceptos del protocolo HTTP, como la diferencia de envio de la petición por medio del método GET o POST, se presuponen ciertos conocimientos del protocolo HTTP, conceptos como cabeceras, tipos de documentos, cookies y demás. Aqui no serán explicados, como mucho se indicará como usarlas en Java, y solo del lado del applet, del lado cliente. Dejo para otro/s artículo/s y quizás otra/s persona/s (se aceptan volutarios) las explicación de dichos conceptos y un uso más avanzado y extensivo de las posibilidades que tienen los servlets, ese no es el objetivo de este artículo.
Peticiones a un servlet por medio de GET
Como sabéis (o deberíais saber si estais metidos en estas cosas), en una petición HTTP por el método GET, la información viaja en la URL, es decir, en la dirección del navegador, algo así como:
http://www.javahispano.com/servlet/MiServlet?param1=valor1¶m2=valor2
Como podéis ver, después de la direccion del recurso, en nuestro caso el servlet "peticion" (http://www.javahispano.com/servlet/MiServlet), ponemos el símbolo "?" y después todos los parametros de la petición en pares "nombre=valor" separados por el símbolo "&".
Así que si queremos hacer una petición al servidor, a un servlet desde nuestro Applet, simplemente tenemos que escribir:
try
{
/*
baseURL es la dirección del servlet
ejemplo: http://www.javahispano.com/servlet/MiServlet.
Y parametros son los datos adosados,
ejemplo: param1=valor1¶m2=valor2
*/
URL direccion = new URL (baseURL + "?" + parametros);
}
catch (MalformedURLException mue)
{
. . .
}
Esta forma de actuar nos devuelve un "fichero HTML" que podríamos mostrar con el método showDocument del applet:
miApplet.getAppletContext().showDocument(direccion);
El único problema que se nos puede plantear es el de codificar los parámetros. Al utilizar el protocolo HTTP tenemos que asegurarnos de que el servidor web nos entienda, por lo que, uno por uno, hay que codificar los parámetros de la petición para eliminar los espacios en blanco y los caracteres no alfanuméricos, no aceptados por el protocolo HTTP. Lo haremos por medio del método estático "encode" de la clase URLEncoder:
String parametros = "param1" + "=" + URLEncoder (valor1) +
"&" + "param2" + "=" + URLEncoder (valor2);
Hay que tener cuidado, ya que no vale hacer simplemente
String parametros = URLEncoder ("param1=" + valor1 + "¶m2=" + valor2);
Es tentador, pero no funcionará, porque entonces nos codificará también los símbolos "=" y "&", así que ya no tendremos la URL que nosotros queremos. Un poco más de trabajo, pero no es grave.
Tunneling
Pero eso de mostrar la página en el navegador muchas veces no es lo que buscamos. Puede ser, pero muchas veces no. Es por eso que tenemos que buscar una manera de recoger esa información que nos envía el servidor y procesarla según nuestras necesidades. A fin de cuentas, ya hemos dicho que una de las razones para usar esta comunicación applet/servlet es la de conseguir una comunicación "avanzada" usando el protocolo HTTP y que no produzca pánico en un administrador del sistema por tener que abrir un puerto en el cortafuegos.
Así que lo que tenemos que utilizar es uno de eso terminos de moda, el "tunneling", que no tengo ni idea de cómo traducir al castellano, aunque sea una palabra relativamente de moda. Es lo malo de vivir "en otro idioma", acabas olvidando ciertas palabras "raras", y no sabes cómo traducir cosas como "usability", porque me han dicho que "usabilidad" no existe :-(, aunque reconozco que suena muy mal. En fin, disculpad el desvarío, solo es para pedir perdón si a veces "le doy patadas al diccionario". El caso es que el llamado "tunneling", quizás traducible como "hacer el embudo" o algo parecido, significa transmitir información que sigue un protocolo por medio de otro. En nuestro caso se llama "HTTP tunneling" ya que usamos el protocolo HTTP para transmitir la información binaria.
Principalmente, lo que tenemos que hacer es capturar el stream o flujo por el que el servidor nos envía los datos y procesarlos de la manera adecuada. El proceso en sí no es difícil, lo que más "destreza" requiere es el procesado de la información, como siempre. El proceso a seguir es el siguiente:
/*
Primero creamos la URL para la conexión.
Tiene sentido construir la dirección de esta forma
tan "complicada" puesto que el applet solo puede
establecer conexiones con su servidor, y así, al
construir la dirección dinámicamente, no tenemos
que retocar el código al irnos a otro servidor.
En todo caso, lo siguiente sería válido:
URL direccion = new URL ("http://www.javahispano.com/servlet/MiServlet");
*/
URL pagina = miApplet.getCodeBase();
String protocolo = pagina.getProtocol();
String servidor = pagina.getHost();
int puerto = pagina.getPort();
int servlet = "/servlet/MiServlet";
URL direccion = new URL (protocolo, servidor, puerto, servlet);
/*
Después creamos una URLConnection con la dirección dada
*/
URLConnection conexion = direccion.openConnection();
/*
Lo siguiente es decirle al navegador que no use su
cache para esta conexión, porque si lo hace vamos a
tener un página estatica, y para eso no nos metemos
en estos líos ;-).
*/
conexion.setUseCaches (false);
/*
Ahora añadimos todas las cabeceras de HTTP que necesitemos,
Cookies, contenido, autorizacion, etc. con el método:
conexion.setRequestProperty ("cabecera", "valor");
Consultar la especificación de HTTP para más detalles
Por ejemplo, para decir que preferentemente hablamos español:
*/
conexion.setRequestProperty ("Accept-Language", "es");
. . .
/*
Despues capturamos el flujo por el que nos llega la respuesta
de la forma habitual, igual que hacemos con ficheros, sockets, etc.
*/
InputStreamReader stream = new InputStreamReader (conexion.getInputStream());
BufferedReader entrada = new BufferedReader(stream);
/*
Procesamos la información de la forma adecuada, según se
trate de datos ASCII o binarios.
Por ejemplo, si tratamos con datos ASCII leemos línea a línea:
*/
String linea;
while ((linea = entrada.readLine())!=null)
{
// procesamiento de la linea
. . .
}
/*
Y finalmente cerramos la conexión.
*/
entrada.close();
¿Sencillo?. Al menos no complicado, no más que cualquier operación de entrada y salida con ficheros, ¿no?.
Más difícil todavía: transmitiendo objetos
Pero si hemos abierto una conexión con el servidor, ¿por qué limitarnos a un sencillo texto ASCII o datos binarios?, ¿qué nos puede impedir enviar objetos por medio de la serialización?. Realmente nada, solo hay que crear un ObjectInputStream en lugar de uno normal y luego hacer el "Cast" de los objetos:
ObjectInputStream entrada = new ObjectInputStream (conexion.getInputStream());
MiClaseSerializable mcs = (MiClaseSerializable) entrada.readObject();
. . .
Siento deciros que una explicación sobre la serialización se escapa del objetivo de este artículo, pero como siempre lo dejo pendiente para que alguien se anime a escribirlo y publicarlo aquí.
Está claro que para poder recibir objetos primero los tiene que enviar el servidor de la forma adecuada. Este envío hay que indicarlo en las cabeceras de la respuesta (no olvidar que seguimos trabajando sobre HTTP). En el método de servicio del Servlet (doPost o doGet, según sea nuestra petición por medio de GET o POST), teniendo la respuesta en el objeto "response" de la clase HTTPServletResponse que esos métodos reciben como parámetro, deberíamos hacer:
/*
Primero especificamos el tipo del contenido de la respuesta
*/
response.setContentType ("application/x-java-serialized-object");
/*
Abrimos el flujo de salida para enviar objetos
*/
ObjectOutputStream salida = new ObjectOutputStream(reponse.getOutputStream());
/*
Escribimos los objetos que queramos enviar
*/
salida.writeObject (mcs);
. . .
/*
Y finalmente, nos aseguramos de que envíe el contenido
forzando el vaciado del buffer.
No hace falta cerrar el flujo de salida. Eso es algo de lo que
se encargará el contenedor de servlets, de la misma forma que
se encarga de muchas cosas.
*/
salida.flush();
Peticiones a un servlet por medio de POST
Pero nos hemos dejado lo mejor para el final, como siempre. Hemos visto cómo enviar datos sencillos, cómo recibir paginas web, cómo procesar los resultados que nos manda el servidor (ya sean ASCII, datos binarios u objetos), pero nos falta lo mejor, la "comunicación total", es decir, cómo enviar objetos al servidor. Si alguno sabe algo de HTML o de Servlets, notará que hasta ahora no hemos hablado de la otra forma, el otro método, de enviar datos de un formulario al servidor, el método POST. Ahora llega.
Como hemos dicho antes, usndo el método GET se envían los parametros de la petición dentro de la direccion URL. En el método POST no es así, y seremos nosotros los que tengamos que abrir una conexión con el servidor para enviar los datos. En el método POST la información de nuestra petición viaja en un flujo de datos toda junta (datos de la cabecera, cookies, parametros, etc), y será el servidor quién tenga que procesarla y separarla en partes. No es preocupeis que lo suele hacer muy bien.
Antes de que se me olvide, tengo que decir que si utilizamos el método POST, nuestro applet no podrá mostrar la página web de la respuesta (si ése fuera el caso) como hicimos en el primer ejemplo. No podemos asociar los datos necesarios del método POST con el constructor de una URL para recibir la respuesta, así que será obligatorio que procesemos la respuesta nosotros mismos. Pero como hemos dicho, eso no deberia ser un problema, puesto que si usamos applets y servlets, normalmente será porque queremos hacer algo más que mostrar páginas HTML.
El proceso es algo más complicado que en el caso de GET, pero también las posibilidades son mayores, así que no es cuestión de quejarse por tener un poco de trabajo más. Los pasos que son iguales a los seguidos al explicar el Tunneling no los repetiré, los tenéis unas líneas más arriba, así que no será problema. Lo siento pero soy un poco vago ;-).
/*
Primero construímos la dirección URL,
despues abrimos la conexión URLConnection,
y le decimos al navegador que no use cache
*/
. . .
/*
Ahora hay que informar al navegador de que queremos
enviar más información, no simplemente recibirla
*/
conexion.setDoOutput(true);
/*
El siguiente paso es crear un flujo de bytes que será
lo que se envíe al servidor. No tengáis miedo, no tenemos que
escribir byte a byte!! ;-).
Esto es necesario porque al enviar los datos hay que indicar
el tamaño exacto, y es mejor contarlo así que manualmente.
El parámetro (512) es el tamaño inicial del buffer. INICIAL, si
hace falta crecerá.
*/
ByteArrayOutputStream bs = new ByteArrayOutputStream(512);
/*
Para no tener que escribir byte a byte, asociamos a ese
flujo de bytes uno de un nivel superior. Según necesitemos
enviar objetos o texto ASCII será ObjectOutputStream o
PrintWriter, o el que queramos.
Despues escribimos los datos necesarios, y forzamos el vaciado
del buffer.
*/
ObjectOutputStream salida = new ObjectOutputStream (bs, true);
salida.writeObject (miClaseSerializable);
. . .
salida.flush();
/*
El siguiente paso es el de ajustar las cabeceras de la petición
HTTP. Como hemos explicado al principio, podemos especificar
todas las que necesitemos, pero en este caso, al menos serán
las relativas al envio de información, es decir, el tamaño y el
tipo de la información enviada.
*/
conexion.setRequestProperty ("Content-Length", String.valueOf (bs.size()));
conexion.setRequestProperty ("Content-Type", "application/x-java-serialized-object");
. . .
/*
Ahora enviamos realmente la información que hemos guardado
en el ByteArrayOutputStream a modo de buffer
*/
bs.writeTo (conexion.getOutputStream());
/*
Ya solo nos queda abrir un flujo de entrada para recibir
la respuesta del servidor. Exactamente como hemos visto
antes, asi que no lo repito.
*/
. . .
Conclusión.
Como habéis podido comprobar, la comunicación entre applet y servlet y el tunneling no son difíciles, al menos no más que cualquier conexión de I/O en Java. Supongo que muchos os habréis quejado de los difícil que es leer un fichero en Java, pero seguro que os habrá gustado saber que el mismo proceso es el que se sigue para realizar conexiones a través de la red, por lo que ya podéis hacer dos cosas por el precio de una ;-).
|
j2ee 
Reader Comments