Développement de site web en pascal (backend)

Dans cet article, je vais vous expliquer pourquoi, pourquoi et comment j'ai commencé à créer des sites en Pascal: Delphi / FPC.
Le "site Pascal" est probablement associé à quelque chose comme:

writeln('Content-type: text/html');

Mais non, tout est beaucoup plus intéressant! Cependant, le code source d'un site réel (presque tous) est disponible sur GitHub.

Pourquoi?


En général, je ne suis jamais un développeur Web professionnel - je crée des jeux. Un jeu, en particulier un jeu en ligne, a besoin d'un site Web. Par conséquent, il se trouve que j'ai commencé à créer plus de sites pour mes jeux. Utiliser CGI sur Perl - au début / milieu des années 2000, il était populaire. Tout allait bien jusqu'à ce qu'un problème survienne.

En 2013, nous avons commencé à organiser des tournois en ligne pour le jeu Spectromancer . Pour ce faire, j'ai créé une page de tournoi sur le site Web du jeu, qui montre avec qui jouer avec qui, les résultats actuels, etc. Au début du tournoi, la page des joueurs a été mise à jour et ... n'a pas été chargée. Les gens ont appuyé sur F5, exacerbant encore le problème. Il s'est avéré que même 4 à 5 requêtes par seconde vers un script CGI, lancé en tant que processus Perl distinct, ralentissaient considérablement le serveur et> 10 requêtes par seconde le rendaient complètement inaccessible.

C'est bien que ce test de stress ait eu lieu pendant le tournoi de répétition: plus tard, j'ai déjà utilisé la page statique mise à jour pour les tournois.

Pourquoi?


Ainsi, lorsque le besoin s'est fait sentir de réaliser ce site pour un nouveau jeu , la question s'est posée - sur quoi? Le freinage CGI sur Perl n'est pas une option. FastCGI en Perl? Je ne peux pas imaginer comment écrire et déboguer un programme multithread en Perl, j'ai eu assez de problèmes avec les scripts ordinaires. Node.js? Ce serait probablement le meilleur choix, sinon pour une certaine hostilité à JS. Et puisque le jeu lui-même et son serveur sont écrits en Pascal (en fait, Delphi, mais FPC est également adapté), l'idée est née - faut-il faire le site dans le même langage? Cela simplifiera l'intégration avec le serveur de jeux. "Essayer n'est pas de la torture!" J'ai réfléchi et j'ai décidé de l'essayer.

Comment?


J'ai choisi SimpleCGI (SCGI) comme interface: c'est un peu plus simple que FastCGI, et les avantages de ce dernier ne sont pas pertinents pour moi - il n'est pas nécessaire de distribuer le backend sur différents serveurs, tout fonctionne sur le même serveur. La tâche s'est donc résumée au développement d'un certain framework SCGI qui traite les requêtes du serveur et génère des pages HTML en réponse à certains blancs, modèles. Le résultat est un tel cadre de module . Il se compose des parties suivantes:

  • Boucle principale : accepte les connexions entrantes, lit les demandes et les place dans une file d'attente pour traitement. Écrit des réponses toutes faites aux demandes traitées sur les sockets de connexion et les ferme.
  • (N ): , . worker' — .
  • : HTML- ( ) . .
  • : ( CGI.pm Perl). , ..


Une fonctionnalité très pratique des scripts Perl est qu'il est très facile d'apporter de petites modifications au site: il suffit de modifier le code du script et c'est tout. Pas besoin de compiler, de déployer. Bien sûr, pascal est un langage compilé, ça ne marchera pas comme ça, mais je voulais quand même pouvoir faire des changements autant que possible sans redémarrer le processus. Par conséquent, j'ai essayé de rendre le système de modèles suffisamment flexible.

Elle travaille comme ça. Les fichiers de modèles se trouvent dans le dossier «modèles»: ils sont chargés au démarrage du processus et également rechargés lorsqu'ils sont modifiés - de cette façon, vous pouvez modifier le contenu dynamique sans redémarrer le processus. Chaque fichier peut avoir un ou plusieurs modèles. Ensemble, ils forment un dictionnaire (ou hachage) de modèles: {"nom" -> "valeur"}. Il s'agit d'un dictionnaire statique de modèles - il est commun à toutes les demandes et son contenu reste inchangé (jusqu'à ce que le contenu des fichiers change). Il en existe un autre - un dictionnaire dynamique, il est créé vide pour chaque demande et rempli avec un gestionnaire de données dynamique - par exemple, à partir de la base de données. En combinant des données statiques et dynamiques, le résultat final est formé.

Exemple de déclaration de modèle:

#NEWSFEED_ITEM:
<div class=NewsHeader>
 <a href='/$LANG_LC/forum/thread/$NEWS_ID'><IF_NEWS_PINNED>[TOP]  </IF_NEWS_PINNED>$NEWS_DATE   $NEWS_TITLE</a>
</div>
<div class=NewsText>$NEWS_TEXT
 <div align=right>
  <a href='/$LANG_LC/forum/thread/$NEWS_ID'>$COMMENTS</a>
 </div>
</div>

