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.
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).
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]]
[[#x]]
< >
" and ampersands into HTML code)
HTMLEncode
function.
Read more here.
[[?'x',1,'y',2]]
URLEncode([])
1.2.0[[@Variants,]]
[[!var i:integer;]]
[[/ ]]
[[:resourcestring SHelloWorld='Hello world!';]]
[[_ ]]
[[* ]]
[[[]]
[[
"[[]]]
]]
"[[& ]]
[[% ]]
[[. ]]
[[, ]]
[[; ]]
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;
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.
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.
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)
function LoadPage(Context:IXxmContext;Address:WideString):IXxmFragment;
function LoadFragment(Context:IXxmContext;Address,RelativeTo:WideString):IXxmFragment;
procedure UnloadFragment(Fragment: IXxmFragment);
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)
procedure Build(const Context: IXxmContext; const Caller: IXxmFragment;
const Values: array of OleVariant;
const Objects: array of TObject); virtual; abstract;
IXxmContext = interface
property URL:WideString
property Page:IXxmFragment
property ContentType:WideString
property AutoEncoding:TXxmAutoEncoding
property Parameter[Key:OleVariant]:IXxmParameter
property ParameterCount:integer
property SessionID:WideString
procedure Send(Data: OleVariant);
procedure SendHTML(Data: OleVariant);
procedure SendFile(FilePath: WideString);
procedure SendStream(s:IStream);
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": true
to the xxm handler's project registry
(xxm.json
).
function ContextString(cs:TXxmContextString):WideString;
function PostData:IStream;
TOleStream
from the AxCtrls unit to use the IStream
interface as a TStream
descendant). See also WebSocket support 1.2.3.function Connected:boolean;
procedure SetStatus(Code:integer;Text:WideString);
procedure Redirect(RedirectURL:WideString; Relative:boolean);
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
procedure SetCookie(Name,Value:WideString); overload;
procedure SetCookie(Name,Value:WideString; KeepSeconds:cardinal;
Comment,Domain,Path:WideString; Secure,HttpOnly:boolean); overload;
procedure DispositionAttach(FileName: WideString);
property BufferSize: integer;
1.1.4Context.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)"bufferSize": <new default value>
to the xxm handler's project registry (xxm.json
).procedure Flush;
1.1.4IXxmParameter = interface
property Name:WideString
property Value:WideString
function AsInteger:integer;
function NextBySameName:IXxmParameter;
IXxmParameterGet = interface(IXxmParameter)
IXxmParameterPost = interface(IXxmParameter)
IXxmParameterPostFile = interface(IXxmParameterPost)
property Size:integer
property MimeType:WideString
procedure SaveToFile(FilePath:string);
function SaveToStream(Stream:IStream):integer;
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;
function HTMLEncode(Data:WideString):WideString; overload;
function HTMLEncode(Data:OleVariant):WideString; overload;
function URLEncode(Data:OleVariant):string;
function URLDecode(Data:string):WideString;
function URLEncode(const KeyValuePairs:array of OleVariant):AnsiString; overload;
1.2.0
It's advised when installing xxm to store the public
and proto
folders together with the binaries. (e.g. C:\InetPub\xxm
).
A new xxm project has by default <CompileCommand>
set to dcc32 -U[[HandlerPath]]public -Q [[ProjectName]].dpr
so these units are available by default.
xxmp.pas
. You are free to change both in your project. In larger projects a specific solution may perform better then the default solution based on a plain TStringList
.<Unit UnitName="xxmSession"/>
), add a SetSession call to your xxmp.pas' LoadPage method (where it says //TODO: link session to request
), and modify the session object to hold the data required by your application.TxxmOutputStream
class you can wrap around a IXxmContext
to have a stream to write to and have the data sent to the response.TStringContext
that enables to catch output from fragments. Use it either by temporarily overriding parser values, or by using its Include
method(s).
TXxmWebSocket
you can inherit from to provide web sockets.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
property RequestHeaders:IXxmDictionaryEx
property ResponseHeaders:IXxmDictionaryEx
IXxmDictionary = interface
property Item[Name:OleVariant]:WideString default;
property Count:integer
property Name[Idx:integer]:WideString
IXxmDictionaryEx = interface(IXxmDictionary)
Content-Type: text/plain; charset="iso-8859-15"
Content-Disposition: inline; name="file1"; filename="somefile.txt"
function Complex(Name:OleVariant;out Items:IXxmDictionary):WideString;
Example:
(Context as IXxmHttpHeaders).ResponseHeaders['X-Something']:='Hello world!';
IXxmParameterCollection
procedure AddParameter(Param: IXxmParameter);
The script parser uses an internal set of values to use with converting certain sections into Delphi source code.
[[=
sections)Context.Send(
[[*=( ]]
to override[[=
sections));
[[*=) ]]
to override[[#
sections)Context.SendHTML(
[[*#( ]]
to override[[#
sections));
[[*#) ]]
to override[[?
sections) 1.2.0Context.Send(URLEncode([
[[*?( ]]
to override[[?
sections) 1.2.0]));
[[*?) ]]
to overrideIt 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:
$l
gets replaced by the line number in the xxm source file where the section starts$v
gets replaced by the current parser value, this allows to add to the parser value$d
gets replaced by the default value, this allows to revert to the default value and add to it in one operationTo 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> element | Default values | ||
---|---|---|---|---|
Open | Close | Open | Close | |
[[& ]] | <Extra1Open> | <Extra1Close> | Extra( | ); |
[[% ]] | <Extra2Open> | <Extra2Close> | Extra( | ); |
[[. ]] | <Extra3Open> | <Extra3Close> | Extra( | ); |
[[, ]] | <Extra4Open> | <Extra4Close> | Extra( | ); |
[[; ]] | <Extra5Open> | <Extra5Close> | Extra( | ); |
IXxmProjectEvents = interface
1.1.4xxmp.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.4IXxmProjectEvents1 = interface
1.1.8xxmp.pas
unit.)
If it does, the xxm handler will call its methods in certain events:function HandleException(Context:IXxmContext;PageClass,ExceptionClass,ExceptionMessage:WideString):boolean;
IXxmProjectEvents.HandleException
procedure ReleasingContexts;
ReleasingContexts
before it waits for all current page requests to complete.procedure ReleasingProject;
ReleasingProject
after it waited for all current page requests to complete.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
procedure AttachAgent(Agent: IXxmUploadProgressAgent; Flags, Step: integer);
Context.Parameter
.
xxmUploadProgressAttach_PostData
: report progress parsing the entire request data (Step value ignored)xxmUploadProgressAttach_FileFields
: report progress parsing each file, report progress every Step bytesIXxmUploadProgressAgent = interface
procedure ReportProgress(FieldName, FileName: AnsiString; Position: integer);
xxmUploadProgressAttach_PostData
: Position into request body (FieldName, FileName not used)xxmUploadProgressAttach_FileFields
: FieldName, FileName of post form field being parsed; Position into file dataIf 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
procedure Suspend(const EventKey: WideString; CheckIntervalMS, MaxWaitTimeSec: cardinal;
const ResumeFragment: WideString; ResumeValue: OleVariant;
const DropFragment: WideString; DropValue: OleVariant);
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<>''
.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.IXxmProjectEvents2 = interface
xxmp.pas
unit.)function CheckEvent(const EventKey:WideString; var CheckIntervalMS: cardinal):boolean;
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.
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.