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 { 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.
Nice, but it compiles the same page script for every request? I guess the compiled page script should be cached for better performance.
ReplyDeleteI fully agree Michael. Thanks.
ReplyDeleteFWIW DWScript supports all the above out of the box, but with a more capable language, faster compiler, faster execution, lower memory usage, more robust runtime, more robust compiler, multi-threading friendliness, and also has a wealth of other capabilities (like being able to compile pascal into javascript for client-side processings).
ReplyDeleteLeast but not least for a server-side usage, unlike PascalScript, DWScript is also sandboxed by default, meaning secure execution is supported, as well as interrupting/killing any script at any time without leaks.
There is a FreePascal port in progress if you're interested with that.
...alas (and this is my fault) demos for that purpose are kinda limited... :(
Hi Eric, thank you for your comments. DWScript sounds really nice.
ReplyDeleteIs this the project page? http://code.google.com/p/dwscript/
I will be taking a look shortly.
PS: A Free Pascal port is most welcome.
With Raudus you can create web-applications using Delphi 7..XE2 and Lazarus. You can create desktop-like and mobile applications. Have a look at demos: http://www.raudus.com/samples/
ReplyDeleteHello friends,
ReplyDeletePascalScript is very nice. I'll implement this nice feature in Brook framework:
http://brookframework.org
Regards,
Silvio Clécio.
ps. The Brook description:
Brook framework is the perfect Free Pascal framework for your web applications. It's pure Pascal. You don't need to leave your preferred programming language.
It's complete: simple actions or configurable actions for database access, advanced routing features, wizard for Lazarus, support for internationalization, high compatibility with JSON structures, eazy and elegant REST implementation, plugins for varied needs, extensible and decoupled brokers... In fact, you have several reasons to adopt Brook as your web development framework.
Thanks for the tip Silvio.
DeleteHello buddy,
ReplyDeleteWhat do you think of we implement this feature in Brook framework? It can be called as BSP (Brook Server Pages).
If you are interested we can open a new issue (https://github.com/silvioprog/brookframework/issues).
Thank you! (y)
Silvio Clécio.