Il s'agit d'un modèle statique pour écrire dans le fil d'actualités avec le nom NEWSFEED_ITEM, à l'intérieur il contient l'inclusion de plusieurs autres modèles, par exemple NEWS_TEXT - un modèle dynamique contenant du texte d'actualités téléchargé depuis la base de données. La traduction est que toutes les sous-chaînes du formulaire $ TEMPLATE_NAME sont récursivement remplacées par la valeur de ce modèle.

Ici, vous pouvez également remarquer un pseudotag pour la traduction conditionnelle: <IF_TEMPLATE_NAME> - pendant la traduction, ces balises sont supprimées et leur contenu est laissé ou également supprimé, selon la valeur du modèle spécifié. J'ai spécifiquement choisi ce format de conditions - sous la forme de balises HTML, de sorte que lors de l'édition dans un éditeur de texte, la mise en évidence de la syntaxe fonctionne et il est facile de voir une balise appariée.

Le code de génération de flux utilisant ce modèle ressemble à ceci:


    result:='';
    //       NEWSFEED_ITEM      result
    for i:=0 to n-1 do begin
      id:=StrToIntDef(sa[i*c],0);
      title:=sa[i*c+1];
      cnt:=StrToIntDef(sa[i*c+2],1)-1;
      flags:=StrToIntDef(sa[i*c+3],0);
      //     
      db.Query('SELECT msg,created FROM messages WHERE topic=%d ORDER BY id LIMIT 1', 
        [id]);
      if db.lastErrorCode<>0 then continue;
      text:=db.Next;
      date:=db.NextDate;
      //    ( temp)
      temp.Put('NEWS_ID',id,true);
      temp.Put('NEWS_DATE',FormatDate(date,true),true);
      temp.Put('NEWS_TITLE',title,true);
      temp.Put('NEWS_PINNED',flags and 4>0,true);
      comLink:='$LNK_READ_MORE | ';
      if cnt>0 then comLink:=comLink+inttostr(cnt)+' $LNK_COMMENTS'
        else comLink:=comLink+'$LNK_LEAVE_COMMENT';
      temp.Put('NEWS_TEXT',text,true);
      temp.Put('COMMENTS',comLink,true);
      //   
      result:=result+BuildTemplate('#NEWSFEED_ITEM');
    end;

Localisation


Les modèles sont également pratiques à utiliser pour la localisation. Pour ce faire, utilisez la variable clientLang globale (dans le contexte de la demande). Cela fonctionne comme ceci: si le gestionnaire de demandes découvre que le client a besoin d'une page en russe, il écrit la valeur «RU» dans clientLang, après quoi le traducteur de modèle, ayant trouvé $ TEMPLATE_NAME dans le texte, essaie toujours d'appliquer d'abord $ TEMPLATE_NAME. Ainsi, pour la localisation, il suffit que chaque modèle avec du texte crée sa version pour une autre langue:

#TITLE_NEWS:News
#TITLE_NEWS_RU:

Un exemple d'utilisation d'un framework


Exemple de code de site simple:

program website;
uses SysUtils, SCGI;

//    
function IndexPage:AnsiString; stdcall;
 begin
   result:=FormatHeaders('text/html')+BuildTemplate('#INDEX.HTM');
 end;

begin
 SetCurrentDir(ExtractFileDir(ParamStr(0)));
 SCGI.Initialize; //  
 AddHandler('/',IndexPage); //     '/'
 SCGI.RunServer; //      
end.

Total


J'ai écrit le framework décrit dans le processus de création du vrai site astralheroes.com fin 2015. Comme cela arrive habituellement, la première crêpe est sortie un peu grumeleuse - le code s'est avéré être quelque peu désordonné et déroutant, le site suivant s'améliore. Néanmoins, je suis satisfait du processus et du résultat: le site fonctionne bien, est facilement débogué et mis à jour.

Résultats:

  • Je m'attendais à ce que par rapport à Perl compact, le code du site soit très gonflé, mais non - la même fonctionnalité écrite en Pascal ne prend que deux fois plus qu'en Perl. Mais cela semble plus clair.
  • ! Perl — , - 100 , , . - - — . Delphi .
  • Perl. -, , , . -, Perl, , .
  • : , , . .
  • . , , ( ), , . , , — . .

    , . — . , :



    , — . , .

?


Sources sur GitHub: github.com/Cooler2/ApusEngineExamples

Notez qu'il existe un sous-module dans le référentiel, il est donc préférable de cloner avec le paramètre " --recursive ".

Le projet de site est dans le fichier: "AH-Website \ Backend \ src \ website.dpr"

Ce n'est pas une copie complète du site actuel: il est clair que je ne peux pas publier le contenu de la base de données avec les données des joueurs, je ne publie pas non plus de scripts CGI, car ils pas lié au sujet. Néanmoins, le projet est en cours de montage, de lancement et de travail, démontrant pleinement le travail du cadre.

La publication du code du site, ainsi que du code moteur qu'il utilise, a été rendue possible grâce au support que j'ai reçu sur Patreon. J'exprime ma gratitude à tous ceux qui m'ont soutenu et vous exhorte à vous joindre - il y a encore beaucoup de choses intéressantes à venir :)

Merci de votre attention!

All Articles