Mise à jour du 15/06/2024.
Avec l'abandon de Java (liée à la politique d'Oracle en matière de licence) nous perdons aussi l'excellent serveur d'application Tomcat produit sous licence "libre". Il a donc été décidé de créer une infrastructure C++ sur la base des librairies QT pour créer un serveur offrant les fonctionnalités d'un serveur d'application. Ce serveur n'a pas pour seule destination de retourner des pages Web construites à la demande. Il peut être utilisé comme fournisseur de services divers (téléchargements, interrogation d'une base de données, etc.). Le protocole HTTP ne sert ici qu'au transport des requêtes et des réponses. Au final, la réponse peut être d'une nature quelconque elle est simplement encapsulée par le protocole HTTP pour son transport.
Nous simplifierons ici la définition d'un serveur d'application en posant qu'il s'agit d'un serveur HTTP dédié à la mise en ligne d'applications. La grande différence avec un site Web traditionnel est double :
Un serveur d'application peut supporter plusieurs applications. Pour cela, il a besoin de distinguer les différentes applications invoquées par l'utilisateur. Or la seule chose qu'il reçoit est un URL. C'est donc via l'URL que la distinction entre deux applications doit être faite.
Chaque URL dispose d'un chemin et c'est ce dernier qui joue le rôle de commutateur d'application. Dans le vocabulaire Qt, cette commutation est appelée "routage". Un article est dédié sur la manière dont notre infrastructure met en oeuvre ce routage. Un chemin ou partie de chemin qui joue le rôle de commutateur est appelé "contexte". On peut par exemple associer un contexte différent à chaque application supportée par le serveur.
Tout ce qui a été écrit jusqu'à présent se trouve être la base de la plupart des serveurs d'appliction. Le serveur reçoit un URL et doit envoyer une réponse, soit sous forme d'un document HTML, soit sous forme de l'envoi d'un document d'un type MIME donné. Ce sont les en-têtes HTTP reçues par le navigateur qui lui permettent d'identifier le type MIME du document reçu. Chaque éditeur de serveur d'application est alors libre d'imaginer l'infrastructure sous-jacente pour élaborer puis émettre cette réponse. Pour notre part, nous avons constater plusieurs points :
La volonté des éditeurs à transformer leurs navigateurs en pseudo système d'exploitation est louable mais dangereuse aux plans de l'économie et de la sécurité.
Du point de vue de la sécurité, comme il n'existe qu'une dizaine de navigateurs répandus, trouver leurs failles, c'est mettre en danger des milliers d'applications (cf. analyse des journaux des serveurs HTTP pour s'en convaincre).
D'un point de vue économique c'est aller à contre-courant de la reprise par le navigateur et son serveur de l'architecture centralisée de type mainframe concentrant et donc diminuant le coût des "experts" (cf. coûts comparés entre client-serveur multi-niveau et mainframe dans les années 80). De plus, la mise au point et en sécurité des documents lourdement chargés en scripts ainsi que leur reprise pour évolution et nettement plus coûteuse que celle du code côté serveur. En gros, on déploie des efforts conséquents côté infrastructure pour rationaliser le code en vue de son évolution et de sa réutilisation mais on fait pratiquement l'inverse côté navigateur : il y a là une forme d'incohérence.
Fort de ces constats nous tirons les règles suivantes :
Le modèle de conception MVC facilite l'évolution d'une application. Pour aller plus loin, il est recommandé de distinguer les services de traitement non lié à une IHM des services de visualisation destiné à produire une IHM avec laquelle l'utilisateur interagit. Cela permet de concevoir et tester les service non IHM en premier. Ces services fournissent alors les spécifications à respecter par les services IHM. Comme l'évolution des applications portent prncipalement sur la présentation des IHM, il sera nettement plus facile de se concentrer sur ces derniers par la suite pour faire du "beau" et du "pratique".
ce paragraphe montre que toute application déportée via un serveur d'application répond à un schéma du type :

Pour permettre le fonctionnement d'une ou plusieurs applications, il est indispensable de pouvoir lire, ajouter, mettre à jour ou supprimer des données. Le schéma ci-dessus montre que ces données ne sont pas toutes du même niveau et qu'elles peuvent être regroupées selon leur niveau de visibilité par un service donné d'une application donnée.
Comme vu auparavant, le "modèle" est constitué de 3 niveaux et à chacun correspond un niveau de visibilité :
Dans les serveurs traditionnels, il existe la notion de service avec état (statefull) et celle de service sans état (stateless). Dans notre cas, la délégation de service à service rend délicate la gestion de services avec état. De plus, un service avec état doit être dupliqué en fonction de son environnement d'utilisation ce qui peut amener à consommer de la mémoire de façon excessive. Pour cette raison, nous avons fait le choix de services sans état. Les données nécessaires au service seront soit fournies par le "modèle" (généralement les données de session). Un autre avantage des services sans état et qu'ils ont une et une seule instance en mémoire. En cas de très fortes montées en charge, on pourrait envisager un pool par services dont le nombre d'instances dépendrait de la fréquence d'appel du service. Chaque service du pool s'exécuterait alors sur un thread dédié. Ce type de fonctionnment impose une infrastructure matérielle conséquente et n'est donc pas envisagé ici. Un service demandé simultanément mettre les demandes successives à la première en attente. Il est donc important que le temps d'exécution du service soit le plus bref possible.
Chaque utilisateur débute une session au premier URL invoqué. La création d'une session et donc implicite. Il ne faut pas confondre authentification de l'utilisateur et session : la session permet de repérer l'utilisateur en ligne qu'il soit connecté ou non.
En revanche, une session peut être achevée explicitement (déconnexion explicite de l'utilisateur). Elle doit toujours pouvoir être fermée implicitement (expiration de la durée d'inactivité de la session). La session dispose d'une durée d'expiration car chacune d'elle consomme de la mémoire. Sans ce mécanisme, il arriverait forcément un moment où la mémoire s'avèrerait insuffisante. Certains serveurs proposent de "sérialiser" la session sur disque en ne laissant en mémoire qu'une amorce pour son rechargement. Ce mécanisme présente plusieurs avantages dont la résilience aux pannes et le transfert à un autre serveur en cas de forte montée en charge du serveur initial ou encore mécanisme de distribution d'un pool de serveur (type round robin par exemple). Mais ce mécanisme complexifie sérieusement le serveur d'application et peut avoir un impact significatif sur les temps de réponse. Il n'est dons pas mis en oeuvre ici.
A la place, nous préférons limiter le nombre de sessions actives pour une application donnée. Cette valeur est une donnée de configuration de l'application et une donnée de configuration du serveur. Dès qu'une nouvelle session à créer excède un des ses paramètres, la session est refusée. C'est généralement la pratique sur une infrastructure donnée qui permet d'ajuster au mieux ces paramètres.
Voici posées les bases de la réalisation de notre serveur d'application. Des choix structurants ont été faits et on peut voir qu'il s'agit pas de développer un serveur aisément paramétrable et utilisable.
Un point important non encore abordé est celui de la modélisation des applications web. Ce processus est complexe car le plus souvent itératif. Il est pratiquement impossible et certainement peu souhaitable de "penser" à l'avance toutes les fonctions d'une application. Il est sans doute plus judicieux d'accepter d'emblée qu'elle évolue avce le besoin et les capacités de réalisation. La notion de "service" le facilite grandement. Elle permet de modéliser la cinématique de départ et de dresser l'inventaire des URL d'appel des services. Si le serveur est bien réaliser, produire une application web revinet à 3 tâches :
De plus, cette façon de procéder permet de raccourcir le délai conception-test. Or les tests amènent non seulement à corriger les bugs mais aussi à rétro-concevoir le contenu des services voir certains srevices eux-mêmes. C'est pouquoi imaginer pouvoir "penser" à l'avance l'application dans son ensemble est une illusion... (qui a fait beaucoup de dégâts dans le passé). De même, imaginer que l'on peut se lancer dans le codage d'une application un peu complexe sans un minimum de recul via la modélisation est tout aussi illusoire... (dégâts actuels). Comme toujours, il s'agit d'une question d'équilibre.
Rédaction par Jean-Marie Piatte (1983-2021)