Estabelecendo conexões HTTP com J2ME – Parte 02

 

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.

 

Atendendo requisições

 

Uma vez que o cliente J2ME envia a requisição, uma aplicação do lado servidor irá recebê-la, interpretá-la e gerar uma resposta.

 

A listagem 4 mostra parte do código de um Servlet feito para atender requisições como a do exemplo.

 

Listagem 4

 

protected void doPost(HttpServletRequest request,

                      HttpServletResponse response)

                      throws ServletException, IOException {

 

    String usuario = request.getParameter("idUsuario");

    String cliente = request.getParameter("idCliente");

    List tasks = getTasks(usuario, cliente);

    ByteArrayOutputStream baos =

                               new ByteArrayOutputStream();

    DataOutputStream dos = new DataOutputStream(baos);

    for (Iterator i = tasks.iterator(); i.hasNext(); ) {

        Task task = (Task) i.next();

        dos.writeInt(task.getIdTask());

        dos.writeUTF(task.getDescr());

        dos.writeInt(task.getStatus());

    }

    byte[] data = baos.toByteArray();

    dos.close();

    baos.close();

    response.setStatus(HttpServletResponse.SC_OK);

    response.setContentLength(data.length);

    response.setContentType("application/octet-stream");

    OutputStream os = response.getOutputStream();

    os.write(data);

    os.close();

}

 

 

 

Neste caso, inicialmente são recuperadas as identificações do consultor e do cliente. Estas informações são passadas ao método getTasks, que retorna uma lista de atividades (Task). Cada atividade possui os atributos identificação (tipo int), descrição (tipo String) e status (tipo int).

 

É criada, então, uma instância de ByteArrayOutputStream e, a partir dela, uma instância de DataOutputStream. ByteArrayOutputStream implementa uma stream de saída onde os dados são gravados em um array de bytes. DataOutputStream permite que tipos primitivos sejam escritos em uma stream de maneira portável, oferecendo uma maneira de recuperá-los facilmente de volta através de DataInputStream.

 

Na sequência, cada atividade da lista é recuperada em um laço e seus atributos são gravados na DataOutputStream, usando o método apropriado para cada tipo.

 

Após processar todas as atividades, é extraído um array de bytes da stream.

 

Em seguida, é iniciada a construção da resposta, informando o código de status da mesma. Isto é feito através da execução do método setStatus, passando o código que significa sucesso no atendimento da solicitação. O HTTP define cinco classes de código de status que indicam, por exemplo, erro na solicitação do cliente, erro no processamento no servidor ou necessidade de redirecionar a solicitação para outra URL.

 

Feito isto, é definido o valor para o cabeçalho Content-Length da resposta, através do método setContentLength. O valor passado é a quantidade de bytes no array montado.

 

Então, é utilizado o método setContentType para preencher o cabeçalho Content-Type com o valor application/octet-stream, cujo significado foi previamente explicado.

 

Finalmente, é obtida uma stream de saída através do método getOutputStream e nela é gravado o conteúdo do array de bytes. Ao fechar esta stream, a resposta é enviada ao cliente J2ME.

 

Interpretando a resposta

 

Voltando ao fonte exibido na listagem 3, o método getResponseCode da HttpConnection tem como valor de retorno o código de status do servidor, que indica se a requisição pôde ser atendida com sucesso.

 

Em caso positivo, é criada uma instância de DataInputStream a partir da stream de saída obtida com o método openInputStream da HttpConnection.

 

Conforme explicado anteriormente, como o conteúdo da DataInputStream foi montado a partir de um conjunto de bytes construídos com DataOutputStream, é fácil fazer a recuperação dos tipos primitivos (e Strings, também).

 

Para tanto, é montado um laço que permanece em execução enquanto existirem dados na stream. A cada iteração, é recuperado da stream um valor do tipo int, um valor do tipo String e outro valor do tipo int. Estes três valores são utilizados para criar um objeto Task, que é incluído em uma lista.

 

Ao final do laço, está disponível o conjunto de atividades a serem executadas pelo consultor em sua visita ao cliente.

 

Para finalizar, é percorrida a lista de listeners registrados para este processo e, em cada um deles, é executado o método loadFinished, passando a lista de atividades como parâmetro.

 

Com isto, a thread secundária é finalizada.

 

Na thread principal, a execução do método loadFinished faz com que a lista de atividades seja persistida num RecordStore. Além disso, caso o usuário não tenha desistido de aguardar a atualização das atividades, uma tela com as informações recuperadas é exibida para ele. No exemplo, quando o usuário indica que deseja interromper a espera pela atualização dos dados, o valor da variável showNewTasks é passado para false.

 

A listagem 5 mostra uma possível implementação para o método loadFinished na classe principal da aplicação.

 

Listagem 5

 

public void loadFinished(Vector list) {

    save(list);

    if (showNewTasks) {

        showTask();

    }

    else {

        showNewTasks = true;

    }

}

 

 

 

Conclusões

 

A escolha por HTTP para fazer a comunicação entre aplicações J2ME e sistemas de back-end é a mais óbvia por dois motivos. Primeiro, o suporte a este protocolo é exigido desde a especificação 1.0 do MIDP. Segundo, hoje o HTTP é praticamente onipresente nas aplicações corporativas.

 

Este modelo pode tirar proveito de sistemas que utilizem o padrão MVC, onde a aplicação J2ME representa apenas outra view do sistema.

 

Neste exemplo foi usado DataStream para formatar as informações retornadas pela aplicação servidora. Esta é uma opção simples e é a mais indicada quando ambas as partes são desenvolvidas utilizando Java. Se a aplicação servidora for construída com outra tecnologia, talvez seja o caso de criar um protocolo específico para esta formatação. Usar XML para formatar os dados, infelizmente, não é apropriado neste caso por aumentar a quantidade de dados trafegados e exigir um maior processamento.

 

O código apresentado diz respeito a uma aplicação de exemplo. Para simplificar sua interpretação, nele não foi feito nenhum tratamento de erro. Uma aplicação real teria que estar preparada para lidar com várias situações adversas que podem ocorrer em sua execução. Esta afirmação é mais forte para o Servlet que atende as solicitações. Ele deve estar preparado para retornar códigos de status que indiquem problemas nos parâmetros recebidos e erros internos.

 

Finalmente, no exemplo presente neste artigo os parâmetros passados ao servidor dizem respeito aos critérios de seleção de uma consulta. Entretanto, eles poderiam ser, perfeitamente, informações para serem atualizadas no destino.