En J2SE tenemos una gran cantidad de clases en el paquete java.net
para
permitir establecer distintos tipos de conexiones en red. Sin embargo, el soportar
esta gran API no es viable en la configuración CLDC dedicada a dispositivos
muy limitados. Por lo tanto en CLDC se sustituye esta API por el marco de conexiones
genéricas (GCF, Generic Connection Framework), con el que se
pretenden cubrir todas las necesidades de conectividad de estos dispositivos
a través de una API sencilla.
Los distintos dispositivos móviles pueden utilizar distintos tipos de redes para conectarse. Algunos utilizan redes de conmutación de circuitos, orientadas a conexión, que necesitarán protocolos como TCP. Otros utilizan redes de transmisión de paquetes en las que no se establece una conexión permanente, y con las que deberemos trabajar con protocolos como por ejemplo UDP. Incluso otros dispositivos podrían utilizar otras redes distintas en las que debamos utilizar otro tipo de protocolos.
El marco de conexiones genéricas (GFC) hará que esta red móvil
subyacente sea transparente para el usuario, proporcionando a éste protocolos
estándar de comunicaciones. La API de GFC se encuentra en el paquete
javax.microedition.io
. Esta API utilizará un único
método que nos servirá para establecer cualquier tipo de conexión
que queramos, por esta razón recibe el nombre de marco de conexiones
genéricas, lo cuál además lo hace extensible para incorporar
nuevos tipos de conexiones. Para crear la conexión utilizaremos el siguiente
método:
Connection con = Connector.open(url);
En el que deberemos especificar una URL como parámetro con el siguiente formato:
protocolo:direccion;parámetros
Cambiando el protocolo podremos especificar distintos tipos de conexiones. Por ejemplo, podríamos utilizar las siguientes URLs:
"http://j2ee.ua.es/pdm" |
Abre una conexión HTTP. |
"datagram://192.168.0.4:6666" |
Abre una conexión por datagramas. |
"socket://192.168.0.4:4444" |
Abre una conexión por sockets. |
"comm:0;baudrate=9600" |
Abre una conexión a través de un puerto de comunicaciones. |
"file:/fichero.txt" |
Abre un fichero. |
Cuando especifiquemos uno de estos protocolos, la clase Connector
buscará en tiempo de ejecución la clase que implemente dicho tipo
de conexión, y si la encuentra nos devolverá un objeto que implemente
la interfaz Connection
que nos permitirá comunicarnos a
través de dicha conexión.
CLDC nos proporciona interfaces para cada tipo genérico de conexión, pero las implementaciones reales de los protocolos pertenecen a los perfiles.
Figura 28. Componentes de GCF
El único protocolo que la especificación de MIDP exige que se implemente es el protocolo HTTP. Este protocolo pertenece a MIDP, y no a CLDC como era el caso de las clases genéricas anteriores. Distintos modelos de dispositivos pueden soportar otro tipo de conexiones, pero si queremos hacer aplicaciones portables deberemos utilizar HTTP.
La conexión mediante el protocolo HTTP es el único tipo de conexión que sabemos que va a estar soportado por todos los dispositivos MIDP. Este protocolo podrá ser implementado en cada modelo de móvil bien utilizando protocolos IP como TCP/IP o bien protocolos no IP como WAP o i-Mode.
Figura 29. Gateway para protocolos no IP
De esta forma nosotros podremos utilizar directamente HTTP de una forma estándar sin importarnos el tipo de red que el móvil tenga por debajo.
Cuando establezcamos una conexión mediante protocolo HTTP, podemos hacer
una conversión cast del objeto Connection
devuelto
a un subtipo HttpConnection
especializado en conexiones HTTP:
HttpConnection con = (HttpConnection)Connector.open("http://j2ee.ua.es/datos.txt");
Este objeto HttpConnection
contiene gran cantidad de métodos
dedicados a trabajar con el protocolo HTTP, lo cuál facilitará
en gran medida el trabajo de los desarrolladores.
HTTP es un protocolo de petición/respuesta. El cliente crea un mensaje de petición y lo envía a una determinada URL. El servidor analizará esta petición y le devolverá una respuesta al cliente. Estos mensajes de petición y respuesta se compondrán de una serie de cabeceras y del bloque de contenido. Cada cabecera tendrá un nombre y un valor. El contenido podrá contener cualquier tipo de información (texto, HTML, imágenes, mensajes codificados en binario, etc). Tendremos una serie de cabeceras estándar con las que podremos intercambiar datos sobre el cliente o el servidor, o bien sobre la información que estamos transmitiendo. También podremos añadir nuestras propias cabeceras para intercambiar datos propios.
Una vez creada la conexión, ésta pasará por tres estados:
La conexión nada más crearse se encuentra en estado de configuración. Pasará automáticamente a estado conectada cuando solicitemos cualquier información sobre la respuesta.
Lectura de la respuesta
Vamos a comenzar viendo cómo leer el contenido de una URL. En este caso no vamos a añadir ninguna información al mensaje de petición, ya que no es necesario. Sólo queremos obtener el contenido del recurso solicitado en la URL.
Imaginemos que queremos leer el fichero en la URL http://j2ee.ua.es/datos.txt
.
Como primer paso deberemos crear una conexión con dicha URL como hemos
visto anteriormente. Una vez tengamos este objeto HttpConnection
abriremos un flujo de entrada para leer su contenido de la siguiente forma:
InputStream in = con.openInputStream();
Una vez hecho esto, la conexión pasará a estado conectada, ya que estamos solicitando leer su contenido. Por lo tanto en este momento será cuando envíe el mensaje de petición al servidor, y se quede esperando a recibir la respuesta. Con el flujo de datos obtenido podremos leer el contenido de la misma, al igual que leemos cualquier otro flujo de datos en Java.
Dado que en este momento ya se ha enviado el mensaje de petición, ya no tendrá sentido realizar modificaciones en la petición. Es por esta razón por lo que la creación del mensaje de petición debe hacerse en el estado de configuración.
Una vez hayamos terminado de leer la respuesta, deberemos cerrar el flujo y la conexión:
in.close();
con.close();
Con esto la conexión pasará a estado cerrada, liberando todos los recursos.
Mensaje de petición
En muchos casos podemos necesitar enviar información al servidor, como por ejemplo el login y el password del usuario para autentificarse en la aplicación web. Esta información deberemos incluirla en el mensaje de petición. Existen distintas formas de enviar información en la petición.
Encontramos los diferentes tipos de mensajes de petición soportados por MIDP:
HttpConnection.GET |
Los datos que se envían al servidor se incluyen
en la misma URL. Por ejemplo, podemos mandar un parámetro |
HttpConnection.POST |
Los datos que se envían al servidor se incluyen como contenido del mensaje. Tiene la ventaja de que se puede enviar la cantidad de datos que queramos, a diferencia del método GET en el que esta cantidad puede estar limitada. Además los datos no serán visibles en la misma URL, ya que se incluyen como contenido del mensaje. |
HttpConnection.HEAD |
No se solicita el contenido del recurso al servidor, sólo información sobre éste, es decir, las cabeceras HTTP. |
Podemos establecer uno de estos tipos utilizando el método setRequestMethod
,
por ejemplo para utilizar una petición POST haremos lo siguiente:
con.setRequestMethod(HttpConnection.POST);
Además podremos añadir cabeceras a la petición con el siguiente método:
con.setRequestProperty(nombre, valor);
Por ejemplo, podemos mandar las siguiente cabeceras:
c.setRequestProperty("IF-Modified-Since", "22 Sep 2002 08:00:00 GMT"); c.setRequestProperty("User-Agent", "Profile/MIDP-1.0 Configuration/CLDC-1.0"); c.setRequestProperty("Content-Language", "es-ES");
Con esto estaremos diciendo al servidor que queremos que nos devuelva una respuesta sólo si ha sido modificada desde la fecha indicada, y además le estamos comunicando datos sobre el cliente. Indicamos mediante estas cabeceras estándar que el cliente es una aplicación MIDP, y que el lenguaje es español de España.
Mensajes POST
Si utilizamos una petición POST, deberemos incluir la información que enviemos al servidor como contenido del mensaje.
Deberemos especificar el tipo MIME del contenido que vamos a añadir. Esto lo haremos mediante una cabecera estándar de HTTP. Por ejemplo, si añadimos texto ASCII, podemos establecer la siguiente cabecera:
con.setRequestProperty("Content-Type", "text/plain");
Para escribir en el contenido del mensaje de petición deberemos abrir un flujo de salida de la siguiente forma:
OutputStream out = con.openOutputStream();
Podremos escribir en este flujo de salida igual que lo hacemos en cualquier otro flujo de salida, con lo que de esta forma podremos escribir cualquier contenido en el mensaje de petición.
Al abrir el flujo para escribir en la petición provocaremos que se pase a estado conectado. Por lo tanto deberemos haber establecido el tipo de petición y todas las cabeceras previamente a la apertura de este flujo, cuando todavía estábamos en estado de configuración.
Tipo y cabeceras de la respuesta
En estado conectado, además del contenido del mensaje de la respuesta, podemos obtener el estado de la respuesta y sus cabeceras. Los estados de respuesta se componen de un código y un mensaje y nos permitirán saber si la petición ha podido atenderse correctamente o si por el contrario ha habido algún tipo de error. Por ejemplo, posibles estados son:
HttpConnection.HTTP_OK |
200 | OK |
HttpConnection.HTTP_BAD_REQUEST |
400 | Bad Request |
HttpConnection.HTTP_INTERNAL_ERROR |
500 | Internal Server Error |
Este mensaje de estado encabeza el mensaje de respuesta. Si el servidor nos devuelve un mensaje con código 200 como el siguiente:
HTTP/1.1 200 OK
Es que se ha procesado correctamente la petición y nos devuelve su respuesta. Si ha ocurrido un error, nos mandará el código y mensaje de error correspondiente. Por ejemplo, el error 400 indica que el servidor no ha entendido la petición que hemos hecho, posiblemente porque la hemos escrito incorrectamente. El error 500 nos dice que se trata de un error interno del servidor, no de la petición realizada.
Podemos obtener tanto el código como el mensaje de estado con los siguientes métodos:
int cod = con.getResponseCode();
String msg = con.getResponseMessage();
Los códigos de estado podemos encontrarlos como constantes de la clase
HttpConnection
como hemos visto para los tres códigos anteriores.
También podemos utilizar este objeto para leer las cabeceras que nos ha devuelto la respuesta. Nos ofrece métodos para leer una serie de cabeceras estándar de HTTP como los siguientes:
getLength |
content-length |
Longitud del contenido, o -1 si la longitud es
desconocida |
getType |
content-type |
Tipo MIME del contenido devuelto |
getEncoding |
content-encoding |
Codificación del contenido |
getExpiration |
expires |
Fecha de expiración del recurso |
getDate |
date |
Fecha de envío del recurso |
getLastModified |
last-modified |
Fecha de última modificación del recurso |
Puede ser que queramos obtener otras cabeceras, como por ejemplo cabeceras propias no estándar. Para ello tendremos una serie de métodos que obtendrán las cabeceras directamente por su nombre:
String valor = con.getHeaderField(nombre); int valor = con.getHeaderFieldInt(nombre); long valor = con.getHeaderFieldDate(nombre);
De esta forma podemos obtener el valor de la cabecera o bien como una cadena,
o en los datos que sean de tipo fecha (valor long
) o enteros también
podremos obtener su valor directamente en estos tipos de datos.
Podremos acceder a las cabeceras también a partir de su índice:
String valor = con.getHeaderField(int indice); String nombre = con.getHeaderFieldKey(int indice);
Podemos obtener de esta forma tanto el nombre como el valor de la cabecera que ocupa un determinado índice.
Tanto los métodos que obtienen un flujo para leer o escribir en la conexión, como estos métodos que acabamos de ver para obtener información sobre la respuesta producirán una transición al estado conectado.
El poder establecer conexiones en red nos permitirá acceder a aplicaciones web corporativas desde el móvil. De esta forma, podremos hacer que estos dispositivos móviles se comporten como front-end de estas aplicaciones corporativas.
Desde los PCs de sobremesa normalmente accedemos a estas aplicaciones utilizando un navegador web. La aplicación web genera de forma dinámica la presentación en el servidor en forma de un documento HTML que será mostrado en los navegadores de los clientes. Podemos aplicar este mismo sistema al caso de los móviles, generando nuestra aplicación web la respuesta en forma de algún tipo de documento que pueda ser interpretado y mostrado en un navegador de teléfono móvil. Por ejemplo estos documentos pueden estar en formato WML, cHTML o XHTML. Esto puede ser suficiente para acceder a algunas aplicaciones desde los móviles.
Utilizar J2ME para realizar este front-end aporta una serie de ventajas sobre el paradigma anterior, como por ejemplo las siguientes:
Normalmente será preferible utilizar HTTP a sockets o datagramas porque esto nos aportará una serie de ventajas. Por un lado, HTTP está soportado por todos los dispositivos MIDP. Al utilizar HTTP tampoco tendremos problema con firewalls intermedios, cosa que puede ocurrir si conectamos por sockets mediante un puerto que esté cerrado. Además las APIs de Java incluyen facilidades para trabajar con HTTP, por lo que será sencillo realizar la comunicación tanto en el cliente como en el servidor.
La conexión de red en los móviles normalmente tiene una alta latencia, un reducido ancho de banda y es posible que se produzcan interrupciones cuando la cobertura es baja. Deberemos tener en cuenta todos estos factores cuando diseñemos nuestra aplicación. Por esta razón deberemos minimizar la cantidad de datos que se intercambian a través de la red, y permitir que la aplicación pueda continuar trabajando correctamente sin conexión.
Optimizaciones en el cliente
Para reducir el número de datos que se envían por la red, evitando que se hagan conexiones innecesarias, es conveniente realizar una validación de los datos introducidos por el usuario en el cliente. Normalmente no podremos validarlos de la misma forma en que se validan en el servidor, ya que por ejemplo no tenemos acceso a las bases de datos de la aplicación, por lo que deberá volverse a validar por el servidor para realizar la validación completa. No obstante, es conveniente realizar esta validación en el cliente como una prevalidación, de forma que detecte siempre que sea posible los datos erróneos en el lado del cliente evitando así realizar una conexión innecesaria con el servidor.
Dado que la red es lenta, las operaciones que necesiten conectarse a la red serán costosas. Estas operaciones será conveniente que sean ejecutadas por hilos en segundo plano, y nunca deberemos establecer una conexión desde un callback. Además siempre que sea posible deberemos mostrar una barra de progreso mientras se realiza la operación, de forma que el usuario tenga constancia de que se está haciendo algo. También será conveniente permitir al usuario que interrumpa estas largas operaciones, siempre que la interrupción pueda hacerse y no cause inconsistencias en los datos.
Un aspecto interesante en los clientes J2ME es la posibilidad de incorporar personalización. La personalización consiste en recordar los datos y las preferencias del usuario, de forma que la aplicación se adapte a estas preferencias y el usuario no tenga que introducir estos datos en cada sesión. Podremos pedir esta información de personalización la primera vez que ejecuta la aplicación y almacenar esta información utilizando RMS o bien registrarla en el servidor de forma remota. Si tuviésemos esta información por duplicado, en local y en remoto, deberemos proporcionar mecanismos para sincronizar ambos registros.
Arquitectura MVC
El patrón de diseño MVC (Modelo-Vista-Controlador) suele aplicarse para diseñar las aplicaciones web J2EE. Podemos aplicar este mismo patrón a las aplicaciones cliente J2ME. En una aplicación cliente tendremos los siguientes elementos:
De esta forma con esta arquitectura estamos aislando datos, presentación y flujo de control. Es especialmente interesante el haber aislado los datos del resto de componentes. De esta forma la capa de datos podrá decidir si trabajar con datos de forma local con RMS o de forma remota a través de HTTP, sin afectar con ello al resto de la aplicación. Este diseño nos facilitará cambiar de modo remoto a modo local cuando no queramos tener que establecer una conexión de red.
Vamos a considerar que en el servidor tenemos una aplicación J2EE. En
este caso accederemos a la aplicación corporativa a través de
un Servlet
. Los servlets son componentes Java en el servidor
que encapsulan el mecanismo petición/respuesta. Es decir, podemos enviar
una petición HTTP a un servlet, y éste la analizará
y nos devolverá una respuesta.
Figura 30. Integración de J2ME y J2EE
La aplicación J2ME se comunicará con un servlet. Dentro de la aplicación J2EE en el servidor este servlet utilizará EJBs para realizar las tareas necesarias. Los EJBs son componentes reutilizables que implementan la lógica de negocio de la aplicación. Estos EJBs podrán utilizar otras APIs para realizar sus funciones, como por ejemplo JMS para enviar o recibir mensajes, JDBC para acceder a bases de datos, CORBA para acceder a objetos distribuidos, o bien acceder a Servicios Web utilizando las APIs de XML.
Normalmente tendremos servlets que se encarguen de generar una respuesta para navegadores web HTML. Para las aplicaciones móviles esta respuesta no es adecuada, por lo que deberemos crear otra versión de estos servlets que devuelvan una respuesta adaptada a las necesidades de los móviles. La arquitectura de capas de J2EE nos permitirá crear servlets para distintos tipos de clientes minimizando la cantidad de código redundante, ya que la lógica de negocio está implementada en los EJBs y estos componentes pueden ser reutilizados desde los diferentes servlets.
El proceso de comunicación entre nuestra aplicación y un servlet será el siguiente:
Content-Type
al tipo correcto de datos
que estemos enviando para que el mensaje sea procesado de forma correcta por
los gateways intermedios por los que pase. Si enviamos el contenido
del mensaje codificado como texto, deberemos establecer este tipo a
text/plain
, mientras que si los datos se envían en binario deberemos
utilizar como tipo application/octet-stream
.Content-Type
y Content-Length
para
asegurarnos de que el mensaje sea procesado correctamente por los gateways
intermedios igual que en el caso del envío de la petición. En
este caso como tipo podremos utilizar por ejemplo text/plain
para texto image/png
para devolver una imagen PNG al cliente,
y application/octet-stream
para devolver un mensaje codificado
en binario.Codificación de los datos
Hemos visto que la aplicación J2ME y el servlet de la aplicación J2EE intercambian datos con una determinada codificación. En J2ME no tenemos disponibles mecanismos de alto nivel para intercambiar información con componentes remotos, como por ejemplo RMI para la invocación de métodos de objetos Java remotos, o las API de análisis de XML para intercambiar información en este formato. Por lo tanto deberemos codificar la información con formatos propios. Seremos nosotros los que decidamos qué formato y codificación deben tener estos datos.
Podemos movernos entre dos extremos: la codificación de los datos en binario y la codificación en XML.
La codificación binaria de los datos será eficiente y compacta.
Será sencillo codificar información en este formato utilizando
los objetos DataOutputStream
y ByteArrayOutputStream
.
Tiene el inconveniente de que tanto el cliente como el servidor deberán
conocer cómo está codificada la información dentro del
array de bytes, por lo que estos componentes estarán
altamente acoplados.
Si hemos definido una serialización para los objetos, podemos aprovechar esta serialización para enviarlos a través de la red. En este caso la serialización la hemos definido manualmente nosotros en un método del objeto, y no se hace automáticamente como en el caso de J2SE, por lo que deberemos tener cuidado de que en el objeto del cliente y en el del servidor se serialice y deserialice de la misma forma. Además, al transferir un objeto entre J2ME y J2EE deberemos asegurarnos de que este objeto utiliza solamente la parte común de la API de Java en ambas plataformas.
En el otro extremo, XML es un lenguaje complejo de analizar y la información ocupa más espacio. Como ventaja tenemos que XML es un lenguaje estándar y autodescriptivo, por lo que reduciremos el acoplamiento de cliente y servidor. Aunque en MIDP no se incluyen librerías para procesar XML, diversos fabricantes proporcionan sus propias implementaciones de las librerías de XML para J2ME. Podemos utilizar estas librerías para crear y analizar estos documentos en los clientes móviles.
Podemos encontrar incluso implementaciones de librerías de XML orientado a RPC para invocar Servicios Web SOAP directamente desde el móvil. Debemos tener cuidado al invocar Servicios Web directamente desde los móviles, ya que la construcción y el análisis de los mensajes SOAP es una tarea demasiado costosa para estos dispositivos. Para optimizar la aplicación, en lugar de invocarlos desde el mismo móvil, podemos hacer que sea la aplicación J2EE la que invoque el servicio, y que el móvil se comunique con esta aplicación a través de mensajes sencillos.
Mantenimiento de sesiones
El protocolo HTTP sigue un mecanismo de petición/respuesta, no mantiene información de sesión. Es decir, si realizamos varias peticiones HTTP a un servidor desde nuestro cliente, cada una de estas peticiones será tratada independientemente por el servidor, sin identificar que se trata de un mismo usuario. Para implementar sesiones sobre protocolo HTTP tendremos que recurrir a mecanismos como la reescritura de URLs o las cookies.
Normalmente cuando accedamos a una aplicación web necesitaremos mantener una sesión para que en todas las peticiones que hagamos al servidor, éste nos identifique como un mismo usuario. De esta forma por ejemplo podremos ir añadiendo con cada petición productos a un carrito de la compra en el lado del servidor, sin perder la información sobre los productos añadidos de una petición a otra.
Para mantener las sesiones lo que se hará es obtener en el cliente un identificador de la sesión, de forma que en cada petición que se haga al servidor se envíe este identificador para que el servidor sepa a qué sesión pertenece dicha petición. Este identificador puede ser obtenido mediante cookies, o bien incluirlo como parte de las URLs utilizando la técnica de reescritura de URLs.
Los navegadores web normalmente implementan las sesiones mediante cookies. Estas cookies son información que el servidor nos envía en la respuesta y que el navegador almacena de forma local en nuestra máquina. En la primera petición el servidor enviará una cookie al cliente con el identificador de la sesión, y el navegador almacenará esta cookie de forma local en el cliente. Cuando se vaya a hacer otra petición al servidor, el navegador envía esta cookie para identificarnos ante el servidor como el mismo cliente. De esta forma el servidor podrá utilizar el valor de la cookie recibida para determinar la sesión correspondiente a dicho cliente, y de esta forma poder acceder a los datos que hubiese almacenado en peticiones anteriores dentro de la misma sesión.
Sin embargo, cuando conectamos desde un cliente J2ME estamos estableciendo una conexión con la URL del servlet desde nuestra propia aplicación, no desde un navegador que gestione automáticamente estas cookies. Por lo tanto será tarea nuestra implementar los mecanismos necesarios para mantener esta sesión desde el cliente.
Vamos a ver como implementar los mecanismos para mantenimiento de sesiones en las aplicaciones J2ME. Será más fiable utilizar reescritura de URLs, ya que algunos gateways podrían filtrar las cookies y por lo tanto este mecanismo fallaría.
Estos mecanismos de cookies y reescritura de URLs se utilizan para que los navegadores mantengan las sesiones de una forma estándar para todas las aplicaciones. Pero lo que pretendemos en última instancia es tener un identificador de la sesión en el cliente que pueda ser enviado al servidor en cada petición. Si nos conectamos desde nuestra propia aplicación podremos utilizar nuestro propio identificador y enviarlo al servidor de la forma que queramos (como cabecera, parámetro, en el post, etc). Por ejemplo, podríamos hacer que en cada petición que haga nuestra aplicación J2ME envíe nuestro login al servidor, de forma que esta información le sirva al servidor para identificarnos en cada momento.
Sin embargo, será conveniente que nuestra aplicación implemente alguno de los mecanismos estándar para el mantenimiento de sesiones, ya que así podremos aprovechar las facilidades que ofrecen los componentes del servidor para mantener las sesiones. Ahora veremos como implementar las técnicas de reescritura de URLs y cookies en nuestras aplicaciones J2ME.
Reescritura de URLs
Algunos navegadores no soportan cookies. Para mantener sesiones en este caso podemos utilizar la técnica de reescritura de URLs. Esta técnica consiste en modificar las URLs a las que accederá el cliente incluyendo en ellas el identificador de sesión como parámetro.
Para utilizar esta técnica deberemos codificar la URL en el servidor
y devolverla de alguna forma al cliente. Por ejemplo, podemos devolver esta
URL modificada como una cabecera HTTP propia. Supongamos que devolvemos esta
URL reescrita como una cabecera URL-Reescrita
. Podremos obtenerla
en la aplicación cliente de la siguiente forma:
String url_con_ID = con.getHeaderField("URL-Reescrita");
En la próxima petición que hagamos al servidor deberemos utilizar la URL que hemos obtenido, en lugar de la URL básica a la que conectamos inicialmente:
HttpConnection con = (HttpConnection)Connector.open(url_con_ID);
De esta forma cuando establezcamos esta segunda conexión el servlet al que conectamos sabrá que se trata de la misma sesión y podremos acceder a la información de la sesión dentro del servidor.
En el código del servlet que atiende nuestra petición en el servidor deberemos rescribir la URL y devolvérsela al cliente como la cabecera que hemos visto anteriormente. Esto podemos hacerlo de la siguiente forma:
String url = request.getRequestURL().toString();
String url_con_ID = response.encodeURL(url);
response.setHeader("URL-Reescrita", url_con_ID);
Manejo de cookies
Los servlets utilizan las cookies para mantener la sesión siempre que detecten que el cliente soporta cookies. Para el caso de estas aplicaciones J2ME el servlet detectará que el cliente no soporta cookies, por lo que utilizará únicamente reescritura de URLs. Sin embargo, si que podremos crear cookies manualmente en el servlet para permitir mantener la información del usuario en el cliente durante el tiempo que dure la sesión o incluso durante más tiempo.
Desde las aplicaciones J2ME podremos implementar las sesiones utilizando cookies. Además con las cookies podremos mantener información del usuario de forma persistente, no únicamente durante una sola sesión. Por ejemplo, podemos utilizar RMS para almacenar las cookies con información sobre el usuario, de forma que cuando se vuelve a utilizar la aplicación en otro momento sigamos teniendo esta información. Con esto podemos por ejemplo evitar que el usuario tenga que autentificarse cada vez que entra a la aplicación.
Estas cookies consisten en una pareja <nombre, valor> y una fecha de caducidad. Con esta fecha de caducidad podemos indicar si la cookie debe mantenerse sólo durante la sesión actual, o por más tiempo.
Podemos recibir las cookies que nos envía el servidor leyendo
la cabecera set-cookie
de la respuesta desde nuestra aplicación
J2ME:
String cookie = con.getHeaderField("set-cookie");
Una vez tengamos la cookie podemos guardárnosla en memoria,
o bien en RMS si queremos almacenar persistentemente esta información.
En las siguientes peticiones que hagamos al servidor deberemos enviarle esta
cookie en la cabecera cookie
:
con.setRequestProperty("cookie", cookie);
Seguridad
Podemos establecer una conexión segura con el servidor mediante SSL
(Secure Sockets Layer) simplemente indicando como protocolo en la URL
de la conexión https
en lugar de http
. Además
el servidor deberá soportar este tipo de conexiones.
En cuanto a la autentificación de los usuarios al conectarse a las aplicaciones corporativas, normalmente utilizaremos seguridad a nivel de la aplicación. Es decir, enviaremos nuestro login y password a un servlet para que los verifique en la base de datos de usuarios que haya en el servidor. Una vez autentificados, podremos mantener esta información de registro en la sesión del usuario, de forma que en sucesivas peticiones podamos obtener la información sin necesidad de volvernos a autentificar en cada una de ellas.
Podemos utilizar la API adicional WMA para enviar o recibir mensajes cortos (SMS, Short Message Service) a través del teléfono móvil. Esta API extiende GFC, permitiendo establecer conexiones para recibir o enviar mensajes. Cuando queramos enviar mensajes nos comportaremos como clientes en la conexión, mientras que para recibirlos actuaremos como servidor. La URL para establecer una conexión con el sistema de mensajes para ser enviados o recibidos a través de una portadora SMS sobre GSM tendrá el siguiente formato:
sms://telefono:puerto
Las clases de esta API se encuentran en el paquete javax.wireless.messaging
.
Aquí se definen una serie de interfaces para trabajar con los mensajes
y con la conexión.
Envio de mensajes
Si queremos enviar mensajes, deberemos crear una conexión cliente proporcionando en la URL el número del teléfono al que vamos a enviar el mensaje y el puerto al que lo enviaremos de forma opcional:
sms://+34555000000
sms://+34555000000:4444
Si no especificamos el puerto se utilizará el puerto que se use por
defecto para los mensajes del usuario en el teléfono móvil. Deberemos
abrir una conexión con una de estas URLs utilizando GFC, con lo que nos
devolverá una conexión de tipo MessageConnection
MessageConnection mc = (MessageConnection)Connector.open("sms://+34555000000");
Una vez creada la conexión podremos utilizarla para enviar mensajes cortos. Podremos mandar tanto mensajes de texto como binarios. Estos mensajes tienen un tamaño limitado a un máximo de 140 bytes. Si el mensaje es de texto el número de caracteres dependerá de la codificación de éstos. Por ejemplo si los codificamos con 7 bits tendremos una longitud de 160 caracteres, mientras que con una codificación de 8 bits tendremos un juego de caracteres más amplio pero los mensajes estarán limitados a 140 caracteres.
WMA permite encadenar mensajes, de forma que esta longitud podrá ser por lo menos 3 veces mayor. El encadenamiento consiste en que si el mensaje supera la longitud máxima de 140 bytes que puede transportar SMS, entonces se fracciona en varios fragmentos que serán enviados independientemente a través de SMS y serán unidos al llegar a su destino para formar el mensaje completo. Esto tiene el inconveniente de que realmente por la red están circulando varios mensajes, por lo que se nos cobrará por el número de fragmentos que haya enviado.
Podremos crear el mensaje a enviar a partir de la conexión. Los mensajes de texto los crearemos de la siguiente forma:
String texto = "Este es un mensaje corto de texto"; TextMessage msg = mc.newMessage(mc.TEXT_MESSAGE); msg.setPayloadText(texto);
Para el caso de un mensaje binario, lo crearemos de la siguiente forma:
byte [] datos = codificarDatos(); BinaryMessage msg = mc.newMessage(mc.BINARY_MESSAGE); msg.setPayloadData(datos);
Antes de enviar el mensaje, podemos ver en cuántos fragmentos deberá ser dividido para poder ser enviado utilizando la red subyacente con el siguiente método:
int num_segmentos = mc.numberOfSegments(msg);
Esto nos devolverá el número de segmentos en los que se fraccionará el mensaje, ó 0 si el mensaje no puede ser enviado utilizando la red subyacente.
Independientemente de si se trata de un mensaje de texto o de un mensaje binario, podremos enviarlo utilizando el siguiente método:
mc.send(msg);
Recepción de mensajes
Para recibir mensajes deberemos crear una conexión de tipo servidor. Para ello en la URL sólo especificaremos el puerto en el que queremos recibir los mensajes:
sms://:4444
Crearemos una conexión utilizando una URL como esta, en la que no se especifique el número de teléfono destino.
MessageConnection mc = (MessageConnection)Connector.open("sms://:4444");
Para recibir un mensaje utilizaremos el método:
Message msg = mc.receive();
Si hemos recibido un mensaje que todavía no hay sido leido este método obtendrá dicho mensaje. Si todavía no se ha recibido ningún mensaje, este método se quedará bloqueado hasta que se reciba un mensaje, momento en el que lo leerá y nos lo devolverá.
Podemos determinar en tiempo de ejecución si se trata de un mensaje de texto o de un mensaje binario. Para ello deberemos comprobar de qué tipo es realmente el objeto devuelto, y según este tipo leer sus datos como texto o como array de bytes:
if(msg instanceof TextMessage) {
String texto = ((TextMessage)msg).getPayloadText();
// Procesar texto
} else if(msg instanceof TextMessage) {
byte [] datos = ((BinaryMessage)msg).getPayloadData();
// Procesar datos
}
Hemos visto que el método receive
se queda bloqueado hasta
que se reciba un mensaje. No debemos hacer que la aplicación se quede
bloqueada esperando un mensaje, ya que éste puede tardar bastante, o
incluso no llegar nunca. Podemos solucionar este problema realizando la lectura
de los mensajes mediante un hilo en segundo plano. Otra solución es utilizar
un listener.
Listener de mensajes
Estos listeners nos servirán para que se nos notifique el momento
en el que se recibe un mensaje corto. De esta forma no tendremos que quedarnos
bloqueados esperando recibir el mensaje, sino que podemos invocar receive
directamente cuando sepamos que se ha recibido el mensaje.
Para crear un listener de este tipo deberemos crear una clase que
implemente la interfaz MessageListener
:
public MiListener implements MessageListener {
public void notifyIncomingMessage(MessageConnection mc) {
// Se ha recibido un mensaje a través de la conexion mc
}
}
Dentro del método notifyIncomingMessage
deberemos introducir
el código a ejecutar cuando se reciba un mensaje. No debemos ejecutar
la operación receive
directamente dentro de este método,
ya que es una operación costosa que no debe ser ejecutada dentro de los
callbacks que deben devolver el control lo antes posible para no entorpecer
el procesamiento de eventos de la aplicación. Deberemos hacer que la
recepción del mensaje la realice un hilo independiente.
Para que la recepción de mensajes le sea notificada a nuestro listener deberemos registrarlo como listener de la conexión con:
mc.setMessageListener(new MiListener());
En WTK 2.0 tenemos disponible una consola WMA con la que podremos simular el envío y la recepción de mensajes cortos que se intercambien entre los emuladores, de forma que podremos probar estas aplicaciones sin tener que enviar realmente los mensajes y pagar por ellos.
Hasta ahora hemos visto que cuando queremos obtener información primero debemos crear la conexión desde el cliente móvil. Para recibir algo antes tenemos que solicitarlo desde nuestra aplicación cliente, esto es lo que se conoce como una conexión pull, en la que el cliente debe tirar de los datos para obtenerlos.
Para determinadas aplicaciones puede ser interesante poder recibir datos sin tener que solicitarlos. Por ejemplo pensemos en una aplicación de chat, en la que hay varios clientes conectados a un mismo servidor que envían mensajes al chat. Nosotros querremos visualizar en nuestro cliente la lista de mensajes publicados en el servidor, pero no sabemos cuando llega un mensaje al servidor, ya que puede haberlo enviado cualquier otro cliente, sólo el servidor sabe cuando llegan nuevos mensajes.
Podemos utilizar una técnica conocida como polling, que consiste en interrogar al servidor cada cierto periodo de tiempo para comprobar si han llegado mensajes nuevos, y en tal caso recibirlos. Esto nos obligará a estar continuamente realizando peticiones al servidor, aunque no se haya recibido ningún mensaje.
Con una conexión de tipo push podremos solucionar este problema, ya que en este caso el servidor podrá enviarnos información sin tener que pedirla nosotros previamente. De esta forma cuando el servidor haya recibido nuevos mensajes nos los enviará mediante push, sin tener que estar interrogándolo nosotros continuamente.
MIDP 2.0 soporta este tipo de conexiones. Debemos especificar en el fichero JAD de la suite la lista de conexiones push entrantes que aceptará nuestra aplicación:
MIDlet-Push-1: sms://:4444, es.ua.j2ee.sms.MIDletRecibir, *
Debemos especificar para cada conexión push que queramos permitir
la URL de la conexión entrante y el MIDlet que se ejecutará cuando
recibamos datos. Además como tercer elemento podemos indicar los remitentes
a los que les permitimos enviarnos datos. Con *
indicamos que aceptamos
datos de cualquier remitente.
Podemos utilizar esta conexión push por ejemplo para la recepción de mensajes cortos. De esta forma no hará falta que nuestra aplicación esté ejecutándose continuamente para esperar recibir mensajes. Con push cuando llegue un mensaje al puerto especificado se ejecutará automáticamente nuestra aplicación para recibirlo.