Push notifications com Server Sent Events na última versão do Spring

Hoje em dia usamos muitas aplicações onde ficamos na mesma tela por muito tempo. Por exemplo, ficamos na home do facebook e somos notificados de novos posts sem termos a necessidade de ficar atualizando a tela. A mesma coisa acontece na aplicação web do Whatsapp. Um bom questionamento é: como que essas aplicações conseguem ficar fazendo isso? Bem antigamente a técnica mais utilizada era realizar requisições ajax de tempos em tempos para o servidor. O grande problema era que o cliente ficava enviando inúmeros requests sem necessidade, obrigando o servidor a processar todos, sem mesmo ter uma necessidade.

A solução é até meio simples. Ao invés do cliente ficar perguntando para o servidor o tempo inteiro se chegou alguma mensagem, o servidor poderia notificar o cliente quando cada mensagem fosse chegando. Solução conhecida como push notification. Desse jeito o servidor não é inundado de requisições inúteis feitas pelo cliente e, além disso, o cliente é notificado quando realmente uma nova mensagem chega. Ao invés de ficar esperando um certo tempo para poder fazer uma nova requisição pedindo pelas novidades. Para a nossa sorte, o HTML5 especificou duas maneiras para possibilitar as notificações enviadas diretamente pelos servidores.

Provavelmente a mais conhecida é a WebSocket, onde tanto o cliente pode enviar mensagens para o servidor, quanto o servidor pode enviar mensagens para o cliente. Esse é o caso necessário para a aplicação web do Whastapp. Caso você a utilize, basta abrir a sua ferramenta favorita para inspecionar o navegador que você vai encontrar a url wss://w5.web.whatsapp.com/ws. É através dela que você envia e recebe as mensagens.

Agora vamos imaginar uma situação na Casa do Código. Precisamos adicionar uma funcionalidade onde o administrador da loja é capaz de surpreender os atuais visitantes com uma promoção relâmpago de algum livro. Perceba que não queremos que o navegador envie nenhuma mensagem para o servidor e sim, que apenas o servidor notifique o navegador de algum evento. Para este tipo de cenário, podemos uma outra especificação que veio no HTML5 chamada Server Sent Event. E agora a parte mais interessante, a última versão do Spring já vem com suporte nativo para essa outra modalidade de push notification. 

Vamos começar pela método do controller que é responsável pela geração da notificação.

        ...
        public class ProductsController {
         ...
         public static final SseEmitter notifier = new SseEmitter();

	 @RequestMapping("habilita/promocao/{id}")
	 @ResponseStatus(value=HttpStatus.OK)
	 public void enableQuickPromoForProduct(@PathVariable("id") Integer id) throws IOException{
		Product product = products.findOne(id);
		notifier.send(new QuickPromoData(product,messageSource));
	 }
        }

Buscamos um produto e geramos um outro objeto do tipo QuickPromoData. Esse objeto tem apenas as informações necessárias que devem ser enviadas para o cliente. O mesmo vai ser serializado como um JSON, então devemos ter cuidado para só expor o necessário e não ficar gerando mais dados do que precisamos. Sem contar que trabalhar diretamente com o objeto gerenciado pelo hibernate, pode nos levar a problemas de lazy load que não tem nada relacionado com a situação que estamos tratando aqui. É uma ótima prática sempre pensar com bastante carinho nos objetos que serão serializados, ao invés de ficar usando diretamente o objeto de domínio. Além disso estamos trabalhando com uma outra variável chamada notifier.  Ela é do tipo SseEmitter, justamente uma nova classe criada pelo Spring para que possamos enviar os dados para o navegador no protocolo exigido pelo SSE. 

E aqui precisamos parar um segundo para discutir o uso do objeto do tipo SseEmitter. Ele funciona como se fosse seu canal de comunicação com todos os clientes que estão conectados na aplicação naquele momento. Por conta disso precisamos manter o estado desse objeto entre as várias requisições. Na verdade não é nada complicado, basta que deixemos essa variável como estática no próprio controller. Caso isso te incomode você também pode deixá-la como variável de instância, já que todo controller por default vive no escopo da aplicação.

Agora que já notificamos os clientes, precisamos que eles abram a conexão com nosso servidor para que possam ser avisados das novas promoções.

    <script>
	var promoSource = new EventSource("${spring:mvcUrl('PC#enableQuickPromoNotifier').build()}");
	promoSource.onmessage = function(event) {
		var data = event.data;
		console.log(data)
	};
   </script>

A classe EventSource está disponível na maioria dos navegadores. O uso dela é bem direto, basta que instanciemos ele passando a url que abre a conexão que deve ser mantida para o recebimento de dados, também chamada de long pooling. Depois disso, associamos uma função que vai ser chamada sempre que nossa aplicação notificar o canal de comunicação. Para concluir, precisamos do método que trata o request solicitando a abertura da conexão.

     @RequestMapping("habilita/promocao")
     public SseEmitter enableQuickPromoNotifier(){
        return notifier;
     }

Perceba que apenas retornamos o objeto do tipo SseEmitter. Apenas reforçando que é importante que você mantenha o estado desse objeto. Você tem que utilizar a mesma instância retornada por este método para enviar as notificações.

Por fim é necessário que a versão do Spring atualizada no seu projeto. Nesse exemplo foi utilizado o update 4.2.0.RC1. Apenas para lembrar, os posts no blog se baseiam no projeto que está no github.

Advertisements

6 thoughts on “Push notifications com Server Sent Events na última versão do Spring

  1. Muito bacana, Alberto!

    Como disse no Facebook eu estava estudando essa solução há 2 semanas atrás. A verdade é que eu desconhecia ela e somente conhecia sua parceira mais famosa, o WebSockets. Contudo, o SSE além de ser mais simples e prever quebra de conexões (reconnects) ele é suficiente para casos onde somente o servidor envia notificação para o cliente, já que como você bem comentou, o WebSockets é bidirecional.

    Um abraço e parabéns pelas postagens!

    Like

    • Essa parte de tratar as quebras de conexões é bem legal mesmo. Outro aspecto que eu não cobri no texto, é a possibilidade de você dar nomes as notificações e só receber as notificações para determinados eventos, como se fosse um filtro :).

      Like

  2. evtSource.addEventListener("productPromo", function(e) {
     codigo aqui
    }, false);
    
      notifier.send(SseEmitter.event().data(product,null).name("productPromo"));
    

    Aí você pode fazer o bind entre os eventos :).

    Like

    • Sim, sim… tem razão! Com SSE podemos declarar nossos próprios eventos customizados, o que é bacana para páginas mais complexas! Não lembrava disso!

      Like

  3. Olá, parabéns pelo post!
    Achei muito bacana!
    Só estou com um problema: na hora de fazer o notifier.send(myObject), estou tendo a seguinte exception: org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: No suitable converter for class java.lang.String

    Você sabe o que pode ser?

    Desde já agradeço.
    Att.
    Sandro

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s