push-notifications-com-socket-io-node-js-express

Quando algo mudar no servidor, como notificar o client/frontend? Embarque comigo nessa aventura pelo universo do Socket.io, onde aprenderemos como criar um server simples para push notifications de nossos APPs e clients.

O código-fonte deste teste está neste repositório do github

Analisando a demanda:

Necessidade: Notificar o client em tempo real quando algo específico acontecer.

Requisitos: Não depender de timeout(), escalável (não pode derrubar o server se houverem muitos usuários), Funcionar em conjunto, porém de forma independente com a API REST do serviço.

Ok, primeiro algumas observações: Uma técnica muito utilizada é o ‘Long Poling’, que consiste em solicitar uma página ao servidor, que só é enviada quando houver uma notificação. Ao receber a página - que só foi enviada pq houve notificação - imediatamente o client começa uma nova requisição. Vê o problema aí? A conexão fica aberta e ativa, e reinicia-se quando chega uma novidade. Aff. (é mano, não reclama tanto assim não, pq muita coisa boa já usou isso. Chamava-se commet (wikipedia, em inglês))

A Solução escolhida

Apesar dos HTML5 Server Send Events (SSE) parecerem super fofos e promissores, vamos de Websockets, que permitem um front-end independente (sem precisar estar no mesmo servidor ou domínio).

Pra facilitar ainda mais, já que estamos usando NodeJs, vamos de Socket.io.

Mãos a obra! - Setup básico

Primeiro, precisamos de uma estrutura básica em Node.JS. Só um server e algumas rotas. Vou usar o Express pra facilitar, então pode criar uma pasta e jogar os seguintes arquivos dentro:
(se não tiver o nodejs, baixe aqui)

[package.json]
1
2
3
4
5
6
7
8
9
10
{
"name": "server-com-push",
"version": "1.0.0",
"main": "server.js",
"dependencies": {
"socket.io": "^1.3.2",
"body-parser": "^1.11.0",
"express": "^4.11.2"
}
}

Já pode ir acessando a pasta que vc criou via terminal e rodar lá dentro:

1
$ npm install

[server.js][crie o arquivo na mesma pasta do package.json]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var express = require('express')
, app = express()
, server = require('http').createServer(app).listen(4555)
, io = require('socket.io').listen(server)
, bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
var port = process.env.PORT || 8080;
var router = express.Router();
/* Socket irá aqui depois */
app.use('/api', router);
router.route('/notificar')
.get(function(req, res){
//aqui vamos receber a mensagem
res.json({message: "testando essa rota"})
})
app.listen(port);
console.log('conectado a porta ' + port);

Note a variável server, onde carregamos o http, criamos o server e setamos a porta para 4555!

importante lembrar que a porta (4555) é apenas para o server http do socket.io. Ela está referenciada na variável server,que entra como parâmetro do listen() do socket.io

A porta da nossa API (que usa express) é definida mais abaixo na var port e usada quando chamados o app.listen(port).

Portanto, temos 2 servidores independentes: um na porta 4555 e outro na porta 8080, sendo o primeiro apenas para o socket.io

Agora rode o server

1
$ node server

E acesse http://localhost:8080/api/notificar veja se recebeu o retorno:

[retorno da rota "/notificar"]
1
2
3
{
"message": "testando essa rota"
}

Criando o middleware para emitir o evento

Pra emitir o evento, criaremos um Middleware. Se você não sabe o que é um, pode querer ler O que é um middleware no ExpressJs e pra que serve? (AKA: Como interceptar requisições).

Logo abaixo do comentário /* Socket irá aqui depois */, vamos inserir nosso middleware:

[server.js]
1
2
3
4
5
6
7
8
9
10
11
12
(...)
/* Socket irá aqui depois */
var emitir = function(req, res, next){
var notificar = req.query.notificacao || '';
if(notificar != '') {
io.emit('notificacao', notificar);
next();
} else {
next();
}
}
(...)

Neste código:
Criamos um Middleware que confere se o parâmetro notificacao veio preenchido (req.query.notificacao). Senão, define a var notificar como string vazia.

Aí vemos:
se notificar NÃO for uma string vazia, usamos io.emit para enviar ela (variável notificar). Isso será feito disparando um evento que demos o nome de notificacao.E daí chamamos o next().
Senão chamamos o next() sem fazer nada.

Injetando o Middleware em nossa rota

Basta inserir a variável na qual declaramos o middleware como primeiro parâmetro do nosso verbo get(), ou ativá-lo com o app.use(). Qual você prefere? (To brincando, quem manda aqui sou eu ;P)

[server.js][insira logo antes app.use('/api', router)]
1
2
3
4
(...)
app.use(emitir);
app.use('/api', router);
(...)

Pimba! Agora em qualquer rota nosso Middleware vai conferir se tem algo pra notificar, e o fará.

Recebendo as notificações.

Legal, agora precisamos receber essas notificações de alguma forma. A grande vibe dessa história é que não precisamos estar puxando arquivos do servidor para que funcione, então criaremos um arquivo html que vai representar o client.

O cliente poderia ser um app Web, ou um App Desktop, ou mesmo Mobile, enfim… Não importa. Crie o arquivo recebedor.html conforme abaixo:

[recebedor.html]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="pt_BR">
<head>
<meta charset="UTF-8">
<title>Recebedor de notificações</title>
</head>
<body>
<script src="http://localhost:4555/socket.io/socket.io.js"></script>
<script>
var socket = io('http://localhost:4555', {transports: ['websocket', 'polling', 'flashsocket']});
socket.on('notificacao', function (data) {
document.getElementById('messagebox').innerHTML = data;
});
</script>
<div id="messagebox">
</div>
</body>
</html>

Aí é importante notarmos:

  1. Carregamos socket.io.js pela url: nossoservidor:porta/socket.io/socket.io.js.
    É possível carregar um arquivo direto, ou até por CDN, mas dessa forma garantimos que é a mesma versão utilizada no servidor, bem como que está funcionando a conexão.

  2. var socket = io('http://localhost:4555', {transports: ['websocket', 'polling', 'flashsocket']}); O primeiro parâmetro é a URL do servidor + porta em que o socket.io está emitindo. O segundo, uma config que define a prioridade de protocolos utilizados, sendo websocket primeiro.
    Isso evita erros terrível de CORS com socket.io

  3. socket.on('notificacao', function (data) {} adiciona um eventListener para o evento notificacao que definimos lá no server.js com io.emit('notificacao', notificar);.
    Ao receber este evento, ele executa o function(data){}, sendo data o conteúdo recebido.

Testando tudo

  1. Execute o servidor

  2. Abra o arquivo recebedor.html

  3. Abra outra janela e acesse: http://localhost:8080/api/notificar?notificacao="isso é uma notificação em tempo real!”

  4. volte para a janela do recebedor.html e veja como foi atualizado o conteúdo :)

Caso queira, o código-fonte deste teste está neste repositório do github

Conclusão

O socket.io é uma coisinha linda do papai pra criar interações em tempo real. Agora imagine que podemos instalar um server NodeJs em uma placa de circuito, tipo a Arduíno, e criar interações em tempo real entre aparelhos diversos e terminais?

No nosso exemplo aqui, falamos apenas de Push Notifications. Essa lógica será usada para notificar o admin de um sistema quando houver novos pedidos, e notificar o cliente quando houver alguma novidade no pedido dele. :)

Éi, mas vem cá… Conta pra mim, o que você já aprontou com Socket.io?