Desenvolvimento de sites em pascal (back-end)

Neste artigo, falarei sobre por que, por que e como comecei a criar sites em Pascal: Delphi / FPC.
Provavelmente, o "site Pascal" está associado a algo como:

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

Mas não, tudo é muito mais interessante! No entanto, o código fonte de um site real (quase todos) está disponível no GitHub.

Pelo que?


Em geral, nunca sou desenvolvedor web profissional - faço jogos. Um jogo, especialmente online, precisa de um site. Por isso, aconteceu que comecei a criar mais sites para meus jogos. Usando CGI no Perl - no início / meados dos anos 2000, era popular. Tudo estava bem até que um problema surgiu.

Em 2013, começamos a realizar torneios on-line para o jogo Spectromancer . Para isso, criei uma página de torneio no site do jogo, que mostra quem jogar com quem, resultados atuais etc. No início do torneio, a página dos jogadores foi atualizada e ... não foi carregada. As pessoas pressionaram F5, exacerbando ainda mais o problema. Acontece que mesmo 4-5 solicitações por segundo para um script CGI, executam como um processo Perl separado, diminuem significativamente o servidor e mais de 10 solicitações por segundo o tornam completamente inacessível.

É bom que esse teste de estresse tenha ocorrido durante o torneio de ensaio: mais tarde, eu já usei a página estática atualizada para os torneios.

Por quê?


Assim, quando surgiu a necessidade de tornar este site para um novo jogo , surgiu a questão - em quê? O CGI do freio no Perl não é uma opção. FastCGI em Perl? Não consigo imaginar como escrever e depurar um programa multithread no Perl, já tive problemas suficientes com scripts comuns. Node.js? Provavelmente seria a melhor escolha, se não fosse por alguma hostilidade ao JS. E como o jogo em si e seu servidor são escritos em Pascal (na verdade, Delphi, mas o FPC também é bom), surgiu a idéia - devemos criar o site no mesmo idioma? Isso simplificará a integração com o servidor do jogo. "Tentar não é tortura!" Eu pensei e decidi tentar.

Quão?


Eu escolhi o SimpleCGI (SCGI) como interface: é um pouco mais simples que o FastCGI, e as vantagens deste último são irrelevantes para mim - não há necessidade de distribuir o back-end para diferentes servidores, tudo roda no mesmo servidor. Portanto, a tarefa se resumiu ao desenvolvimento de uma certa estrutura SCGI que processa solicitações do servidor e gera páginas HTML em resposta a determinados modelos pré-fabricados. O resultado é uma estrutura de módulo . Consiste nas seguintes partes:

  • Loop principal : aceita conexões de entrada, lê solicitações e as coloca em uma fila para processamento. Grava respostas prontas para solicitações processadas em soquetes de conexão e as fecha.
  • (N ): , . worker' — .
  • : HTML- ( ) . .
  • : ( CGI.pm Perl). , ..


Um recurso muito conveniente dos scripts Perl é que é muito fácil fazer pequenas alterações no site: basta editar o código do script e pronto. Não há necessidade de compilar, implantar. Certamente, pascal é uma linguagem compilada, não funcionará dessa maneira, mas ainda assim eu queria poder fazer alterações sempre que possível, sem reiniciar o processo. Portanto, tentei tornar o sistema de modelos suficientemente flexível.

Ela trabalha assim. Os arquivos de modelos estão na pasta "modelos": eles são carregados quando o processo é iniciado e também recarregados quando alterados - dessa forma, você pode alterar o conteúdo dinâmico sem reiniciar o processo. Cada arquivo pode ter um ou mais modelos. Juntos, eles formam um dicionário (ou hash) de modelos: {"nome" -> "valor"}. Este é um dicionário estático de modelos - é comum a todas as solicitações e seu conteúdo permanece inalterado (até que o conteúdo dos arquivos seja alterado). Há outro - um dicionário dinâmico, criado vazio para cada solicitação e preenchido com manipulador de dados dinâmicos - por exemplo, do banco de dados. Combinando dados estáticos e dinâmicos, o resultado final é formado.

Exemplo de declaração de modelo:

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

Este é um modelo estático para um feed de notícias com o nome NEWSFEED_ITEM, que contém vários outros modelos, por exemplo, NEWS_TEXT - um modelo dinâmico que contém o texto de notícias baixado do banco de dados. A tradução é que todas as substrings do formulário $ TEMPLATE_NAME são recursivamente substituídas pelo valor desse modelo.

Aqui você também pode observar o pseudotag para tradução condicional: <IF_TEMPLATE_NAME> - durante a tradução, essas tags são excluídas e seu conteúdo é deixado ou também excluído, dependendo do valor do modelo especificado. Escolhi especificamente esse formato de condições - na forma de tags HTML, para que, ao editar em um editor de texto, a realce da sintaxe funcione e seja fácil ver uma tag emparelhada.

O código de geração de feeds usando este modelo é mais ou menos assim:


    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;

Localização


Também é conveniente usar modelos para localização. Para fazer isso, use a variável global clientLang (no contexto da solicitação). Funciona assim: se o manipulador de solicitações descobrir que o cliente precisa de uma página em russo, ele escreverá o valor "RU" em clientLang, após o qual o tradutor de modelos, tendo encontrado $ TEMPLATE_NAME no texto, sempre tenta aplicar primeiro $ TEMPLATE_NAME. Assim, para a localização, é necessário apenas para cada modelo com texto criar sua versão para outro idioma:

#TITLE_NEWS:News
#TITLE_NEWS_RU:

Um exemplo de uso de uma estrutura


Exemplo simples de código do site:

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


Eu escrevi a estrutura descrita no processo de criação do site real astralheroes.com no final de 2015. Como geralmente acontece, a primeira panqueca saiu um pouco irregular - o código acabou sendo um pouco confuso e confuso, o próximo site está melhorando. No entanto, estou satisfeito com o processo e o resultado: o site funciona bem, é facilmente depurado e atualizado.

Constatações:

  • Eu esperava que, comparado ao Perl compacto, o código do site esteja muito inchado, mas não - a mesma funcionalidade escrita em Pascal leva apenas o dobro do que em Perl. Mas parece mais claro.
  • ! Perl — , - 100 , , . - - — . Delphi .
  • Perl. -, , , . -, Perl, , .
  • : , , . .
  • . , , ( ), , . , , — . .

    , . — . , :



    , — . , .

?


Fontes no GitHub: github.com/Cooler2/ApusEngineExamples

Observe que há um submódulo no repositório; portanto, é melhor clonar com o parâmetro " --recursive ".

O projeto do site está no arquivo: "AH-Website \ Backend \ src \ website.dpr"

Esta não é uma cópia completa do site atual: é claro que não posso publicar o conteúdo do banco de dados com os dados dos players, também não publico scripts CGI, pois eles não relacionado ao tópico. No entanto, o projeto está sendo montado, lançado e funcionando, demonstrando plenamente o trabalho da estrutura.

A publicação do código do site, bem como do código do mecanismo que ele usa, foi possível graças ao suporte que recebi no Patreon. Expresso minha gratidão a todos aqueles que me apoiaram e exorto você a participar - ainda há muitas coisas interessantes pela frente :)

Obrigado pela atenção!

All Articles