You are not logged in.
Pages: 1
Current version of the main framework units target only Win32 and Win64 systems.
It allows to make easy self-hosting of mORMot servers for local business applications in any corporation, or pay cheap hosting in the Cloud, since mORMot CPU and RAM expectations are much lower than a regular IIS-WCF-MSSQL-.Net stack.
But in a Service-Oriented Architecture (SOA), you would probably need to create clients for platforms outside the Windows world, especially mobile devices.
A set of cross-platform client units is therefore available in the CrossPlatform sub-folder of the source code repository. It allows writing any client in modern object pascal language, for:
- Any version of Delphi, on any platform (Mac OSX, or any mobile supported devices);
- FreePascal Compiler 2.7.1;
- Smart Mobile Studio 2.1, to create AJAX or mobile applications (via PhoneGap, if needed).
This forum thread is the comment base for the following blog articles:
- http://blog.synopse.info/post/2014/08/1 … -Platforms
- http://blog.synopse.info/post/2014/08/1 … erate-Code
- http://blog.synopse.info/post/2014/08/1 … FreePascal
- http://blog.synopse.info/post/2014/08/1 … bileStudio
The SAD 1.18 pdf contains the latest version of this documentation!
Enjoy!
Offline
HI, I just test your big works.
but, using XE6, and following your instructions I tried to modify the sample 14..
Everything works perfectly, but there seems to be a small error output:
mormotclient.pas:
function TServiceCalculator.Add(const n1: integer; const n2: integer): integer;
var res: TVariantDynArray;
begin
fClient.CallRemoteService(self,'Add',1, // raise EServiceException on error
[n144n2],res); <------------------------- 44 ? [n1,n2] I think
Result := res[0];
end;
44 is ord(',')
Offline
I find solution....
I HATE delphi unicode!
from mormot.pas:
{$ifndef NOVARIANTS}
function TInterfaceFactory.ContextFromMethods(aRegisteredTypes: TRawUTF8List): variant;
const VERB_DELPHI: array[boolean] of string[9] = ('procedure','function');
NULL_OR_COMMA: array[boolean] of RawUTF8 = ('null','","');
var methods,arguments: TDocVariantData;
m,a,r: integer;
arg: variant;
begin
methods.Init(JSON_OPTIONS[true]);
for m := 0 to fMethodsCount-1 do
with fMethods[m] do begin
r := 0;
arguments.Init(JSON_OPTIONS[true]);
for a := 1 to high(args) do begin // ignore self as a=0
arg := args[a].ContextFromArguments(aRegisteredTypes);
if (args[a].ValueDirection in [smdConst,smdVar]) and (a<ArgsInLast) then begin
arg.commaIn := '; ';
arg.commaInSingle := ','; // <---------------------------------------------------------------------------------------
end;
if (args[a].ValueDirection in [smdVar,smdOut]) and (a<ArgsOutNotResultLast) then
arg.commaOut := '; ';
if args[a].ValueDirection in [smdVar,smdOut,smdResult] then begin
arg.indexOutResult := UInt32ToUtf8(r)+']';
inc(r);
if a<ArgsOutLast then
arg.commaOutResult := '; ';
end;
arguments.AddItem(arg);
end;
methods.AddItem(_ObjFast(['methodName',URI,'verb',VERB_DELPHI[ArgsResultIndex>=0],
'args',variant(arguments),'argsOutputCount',r]));
arguments.Clear;
end;
result := variant(methods);
end;
{$endif}
arg.commaInSingle is Variant and ',' is a char.
The solution is to force the type: arg.commaInSingle := string(',');
automatic typecasting is EVIL
Offline
Congratulations!It's one more step closer to the perfect world - the server supports Mac and Linux
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
@Sabbiolina
I was not able to reproduce the issue on Delphi 7 or Delphi XE6 (without update 1).
It is an awful bug of the Delphi compiler/RTL itself.
Does it affect XE6 update 1? Is it a regression? Is there a QC? Is it for some targets only?
I tried to find all occurrences of such a constant char assigned to a variant.
Should be fixed by http://synopse.info/fossil/info/cde30e2b2c0b
Thanks a lot for the debugging and fix!
Your input is very valuable.
@edwinsn
The more I think about it, the less I would make the server part compatible with Linux, unless Delphi supports this platform officially.
We can afford cheap Windows hosting for our mORMot server projects - no need to have a huge configuration: it uses much less resources than the IIS/MSSQL/.Net/WCF stack.
The time spent on refactoring the code would be higher for us than our hosting costs.
Offline
xe6 works ok
it's old xe2 update 4 hotfix 1 with bugs
Offline
@edwinsn
The more I think about it, the less I would make the server part compatible with Linux, unless Delphi supports this platform officially.
We can afford cheap Windows hosting for our mORMot server projects - no need to have a huge configuration: it uses much less resources than the IIS/MSSQL/.Net/WCF stack.
The time spent on refactoring the code would be higher for us than our hosting costs.
It is sad to hear that but I understand, I'm researching right now hosting options, I'm undecided between Dedicated Server/VPS or some cloud computing offerings like Amazon (btw the cheaper options on Amazon AWS are for Linux)
Amazon has many attractive options out of the box, their windows pricing is just too expensive for us; I'm not convinced in general with the pricing but on the DB side (RDS) I will feel better with the high availability service, automated backups, pre-configured parameters, automatic software patching etc. we are not many people and that kind of service means less effort on our side to have our solution working.
What are others doing?
What do you think of a combo VPS/Amazon RDS (PostgreSQL), Won't that defeat the performance gains of mormot because of the added latency to Amazon RDS? I'm going to start with the Amazon AWS free tier and then compare with a Cheap VPS/Dedicated but if anyone wants to share their experiences I would love to hear how they are doing.
Regards,
Mocte
Offline
AFAIR Amazon or Azure are indeed very expensive.
Here in France we have e.g. http://express.ikoula.com/serveur-virtuel which offers the Flex'Server 2, with 2 Xeon Cores, 2GB of RAM, 70 GB of storage, Windows Server 2008 R2 or 2012 for €19.99 per month.
Or you can run a Linux dedicated server, then run your own Windows within KVM - using e.g. https://www.proxmox.com/proxmox-ve/get-started if you need something easy to administrate.
To limit the RAM and CPU use of your Windows machine, take a look at this series of great blog articles:
http://www.delphitools.info/2013/09/09/ … ud-part-1/
Offline
This is really exceptionel, superb, awesome....
I did a quick test with some of my models and it generally works but I have some issues with the generated code.
1) when I have a TSQLRecord that has 3 different published boolean properties I get code like this generated:
...
type // define some enumeration types, used below
Boolean = (False, True);
Boolean = (False, True);
Boolean = (False, True);
...
which obviously can't be compiled.
I have more issues and am willing to send you my model code for testing purposes
Last edited by martin.suer (2014-08-13 14:36:07)
Offline
You can put a link here to some download site, or send me any file at webcontact01 at synopse dot info
Duplicated types, and unexpected "boolean" definition should be fixed now.
See http://synopse.info/fossil/info/6630ad99cc0a
I suspect there could be more issues, since a lot of types and combinations are possible in the real world!
Thanks a lot for the feedback!
Offline
I will create some code that causes the issues I've discovered and that's free of other overhead and share it in the forum
Offline
Ok, I have prepared some code:
program mORMotWrapGenTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
Classes,
SynCommons,
mORMot,
mORMotWrappers,
mORMotHttpClient,
mORMotHttpServer,
mORMotSQLite3,
SynSQLite3Static;
type
TAddress = packed record
Street, Zip, City : RawUTF8;
end;
TSex = (male, female);
TPerson = packed record
Name, Phone : RawUTF8;
Sex : TSex;
end;
TContacts = array of TPerson;
TOrderPosition = class(TCollectionItem)
private
fDate: TDateTime;
fDescription: RawUTF8;
fVolume: double;
published
property Date: TDateTime read fDate write fDate;
property Description: RawUTF8 read fDescription write fDescription;
property Volume: double read fVolume write fVolume;
end;
TOrdersCollection = class(TInterfacedCollection)
private
function GetCollItem(Index: Integer): TOrderPosition;
protected
class function GetClass: TCollectionItemClass; override;
public
function Add: TOrderPosition;
property Item[index: Integer]: TOrderPosition
read GetCollItem; default;
end;
TSQLCustomer = class(TSQLRecord)
private
fName: RawUTF8;
fAddress: TAddress;
fContacts: TContacts;
fOrders: TOrdersCollection;
public
constructor Create; override;
destructor Destroy; override;
published
property Name : RawUTF8 read fName write fName;
property Address : TAddress read fAddress write fAddress;
property Contacts : TContacts read fContacts write fContacts;
property Orders : TOrdersCollection read fOrders write fOrders;
end;
ICustomerMgmt = interface (IInvokable)
['{9FE0D344-4488-4460-81D7-C89F8D29EC4A}']
procedure MoveCustomerToNewAddress(CustomerID : integer;
const NewAddress: TAddress);
procedure AddContactToCustomer(CustomerID : integer;
const NewContact: TPerson);
end;
IOrderMgmt = interface (IInvokable)
['{BF335418-61B4-4657-AA84-314FEE6BB342}']
function CreateOrderForCustomer(WithCustomerID : integer) : integer;
end;
TCustomerMgmt = class(TInterfacedObject, ICustomerMgmt)
public
procedure MoveCustomerToNewAddress(CustomerID : integer;
const NewAddress: TAddress);
procedure AddContactToCustomer(CustomerID : integer;
const NewContact: TPerson);
end;
TOrderMgmt = class(TInterfacedObject, IOrderMgmt)
public
function CreateOrderForCustomer(WithCustomerID : integer) : integer;
end;
var
FModel: TSQLModel;
FServer: TSQLRestServer;
FHTTPServer: TSQLHttpServer;
{ TOrderMgmt }
function TOrderMgmt.CreateOrderForCustomer(WithCustomerID: integer): integer;
begin
Result := WithCustomerID; // just to return something - it's only a test
end;
{ TCustomerMgmt }
procedure TCustomerMgmt.AddContactToCustomer(CustomerID: integer;
const NewContact: TPerson);
begin
// Read Customer, Add NewContact to Contacts, Save Customer
end;
procedure TCustomerMgmt.MoveCustomerToNewAddress(CustomerID: integer;
const NewAddress: TAddress);
begin
// Read Customer, assign new Address, Save Customer
end;
{ TOrdersCollection }
function TOrdersCollection.Add: TOrderPosition;
begin
result := inherited Add as TOrderPosition;
end;
class function TOrdersCollection.GetClass: TCollectionItemClass;
begin
result := TOrderPosition;
end;
function TOrdersCollection.GetCollItem(Index: Integer): TOrderPosition;
begin
result := Items[index] as TOrderPosition;
end;
{ TSQLCustomer }
constructor TSQLCustomer.Create;
begin
inherited;
fOrders := TOrdersCollection.Create;
end;
destructor TSQLCustomer.Destroy;
begin
fOrders.Free;
inherited;
end;
begin
fModel := TSQLModel.Create([TSQLCustomer]);
fServer := TSQLRestServerDB.Create(fModel, ChangeFileExt(paramstr(0), '.db3'),True);
fServer.CreateMissingTables;
AddToServerWrapperMethod(fServer,
['..\..\..\CrossPlatform\templates', '..\..\..\..\CrossPlatform\templates']);
fServer.ServiceRegister(TCustomerMgmt,[TypeInfo(ICustomerMgmt)],sicSingle);
fServer.ServiceRegister(TOrderMgmt,[TypeInfo(IOrderMgmt)],sicSingle);
fHTTPServer := TSQLHttpServer.Create('888',[fServer],'+',useHttpApiRegisteringURI);
fHTTPServer.AccessControlAllowOrigin := '*';
WriteLn ('Server is running. Hit any key to exit.');
ReadLn;
end.
In the generated wrapper some types are missing:
* TPerson - which is used in an interface method procedure AddContactToCustomer(CustomerID: integer; NewContact: TPerson; onSuccess: procedure(); onError: TSQLRestEvent);
* The generated type TSQLCustomer has a property Orders: TOrdersCollection read fOrders write fOrders; but the type TOrdersCollection is missing.
Last edited by martin.suer (2014-08-14 07:53:49)
Offline
I should note that I've created the code with XE6 ...
Offline
See http://synopse.info/fossil/info/507d7d8313a
and http://synopse.info/fossil/info/4433caa12c6
for some missing record or enumeration information, which should be there now.
But there are still some limitations in Cross-Platform mode:
- collections types are not generated yet
- record published properties won't be handled yet
- dynamic arrays are retrieved as binary yet - we need http://synopse.info/fossil/tktview?name=27353dad25 to be implemented in order to allow JSON serialization as expected
Offline
Can you confirm that smartmsgenerator can not make a record as an object's property?
type
TAddress = packed record
Street, Zip, City : RawUTF8;
end;
TSQLCustomer = class(TSQLRecord)
private
fAddress: TAddress;
public
published
property Address : TAddress read fAddress write fAddress;
end;
Offline
Arnaud,
just a quick question:
are you sure that CreateAndFillPrepare is generally working with the generated client code in the latest code revision?
Whatever I do, I always get Error 400 in the response.
Other Methods ( Retrieve, Add, etc ) are working.
have tried with generated code for sms and cross plattform.
If it works for others, I have to investigate what I am doing wrong maybe on the server side but everything looks like explained in your docs.
Offline
The following generated unit: https://github.com/synopse/mORMot/blob/ … Client.pas
works with the following regression tests: https://github.com/synopse/mORMot/blob/ … mTests.pas
There are several TSQLRecordPeople.CreateAndFillPrepare() tests in those regression tests.
For instance:
people := TSQLRecordPeople.CreateAndFillPrepare(fClient,'','',[]);
....
people := TSQLRecordPeople.CreateAndFillPrepare(fClient,
'YearOFBIRTH,Yearofdeath,id','',[]);
....
people := TSQLRecordPeople.CreateAndFillPrepare(fClient,'',
'yearofbirth=?',[1900]);
I suppose you are not using CreateAndFillPrepare() as it should.
Ensure you read the corresponding paragraph of the latest 1.18 SAD pdf, about how the WHERE clause is expected to be formatted.
Offline
Arnaud,
I am using it correctly. It seems that it has to do with the TSQLRecord I have used to generate the mORMotClient (number or types of the properties, don't know yet...).
Anyway. I am starting with something easier and get another issue:
Using this simple server:
program mORMotWrapGenTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
Classes,
SynCommons,
mORMot,
mORMotWrappers,
mORMotHttpServer,
mORMotSQLite3,
SynSQLite3Static;
type
TSQLCustomer = class(TSQLRecord)
private
fName: RawUTF8;
fAge: integer;
published
property Name : RawUTF8 read fName write fName;
property Age : integer read fAge write fAge;
end;
var
FModel: TSQLModel;
FServer: TSQLRestServer;
FHTTPServer: TSQLHttpServer;
fname : String;
customer : TSQLCustomer;
begin
fModel := TSQLModel.Create([TSQLCustomer]);
fname := ChangeFileExt(paramstr(0), '.db3');
if FileExists(fname) then
DeleteFile(fname);
fServer := TSQLRestServerDB.Create(fModel, fname, True);
fServer.CreateMissingTables;
AddToServerWrapperMethod(fServer,
['..\..\..\CrossPlatform\templates', '..\..\..\..\CrossPlatform\templates']);
fHTTPServer := TSQLHttpServer.Create('888',[fServer],'+',useHttpApiRegisteringURI);
fHTTPServer.AccessControlAllowOrigin := '*';
customer := TSQLCustomer.Create;
customer.Name := 'Testname';
customer.Age := 20;
fServer.Add(customer,true);
customer.Free;
WriteLn ('Server is running. Hit any key to exit.');
ReadLn;
end.
the generated code for smart contains:
class function TSQLCustomer.ComputeRTTI: TRTTIPropInfos;
begin
result := TRTTIPropInfos.Create(
['Name','Age',],
[sftUnspecified,sftUnspecified,]);
end;
look at the comma after 'Age' and after sftUnspecified
The corresponding template context contains those commata.
When I manually correct the generated sms mORMotClient (removing the commata) I can use the unit with smart and CreateAndFillPrepare is working with that unit...
Last edited by martin.suer (2014-08-17 19:03:46)
Offline
Ensure you got the latest version of the source.
This comma issue (a Delphi XE 2 bug if I remember well) has been fixed some days ago.
See above in this forum thread. http://synopse.info/forum/viewtopic.php … 790#p11790
Which version of Delphi are you using?
How do you use CreateAndFillPrepare?
You do not show this part of the code.
Offline
I have the latest source (nightly build, downloaded today)
Both issues are not related to each other.
generated source:
/// remote access to a mORMot server using SmartMobileStudio
// - retrieved from http://localhost:888/root/wrapper/SmartMobileStudio/mORMotClient.pas.txt
// at 2014-08-17 22:02:42 using "SmartMobileStudio.pas.mustache" template
unit mORMotClient;
{
WARNING:
This unit has been generated by a mORMot 1.18 server.
Any manual modification of this file may be lost after regeneration.
Synopse mORMot framework. Copyright (C) 2014 Arnaud Bouchez
Synopse Informatique - http://synopse.info
This unit is released under a MPL/GPL/LGPL tri-license,
and therefore may be freely included in any application.
This unit would work on Smart Mobile Studio 2.1 and later.
}
interface
uses
SmartCL.System,
System.Types,
SynCrossPlatformSpecific,
SynCrossPlatformREST;
type
/// map "Customer" table
TSQLCustomer = class(TSQLRecord)
protected
fName: string;
fAge: integer;
// those overriden methods will emulate the needed RTTI
class function ComputeRTTI: TRTTIPropInfos; override;
procedure SetProperty(FieldIndex: integer; const Value: variant); override;
function GetProperty(FieldIndex: integer): variant; override;
public
property Name: string read fName write fName;
property Age: integer read fAge write fAge;
end;
const
/// the server port, corresponding to http://localhost:888
SERVER_PORT = 888;
/// return the database Model corresponding to this server
function GetModel: TSQLModel;
/// create a TSQLRestClientHTTP instance and connect to the server
// - it will use by default port 888
// - secure connection will be established via TSQLRestServerAuthenticationDefault
// with the supplied credentials
// - request will be asynchronous, and trigger onSuccess or onError event
procedure GetClient(const aServerAddress, aUserName,aPassword: string;
onSuccess, onError: TSQLRestEvent; aServerPort: integer=SERVER_PORT);
implementation
{ TSQLCustomer }
class function TSQLCustomer.ComputeRTTI: TRTTIPropInfos;
begin
result := TRTTIPropInfos.Create(
['Name','Age',],
[sftUnspecified,sftUnspecified,]);
end;
procedure TSQLCustomer.SetProperty(FieldIndex: integer; const Value: variant);
begin
case FieldIndex of
0: fID := Value;
1: fName := Value;
2: fAge := Value;
end;
end;
function TSQLCustomer.GetProperty(FieldIndex: integer): variant;
begin
case FieldIndex of
0: result := fID;
1: result := fName;
2: result := fAge;
end;
end;
function GetModel: TSQLModel;
begin
result := TSQLModel.Create([TSQLCustomer,TSQLAuthUser,TSQLAuthGroup],'root');
end;
procedure GetClient(const aServerAddress, aUserName,aPassword: string;
onSuccess, onError: TSQLRestEvent; aServerPort: integer);
begin
var client := TSQLRestClientHTTP.Create(aServerAddress,aServerPort,GetModel,true);
client.Connect(
lambda
try
if client.ServerTimeStamp=0 then begin
if Assigned(onError) then
onError(client);
exit;
end;
if not client.SetUser(TSQLRestServerAuthenticationDefault,aUserName,aPassword) then begin
if Assigned(onError) then
onError(client);
exit;
end;
if Assigned(onSuccess) then
onSuccess(client);
except
if Assigned(onError) then
onError(client);
end;
end,
onError);
end;
end.
using this way:
procedure TForm1.W3Button1Click(Sender: TObject);
var
Client : TSQLRestClientURI;
customer : TSQLCustomer;
s : String;
begin
GetClient('127.0.0.1','User','synopse',
lambda (aClient: TSQLRestClientURI)
Client := aClient;
customer := TSQLCustomer.CreateAndFillPrepare(Client,'','',[]);
while customer.FillOne do
begin
s := customer.Name + ' ' + IntToStr(customer.Age);
ShowMessage(s);
end;
end,
lambda
ShowMessage('Impossible to connect to the server!');
end);
end;
(which works with this simple TSQLCustomer and the deleted commata)
Offline
the comma - bug you're referring to has been a different one
that was an encoding bug
in this case the "comma" variable in the template context shouldn't exist for the last field but it is set to ","
Offline
Which version of Delphi are you using?
What is the data context json?
Did you try to find out why comma is not null?
It should have been fixed by the previous fix.
It is not clear if the comma is the only issue.
Does CreateAndFillprepare work?
Offline
CreateAndFillPrepare works.
The context is:
{"time":"2014-08-17 22:14:04","year":2014,"mORMotVersion":"1.18","root":"root","orm":[{"tableName":"Customer","className":"TSQLCustomer","fields":[{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":1,"name":"Name","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"comma":","},{"typeName":"ptInteger","typeDelphi":"integer","typePascal":"integer","typeCS":"integer","typeJava":"int","index":2,"name":"Age","sql":5,"sqlName":"sftInteger","typeKind":0,"typeKindName":"sftUnspecified","attr":0}],"isInMormotPas":null,"comma":","},{"tableName":"AuthUser","className":"TSQLAuthUser","fields":[{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":1,"name":"LogonName","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":1,"unique":true,"width":20,"comma":","},{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":2,"name":"DisplayName","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"width":50,"comma":","},{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":3,"name":"PasswordHashHexa","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"width":64,"comma":","},{"typeName":"ptCardinal","typeDelphi":"cardinal","typePascal":"cardinal","typeCS":"uint","typeJava":"long","index":4,"name":"GroupRights","sql":6,"sqlName":"sftID","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"comma":","},{"typeName":"ptRawByteString","typeDelphi":"TSQLRawBlob","typePascal":"TSQLRawBlob","typeCS":"byte[]","typeJava":"byte[]","isBlob":true,"toVariant":"BlobToVariant","fromVariant":"VariantToBlob","index":5,"name":"Data","sql":15,"sqlName":"sftBlob","typeKind":3,"typeKindName":"sftBlob","attr":0}],"isInMormotPas":true,"comma":","},{"tableName":"AuthGroup","className":"TSQLAuthGroup","fields":[{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":1,"name":"Ident","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":1,"unique":true,"width":50,"comma":","},{"typeName":"ptInteger","typeDelphi":"integer","typePascal":"integer","typeCS":"integer","typeJava":"int","index":2,"name":"SessionTimeout","sql":5,"sqlName":"sftInteger","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"comma":","},{"typeName":"ptRawUTF8","typeDelphi":"RawUTF8","typePascal":"string","typeCS":"string","typeJava":"String","index":3,"name":"AccessRights","sql":2,"sqlName":"sftUTF8Text","typeKind":0,"typeKindName":"sftUnspecified","attr":0,"width":1600}],"isInMormotPas":true,"comma":null}],"soa":null,"authClass":"TSQLRestServerAuthenticationDefault","uri":"root/wrapper/context","host":"localhost:888","port":888}
haven't found out why it's not null
am using xe6 (without update)
Offline
There is another "comma" at highest context level.
Should be fixed by http://synopse.info/fossil/info/25d6493fcb
Offline
confirmed, does work now
Offline
hello AB.
I am trying to make changes to the project 14 to generate the client for android.
unit Project14Interface;
interface
type
TTagList = packed record
Base: widestring;
L: array of packed record
name: WideString;
age: integer;
end;
end;
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FF}']
function Add(n1,n2: integer): integer;
function Sub(n1b,n2b: integer): integer;
procedure list(tag:string;var tagList:TTaglist);
end;
const
ROOT_NAME = 'root';
PORT_NAME = '888';
APPLICATION_NAME = 'RestService';
implementation
end.
program Project14ServerHttp;
{$APPTYPE CONSOLE}
uses
SysUtils,
Classes,
SynCommons,
mORMot,
mORMotHttpServer,
mORMotWrappers,
Project14Interface in 'Project14Interface.pas';
type
TServiceCalculator = class(TInterfacedObject, ICalculator)
public
function Add(n1,n2: integer): integer;
function Sub(n1b,n2b: integer): integer;
procedure list(tag:string;var tagList:TTaglist);
end;
function TServiceCalculator.Add(n1, n2: integer): integer;
begin
result := n1+n2;
end;
function TServiceCalculator.Sub(n1b, n2b: integer): integer;
begin
result := n1b-n2b;
end;
procedure TServiceCalculator.list(tag:string;var tagList:TTaglist);
begin
//
end;
var
aModel: TSQLModel;
aServer: TSQLRestServer;
aHTTPServer: TSQLHttpServer;
begin
// define the log level
with TSQLLog.Family do begin
Level := LOG_VERBOSE;
EchoToConsole := LOG_VERBOSE; // log all events to the console
end;
// create a Data Model
aModel := TSQLModel.Create([],ROOT_NAME);
try
// initialize a TObjectList-based database engine
aServer := TSQLRestServerFullMemory.Create(aModel,'test.json',false,true);
try
// add the http://localhost:888/root/wrapper code generation web page
AddToServerWrapperMethod(aServer,
['..\..\..\CrossPlatform\templates','..\..\..\..\CrossPlatform\templates']);
// register our ICalculator service on the server side
aServer.ServiceRegister(TServiceCalculator,[TypeInfo(ICalculator)],sicShared);
// launch the HTTP server
aHTTPServer := TSQLHttpServer.Create(PORT_NAME,[aServer],'+',useHttpApiRegisteringURI);
try
aHTTPServer.AccessControlAllowOrigin := '*'; // for AJAX requests to work
writeln(#10'Background server is running.'#10);
writeln('Press [Enter] to close the server.'#10);
readln;
finally
aHTTPServer.Free;
end;
finally
aServer.Free;
end;
finally
aModel.Free;
end;
end.
but I get this error:
20140818 21284808 call TSQLRestServerFullMemory(0035C020) Wrapper
20140818 21284808 srvr GET root/wrapper ERROR=400 (Please copy some .mustache files in the expected folder (e.g. c:\xe.local\comp\CrossPlatform\templates))
in that folder obviously there are 3 files mustache
what's wrong ?
Offline
Could you use the debugger on the server and find out why the method is complaining about missing templates?
And perhaps at first, copy the template files with the .exe and use '.' as folder.
Offline
probably the series of /../../../ ..
who knows where they take you, if you change the base path
now I fix with a root path
Offline
but..... this code is not generated?
{$ifdef TESTRECORD}
{ TSQLRecordPeople }
const
__TTestCustomJSONArraySimpleArray =
'F RawUTF8 G array of RawUTF8 '+
'H {H1 integer H2 WideString H3{H3a boolean H3b RawByteString}} I TDateTime '+
'J [J1 byte J2 TGUID J3 TRecordEnum]';
class procedure TSQLRecordPeople.InternalRegisterCustomProperties(
Props: TSQLRecordProperties);
begin
Props.RegisterCustomPropertyFromRTTI(Self,TypeInfo(TTestCustomJSONArraySimpleArray),
'Simple',@TSQLRecordPeople(nil).fSimple);
end;
initialization
TTextWriter.RegisterCustomJSONSerializerFromTextSimpleType(TypeInfo(TRecordEnum));
TTextWriter.RegisterCustomJSONSerializerFromText(
TypeInfo(TTestCustomJSONArraySimpleArray),__TTestCustomJSONArraySimpleArray);
{$endif TESTRECORD}
Offline
I'm looking for the right way to get a list of records from a call.
Offline
hello AB.
unit Project14Interface;
interface
uses
SynCommons,
mORMot,
mORMotHttpServer,
mORMotWrappers,
SynMustache,
SysUtils;
type
TTagList = packed record
Base: widestring;
age:integer;
L: array of packed record
fname: WideString;
fsize: integer;
end;
end;
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FF}']
function Add(n1,n2: integer): integer;
function list(tag:string;var tagList:TTaglist):string;
end;
TServiceCalculator = class(TInterfacedObject, ICalculator)
public
function Add(n1,n2: integer): integer;
function list(tag:string;var tagList:TTaglist):string;
end;
const
ROOT_NAME = 'root';
PORT_NAME = '888';
APPLICATION_NAME = 'RestService';
implementation
function TServiceCalculator.Add(n1, n2: integer): integer;
begin
result := n1+n2;
end;
function TServiceCalculator.list(tag:string;var tagList:TTaglist):string;
begin
result := UTF8ToString(RecordSaveJSON(tagList,TypeInfo(TTagList)));
tagList.Base:='Test';
tagList.age:=10;
(* remove comment to work
setlength(tagList.L,2);
tagList.L[0].fname:='name1';
tagList.L[0].fsize:=10;
tagList.L[1].fname:='name2';
tagList.L[1].fsize:=20;
remove comment to work *)
end;
const
__TTagList='base WideString age integer L [fname widestring fsize integer]';
initialization
TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTagList),__TTagList);
end.
I was able to communicate with the server (win32) and client (android) and to return a complex structure with an array in it.
But, probably because of my mistake, I noticed that if the array is empty the appicazione returns this error:
Error calling calculator.list - received 0 parameters (expected 2)
Return the error also filling in for FMX.win32
I'm using Xe6upd1 with the last mORMot.
Offline
BTW, why are you using WideString and not String?
This type is slower and less integrated than the XE6 string type.
Under XE6, it is not mandatory to call RegisterCustomJSONSerializerFromText() for a record.
The framework will use the available RTTI (since Delphi 2010) for the serialization.
But I think it is not the problem.
How is the client wrapper unit generated, with SynCrossPlatform?
Are you using the latest version of the source code, from http://synopse.info/files/mORMotNightlyBuild.zip
Offline
WideString was a test
without RegisterCustomJSONSerializerFromText there is a bug in mORMotClient.pas:
type // define some record types, used as properties below
TTagList = record
Base: string;
age: integer;
L: array of :TTagList.:1; // <--------------------------------------------
end;
this is generated unit:
using:
const
__TTagList='base String age integer L [fname string fsize integer]';
initialization
TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTagList),__TTagList);
/// remote access to a mORMot server using SynCrossPlatform* units
// - retrieved from http://localhost:888/root/wrapper/CrossPlatform/mORMotClient.pas.txt
// at 2014-08-21 15:38:12 using "CrossPlatform.pas.mustache" template
unit mORMotClient;
{
WARNING:
This unit has been generated by a mORMot 1.18 server.
Any manual modification of this file may be lost after regeneration.
Synopse mORMot framework. Copyright (C) 2014 Arnaud Bouchez
Synopse Informatique - http://synopse.info
This unit is released under a MPL/GPL/LGPL tri-license,
and therefore may be freely included in any application.
This unit would work on Delphi 6 and later, under all supported platforms
(including MacOSX, and NextGen iPhone/iPad), and the Free Pascal Compiler.
}
interface
uses
SynCrossPlatformJSON,
SynCrossPlatformSpecific,
SynCrossPlatformREST;
type // define some record types, used as properties below
TTagList = record
base: string;
age: integer;
L: array of record
fname: string;
fsize: integer;
end;
end;
type
/// service implemented by TServiceCalculator
// - you can access this service as such:
// !var aCalculator: ICalculator;
// !begin
// ! aCalculator := TCalculator.Create(aClient);
// ! // now you can use aCalculator methods
// !...
ICalculator = interface(IServiceAbstract)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FF}']
function Add(const n1: integer; const n2: integer): integer;
function list(const tag: string; var tagList: TTagList): string;
end;
/// implements ICalculator from http://localhost:888/root/Calculator
// - this service will run in sicShared mode
TServiceCalculator = class(TServiceClientAbstract,ICalculator)
public
constructor Create(aClient: TSQLRestClientURI); override;
function Add(const n1: integer; const n2: integer): integer;
function list(const tag: string; var tagList: TTagList): string;
end;
const
/// the server port, corresponding to http://localhost:888
SERVER_PORT = 888;
/// return the database Model corresponding to this server
function GetModel: TSQLModel;
/// create a TSQLRestClientHTTP instance and connect to the server
// - it will use by default port 888
// - secure connection will be established via TSQLRestServerAuthenticationDefault
// with the supplied credentials - on connection or authentication error,
// this function will raise a corresponding exception
function GetClient(const aServerAddress, aUserName,aPassword: string;
aServerPort: integer=SERVER_PORT): TSQLRestClientHTTP;
implementation
{ Some helpers for record types }
function Variant2TTagList(const _variant: variant): TTagList;
var _a: integer;
_arr: PJSONVariantData;
begin
result.base := _variant.base;
result.age := _variant.age;
_arr := JSONVariantDataSafe(_variant.L,jvArray);
SetLength(result.L,_arr^.Count);
for _a := 0 to high(result.L) do
with result.L[_a] do begin
fname := _arr^.Values[_a].fname;
fsize := _arr^.Values[_a].fsize;
end;
end;
function TTagList2Variant(const _record: TTagList): variant;
var i: integer;
res: TJSONVariantData;
begin
res.Init;
res.SetPath('base',_record.base);
res.SetPath('age',_record.age);
with res.EnsureData('L')^ do
for i := 0 to high(_record.L) do
with AddItem^, _record.L[i] do begin
AddNameValue('fname',fname);
AddNameValue('fsize',fsize);
end;
result := variant(res);
end;
function GetModel: TSQLModel;
begin
result := TSQLModel.Create([TSQLAuthUser,TSQLAuthGroup],'root');
end;
function GetClient(const aServerAddress, aUserName,aPassword: string;
aServerPort: integer): TSQLRestClientHTTP;
begin
result := TSQLRestClientHTTP.Create(aServerAddress,aServerPort,GetModel,true); // aOwnModel=true
try
if (not result.Connect) or (result.ServerTimeStamp=0) then
raise ERestException.CreateFmt('Impossible to connect to %s:%d server',
[aServerAddress,aServerPort]);
if not result.SetUser(TSQLRestServerAuthenticationDefault,aUserName,aPassword) then
raise ERestException.CreateFmt('%s:%d server rejected "%s" credentials',
[aServerAddress,aServerPort,aUserName]);
except
result.Free;
raise;
end;
end;
{ TServiceCalculator }
constructor TServiceCalculator.Create(aClient: TSQLRestClientURI);
begin
fServiceName := 'Calculator';
fServiceURI := 'Calculator';
fInstanceImplementation := sicShared;
fContractExpected := '402490DC946DB090';
inherited Create(aClient);
end;
function TServiceCalculator.Add(const n1: integer; const n2: integer): integer;
var res: TVariantDynArray;
begin
fClient.CallRemoteService(self,'Add',1, // raise EServiceException on error
[n1,n2],res);
Result := res[0];
end;
function TServiceCalculator.list(const tag: string; var tagList: TTagList): string;
var res: TVariantDynArray;
begin
fClient.CallRemoteService(self,'list',2, // raise EServiceException on error
[tag,TTagList2Variant(tagList)],res);
tagList := Variant2TTagList(res[0]);
Result := res[1];
end;
end.
I download night mormot every day
Offline
There was indeed an issue.
[] and {} JSON patterns were not recognized as void array or object by SynCrossPlatformJSON.
It should be fixed by http://synopse.info/fossil/info/0c772c9484
This case is now included in our regression tests.
For XE6 record definition, I'll check it also.
Offline
Thanks!
Now it works on Android.
Offline
Hi there,
Any idea how to expose a method based service (http://localhost:888/service/EchoString) to SmartMS?
---
If you create a procedure without paramenters, the wrapper will generate invalid colon as:
procedure TServiceCalculator._list( : );
---
to retrieve the session using interface-based services,
Result := IntToStr(context.Request.Session); -->the result is: {"result":["21546988"]}
I don't know if this is possible with "Returns"
If i use:
context.Request.Returns([IntToStr(context.Request.Session)]); // Error calling the method
--> {"result":[""
Last edited by warleyalex (2014-08-22 03:03:46)
Offline
In mORmot, a method-based service is diverse than an interface-based service.
You are confusing the two.
You should NOT use context.Request to set the returned context from an interface-based service.
Please see the documentation about TServiceCustomAnswer, especially "17.7.4.2.3. Custom returned content" in the SAD 1.18 pdf.
But this TServiceCustomAnswer is not supported (nor wanted) with SMS.
If you want full access to the transmitted context, just use a regular mORMot method-based service (and not an interface-based service), and use simply the TSQLRestClientURI.CallBackGet() and TSQLRestClientURI.CallBackGetResult() methods of SynCrossPlatformREST unit.
About _Test( : ) I was able to reproduce the issue with mORmotWrapper.pas, but if I run the mustache template+context outside mORMotWrapper.pas (e.g. in sample TestMustache.pas), the content is rendered as expected...
In fact, void TDocVariant was not recognized as null variant when returning a TVarData...
I've fixed unexpected errors by SynMustache with TDocVariant generated content, which expects exact null types for {{#sections}}, e.g. for a method without any parameter, the generated signature was "Test( : );" instead of "Test()".
See http://synopse.info/fossil/info/4db01de34a
Thanks for the feedback!
Offline
If you want full access to the transmitted context, just use a regular mORMot method-based service (and not an interface-based service)
Nowadays, would be possible to change password using JS client side? I would like to implement an experimental "User Login Details" with SmartMS, after default authentication, when I clicked on "User Profile" you could change password: *****.
-----
I have made some changes at SynCrossPlatformREST unit (very nice indeed) to call some remote methods from SmartMS. Now, I can use interface-based service and method-based service methods with default authentication... and I've discovered something interesting: you actually can use the same port and the same base root URI to interface-based services and method-based services methods. e.g.
{this is a interface-based service method}
http://127.0.0.1:888/service/Calculator … d50434e086
http://127.0.0.1:888/service/Calculator … d5aba009b7
{this is a method-based service method}
http://localhost:888/service/GetCu?=1&s … 120052bffa
Last edited by warleyalex (2014-08-24 00:53:30)
Offline
Yes, of course, ORM, interface-based services and method-based services are hosted by the same TSQLREstServer, so do share the same root!
And thanks to http.sys, you can even have several TSQLRestServer on the same IP and port, each one with its own route.
Offline
Pages: 1