O Java EE 7 introduziu um conjunto de novas APIs e também melhorias em APIs existentes voltadas ao desenvolvimento web com HTML5. As mais importantes delas são: a nova API para processar documentos JSON, uma atualização importante no JSF para suportar novos atributos em elementos HTML, e a nova API para trabalhar com o protocolo WebSocket, o qual é uma das várias tecnologias que compõem o HTML5.
O protocolo WebSocket muda a forma como os servidores web reagem frente às requisições de seus clientes: em vez de encerrar a conexão, o servidor devolve um status 101 e mantém a conexão aberta para que ele mesmo ou o cliente possam escrever novas mensagens no fluxo de comunicação.
Ao contrário do HTTP, o protocolo WebSocket suporta comunicação full-duplex, de forma que o cliente (normalmente um navegador web) e o servidor possam trocar mensagens simultaneamente.
O protocolo WebSocket é definido pela RFC 6455 da IETF.
Para estabelecer uma conexão WebSocket, o cliente envia uma requisição de handshake, que é respondida pelo servidor, conforme mostrado no exemplo a seguir:
GET /mychat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
Origin: http://example.com
Eis a resposta do servidor:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
Ao final do processo de handshake (ou negociação), o cliente e o servidor são conectados e ambos podem trocar mensagens, assim como encerrar a conexão.
Do lado cliente, todo o trabalho é feito com JavaScript com a API definida pela W3C para esse propósito.
As aplicações WebSocket em Java consistem de endpoints WebSocket, que são objetos que representam uma das pontas ou términos da conexão WebSocket.
A API de WebSocket do Java modela cada participante de uma sessão com o endpoint como uma instância da interface RemoteEndpoint. Essa interface e seus dois subtipos (RemoteEndpoint.Whole e RemoteEndpoint.Partial) contêm uma variedade de métodos para enviar mensagens WebSocket a partir do endpoint para os participantes.
Existem duas formas principais de se implementar um endpoint. A primeira é implementando algumas classes da API WebSocket do Java, que contêm o comportamento exigido para controlar o ciclo de vida do endpoint, consumir e enviar mensagens, publicar-se e conectar-se a um peer:
session.addMessageHandler(new MessageHandler.Whole<String>() { public void onMessage(String text) { try { remote.sendText("Got your message (" + text + "). Thanks !"); } catch (IOException ioe) { ioe.printStackTrace(); } }
A segunda forma é decorar uma classe POJO (comum) com algumas das anotações da API WebSocket do Java. Em tempo de execução, a implementação subjacente identifica essas classes POJO anotadas e cria os objetos apropriados para implantá-las como endpoints WebSocket.
@ServerEndpoint("/hello") public class MyHelloServer { @OnMessage public String handleMessage(String message) { return "Got your message (" + message + "). Thanks !"; } }
A API limita a apenas um a quantidade de MessageHandlers registrados por tipo nativo de mensagem WebSocket (texto, binário, pong) em uma mesma sessão. Essa limitação pode mudar no futuro.
O endpoint participa do processo de handshake que estabelece a conexão WebSocket. Normalmente em uma conexão WebSocket o endpoint envia e recebe uma extensa variedade de mensagens. O ciclo de vida do endpoint é completado quando a conexão é finalmente encerrada.
Se uma conexão com o endpoint WebSocket deve ser encerrada por algum motivo, seja pelo recebimento de um evento de encerramento vindo do peer ou porque a implementação subjacente decidiu encerrar a conexão, a implementação WebSocket deve invocar o método onClose() do respectivo endpoint.
Antes mesmo do lançamento do Java EE 7 já era possível implementar aplicações WebSocket em Java com uma diversidade de opções disponíveis. Essas APIs eram muito diferentes, e aplicações WebSocket escritas para o Tomcat 7, por exemplo, tinham que ser modificadas para funcionar no Jetty, JBoss ou Resin. A chegada de um método padronizado de se fazer isso no Java EE é certamente muito bem-vindo.