Aplicativos web em C++ usando o Tufão

Quando eu comento sobre desenvolvimento web em C++ a alguns programadores, eles demonstram uma falta de fé perceptível na ideia, enquanto outros ficam curiosos e começam a discutir como as peculiaridades da linguagem afetam o seu uso na área. Inspirado pela framework Node.js, desenvolvi o Tufão.

[ Hits: 23.720 ]

Por: Vinícius dos Santos Oliveira em 12/06/2012 | Blog: https://vinipsmaker.github.io/


Criando uma aplicação de chat



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.

Página anterior    

Páginas do artigo
   1. Introdução
   2. Um pouco sobre HTTP
   3. Arquitetura do Tufão
   4. WebSocket
   5. Criando uma aplicação de chat
Outros artigos deste autor

A história do hardware

GNU Emacs, o primeiro GNU

História da informática: Um pouco de datas e especificações

A história do ogg na web

Próximas Tecnologias do Sistema GNU/Linux

Leitura recomendada

Criando aplicações RESTful com Qt e Cutelyst

DotGNU: a resposta Open Source ao dotNET

Biblioteca VBMcgi: Crie aplicações Web CGI em C++ com acesso ao banco Interbase/Firebird sem mistério

Dynamic libraries com libtool

Acessando PostgreSQL com C

  
Comentários
[1] Comentário enviado por bolche em 12/06/2012 - 14:23h

Legal, tava querendo brincar de aplicativos web em C++ faz um tempo.
Você podia aproveitar a infraestrutura de outro servidor web ao invés de fazer tudo do zero, por exemplo o apache, modificar o processo de construção para construir um módulo do apache ao invés de um servidor standalone. Talvez um protocolo mais agnóstico como FastCGI seja ainda melhor.
De qualquer maneira, muito legal!

[2] Comentário enviado por vinipsmaker em 12/06/2012 - 15:59h


[1] Comentário enviado por bolche em 12/06/2012 - 14:23h:

Legal, tava querendo brincar de aplicativos web em C++ faz um tempo.
Você podia aproveitar a infraestrutura de outro servidor web ao invés de fazer tudo do zero, por exemplo o apache, modificar o processo de construção para construir um módulo do apache ao invés de um servidor standalone. Talvez um protocolo mais agnóstico como FastCGI seja ainda melhor.
De qualquer maneira, muito legal!


Não criei tudo do 0, utilizo o parser HTTP criado durante o projeto Node.js.

E já pensei sobre o uso de FastCGI, mas queria diminuir o gargalo para aumentar o desempenho e fazer um servidor standalone é uma das estratégias. Isso, só ressaltando, traz outras implicações, mas é fácil contornar essa decisão usando, no Apache mesmo, proxy reverso ( http://httpd.apache.org/docs/2.0/mod/mod_proxy.html ).

[3] Comentário enviado por julio_hoffimann em 12/06/2012 - 19:45h

Parabéns pelo projeto Vinícius!

Abraço!

[4] Comentário enviado por fabio em 12/06/2012 - 20:00h

Muito bom mesmo. Como é o desempenho de sites em C++ se comparados com linguagens interpretadas, tal como o PHP? Vale a pena?

[5] Comentário enviado por julio_hoffimann em 12/06/2012 - 20:13h

Estava tentando lembrar o nome de um projeto recente da Google para aplicações web em código nativo: http://www.youtube.com/watch?v=UUnC5y4j0As

A idéia é poder rodar aplicações C/C++ ou outras linguagem de alta performance no navegador de forma segura, talvez seja de interesse do autor. ;-)

Abraço!

[6] Comentário enviado por vinipsmaker em 12/06/2012 - 22:24h


[5] Comentário enviado por julio_hoffimann em 12/06/2012 - 20:13h:

Estava tentando lembrar o nome de um projeto recente da Google para aplicações web em código nativo: http://www.youtube.com/watch?v=UUnC5y4j0As

A idéia é poder rodar aplicações C/C++ ou outras linguagem de alta performance no navegador de forma segura, talvez seja de interesse do autor. ;-)

Abraço!


http://en.wikipedia.org/wiki/Google_Native_Client ? Já conhecia.
:P

[7] Comentário enviado por vinipsmaker em 13/06/2012 - 09:09h


[4] Comentário enviado por fabio em 12/06/2012 - 20:00h:

Muito bom mesmo. Como é o desempenho de sites em C++ se comparados com linguagens interpretadas, tal como o PHP? Vale a pena?


Tem um texto onde eu discuti bastante sobre a escolha da linguagem para o Tufão:
http://vinipsmaker.wordpress.com/2012/05/07/understanding-tufao-part-1/

Mas, para discutir sobre desempenho, eu preciso fazer benchmarks, para não caminhar muito na direção do "achismo". Fico devendo um benchmark.


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner
Linux banner
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts