Créer un WebService en C++ avec Qt

Un petit article pour évoquer la possibilité de faire un webservice, en C++/Qt.

Mon besoin est simple, je veux fournir une interface web pour réaliser des lancer de dés avec le système de lancement de dés DiceParser, issu de Rolisteam.

DiceParser est écrit en C++ avec Qt5. A partir de ce constat, je pouvais soit faire un serveur web en C++, soit faire un site web en python/php/whatever pour lancer une commande système et utiliser dice, le client en ligne de commande de DiceParser.

La deuxième solution est  la plus facile mais clairement un peu «sale» et niveau sécurité ce n’est pas idéal. Le «challenge» se trouve dans la première méthode.
Je n’avais pas envie de réinventer la roue donc j’ai cherché une solution technique pour satisfaire mon besoin. Un composant C++/Qt qui permet de créer un serveur http avec possibilité d’être notifier à chaque requête. J’ai trouvé un composant qui réalise cela.

Le dépôt git du composant : https://github.com/azadkuh/qhttp.git

Implémentation du serveur

  m_server = new qhttp::server::QHttpServer(this);
    m_server->listen( // listening on 0.0.0.0:8080
            QHostAddress::Any, port,
            [=](qhttp::server::QHttpRequest* req, qhttp::server::QHttpResponse* res)
            {
                req->collectData(1024);

		// Ici mettre le code d'analyse de la requête

        });
    if ( !m_server->isListening() ) {
            qDebug() << "failed to listen";

        }
    else
    {
        qDebug()<< "Server is On!!";
    }
Instancier le serveur web

Nous créons une instance du serveur, nous démarrons l’écoute sur le port (80 par défaut pour le protocole http).
L’écoute est réalisé par une fonction lambda, cela n’a rien d’obligatoire, mais c’est plus minimaliste.

Notre fonction est notifiée à la réception d’une requête http.

Récupérer les informations d’une requête

// Récupère les données
req->collectData(1024);

// Récupère l'url (les données du GET)
QString getArg = req->url().toString();

// Découpage des arguments
getArg=getArg.replace("/?","");
QStringList args = getArg.split('&');
QHash<QString,QString> m_hashArgs;
for( auto argument : args)
{
    QStringList keyValue = argument.split('=');
    if(keyValue.size()==2)
    {
        m_hashArgs.insert(keyValue[0],keyValue[1]);
    }
}

// recherche dans la table des arguments la présence de fonction.
if(m_hashArgs.contains("cmd"))
{
	// répondre à la requête reconnue.
}
Découpage

Ici, nous analysons la requête pour identifier les actions à réaliser. Nous cherchons le paramètre cmd, pour trouver la commander à identifier. Le travail préalable est des créer une tableau associatif (dans une QHash) pour conserver les paramètres et leur valeur.

Une fois la commande de dés identifiés, il faut l’exécuter.

Exécuter la commande et répondre


                    QString result = startDiceParsing(QUrl::fromPercentEncoding(m_hashArgs["cmd"].toLocal8Bit()));
                    

                    res->setStatusCode(qhttp::ESTATUS_OK);
                    res->addHeader("Access-Control-Allow-Origin", "*");
                    res->addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
                    res->addHeader("Access-Control-Allow-Headers", "x-requested-with");


                    QString html("<!doctype html>\n"
                             "<html>\n"
                             "<head>\n"
                             "  <meta charset=\"utf-8\">\n"
                             "  <title>Rolisteam Dice System Webservice</title>\n"
                             "  <style>.dice {color:#FF0000;font-weight: bold;}</style>"
                             "</head>\n"
                             "<body>\n"
                             "%1\n"
                             "</body>\n"
                             "</html>\n");

res->end(html.arg(result).toLocal8Bit());

La première action est de convertir la commande pour la transformer en données lisibles. Elle est actuellement encodé en mode pourcent. Qt fournit une méthode statique pour faire la conversion. Le resultat de cette conversion est ensuite envoyé à une fonction qui exécute la commande et donne le résultat.

Il faut ensuite générer la réponse. Le premier truc à définir est le code réponse du protocole http. Le module qhttp propose des raccourcis pour cela.

Ensuite, la réponse doit contenir 3 paramètres dans les headers afin d’être accessible par des frameworks Ajax/javascript/Web 2.0 (vous ressentez mon dédain envers ces techno ?).
Après la définition des headers, il faut créer le code html (ou autre) de la réponse.

Créer une page web cliente du service

<!DOCTYPE html>                                                                                               
<html>      
<head>                       
<meta charset="utf-8"> 
<meta name="Author" content="Renaud GUEZENNEC"/>                                                            
  <title>Rolisteam Dice System Webservice</title>  
  <style>.dice {color:#FF0000;font-weight: bold;}</style>                                                     
  <script type="text/javascript" src="js/jquery-3.1.1.min.js" ></script>            
  <script type="text/javascript">  
    function displayResult() {                                                                                                         
      var request = $.ajax({   
        method: "get",    
        url: "http://127.0.0.1:8085/",  
        dataType: "html",  
        async: false,
        data: {cmd: $('#cmd').val()} 
      });                
      request.done(function(data){  
        var str ="<p>" 
        str = str.concat(data,"</p>");                                                                        
        $('.diceresult').prepend(str);    
      }); 
    }   
  </script>  
</head>       
<body>    
<form method="POST" action=""/> 
<input id="cmd" name="cmd"/>  
<input id="roll" name="roll" type="button" onclick="javascript:displayResult(); return 0;" value="Roll"/>
</form>                
<a href="index.html">Clear</a> 
<div class="diceresult">
</div>      
</body> 
</html>
Page cliente

Le travail ici est assez simple. Il faut créer un formulaire pour permettre à l’utilisateur de saisir la commande de dés. J’ai utilisé JQuery comme framework javascript pour récupérer la commande et envoyer la requête de mon webservice. Quand la requête s’est bien passée, le résultat s’affiche dans la page sans recharger la page.

Le lien vers le code source: https://github.com/Rolisteam/DiceParser/tree/master/webserver
Le webservice fait partie de projet DiceParser: https://github.com/Rolisteam/DiceParser

One comment

  1. Salut,
    L’API Qt est vraiment bas niveau, et pas tellement intuitive. Tu aurais pu utiliser libnavajo en C++…
    A+

Leave a Reply

Your email address will not be published. Required fields are marked *