Website development in pascal (backend)

In this article I will talk about why, why, and how I started making sites in Pascal: Delphi / FPC.
Probably the “Pascal site” is associated with something like:

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

But no, everything is much more interesting! However, the source code of a real site (almost all) is available on GitHub.

What for?


In general, I’m never a professional web developer - I make games. A game, especially an online one, needs a website. Therefore, it so happened that I began to make more sites for my games. Using CGI on Perl - in the early / mid 2000s it was popular. Everything was fine until a problem arose.

In 2013, we started holding online tournaments for the Spectromancer game. To do this, I made a tournament page on the game’s website, which shows who to play with, current results, etc. At the start of the tournament, the players page was updated and ... did not load. People pressed F5, further exacerbating the problem. It turned out that even 4-5 requests per second to a CGI script, launched as a separate Perl process, significantly slow down the server, and> 10 requests per second make it completely inaccessible.

It’s good that this stress test took place during the rehearsal tournament: later on I already used the updated static page for tournaments.

Why?


Thus, when the need arose to make this site for a new game , the question arose - on what? Brake CGI on Perl is not an option. FastCGI in Perl? I can not imagine how to write and debug a multithreaded program in Perl, I had enough problems with ordinary scripts. Node.js? Probably it would be the best choice, if not for some hostility to JS. And since the game itself and its server are written in Pascal (actually Delphi, but FPC is also good), the idea arose - should we make the site in the same language? This will simplify integration with the game server. “Trying is not torture!” I thought, and decided to give it a try.

How?


I chose SimpleCGI (SCGI) as the interface: it is somewhat simpler than FastCGI, and the advantages of the latter are irrelevant for me - there is no need to distribute the backend to different servers, everything runs on the same server. So the task boiled down to the development of a certain SCGI framework that processes requests from the server and generates HTML pages in response from certain prefabricated templates. The result is such a module framework . It consists of the following parts:

  • Main loop : accepts incoming connections, reads requests and puts them in a queue for processing. Writes ready-made responses to processed requests to connection sockets and closes them.
  • (N ): , . worker' — .
  • : HTML- ( ) . .
  • : ( CGI.pm Perl). , ..


A very convenient feature of Perl scripts is that it is very easy to make small changes to the site: just edited the script code and that’s it. No need to compile, deploy. Of course, pascal is a compiled language, it won’t work out like that, but still I wanted to be able to make changes whenever possible without restarting the process. Therefore, I tried to make the template system flexible enough.

She works like that. The templates files are in the “templates” folder: they are loaded when the process starts and also reloaded when changed - this way you can change dynamic content without restarting the process. Each file can have one or more templates. Together, they form a dictionary (or hash) of templates: {"name" -> "value"}. This is a static dictionary of templates - it is common to all requests and its contents remain unchanged (until the contents of the files change). There is another one - a dynamic dictionary, it is created empty for each request and filled with dynamic data handler - for example, from a database. Combining static and dynamic data, the final result is formed.

Example template declaration:

#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>

This is a static template for writing in the news feed with the name NEWSFEED_ITEM, inside it contains the inclusion of several other templates, for example NEWS_TEXT - a dynamic template containing news text downloaded from the database. The translation is that all substrings of the form $ TEMPLATE_NAME are recursively replaced with the value of this template.

Here you can also notice a pseudotag for conditional translation: <IF_TEMPLATE_NAME> - during the translation, such tags are deleted and their contents are left or also deleted, depending on the value of the specified template. I specifically chose this format of conditions - in the form of HTML tags, so that when editing in a text editor syntax highlighting works and it is easy to see a paired tag.

The feed generation code using this template looks something like this:


    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;

Localization


Templates are also convenient to use for localization. To do this, use the global (in the context of the request) clientLang variable. It works like this: if the request handler finds out that the client needs a page in Russian, he writes the value “RU” in clientLang, after which the template translator, finding $ TEMPLATE_NAME in the text, always tries to apply $ TEMPLATE_NAME_RU first. Thus, for localization it is only necessary for each template with text to create its version for another language:

#TITLE_NEWS:News
#TITLE_NEWS_RU:

An example of using a framework


Simple site code example:

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


I wrote the described framework in the process of creating the real site astralheroes.com at the end of 2015. As it usually happens, the first pancake came out a little lumpy - the code turned out to be somewhat messy and confusing, the next site is getting better. Nevertheless, I am satisfied with the process and the result: the site works well, is easily debugged and updated.

Findings:

  • I expected that compared to compact Perl, the site code is very bloated, but no - the same functionality written in Pascal takes only about twice as much as in Perl. But it looks more clear.
  • ! Perl — , - 100 , , . - - — . Delphi .
  • Perl. -, , , . -, Perl, , .
  • : , , . .
  • . , , ( ), , . , , — . .

    , . — . , :



    , — . , .

?


Sources on GitHub: github.com/Cooler2/ApusEngineExamples

Note that there is a submodule in the repository, so it's better to clone with the " --recursive " parameter .

The site project is in the file: “AH-Website \ Backend \ src \ website.dpr”

This is not a complete copy of the current site: it is clear that I cannot publish the contents of the database with the data of the players, I also do not publish CGI scripts, since they not related to the topic. Nevertheless, the project is being assembled, launched and working, fully demonstrating the work of the framework.

The publication of the site code, as well as the engine code that it uses, was made possible thanks to the support I received on Patreon. I express my gratitude to all those who supported me and urge you to join - there is still a lot of interesting things ahead :)

Thank you for your attention!

All Articles