Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Pascal

Pascal Server Pages – Pascal Script

5.00/5 (1 vote)
17 Dec 2012CPOL2 min read 17.1K  
Pascal Server Pages

Without getting too technical, I would define a Pascal Server Page (PSP) as a dynamic web page containing embedded Pascal Script (PS) code. When a web request is made, the PS code needs to be executed (interpreted) in the server side and outputted into the proper format (HTML, XML, JSON, text, etc.). A PSP is commonly stored as a text file in the Web Server and it could be a mixture of PS code plus any other static content.

This is an example of PSP:

HTML
<html>
  <head>
    <title>This is a Pascal Server Page</title>
  </head>
  <body>
    <% begin
         Write('Hello World');
       end.
    %>
    <p>I am going to use Pascal Script to write a few numbers...</p>
    <% var
         i: Integer;
       begin
         for i:=1 to 10 do
           Writeln(i);  
       end.
    %> 
  </body>
</html>

The code above is an HTML armature containing some PS code. The PS code has been isolated within the “<%” and “%>” tokens. The PS code is executed in the server and the output (if any) is embedded into the HTML template.

So, if a browser asks for the page above, it will actually get plain HTML code as the one below:

HTML
<html>
  <head>
    <title>This is a Pascal Server Page</title>
  </head>  
  <body>    
    Hello World
    <p>I am going to use Pascal Script to write a few numbers...</p>
    1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>
  </body>
</html>

This is all good. The only problem is that the PS code is not going to be magically executed. We need a server side component to do the PS interpretation.

I have seen a couple of intents to build such server side component in the Internet. Anyhow, I bring you my own proposal: create a Web Broker application with Pascal Scripting capabilities. To provide the Web Broker application with the scripting capabilities, I will use Pascal Script from RemObjects. You need to download and install Pascal Script if you want to try my code.

The workflow goes as follows:

  1. The Web Broker application receives a Web Request.
  2. The Web Broker application finds the corresponding Pascal Server Page and loads its content to a buffer variable.
  3. The content of the buffer variable is parsed in order to find the PS tokens (I will use RegEx to do the parsing).
  4. Each PS block is compiled to bytecode and then executed in the server. (I will use the Pascal Script library from RemObjects for this purpose.)
  5. The output generated from the execution of each PS block replaces its corresponding “<%......%>” block.
  6. The Web Broker app serves the response.

I developed a VCL standalone Web Broker application as a proof of concept (it could be an ISAPI DLL as well). See it in action in the following video:

That application is just a prototype. I really believe that we could build a robust server side component to leverage enterprise Pascal Server Pages. I used Web Broker in this example, but we could also build Apache Modules with Free Pascal.

I am posting below the code of the TWebModule1 class, which is the core of the Web Broker app. The full source code and executable can be downloaded here (the code was compiled with Delphi XE2). Note that the code is somewhat messy; this was taken directly from my sandbox. Ah, I copy-pasted (and adjusted) the Pascal Script routines from this example: Introduction to Pascal Script.

Pascal
unit WebModuleUnit1;

interface

uses System.SysUtils, System.Classes, Web.HTTPApp,
     RegularExpressions;

