DataSnap-like Client-Server JSON RESTful Services in Delphi 7-2010
By A.Bouchez on 2010, Sunday July 18, 17:44 - SQLite3 Framework - Permalink
You certainly knows about the new DataSnap Client-Server features, based on
JSON, introduced in Delphi 2010.
http://docwiki.embarcadero.com/RADStudi
… plications
We added such communication in our SQLite3 Framework, in a KISS (i.e. simple) way: no expert, no new unit or new class. Just add a published method Server-side, then use easy functions about JSON or URL-parameters to get the request encoded and decoded as expected, on Client-side.
We'll implement the same example as in the official Embarcadero docwiki page
above. Add two numbers. Very useful service, isn't it? ![]()
Server side
We'll first code the Server-side:
First the declaration of the class:
TSQLRestServerTest = class(TSQLRestServerDB)
published
function Sum(aRecord: TSQLRecord; aParameters: PUTF8Char;
const aSentData: RawUTF8; out aResp, aHead: RawUTF8): Integer;
end;
This method name will be used for the URL encoding, and will be called here
with ModelRoot/Sum URL. The ModelRoot is the one defined in
the Root parameter of the model used by the
application.
This method, like all Server-side methods, MUST have all parameters of the
TSQLRestServerCallBack prototype:
type
TSQLRestServerCallBack = function(aRecord: TSQLRecord;
aParameters: PUTF8Char; const aSentData: RawUTF8;
out aResp, aHead: RawUTF8): Integer of object;
Then we implement this method:
function TSQLRestServerTest.Sum(aRecord: TSQLRecord; aParameters: PUTF8Char;
const aSentData: RawUTF8; out aResp, aHead: RawUTF8): Integer;
var a,b: Extended;
begin
if not UrlDecodeNeedParameters(aParameters,'A,B') then
begin
result := 404; // invalid Request
exit;
end;
while aParameters<>nil do
begin
UrlDecodeExtended(aParameters,'A=',a);
UrlDecodeExtended(aParameters,'B=',b,@aParameters);
end;
aResp := JSONEncodeResult([a+b]);
// same as : aResp := JSONEncode(['result',a+b],TempMemoryStream);
result := 200; // success
end;
Not difficult to follow, isn't?
On the Server side, you can use the UrlDecodeNeedParameters
function to check that an expected parameters were supplied by the caller, then
call UrlDecodeInteger / UrlDecodeInt64 / UrlDecodeExtended /
UrlDecodeValue functions (all defined in SynCommons.pas) to
retrieve each individual parameter as standard JSON content. The powerful
UrlDecodeObject function (defined in
SQLite3Commons.pas) can be used to unserialize most class instance
from its textual JSON representation.
The Client is always right
OK, now the client-side:
function Sum(aClient: TSQLRestClientURI; a, b: double): double;
var err: integer;
begin
val(aClient.CallBackGetResult('sum',['a',a,'b',b]),Result,err);
end;
And... that's all!
You could even implement this method in a dedicated client method:
type
TMyClient = class(TSQLite3HttpClient) // could be TSQLRestClientURINamedPipe
(...)
function Sum(aClient: TSQLRestClientURI; a, b: double): double;
(...)
function TMyClient.Sum(a, b: double): double;
var err: integer;
begin
val(CallBackGetResult('sum',['a',a,'b',b]),Result,err);
end;
This later implementation is to be preferred on real applications.
You have to create the server instance, and the corresponding
TSQLRestClientURI (or TMyClient), with the same
database model, just as usual...
On the Client side, you can use the CallBackGetResult method to
call the service from its name and its expected parameters, or create your own
caller using the UrlEncode() function. Note that you can specify
most class instance into its JSON representation by using some
TObject into the parameter lists:
function TMyClient.SumMyObject(aClient: TSQLRestClientURI; a, b: TMyObject): double;
var err: integer;
begin
val(aClient.CallBackGetResult('summyobject',['a',a,'b',b]),Result,err);
end;
Whatever you need
Note that this Client-Server protocol uses JSON here, but you can serve any kind of data, binary, HTML, whatever...
The usual protocols of our framework can be used: HTTP/1.1, Named Pipe, Windows GDI messages, direct in-memory/in-process access.
Just to be noticed that the data transmitted is a valid JSON content, like this one:
{ "result":3.141592653 }
So you can consume these services, implemented Server-Side in fast Delphi code, with any AJAX application Client-Side.
Of course, these services can be related to any table/class of our ORM
framework, so you would be able to create easily any RESTful compatible
requests on URL like ModelRoot/TableName/ID/MethodName. For
example, here we return a BLOB field content as hexadecimal:
function TSQLRestServerTest.DataAsHex(aRecord: TSQLRecordPeople; aParameters: PUTF8Char;
const aSentData: RawUTF8; var aResp, aHead: RawUTF8): Integer;
var aData: TSQLRawBlob;
begin
result := 404; // invalid Request
if (self=nil) or (aRecord=nil) or not aRecord.InheritsFrom(TSQLRecord) or
(aRecord.ID<0) then
exit; // we need a valid record and its ID
if not RetrieveBlob(TSQLRecordPeople,aRecord.ID,'Data',aData) then
exit; // impossible to retrieve the Data BLOB field
aResp := JSONEncodeResult([SynCommons.BinToHex(aData)]);
// idem: aResp := JSONEncode(['result',BinToHex(aRecord.fData)],TempMemoryStream);
result := 200; // success
end;
Full source code is available in our
Source Code Repository.
It should work from Delphi 6 to Delphi XE.
Article update:
The server side call back signature changed since this article was
published.
Please refer to the documentation or
this blog article.