Para um de nossos clientes, temos que mostrar um mapa com a posição exata dos veículos e que a atualização seja em tempo real. Então comecei a criar um protótipo usando o framework Play!, na sua última versão liberada (a 2.0), utilizando o Java API. Comecei pelo websocket-chat das amostras do Play! 2.0.
A finalidade do protótipo é mostrar um veículo em movimento no mapa. A localização dele é enviada para o servidor por meio de uma ligação REST (no final, isto será enviado por um aplicativo do Android), e os usuários conectados serão capazes de ver no mapa o veículo se movimentando em tempo real.
Primeiro, vamos ver uma pequena demonstração!
Para tornar as coisas um pouco melhores, decidi integrar o Twitter Boostrap (v2.0.1) usando o LessCss. Para isso, usei as dicas deste artigo (nada difícil aqui).
Depois, integrei o OpenLavers, um framework Javascript usado para visualização de mapas. Usei o exemplo de integração do GoogleMaps e acrescentei algumas camadas KML. Isso é feito em arquivos map.scala.html e maptracker.js. Nada demais até aqui (puro Javascripit e olha que eu nem sou um expert…).
A parte interessante é aquela onde se usa o WebSockets. Do ponto de vista do cliente, é perfeitamente normal:
var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket
var mapSocket = new WS("@routes.Application.mapsocket().webSocketURL(request)");
mapSocket.onmessage = function(event) {
var data = JSON.parse(event.data);
marker = moveMaker(map, marker, data.longitude, data.latitude);
}
// if errors on websocket
var onalert = function(event) {
$(".alert").removeClass("hide");
}
mapSocket.onerror = onalert;
mapSocket.onclose = onalert;
Quando o cliente recebe um dado JSON pelo websocket, ele move o marcador no mapa. E se um erro ocorre no websocket (o servidor parar, por exemplo), um quadro de erro é exibido gracas ao Twitter Boostrap:

Na parte do servidor, o WebSocket é criado pelo controlador do aplicativo, e é manipulado pelo Actor Akka MapAnime.java; ele acessa as bibliotecas nativas Akka para lidar com os eventos do controlador.
public class MapAnime extends UntypedActor {
static ActorRef actor = Akka.system().actorOf(new Props(MapAnime.class));
Map<String, WebSocket.Out<JsonNode>> registrered = new HashMap<String, WebSocket.Out<JsonNode>>();
/**
*
* @param id
* @param in
* @param out
* @throws Exception
*/
public static void register(final String id,
final WebSocket.In<JsonNode> in, final WebSocket.Out<JsonNode> out)
throws Exception {
actor.tell(new RegistrationMessage(id, out));
// For each event received on the socket,
in.onMessage(new Callback<JsonNode>() {
@Override
public void invoke(JsonNode event) {
// nothing to do
}
});
// When the socket is closed.
in.onClose(new Callback0() {
@Override
public void invoke() {
actor.tell(new UnregistrationMessage(id));
}
});
}
public static void moveTo(float longitude, float latitude) {
actor.tell(new MoveMessage(longitude, latitude));
}
@Override
public void onReceive(Object message) throws Exception {
if (message instanceof RegistrationMessage) {
// Received a Join message
RegistrationMessage registration = (RegistrationMessage) message;
Logger.info("Registering " + registration.id + "...");
registrered.put(registration.id, registration.channel);
} else if (message instanceof MoveMessage) {
// Received a Move message
MoveMessage move = (MoveMessage) message;
for (WebSocket.Out<JsonNode> channel : registrered.values()) {
ObjectNode event = Json.newObject();
event.put("longitude", move.longitude);
event.put("latitude", move.latitude);
channel.write(event);
}
} else if (message instanceof UnregistrationMessage) {
// Received a Unregistration message
UnregistrationMessage quit = (UnregistrationMessage) message;
Logger.info("Unregistering " + quit.id + "...");
registrered.remove(quit.id);
} else {
unhandled(message);
}
}
public static class RegistrationMessage {
public String id;
public WebSocket.Out<JsonNode> channel;
public RegistrationMessage(String id, WebSocket.Out<JsonNode> channel) {
super();
this.id = id;
this.channel = channel;
}
}
public static class UnregistrationMessage {
public String id;
public UnregistrationMessage(String id) {
super();
this.id = id;
}
}
public static class MoveMessage {
public float longitude;
public float latitude;
public MoveMessage(float longitude, float latitude) {
this.longitude = longitude;
this.latitude = latitude;
}
}
}
Os métodos « register » e « moveTo » são chamados pelo controlador e enviam mensagem para o sistema Akka. Essas mensagens são processadas pelo método « onReceive ». Assim, quando é recebida uma MoveMessage, cria-se um objeto JSON com a longitude e a latitude e isso é enviado para os clientes por meio dos websockets.
Eu também escrevi rapidamente uma classe de teste que analisa um arquivo de texto e envia solicitações de REST com um novo local para o servidor a cada 100ms.
O projeto está hospedado no GitHub. Ele funciona com o Google Chrome v17 ev11 Firefox.
Para testá-lo:
- Baixe o Play! 2.0;
- Clone o Git repo;
- Inicie o « play run » no diretório do projeto;
- Conecte o « http://localhost:9000/map «;
- Em outro terminal, rode o « play test » para enviar solicitações REST e para fazer o veículo mover.
O problema que precisei resolver foi que a aplicação não é Stateless, porque no Actor eu armazeno um mapa de clientes conectados. Talvez eu tenha que olhar para o Rediz ou algo parecido, qualquer ajuda seria muito bem-vinda.
Então, como conclusão, fui capaz de desenvolver rapidamente um protótipo funcional e eu acho que tentarei usar o Play! 2.0 em vários projetos.
- O que é bom:
- Altamente produtivo;
- Modelos typesafe view baseados no Scala;
- Menor integração CSS;
- Integração Akka;
- Javascript compilado com o Google Closure Compiler;
- Não precisa aprender o Scala para o momento.
- A ser melhorado:
- A compilação de tempo do Scala deve ser aumentada, por que no meu computador ele leva até 4s para compilar uma exibição;
- Os erros do compilador do Scala são enigmáticos;
- Não posso implantar uma demonstração sobre o Heroku porque ele não suporta (ainda?) websockets.
Atualização: pouco depois, achei um artigo do @steve_objectify usando tecnologias similares.
***
Artigo original disponível em: hhttp://www.ekito.fr/portail/playing-2-0-with-twitter-bootstrap-websockets-akka-and-openlayers?lang=en







