Estabelecendo conexões HTTP com J2ME - Parte 01

O desenvolvimento de aplicações para dispositivos móveis tem crescido de maneira vertiginosa. Poder construir soluções que estejam disponíveis para o usuário onde quer que ele esteja representa uma considerável possibilidade de expansão para o mercado de software.

 

Dentre as tecnologias existentes endereçadas para a construção de aplicações wireless, o J2ME (Java 2 Platform, Micro Edition) é a que, certamente, pode ser encontrada mais facilmente em telefones celulares.

 

Dada a necessidade de atender uma gama de dispositivos com recursos bem variados, o J2ME foi dividido em duas configurações. Uma configuração é responsável por fornecer uma máquina virtual Java e um conjunto básico de bibliotecas. A configuração CDC (Connected Device Configuration) é indicada para equipamentos com recursos mais ricos, como PDAs high-end. Já a configuração CLDC (Connected Limited Device Configuration) é destinada a equipamentos com recursos mais restritos. É ela que está disponível nos telefones celulares.

 

A arquitetura J2ME foi projetada prevendo, também, a construção de um conjunto de bibliotecas de mais alto nível a partir das bibliotecas de uma configuração. Isto recebeu o nome de perfil (Profile, em inglês). Para aparelhos celulares foi definido o padrão MIDP (Mobile Information Device Profile).

 

O objetivo deste artigo é demonstrar como fazer a comunicação via HTTP entre um dispositivo móvel que implemente o MIDP do J2ME e outros computadores.

 

Comunicação entre equipamentos

 

Uma das primeiras preocupações que surgem quando se inicia o desenvolvimento de, praticamente, qualquer tipo de aplicativo é como acontecerá a comunicação com outros sistemas. Isto vale para aplicações corporativas e, até mesmo, para jogos. Neste último caso, mesmo que não se esteja falando de um produto multi-player, a capacidade de trocar informações com um servidor pode ser necessária para o aumento do valor oferecido aos usuários.

 

Quando se pensa em aplicações corporativas, a conectividade se torna uma questão bem mais crítica. De maneira geral, o programa executado no dispositivo faz parte de um sistema maior.  Este sistema possui interfaces específicas para cada classe de equipamento a ser utilizado. Neste contexto, de acordo com sua natureza a atividade é direcionada para a classe de equipamento mais apropriada.

 

Tomemos como exemplo uma aplicação para o gerenciamento das atividades dos consultores de uma empresa. Cabe à interface acessada via desktop a entrada dos dados mais volumosos. É através dela que serão cadastradas as informações do consultor, do cliente, da visita a ser feita e do trabalho a ser executado. Utilizando a interface para telefone celular, o consultor irá receber estes dados e apenas sinalizará o andamento de cada atividade.

 

Na versão standard do Java as funcionalidades de comunicação são encontradas nos pacotes java.net e java.io.  Evidentemente, as restrições impostas pelos, ainda limitados, recursos dos dispositivos móveis não permitem que sejam disponibilizadas todas as facilidades presentes nestes pacotes. Em seu lugar, na configuração CLDC do J2ME pode ser encontrado um framework de conexão denominado Generic Connection Framework (GCF).

 

O Generic Connection Framework

 

O GCF está presente no pacote javax.microedition.io. Ele é formado por uma hierarquia de interfaces e classes que implementam o pattern factory. Na base da hierarquia de interfaces está Connection. A partir dela são derivadas outras interfaces para atender necessidades e protocolos específicos.

 

A maneira de criar um objeto que implemente a interface Connection é através do método estático open da classe Connector. Este método recebe como parâmetro uma URL, que indica o recurso ao qual se deseja conectar.

 

Abaixo é apresentada a estrutura de uma URL e o significado de cada elemento.

 

scheme://user:password@host:port/url-path;parameters

 

scheme: Protocolo a ser usado na comunicação. Ex: http;

user: Identificação do usuário (opcional);

password: Senha do usuário (opcional);

host: Nome ou IP do host onde o recurso está localizado;

port:Porta do host a ser acessada (opcional);

url-path:Caminho para o recurso;

parameters:Parâmetros a serem passados ao recurso (opcional).

 

O GCF foi projetado para ser facilmente expandido. Isto é especialmente importante se for considerada a natureza dos protocolos. Protocolos não são genéricos e sempre possuem características bem específicas. Logo é imprescindível que um framework deste tipo seja expansível para acomodar mais facilmente novas formas de comunicação.

 

