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>
<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>
<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:
- The Web Broker application receives a Web Request.
- The Web Broker application finds the corresponding Pascal Server Page and loads its content to a buffer variable.
- The content of the buffer variable is parsed in order to find the PS tokens (I will use RegEx to do the parsing).
- 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.) - The output generated from the execution of each PS block replaces its corresponding “
<%......%>
” block. - 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.
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
public
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: string;
implementation
uses
uPSCompiler, uPSRuntime;
procedure Write(P1: Variant);
begin
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;
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
HTMLSource:= GetCurrentDir + '\testPage.htm';
HTMLPascalScriptEmbedded:= LoadStrFromFile(HTMLSource);
Response.Content:= ProducePage(HTMLPascalScriptEmbedded);
end;
end.