#1 2014-08-11 18:35:26

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Generates Cross-Platform mORMot Clients

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

#2 2014-08-11 22:04:56

Sabbiolina
Member
Registered: 2014-05-20
Posts: 119

Re: Generates Cross-Platform mORMot Clients

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

#3 2014-08-11 23:32:38

Sabbiolina
Member
Registered: 2014-05-20
Posts: 119

Re: Generates Cross-Platform mORMot Clients

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

#4 2014-08-12 06:10:08

edwinsn
Member
Registered: 2010-07-02
Posts: 634

Re: Generates Cross-Platform mORMot Clients

Congratulations!It's one more step closer to the perfect world -  the server supports Mac and Linux smile


Delphi XE4 Professional. Windows 7 64bit.

Offline

#5 2014-08-12 07:32:54

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

@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

#6 2014-08-12 12:31:40

Sabbiolina
Member
Registered: 2014-05-20
Posts: 119

Re: Generates Cross-Platform mORMot Clients

xe6 works ok

it's old xe2 update 4 hotfix 1 with bugs

Offline

#7 2014-08-12 15:11:53

moctes
Member
From: Mexico
Registered: 2013-05-11
Posts: 86

Re: Generates Cross-Platform mORMot Clients

ab wrote:

@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

#8 2014-08-12 15:32:07

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

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

#9 2014-08-13 14:35:41

martin.suer
Member
Registered: 2013-12-15
Posts: 73

Re: Generates Cross-Platform mORMot Clients

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

#10 2014-08-13 18:19:52

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

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

#11 2014-08-13 18:29:04

martin.suer
Member
Registered: 2013-12-15
Posts: 73

Re: Generates Cross-Platform mORMot Clients

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

#12 2014-08-13 18:36:47

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

Great!

I would probably use it to add some dedicated regression tests.

Offline

#13 2014-08-14 07:52:28

martin.suer
Member
Registered: 2013-12-15
Posts: 73

Re: Generates Cross-Platform mORMot Clients

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

#14 2014-08-14 07:59:57

martin.suer
Member
Registered: 2013-12-15
Posts: 73

Re: Generates Cross-Platform mORMot Clients

I should note that I've created the code with XE6 ...

Offline

#15 2014-08-14 14:58:58

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

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

#16 2014-08-14 19:56:58

warleyalex
Member
From: Sete Lagoas-MG, Brasil
Registered: 2013-01-20
Posts: 240

Re: Generates Cross-Platform mORMot Clients

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

#17 2014-08-14 20:05:27

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

Smart mobile does handle record properties.
But SynCrossPlatform not yet.

Offline

#18 2014-08-15 18:57:50

martin.suer
Member
Registered: 2013-12-15
Posts: 73

Re: Generates Cross-Platform mORMot Clients

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

#19 2014-08-15 21:01:49

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

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

#20 2014-08-17 19:00:01

martin.suer
Member
Registered: 2013-12-15
Posts: 73

Re: Generates Cross-Platform mORMot Clients

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

#21 2014-08-17 19:22:35

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

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

#22 2014-08-17 20:04:10

martin.suer
Member
Registered: 2013-12-15
Posts: 73

Re: Generates Cross-Platform mORMot Clients

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

#23 2014-08-17 20:07:51

martin.suer
Member
Registered: 2013-12-15
Posts: 73

Re: Generates Cross-Platform mORMot Clients

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

#24 2014-08-17 20:09:12

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

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

#25 2014-08-17 20:14:53

martin.suer
Member
Registered: 2013-12-15
Posts: 73

Re: Generates Cross-Platform mORMot Clients

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

#26 2014-08-17 20:19:36

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

There is another "comma" at highest context level.

Should be fixed by http://synopse.info/fossil/info/25d6493fcb

Offline

#27 2014-08-18 07:22:46

martin.suer
Member
Registered: 2013-12-15
Posts: 73

Re: Generates Cross-Platform mORMot Clients

confirmed, does work now

Offline

#28 2014-08-18 07:47:44

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

Sorry for the inconvenience.
And thanks for the feedback.
smile

Offline

#29 2014-08-18 19:36:34

Sabbiolina
Member
Registered: 2014-05-20
Posts: 119

Re: Generates Cross-Platform mORMot Clients

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

#30 2014-08-18 20:55:04

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

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

#31 2014-08-18 21:09:42

Sabbiolina
Member
Registered: 2014-05-20
Posts: 119

Re: Generates Cross-Platform mORMot Clients

probably the series of /../../../ ..

who knows where they take you, if you change the base path

now I fix with a root path

Offline

#32 2014-08-18 21:20:02

Sabbiolina
Member
Registered: 2014-05-20
Posts: 119

Re: Generates Cross-Platform mORMot Clients

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

#33 2014-08-18 21:50:26

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

What do you mean?

Offline

#34 2014-08-18 21:52:49

Sabbiolina
Member
Registered: 2014-05-20
Posts: 119

Re: Generates Cross-Platform mORMot Clients

I'm looking for the right way to get a list of records from a call.

Offline

#35 2014-08-21 09:17:10

Sabbiolina
Member
Registered: 2014-05-20
Posts: 119

Re: Generates Cross-Platform mORMot Clients

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

#36 2014-08-21 13:16:10

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

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

#37 2014-08-21 13:43:55

Sabbiolina
Member
Registered: 2014-05-20
Posts: 119

Re: Generates Cross-Platform mORMot Clients

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 wink

Offline

#38 2014-08-21 14:53:56

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

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

#39 2014-08-21 15:13:27

Sabbiolina
Member
Registered: 2014-05-20
Posts: 119

Re: Generates Cross-Platform mORMot Clients

Thanks!
Now it works on Android.

Offline

#40 2014-08-21 15:25:57

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

Thanks for the feedback.

Offline

#41 2014-08-21 23:29:47

warleyalex
Member
From: Sete Lagoas-MG, Brasil
Registered: 2013-01-20
Posts: 240

Re: Generates Cross-Platform mORMot Clients

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

#42 2014-08-22 07:03:37

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

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

#43 2014-08-24 00:50:51

warleyalex
Member
From: Sete Lagoas-MG, Brasil
Registered: 2013-01-20
Posts: 240

Re: Generates Cross-Platform mORMot Clients

ab wrote:

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

#44 2014-08-24 08:28:29

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 9,070
Website

Re: Generates Cross-Platform mORMot Clients

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

Board footer

Powered by FluxBB