No MIDP, desde sua versão 1.0, é definida uma nova interface a partir de Connection para tratar das especificidades do protocolo HTTP: HttpConnection.

 

Construindo requisições HTTP

 

A maneira mais apropriada de fazer a conexão entre uma aplicação J2ME e uma outra aplicação externa é através da interface HttpConnection. Por intermédio desta interface é possível provocar a execução de um programa em um servidor HTTP e receber o resultado disto.

 

O programa a ser executado tanto pode ser um já existente, contendo regras de negócio de uma aplicação em camadas, quanto um construído especificamente para recuperar informações de SGBD e retorná-las ao aplicativo J2ME.

 

Evidentemente, este programa não precisa ser escrito, necessariamente, em Java. Na verdade, pode ser utilizada qualquer tecnologia capaz de tratar requisições HTTP, como PHP ou .NET. Entretanto, neste artigo será exemplificada a comunicação entre J2ME e um Servlet em um servidor J2EE.

 

Mais adiante serão apresentadas partes do código de uma aplicação utilizada por um consultor para recuperar a relação de atividades que deve executar em uma visita a um cliente.

 

O primeiro ponto que deve ser observado ao estabelecer conexões é que as mesmas podem consumir muito tempo. Sendo assim, é importante sempre dar ao usuário a opção de não ficar aguardando sua conclusão. Este efeito é obtido obedecendo ao princípio de sempre realizar conexões em threads separadas.

 

Para atender este requisito, neste exemplo existe uma classe (LoadTask) que estende Thread. É ela que realmente recupera a lista de atividades.

 

A listagem 1 exibe parte do código do programa que utiliza esta classe.

 

Listagem 1

 

LoadTask loader = new LoadTask();

loader.setParam(idUsuario,idCliente);

loader.setLoadListener(this);

loader.start();

showWaintingTasks();

 

 

 

Na aplicação J2ME, quando o usuário executa o comando relativo à atualização da lista de atividades, é criada uma instância de LoadTask.  Em seguida, é passado para este objeto, através do método setParam, a identificação do consultor e do cliente que ele irá visitar.

 

O método setLoadListener é utilizado para que o programa se registre como um listener do processo de atualização das atividades, que acontecerá na outra thread. Para tanto, é necessário que ele, além de estender a classe Midlet, também implementa a interface LoadListener.

 

Na listagem 2 é apresentada a estrutura da interface LoadListener.

 

Listagem 2

 

import java.util.Vector;

public interface LoadListener {

    public void loadFinished(Vector list);

}

 

 

 

Esta interface define apenas o método loadFinished. Este método será executado em cada listener registrado na instância de LoadTask. Ele tem como parâmetro a lista de atividades recuperada. É desta maneira que a thread secundária devolve para a thread principal as informações obtidas através da conexão HTTP.

 

Retornando à listagem 1, ao ser executado o método start é que a thread secundária será criada. A principal, então, continuará sua execução, chamando o método showWaintingTasks.

 

O método showWaintingTasks mostra uma nova tela ao usuário, que indica que dados estão sendo atualizados. Nesta tela há um comando de Cancelar que, se acionado, provoca a exibição da tela principal da aplicação. Desta maneira, o usuário tem a possibilidade de não aguardar o final da execução da thread secundária e pode continuar a executar outra parte da aplicação, se desejar .

 

Agora será analisado o que acontece na thread criada através da execução do método start na instância da classe LoadTask. A listagem 3 mostra a estrutura desta classe.

 

Listagem 3

 

import java.io.*;

import javax.microedition.io.*;

import java.util.Vector;

 

public class LoadTask extends Thread {

        

    private Vector listeners = new Vector();

    private int idUsuario, idCliente;

        

    public void setLoadListener(LoadListener listener) {

        listeners.addElement(listener);

    }

 

    public void setParam(int idUsuario, int idCliente) {

            this.idUsuario = idUsuario;

            this.idCliente = idCliente;

    }

        