type
  TWebModule1 = class(TWebModule)
    procedure WebModule1DefaultHandlerAction(Sender: TObject;
      Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  private
    { Private declarations }
  public
    { Public declarations }
    function LoadStrFromFile(aFilename: string): string;
    function ProducePage(aContent: string): string;
    function ExecPascalScript(const Match: TMatch): string;
    function CompileScript(Script: AnsiString; out Bytecode, Messages: AnsiString): Boolean;
    function RunCompiledScript(Bytecode: AnsiString; out RuntimeErrors: AnsiString): Boolean;
  end;

var
  WebModuleClass: TComponentClass = TWebModule1;

  //ScriptOutput is not thread safe.
  //ScriptOutput is a global variable.
  //We should avoid global variables.
  //TODO: Find a better way to store the script output
  ScriptOutput: string;

implementation

uses
  uPSCompiler, uPSRuntime;

{$R *.dfm}

procedure Write(P1: Variant);
begin
  //This try...except is ugly.
  //TODO: Use a conditional checking instead
  try
    ScriptOutput:= ScriptOutput + String(P1);
  except
    ScriptOutput:= '';
  end;
end;

procedure Writeln(P1: Variant);
begin
  Write(P1);
  ScriptOutput:= ScriptOutput + '</br>';
end;

function TWebModule1.LoadStrFromFile(aFilename: string): string;
begin
  Result:= '';
  if not FileExists(aFilename) then Exit;

  with TStringStream.Create do
  try
    LoadFromFile(aFilename);
    Result:= DataString;
  finally
    Free;
  end;
end;

function TWebModule1.ProducePage(aContent: string): string;
var
  RegEx: TRegEx;
begin
  ScriptOutput:= '';
  aContent:= StringReplace(aContent, #13#10, '', [rfReplaceAll]);
  RegEx.Create('\<\%(.)*?\%\>');
  Result:= regex.Replace(aContent, ExecPascalScript);
end;

function TWebModule1.ExecPascalScript(const Match: TMatch): string;
var
  Bytecode,
  Messages,
  RuntimeErrors: AnsiString;
  PS: string;
begin
  Result:= '';
  Bytecode:= '';
  ScriptOutput:= '';
  PS:= Match.Value;
  PS:= StringReplace(PS, '<%', '', []);
  PS:= StringReplace(PS, '%>', '', []);
  if CompileScript(PS, Bytecode, Messages) then
    if RunCompiledScript(Bytecode, RuntimeErrors) then
      Result:= ScriptOutput;
end;

function ExtendCompiler(Compiler: TPSPascalCompiler; const Name: AnsiString): Boolean;
begin
  Result := True;
  try
    Compiler.AddDelphiFunction('procedure Writeln(P1: Variant);');
    Compiler.AddDelphiFunction('procedure Write(P1: Variant);');
  except
    Result := False; // will halt compilation
  end;
end;

function TWebModule1.CompileScript(Script: AnsiString; out Bytecode, Messages: AnsiString): Boolean;
var
  Compiler: TPSPascalCompiler;
  i: Integer;
begin
  Bytecode:= '';
  Messages:= '';

  Compiler:= TPSPascalCompiler.Create;
  Compiler.OnUses:= ExtendCompiler;
  try
    Result:= Compiler.Compile(Script) and Compiler.GetOutput(Bytecode);
    for i:= 0 to Compiler.MsgCount - 1 do
      if Length(Messages) = 0 then
        Messages:= Compiler.Msg[i].MessageToString
      else
        Messages:= Messages + #13#10 + Compiler.Msg[i].MessageToString;
  finally
    Compiler.Free;
  end;
end;

procedure ExtendRuntime(Runtime: TPSExec; ClassImporter: TPSRuntimeClassImporter);
begin
  Runtime.RegisterDelphiMethod(nil, @Writeln, 'Writeln', cdRegister);
  Runtime.RegisterDelphiMethod(nil, @Write, 'Write', cdRegister);
end;

function TWebModule1.RunCompiledScript(Bytecode: AnsiString; out RuntimeErrors: AnsiString): Boolean;
var
  Runtime: TPSExec;
  ClassImporter: TPSRuntimeClassImporter;
begin
  Runtime:= TPSExec.Create;
  ClassImporter:= TPSRuntimeClassImporter.CreateAndRegister(Runtime, false);
  try
    ExtendRuntime(Runtime, ClassImporter);
    Result:= Runtime.LoadData(Bytecode)
          and Runtime.RunScript
          and (Runtime.ExceptionCode = erNoError);
    if not Result then
      RuntimeErrors:=  PSErrorToString(Runtime.LastEx, '');
  finally
    ClassImporter.Free;
    Runtime.Free;
  end;
end;

procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
  HTMLSource,
  HTMLPascalScriptEmbedded: string;
begin
  //Set up HTMLSource at your convenience
  HTMLSource:= GetCurrentDir + '\testPage.htm';
  HTMLPascalScriptEmbedded:= LoadStrFromFile(HTMLSource);
  Response.Content:= ProducePage(HTMLPascalScriptEmbedded);
end;

end.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)