E para terminar esse texto, demonstro a criação de uma aplicação simples de chat no Tufão. Ela deve suportar o seguinte:
- Uso de WebSocket: para permitir que o servidor envie mensagens aos clientes conectados.
- Envio de arquivos estáticos: para o envio das páginas HTML que formam a interface.
- Suporte a plugins: para que possamos mudar o código que está rodando em tempo de execução.
Começamos criando uma aplicação do modelo "application", no QtCreator com o plugin do Tufão instalado. Como pode ser observado no arquivo main.cpp, há um roteador de requisições com 4 rotas configuradas, na seguinte ordem:
- Um HttpPluginServer para todas as requisições. Ele lê as rotas a partir do arquivo routes.conf, que deve estar presente na pasta a partir da qual o binário for executado.
- Um handler customizado que força o HttpPluginServer a recarregar os plugins para a url "/reload". Por questões de segurança, ele só tratará a requisição caso ela tenha sido originada a partir de localhost.
- Um HttpFileServer para todas as requisições. Os arquivos a serem servidos devem estar presentes na pasta public, que deve estar presente na pasta a partir da qual o binário for executado.
- Um handler customizado que responde com página não encontrada para todas as requisições.
A primeira modificação que faremos nessa aplicação é adicionar suporte a WebSocket. Para tal, crie uma classe que herde HttpServer e reimplemente o método upgrade com o seguinte conteúdo:
// Assim como requisições comuns, conexões do tipo WebSocket também podem
// servir vários recursos diferentes
if (request->url() != "/chat") {
Tufao::HttpServerResponse response(request->socket(),
request->responseOptions());
response.writeHead(Tufao::HttpServerResponse::NOT_FOUND);
response.end("Not found");
request->socket()->close();
return;
}
Tufao::WebSocket *socket = new WebSocketChat(this);
if (!socket->startServerHandshake(request, head)) {
socket->deleteLater();
return;
}
socket->setMessagesType(Tufao::WebSocket::TEXT_MESSAGE);
connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
Agora crie as classes WebSocketChatMessages e WebSocketChat, que devem herdar de Tufao::WebSocket. A classe WebSocketChat deve tratar a conexão WebSocket, enquanto a classe WebSocketChatMessages deve ser um Singleton que centraliza e encaminha as mensagens para as conexões.
Há vários meios de se realizar tal encaminhamento. A técnica que eu uso é a mais simples que consigo imaginar no momento e é baseada no mecanismo de Signals & Slots do Qt. O diagrama abaixo ajuda a entender como essa comunicação funciona:
Há um objeto da classe WebSocketChatMessages que emite o sinal newMessage sempre que alguém envia uma nova mensagem. Todos os objetos da classe WebSocketChat se conectam a esse sinal enviam a mensagem recebida para o cliente. O código completo para essa aplicação está
disponível aqui. Estude-o caso tenha alguma dúvida.
Agora que terminamos o código relacionado ao chat do lado do servidor, é hora de criar o código JavaScript que deve ser executado do lado do cliente. Começamos com o modelo de uma página HTML simples:
<html>
<head>
<title>WebSocket chat example</title>
<script type="text/javascript">
var socket = new WebSocket('ws://localhost:8080/chat');
// Código restante entra aqui
</script>
</head>
<body>
<div id="chatarea">
</div>
<form action="#">
<input type="text" id="message" placeholder="Text">
<button type="submit">Send</button>
</form>
</body>
</html>
No código anterior, criamos uma conexão com o servidor tão breve quanto possível, após a página ser carregada. Temos também as interfaces de controle necessárias para o funcionamento do chat, formadas pela área de texto do chat (o elemento div), o text box para a mensagem a ser enviada e o botão de envio.
Para que o código esteja pronto para uso, resta somente configurar os callbacks a serem chamados durante os eventos apropriados. Quando uma nova mensagem for recebida ela deve ser escrita no elemento div e quando o botão de envio for clicado a mensagem presente no text box deve ser enviada. Uma implementação simples ficaria assim:
<html>
<head>
<title>WebSocket chat example</title>
<script type="text/javascript">
var socket = new WebSocket('ws://localhost:8080/chat');
<i>socket.onmessage = function(msg) {
document.getElementById('chatarea').innerHTML += '<p>' + msg.data
+ '</p>';
};
function onSubmit()
{
if (socket.readyState != WebSocket.OPEN) {
alert('Connection is down');
return false;
}
socket.send(document.getElementById('message').value);
return false;
}</i>
</script>
</head>
<body>
<div id="chatarea">
</div>
<form action="#" <i>onsubmit="return onSubmit()"</i>>
<input type="text" id="message" placeholder="Text">
<button type="submit">Send</button>
</form>
</body>
</html>
Salve esse arquivo com o nome de index.html e coloque-o em uma pasta chamada public, dentro da pasta onde se encontra o executável, e execute-o a partir dessa pasta. Você deve ter uma versão inicial que deve funcionar nos navegadores mais recentes acessando o endereço:
http://localhost:8080/index.html
Há, entretanto, um problema de segurança com nossa implementação. Da forma como foi projetado, esse código permite que usuários injetem códigos maliciosos para serem executados nos navegadores de todos os usuários conectados.
Para resolver o problema, é suficiente escapar o código HTML recebido antes de inseri-lo:
<html>
<head>
<title>WebSocket chat example</title>
<script type="text/javascript">
var socket = new WebSocket('ws://localhost:8080/chat');
socket.onmessage = function(msg) {
document.getElementById('chatarea').innerHTML += '<p>'
+ msg.data<i>.split("&").join("&").split( "<").join("<")
.split(">").join(">")</i> + '</p>';
};
function onSubmit()
{
if (socket.readyState != WebSocket.OPEN) {
alert('Connection is down');
return false;
}
socket.send(document.getElementById('message').value);
return false;
}
</script>
</head>
<body>
<div id="chatarea">
</div>
<form action="#" onsubmit="return onSubmit()">
<input type="text" id="message" placeholder="Text">
<button type="submit">Send</button>
</form>
</body>
</html>
Problema de segurança resolvido, resta resolver alguns problemas de usabilidade. Um deles, que talvez você tenha notado, é que se tentar acessar a raiz do site (http://localhost:8080/), vai receber um "página não encontrada".
Para resolver esse problema, basta responder com um "301 Moved Permanently", definindo o local "/index.html" no cabeçalho Location. Entretanto, para demonstrar outra funcionalidade do Tufão, resolveremos o problema de forma diferente, usando plugins. Com o uso de plugins não precisamos sequer parar o servidor para adicionar esse novo código.
Comece criando uma nova aplicação no QtCreator usando o modelo plugin:
Então coloque o seguinte código no método handleRequest:
response->writeHead(Tufao::HttpServerResponse::MOVED_PERMANENTLY);
// O padrão proibe o uso de URLs relativas no cabeçalho Location,
// mas como a maioria dos navegadores aceita esse recurso
// e por questões de praticidade, o mesmo é utilizado aqui.
// Entretanto, desencorajo seu uso em projetos reais.
response->headers().insert("Location", "/index.html");
response->end();
return true;
Compile o projeto e então crie um arquivo de configuração de rotas usando o editor de rotas do Tufão. Coloque o arquivo criado dentro da pasta a partir da qual o servidor está rodando e use o nome routes.conf.
Após isso recarregue os plugins acessando a URL "http://localhost:8080/reload" a partir de localhost. Em caso de sucesso você deve ver uma página parecida com a seguinte:
Teste a URL "http://localhost:8080/" novamente e dessa vez deve funcionar.
Isso é tudo, pessoal
Existe
uma lista com os próximos passos na página do projeto, mas ultimamente ando mais ocupado e seu desenvolvimento deve desacelerar por enquanto.
Espero que tenham achado o mesmo interessante.