    public void run() {

        Vector tasks = new Vector();

        try {

            HttpConnection conn = (HttpConnection)

            Connector.open(

              "http://localhost:8080/fieldcontrol/gettask");

            conn.setRequestMethod(HttpConnection.POST);

            conn.setRequestProperty("Content-Type",

                       "application/x-www-form-urlencoded");

            conn.setRequestProperty(

      "User-Agent","Profile/MIDP-1.0 Configuration/CLDC-1.0"

             );

            conn.setRequestProperty(

                              "Content-Language", "pt-BR");

            conn.setRequestProperty("Accept",

                               "application/octet-stream");

            conn.setRequestProperty("Connection", "close");

            String formData = "idUsuario=" + idUsuario +

                              "&idCliente=" + idCliente;

            byte[] data = formData.getBytes();

            conn.setRequestProperty("Content-Length",

                          Integer.toString(data.length));

            OutputStream os = conn.openOutputStream();

            os.write(data);

            os.close();

            int rc = conn.getResponseCode();

            if (rc == HttpConnection.HTTP_OK) {

                DataInputStream dis = new

                    DataInputStream(conn.openInputStream());

                while (dis.available() > 0) {

                    Task task = new Task();

                    task.setIdTask(dis.readInt());

                    task.setDescr(dis.readUTF());

                    task.setStatus(dis.readInt());

                    tasks.addElement(task);

                }

                dis.close();

            }

        }

        catch(Exception rse) {

            rse.printStackTrace();

        }

        if (listeners != null) {

            for (int i = 0; i < listeners.size(); i++) {

                    LoadListener listener = (LoadListener)

                                     listeners.elementAt(i);

                    listener.loadFinished(tasks);

            }

        }

    }

        

}

 

 

A thread começa sua execução no método run e é nele que realmente é feita a conexão HTTP.

 

O protocolo HTTP segue um modelo de requisição-resposta. Cada requisição possui um conjunto de cabeçalhos que fornecem diferentes informações sobre ela. Estes cabeçalhos devem ter seus valores definidos antes que a requisição seja enviada. A interface HttpConnection fornece métodos para atribuir valores a eles.

 

O corpo do método run inicia com a utilização o método open do factory Connector para criar um objeto que implementa HttpConnection. Neste momento a requisição ainda não foi enviada e encontra-se no estado de Setup.

 

Na sequência, através do método setRequestMethod, é definido qual o método HTTP que será utilizado. Dos vários métodos definidos para este protocolo, o MIDP suporta apenas GET e POST.

 

Ambos os métodos passam informações através da URL de conexão e de vários cabeçalhos. O que difere ambos é a maneira como dados complementares são enviados. No exemplo, estes dados complementares são a identificação do consultor e do cliente a visitar.

 

No método GET, dados deste tipo são passados como parâmetros na URL. No método POST eles são enviados através de uma stream de saída, que é criada depois de os cabeçalhos terem sido definidos. Como existe uma restrição para a quantidade de caracteres na URL, é interessante adotar como padrão o método POST. Além disso, a passagem de conteúdo binário só é possível através deste método.

 

Em seguida, o método setRequestProperty é executado várias vezes para definir o valor de alguns cabeçalhos HTTP.

 

O cabeçalho Content-Type indica o tipo MIME do conteúdo enviado que não faz parte nem da URL e nem dos cabeçalhos. O tipo MIME application/x-www-form-urlencoded indica que o conteúdo extra está na forma de pares nome-valor.

 

O cabeçalho User-Agent é usado para indicar que tipo de cliente está enviando a requisição HTTP. Aqui, se o cliente fosse um browser WEB o valor poderia ser “Mozilla/4.0”.  Para aplicações J2ME o usual é identificar o cliente como “Profile/MIDP-1.0 Configuration/CLDC-1.0”.

 

O cabeçalho Accept indica o tipo MIME que o cliente aceita para as informações que forem devolvidas para ele. O valor application/octet-stream significa que o cliente espera receber a resposta num formato binário de dados. Neste caso, o cliente tem que saber exatamente como interpretar o conjunto de bytes recebidos, que não estão num formato padronizado.

 

O objetivo do cabeçalho Connection com o valor close é informar ao servidor que não há a intenção de fazer novas requisições logo em seguida. Isto faz com que a conexão seja realmente fechada, assim que a requisição corrente for atendida.

 

Apesar de o protocolo HTTP não exigir que um valor para o cabeçalho Content-Length seja definido, é importante fazer isto sempre que possível. Este cabeçalho indica a quantidade de dados complementares que serão enviados na requisição.

 

No exemplo, é construída uma string com a identificação do consultor e do cliente a visitar (no formato nome-valor) e, então, é extraída dela um array de bytes. Este array é utilizado, inicialmente, para definir o valor do cabeçalho Content-Length. Feito isto, através do método openOutputStream da HttpConnection, é obtida uma stream de saída e nela é gravado o conteúdo do array de bytes. Ao ser fechada esta stream, a requisição é enviada ao servidor.