xxm

Introduction

xxm builds an xxm project into an xxm library. It does this by pre-parsing the xxm and xxmi files into pas files, generating other required Delphi project files, and calling the Delphi compiler. Depending on which installation you choose, you can either call the conversion utility to prepare a project for compiling, or use one of the development installations, to have the xxm library re-compiled when a source file changes.

Project

An xxm project is a directory with:

When the project is built into a library, following files are created:

To transport a website into a live environment, copy only these files:

Fragment

The URL of a request to an xxm website is first parsed to determine which xxm project to send the request to. (Each handler has its specific project registry.) The project (IXxmProject) is loaded if not loaded already, and asked to provide a page object to handle the request (IXxmPage). A page can include fragments to help construct the response (IXxmInclude). Both pages and includes are fragments (IXxmFragment) provided by the project's fragment registry. Because an xxm project has to provide a fragment registry, it has freedom of how to construct portion of the URL it processes. A stock-implementation of a fragment registry is available by default for new xxm projects (xxmFReg.pas).

Syntax

xxm and xxmi files contain both HTML and Delphi source code. Delphi code is enclosed in double square brackets ("[[ ]]").
A code section may have an alternate destination by starting with one of these characters:

[[=i]]
send: the expression is evaluated and sent to the context.
[[#x]]
send HTML: the expression is evaluated and sent to the context, without converting the text into HTML (i.e.: without replacing angled brackets "< >" and ampersands into HTML code)
!!!: to prevent script injection, it is important that any variable data is properly encoded. Use the HTMLEncode function. Read more here.
[[?'x',1,'y',2]]
URL encode: encodes key-value pairs into a query string, see also URLEncode([]) 1.2.0
[[@Variants,]]
uses clause: adds units to the uses clause. Separate unit names with comma. (1.0.3 A trailing comma is not required).
[[!var i:integer;]]
header: adds code in front of 'begin' of the fragment's Build method implementation, use this to declare local variables and constants,
*: read below remarks for proper use
[[/ ]]
comment: excludes a section of the xxm code
[[:resourcestring SHelloWorld='Hello world!';]]
definitions: adds code in front of the fragment's Build method implementation,
*: read below remarks for proper use
[[_ ]]
footer: adds code right after the fragment's Build method implementation,
*: read below remarks for proper use
[[* ]]
override parser value 1.1.3
[[[]]
opening square brackets: is replaced by two opening square brackets "[["
[[]]]
closing square brackets: is replaced by two closing square brackets "]]"
[[& ]] [[% ]] [[. ]] [[, ]] [[; ]]
non-pre-defined section types for use with parser values 1.2.0

Attention*: you can declare (nested) procedures and functions in header, footer and definition blocks, but you can't use the [[= ]] and [[# ]] syntax and embedded HTML since this will break the [[ ]]-block the declaration is in. It is advised to use include fragments with Context.Include instead.

Within a Delphi code block (including comment blocks), the number of opening and closing brackets and braces is counted (disregarding those in strings), this enables code like this: [[Context.Send(']]');]] and [[=MyArray[x]]], but also enables a comment block to 'comment out' a full block of both HTML and code blocks.

Each xxm and xxmi source file is converted into a pas file, using a prototype file. By default the prototype files are used from the "proto" directory provided with the xxm installation, but an xxm project can provide custom prototype files.

When the default prototype files are used, each resulting pas file declares a descendant TXxmFragment class (either TXxmPage or TXxmInclude). The Delphi code blocks are duplicated in the predesignated spot, and all HTML is converted into calls to Context.SendHTML(); inside of the Build method implementation.

Attention: longer portions of HTML may be split into several Context.SendHTML(); calls, so be carefull whith code like this:

if MyBoolean then ]]<b>priority:[[=PriorityLevel]]</b>[[

which gets parsed into:

if MyBoolean then
Context.SendHTML('<b>priority:');
Context.Send(PriorityLevel);
Context.SendHTML('</b>');

note that the two last lines are not part of the if-statement. So be sure to use begin and end:

if MyBoolean then begin]]<b>priority:[[=PriorityLevel]]</b>[[end;

Embed HTML 1.1.1

To improve readability, sections with HTML elements can also be enclosed in an extra set of angled brackets ("<>") to escape them from a code block, for example:

if ShowExtraWarning then <<span class="warning">be carefull!</span>>

As noted above, remember to use begin and end with larger HTML blocks or when the section has embedded code blocks.

if MyBoolean then begin <<b>priority:[[=PriorityLevel]]</b>> end;

1.1.7 Blocks enclosed this way also support the alternate destination prefixes:

<<p>Message: <b>>=Message<</p>>

1.2.2 If a line in a code section only contains a HTML tag, the extra opening and closing angled brackets ("<>") are not required.
Please note that consecutive lines like this will have the end-of-line and surrounding whitespace converted to the module code and not the HTML output, which in some cases might slightly alter the layout.

1.2.2 If a line in a code section only contains another section with a destination prefix (e.g. "[[# ]]"), it's not required to close the current code section ("]]") and open a new one right after ("[[").
Please note that consecutive lines like this will have the end-of-line and surrounding whitespace converted to the module code and not the HTML output, which in some cases might slightly alter the layout.

Proto

To convert .xxm and .xxmi files into .pas files, the converter uses prototype files from the "proto" folder. By default the files are used from the "proto" folder of the xxm installation, but an xxm project may provide its own "proto" folder with alternative prototype files. This allows to add used units or declarations ready for use in all pages and includes of an xxm project.

Interface

An xxm project uses the xxm.pas file that declares classes, interfaces and types required for the xxm interface. Your xxm installation should provide a recent copy of the xxm.pas file in the "public" folder.

TXxmProjectLoadProc = function(AProjectName:WideString): IXxmProject; stdcall;

Each xxm project must export a function named "XxmProjectLoad", that creates an instance of the project. (by default provided in the xxmp.pas file)

TXxmProject = class(TInterfacedObject, IXxmProject)
An xxm project implements the IXxmProject. (by default provided in the xxmp.pas file)
function LoadPage(Context:IXxmContext;Address:WideString):IXxmFragment;
The project's LoadPage function is called to provide a page to process an incoming request, e.g. to link it to a session (use Context.SessionID for this).
function LoadFragment(Context:IXxmContext;Address,RelativeTo:WideString):IXxmFragment;
The project's LoadFragment function is called to provide a fragement, resolve RelativeTo to allow relative calls to Context.Include
procedure UnloadFragment(Fragment: IXxmFragment);
Pages and include fragments are passed to the UnloadFragment procedure to allow the xxm project to perform clean-up or re-use of object instances.

The TXxmProject object can also implement extra interfaces to add functionality, see optional project interfaces

TXxmFragment = class(TInterfacedObject, IXxmFragment)
TXxmPage = class(TXxmFragment, IXxmPage)
TXxmInclude = class(TXxmFragment, IXxmInclude)
The default xxm and xxmi prototype files convert xxm and xxmi source files into a definition of a descendant class of TXxmPage or TXxmInclude.
procedure Build(const Context: IXxmContext; const Caller: IXxmFragment;
  const Values: array of OleVariant;
  const Objects: array of TObject); virtual; abstract;
A fragment's Build method is called to build the response
IXxmContext = interface
an xxm context handles all interfacing to the incoming request and the outgoing response
property URL:WideString
read-only, the full URL of the request
property Page:IXxmFragment
read-only, the page the project provided to handle the request, also when an include fragment is called from the page or from another include fragment
property ContentType:WideString
the MIME type of the outgoing response, by default set to 'text/html'
property AutoEncoding:TXxmAutoEncoding
the encoding used for the outgoing response
property Parameter[Key:OleVariant]:IXxmParameter
property ParameterCount:integer
the parameters provided in the request, see below for more about IXxmParameter and its descendants
property SessionID:WideString
read-only, a unique string constructed with random data, included in the response as a cookie, to identify following requests as requests from the same browser session. A project may use this property to perform session management.
procedure Send(Data: OleVariant);
sends data to the response
procedure SendHTML(Data: OleVariant);
sends data to the response without HTMLEncode
procedure SendFile(FilePath: WideString);
sends the contents of the file to the response. If the response has not started sending data, the ContentType is set to the MIME-type string found in the Windows registry by the file extention (HKEY_CLASSES_ROOT)
procedure SendStream(s:IStream);
sends the contents of the stream to the response (use TStreamAdapter from the Classes unit to provide an IStream interface on a TStream object)
procedure Include(Address: WideString); overload;
procedure Include(Address: WideString;
  const Values: array of OleVariant); overload;
procedure Include(Address: WideString;
  const Values: array of OleVariant;
  const Objects: array of TObject); overload;

load an include fragment and call the Build method with provided Values and Objects. Use Context.Include in favor of creating an instance of the fragment class directly, to enable xxm's built-in exception handling and to enable performance enhancements provided by the fragment registry.

1.1.3 To load a fragment from another project, prefix the fragment address with xxm://project2/ where project2 is the name of the other project. Attention: this is disallowed by default, to enable this add AllowInclude="1" to the xxm handler's project registry (Windows registry or xxm.xml).

function ContextString(cs:TXxmContextString):WideString;
provides information about the incoming request
function PostData:IStream;
provides access to the data posted in the request (use TOleStream from the AxCtrls unit to use the IStream interface as a TStream descendant). See also WebSocket support 1.2.3.
function Connected:boolean;
checks whether the client disconnected, typically when a user presses abort, or the connection is lost
procedure SetStatus(Code:integer;Text:WideString);
sets the HTTP response code and text to respond with, by default set to 200,'OK'
procedure Redirect(RedirectURL:WideString; Relative:boolean);
redirects the browser to a different URL. Pass Relative as false when redirecting to a URL within the same project to optimally use resources. Attention: a call to Redirect throws an EXxmPageRedirected exception to halt execution of the current page. If you catch exceptions, re-raise these to make sure the redirect is correctly handled.
property Cookie[Name:WideString]:WideString
reads a cookie from the request
procedure SetCookie(Name,Value:WideString); overload;
procedure SetCookie(Name,Value:WideString; KeepSeconds:cardinal;
  Comment,Domain,Path:WideString; Secure,HttpOnly:boolean); overload;
includes a cookie in the response, Attention: calling Cookie[] with the same Name as SetCookie has been called, will not return the same Value in the same request as SetCookie has been called.
procedure DispositionAttach(FileName: WideString);
notifies the browser to show a 'save as' dialog to store the contents of the response
property BufferSize: integer; 1.1.4
set a buffer size to store sent data before actually transmitting. When the buffer is full, data is transmitted. It's not guaranteed that the size of sent chunks will be of the exact number of bytes as defined. Set buffer size to 0 to disable buffering and send everything rightaway. (This will perform less than optimal onder load). It's highly recommended to set a buffer size, for example Context.BufferSize=$100000;, for example from the LoadPage method in xxmp.pas. This improves performance on projects that call the Send methods many times with small bits if data, but could be confusing when debugging a project. (default value: 0)
It is allowed to set BufferSize to a new value while data is already sent, but whether any data still in the output buffer will get flushed or not depends on the implementation of the xxm handler.
1.2.2 Optionally override the default value by adding BufferSize="<new default value>" to the xxm handler's project registry (Windows registry or xxm.xml).
procedure Flush; 1.1.4
write any data waiting in the output buffer rightaway. Call this right before any operation that could take some time server-side, to be sure any progress display HTML is actually transmitted to the browser.
IXxmParameter = interface
all parameters inherit from IXxmParameter
property Name:WideString
gets the parameter name
property Value:WideString
gets the value of the parameter, by default ''
function AsInteger:integer;
gets the value of the parameter as an integer value, by default 0
function NextBySameName:IXxmParameter;
gets the next parameter in sequence with the same name, if any
IXxmParameterGet = interface(IXxmParameter)
a parameter provided in the QueryString URL
IXxmParameterPost = interface(IXxmParameter)
a parameter provided in posted data
IXxmParameterPostFile = interface(IXxmParameterPost)
a file uploaded in posted data
property Size:integer
gets the file size
property MimeType:WideString
gets the MIME type of the file
procedure SaveToFile(FilePath:string);
saves the uploaded file
function SaveToStream(Stream:IStream):integer;
saves the uploaded content to a stream (use TStreamAdapter from the Classes unit to provide an IStream interface on a TStream object)
function XxmVersion:TXxmVersion;
TXxmVersion = record Major,Minor,Release,Build:integer; end;
gets the xxm version, use the xxm version to check if newer features are available
function HTMLEncode(Data:WideString):WideString; overload;
function HTMLEncode(Data:OleVariant):WideString; overload;
encodes text for safe output into a HTML document
function URLEncode(Data:OleVariant):string;
function URLDecode(Data:string):WideString;
function URLEncode(const KeyValuePairs:array of OleVariant):AnsiString; overload;
1.2.0
encodes/decodes data to/from URL notation

Headers 1.1.0

The xxm context objects provide separate interfaces to allow access to request and response HTTP headers and parameters. Include the xxmHeaders.pas unit and query the Context object for an IXxmHttpHeaders or IXxmParameterCollection interface pointer.

IXxmHttpHeaders = interface
an interface provided by the context object to allow access to request and response HTTP headers.
property RequestHeaders:IXxmDictionaryEx
a read-only dictionary of name-value pairs of the request HTTP header
property ResponseHeaders:IXxmDictionaryEx
a dictionary of name-value pairs of the response HTTP header
IXxmDictionary = interface
a dictionary contains name-value pairs
property Item[Name:OleVariant]:WideString default;
gets or sets the value for the name-value pair
property Count:integer
read-only, the number of name-value pair in the dictionary
property Name[Idx:integer]:WideString
gets or sets the name for the name-value pair
IXxmDictionaryEx = interface(IXxmDictionary)
extends IXxmDictionary with support for complex entries with sub-values, e.g.:
Content-Type: text/plain; charset="iso-8859-15"
Content-Disposition: inline; name="file1"; filename="somefile.txt"
function Complex(Name:OleVariant;out Items:IXxmDictionary):WideString;
returns opening value and sets Items to a dictionary containing further name-value pairs

Example:

(Context as IXxmHttpHeaders).ResponseHeaders['X-Something']:='Hello world!';

IXxmParameterCollection
an interface provided by the context object that enables adding extra parameters, e.g. from the project's LoadPage method.
procedure AddParameter(Param: IXxmParameter);
adds an object to the parameter collection that implements the IXxmParameter interface.

Override parser values 1.1.3

The script parser uses an internal set of values to use with converting certain sections into Delphi source code.

Send open (start of [[= sections)
default value: Context.Send(
use [[*=(   ]] to override
Send close (end of [[= sections)
default value: );
use [[*=)   ]] to override
Send HTML open (start of [[# sections)
default value: Context.SendHTML(
use [[*#(   ]] to override
Send HTML close (end of [[# sections)
default value: );
use [[*#)   ]] to override
URL encode open (start of [[? sections) 1.2.0
default value: Context.Send(URLEncode([
use [[*?(   ]] to override
URL encode close (end of [[? sections) 1.2.0
default value: ]));
use [[*?)   ]] to override

It is possible to override several parser values in one [[* ]] section by defining each one on a line by itself, e.g.:

[[*
  =(Context.Send(qr['
  =)']);
  #(Context.SendHTML(qr['
  #)']);
]]

Use an empty value, e.g. [[*=(]], to reset a parser value to its default value. Use [[*]] to reset all parser values to the default values.

1.1.8 Parser values for opening sections may optionally contain:

To set parser values for the entire project, use xxmProject.exe. The values are stored in the <ParserValues> node in Web.xxmp.

1.2.0 Additional section prefixes are available that use these parser values:

Section<ParserValues> elementDefault values
OpenCloseOpenClose
[[& ]]<Extra1Open><Extra1Close>Extra();
[[% ]]<Extra2Open><Extra2Close>Extra();
[[. ]]<Extra3Open><Extra3Close>Extra();
[[, ]]<Extra4Open><Extra4Close>Extra();
[[; ]]<Extra5Open><Extra5Close>Extra();

Optional project interfaces

IXxmProjectEvents = interface 1.1.4
the project object can optionally expose this interface. (The project object is typically defined in the xxmp.pas unit.) If it does, the xxm handler will call its methods in certain events:
function HandleException(Context:IXxmContext;PageClass:WideString;Ex:Exception):boolean; 1.1.4
when an exception occurred while building a page Return whether the exception was handled. Return false to have the xxm handler send the default exception message.
IXxmProjectEvents1 = interface 1.1.8
the project object can optionally export this interface. (The project object is typically defined in the xxmp.pas unit.) If it does, the xxm handler will call its methods in certain events:
function HandleException(Context:IXxmContext;PageClass,ExceptionClass,ExceptionMessage:WideString):boolean;
when an exception occured while building a page (and the project doesn't implement IXxmProjectEvents)
Use this interface if a Delphi version conflict occurs using IXxmProjectEvents.HandleException
Return whether the exception was handled. Return false to have the xxm handler send the default exception message.
procedure ReleasingContexts;
when the xxm handler is about to unload the project (due to a shutdown or an auto-update or auto-build event), it calls ReleasingContexts before it waits for all current page requests to complete.
procedure ReleasingProject;
when the xxm handler is about to unload the project, it calls ReleasingProject after it waited for all current page requests to complete.

Upload progress 1.1.4

To display the progress of uploading a large file, you need to attach an upload progress agent to the context before any context parameter is accessed. The request data is parsed when Context.Parameter is first accessed.

IXxmUploadProgressService = interface
an interface provided by the context object that enables attaching an upload progress agent.
procedure AttachAgent(Agent: IXxmUploadProgressAgent; Flags, Step: integer);
attaches an upload progress agent to the context. Attach any agents before calling Context.Parameter.
IXxmUploadProgressAgent = interface
implement this interface on an object to use it as an upload progress agent
procedure ReportProgress(FieldName, FileName: AnsiString; Position: integer);

Long polling support 1.2.2

If you want to implement a Long polling scenario, it might be tempting to write something like this:

while Context.Connected do
begin
  while not NewDataIsAvailable do Sleep(500);
  Context.SendHTML(GetDataAsHTML);
  Context.Flush; //for in case Context.BufferSize is set
end;

This will keep the worker thread occupied for the duration of the connection, and the number of worker threads is limited by the thread pool size configuration. This method doesn't adapt to situations where the server has to handle more requests, or is asked to shut down cleanly. If you want to use long polling requests, use these interfaces:

IXxmContextSuspend = interface
an interface provided by the context object that has long polling support.
procedure Suspend(const EventKey: WideString; CheckIntervalMS, MaxWaitTimeSec: cardinal; const ResumeFragment: WideString; ResumeValue: OleVariant; const DropFragment: WideString; DropValue: OleVariant);
register this context to keep the connection open after this Build call completes. Then check for an event approximately every CheckIntervalMS milliseconds. When IXxmProjectEvents2.CheckEvent(EventKey) returns true, resume the context and call Context.Include with ResumeFragment and ResumeValue. If it doesn't by MaxWaitTimeSec, resume the context and call Context.Include with DropFragment and DropValue if DropFragment<>''.
If ResumeValue or DropValue is an array of variants, it is passed to Context.Include as the Values parameter; else it's available as Values[0]; use Null to not pass any value.
Attention: the timing values may not be strictly adhered to. Event checking is performed separate from the worker threads and the sequence and priority of events may cause intervals to be longer or suspended contexts to be dropped sooner.
IXxmProjectEvents2 = interface
implement this interface on the project object. (The project object is typically defined in the xxmp.pas unit.)
function CheckEvent(const EventKey:WideString; var CheckIntervalMS: cardinal):boolean;
called periodically as configured by IXxmContextSuspend.Suspend. Return true to resume suspended context(s). When IXxmContextSuspend.Suspend is called with several CheckIntervalMS values, the smaller value takes precedence. Modify its value to control the timing of the next call. Attention: the timing values may not be strictly adhered to. Event checking is performed separate from the worker threads and the sequence and priority of events may cause intervals to be longer.
Attention: keep the work done by this call as light and performant as possible, optionally checking values set by a separate thread managed by the xxm project.

WebSocket support 1.2.3

To implement a WebSocket, create class that inherits from TXxmWebSocket declared in the xxmWebSocket.pas unit. It implements the IXxmPage interface, so can be registered with the fragment registry (e.g. xxmFReg.pas) which also may determine what URL the WebSocket will be accessible with (unless the project's LoadPage does other mapping of URLs).

Override the virtual methods ReadMessage or ReadBinary to handle incoming messages. Optionally override virtual methods ConnectSuccess and ConnectionLost to handle opening and closing socket connections.