#101 Re: mORMot 1 » ORM and Dynamic models » 2017-03-18 22:50:56

Thanks to its TDocVariant support, mORMot's ORM can do exactly what you describe: schema-less information.

Thanks, that's sounds hopeful!

Actually MongoDB 3.4 is most interesting option, it supports document validation, it use new storage engine (WiredTiger) that provide locks per document which was improved performance.
But i'm afraid about data consistency and integrity.
Even "isolated write" operation does not provide “all-or-nothing” atomicity (also it locks entire collection which break down performance).

But, MongoDB provide big freedom for architectural design.
Assume that is possible to use single collection for all custom objects (even came from different user classes. Validator will check data depending on "field" with type name).
Fields with "document references" (in cooperation with validation rules) will work as primary keys.
Main problem is how to change few documents in single transaction.
Two Phase Commits can help here. Probably hmm

This is the pattern we use in production, with great benefit.

And you not experience any data inconsistencies?

#102 mORMot 1 » ORM and Dynamic models » 2017-03-18 20:43:00

George
Replies: 22

ORM can help if application has good knowledge about data structure and relations.
I mean, properly designed classes with required fields, predefined models and so on.

Other interesting and not trivial situation - when server have no complete information about data model until start.
Such behavior often seen in business software that allow add metadata on the fly (by metadata, i mean information, that describe data model).
On server side, in best way, server knows only about base classes, while client side may take serialized base class as template, add own fields and tell server - hello, i have new data structure "TCustomObj(TBaseClass)", please make all necessary changes in database.
But, new data structure may include not only simple fields, but also relations to another custom data structures like TSecondCustomObj(TBaseClass).

I know that other available ORM's not fits this requirement, which is predictable due rareness.
I'm almost sure that mORMot can't help here too, but if i'm wrong, possibilities can blow mind smile

#104 Re: SyNode » Beta release of SyNode - ES6 JavaScript + NPM modules » 2017-03-18 17:19:46

Just for experiment purpose, i've created SyNode app with multiple FSMManager's each in separate thread.
For example if application need to work separately with different "coremodules" folders, with different "MaxPerEngineMemory" values, and so on.
That may be useless or not, don't know now)

#105 Re: mORMot 1 » HTTP/2 web server » 2017-03-16 19:59:42

Here is discussion about how to disable http/2 on windows 10.
Seems http/2 supported but only with secure connections.

Realized that it's too early to strongly lean on http/2 today.
Well, someday multi-channel connection and server pushes will come smile

#106 mORMot 1 » HTTP/2 web server » 2017-03-16 15:16:27

George
Replies: 2

Hello!
Under Windows 10, HTTP.Sys use HTTP API 2.0.
In documentation i've not found anything about http/2.
Is it not supported or maybe just undocumented?)

#107 Re: SyNode » Beta release of SyNode - ES6 JavaScript + NPM modules » 2017-03-10 19:35:40

SyNode evaluates threadID automatically via "GetCurrentThreadId".
I have thread pool, which means that thread ID may be different.
While looking how to pass thru custom ID inside ThreadSafeEngine, i've found comment line: "SM 45 expects the context to be released in the same thread".

So, SyNode should not be used in applications with thread pool, right?
Then what is the best approach, may be private threads for each SM Engine?

#108 Re: SyNode » Beta release of SyNode - ES6 JavaScript + NPM modules » 2017-02-19 18:23:15

Wow, node.js inside delphi!
Great work!
Is there a way to use VSCODE as development tool with their debugger?
I assume that map files (i have typescript sources) will not work?
How fast SyNode in comparison with NodeJS?

#109 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-08-16 22:04:54

Hello!

I found that my test project was included in ThirdPartyDemos, which is not bad smile
But it's modified version. would be correct if source code will be moved to author folder (Alf?).
Besides, this url not work: https://github.com/LongDirtyAnimAlf/mORMot.REST

If "\ThirdPartyDemos\George\REST-Tester" folder will persist, i would like to see unchanged source.
For example, there should be as well "Win32/Debug/enable_https" folder with test certificate and bat files to install/uninstall it.
I have problems with free time currently, but in future, possibly, i will upload some updates to my git repository (there is few undone 2do entries).

#111 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-20 11:41:38

Does framework automatically delete session objects from ORM when session timeout happen?
Or i must inherit server class and use OnSessionClosed event?

For test purpose i set SQLAuthGroup.SessionTimeout := 1.
Client app was successfully authenticated and authorized which mean, SessionTimeout was applied.
Next, from client i call server method SetDataToUserSession which save string on server side to TSQLAuthUserEx.SessionData field, then i close client app.
After 2 min i connect again and call GetDataFromUserSession.
SessionData still exists..

#112 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-20 09:57:14

I changed class definition from:

TSQLAuthUserEx = class(TSQLAuthUser)
  public
    SessionData: string;
  end;

to

  TSQLAuthUserEx = class(TSQLAuthUser)
  private
    fSessionData: string;
  published
    property SessionData: string read fSessionData write fSessionData;
  end;

Now my methods SetDataToUserSession, GetDataFromUserSession works.

#113 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-19 16:11:34

Code above is server side, not client.
Should i use anyway RetrieveBlob?

#114 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-19 12:11:44

I've implemented another interface with sicPerSession instance implementation.
Also i created custom session class:

TSQLAuthUserEx = class(TSQLAuthUser)
  public
    SessionData: string;
  end;

Model initialization use my class:

fModel := TSQLModel.Create([TSQLAuthGroup, TSQLAuthUserEx], ROOT_NAME);

And i'm trying change SessionData value for session.
So, i've added two server methods (set and get session data):

// Set user session data
function TRestMethodsEx.SetDataToUserSession(data: string): Boolean;
var
  UserSession: TSQLAuthUserEx;
begin
  UserSession := TSQLAuthUserEx.Create(fServer, ServiceContext.Request.SessionUser, True);
  UserSession.SessionData := data; // set session value from client
  try
    Result := fServer.Update(UserSession);
  except
    Result := False;
  end;
  fServer.UnLock(UserSession);
  UserSession.Free;
end;

// Get user session data
function TRestMethodsEx.GetDataFromUserSession(): string;
var
  UserSession: TSQLAuthUserEx;
begin
  UserSession := TSQLAuthUserEx.Create(fServer, ServiceContext.Request.SessionUser);
  Result := UserSession.SessionData; // get session value from session
  UserSession.Free;
end;

// Get user name
function TRestMethodsEx.GetUserSessionLogin(): string;
begin
  Result := UTF8ToString(ServiceContext.Request.SessionUserName); // Returns correct user name
end;

I can't find why GetDataFromUserSession() returns empty string...
Samples and documentation was read before post.

#115 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-16 15:39:56

Yes, i use cmd to check what happen before and after server start/stop.
I have no problems with registration, i can't remove registration via "RemoveUrl".
I've tested in windows 10, 8.1 and windows 7 - same problem.

If RemoveUrl work in your test application, can you share source of test project? So i would be able to see how and where you use RemoveUrl, compile and test on my machine.

#116 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-16 14:20:06

Thanks)
Don't know why i missed that overloaded constructor.
Now test project include all available protocols, authentication schemes and authorization rules.
Next i want add proxy usage and additional method interface with session usage.

(i found that under windows 7 certificate installation and port registration via my bat file not work as expected, so i will investigate it).
In general, indy provide much easier https deployment and support any windows, rather than httpapi... (just thoughts).

About URI unregistration, on your machine it works as expected?

#117 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-15 20:42:57

While i have no idea why unregistration not work (maybe there is a bug in winapi library?) i'm trying to use ssl.

AFAIK Https may be used only over http.sys, right?
i've created self signed certificates (ca and signed server cert), both installed on local machine.
Successfully registered cert on https port:

netsh http add sslcert ipport=0.0.0.0:777 certhash=9707a1065f2be25fc8d6f634f66196e151f49625 appid={AA4AC37D-B812-46A7-BEFB-A68167A05BA7}

In addition i've created bat file for simple cert installation.
Another bat file provide cleaning features (delere port, certs and so on).
(available on GitHub).

Successfully started server with secSSL security parameter:

TSQLHttpServer.Create(AnsiString(fServerSettings.Port), [fRestServer], '+', useHttpApiRegisteringURI, 32, TSQLHttpServerSecurity.secSSL)

Cant find what should be done on client side,
but something should be. Isn't it?

fClient := TSQLHttpClientWinHTTP.Create('127.0.0.1', '777', fModel);

Test client application show error message "EWinHTTP:Winhttp.dll error 12002 (timeout)" when i'm trying to connect.
While browser return proper result (https://127.0.0.1:777/service/RestMethods.HelloWorld):

{"result":["Hello world"]}

#118 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-15 13:34:09

If you mean GetLastError() method from module "WindowsAPIs.inc", it returns 0.

  //todo: getlasterror
function GetLastError: Integer; stdcall;
  external kernel name 'GetLastError';

In this place: (method THttpApiServer.RemoveUrl)

result := Http.RemoveUrlFromUrlGroup(fUrlGroupID,pointer(uri),0)

fUrlGroupID = -648518319497805279 looks weird, but probably correct (i've discovered that value comes from Httpapi.dll).

Maybe i'm doing something wrong...
Can anyone else test URI unregistration?

#119 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-15 11:14:44

I found that under windows 10, URL unregistration not works, while result = 0 (no errors).
App run as admin, url auto registration works (useHttpApiRegisteringURI).

RemUrlResult := THttpApiServer(fHTTPServer.HttpServer).RemoveUrl(ROOT_NAME, fHTTPServer.Port, False, '+');
// THttpApiServer(fHTTPServer.HttpServer).RemoveUrl('service', '777', False, '+');

Cmd command works as expected:

netsh http delete urlacl http://+:777/service/

#121 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-14 21:46:21

I've tested ServiceFactoryServer.SetOptions([], [optErrorOnMissingParam]).
If body is empty, paranoid check not catch execution.
If body = "[]" my test function now returns cool result:

{
"errorCode":406,
"errorText":"(sicSingle) execution failed (probably due to bad input parameters) for RestMethods.SendMultipleCustomRecords"
}

Which is very nice!

Something like that should help:

...
// decode input parameters (if any) in f*[]
if (ArgsInLast - ArgsInFirst > 0) and (Par=nil) and (optErrorOnMissingParam in Options) then
  exit; // paranoid setting
if (Par<>nil) or (ParObjValues<>nil) then
...

#122 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-14 21:35:15

Thanks!

I guess, you mean 'a JSON object as POST body'.

Here is the sample:

function SendMultipleCustomRecords(const CustomResult: rCustomRecord; const CustomComplicatedRecord: rCustomComplicatedRecord): Boolean;
[  
   {  
      "ResultCode":200,
      "ResultStr":"Awesome",
      "ResultArray":[  
         "str_0",
         "str_1",
         "str_2"
      ],
      "ResultTimeStamp":"2016-06-01T19:42:14"
   },
   {
   	"SimpleString": "Simple string, Простая строка",
   	"SimpleInteger":100500,
   	"AnotherRecord": {
	      "ResultCode":200,
	      "ResultStr":"Awesome",
	      "ResultArray":[  
	         "str_0",
	         "str_1",
	         "str_2"
	      ],
	      "ResultTimeStamp":"2016-06-01T19:42:14"   		
   	}
   }
]

#123 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-14 18:35:04

I use most reliable way - array of JSON objects as POST body.
URI length may be limited to 2000 characters in some client realizations.

At URI level and with a JSON object as POST body, any missing parameter would be replaced with the default value.

Well, unfortunately it's not good sad
When developer implement server method with mandatory parameters, client must send all parameters while server should check that all required by developer parameters was sent, otherwise error message should be returned to client side.
Besides, that's how works datasnap (datasnap check even json object type).
And Delphi works in same way, mandatory parameters must be filled, it's intuitively expected behavior.

My test application targeted to Delphi client and http/s client (now represented as JMeter test plan, but http client may be implemented in any language that support http requests/responses).
So server side should not suppose that client send data as server expect.

That's my architectural vision, of course you may disagree wink

#124 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-14 08:30:31

Hello!

With "Client-Server services via interfaces" architecture,
framework can't detect empty parameters.
Am i right?

My test project include JMeter test plan.
I have implemented server method "SendCustomRecord".
Server side not detect empty parameter even if entire body is empty.

I know, that datasnap check parameters and even types (not only simple types, but also record and class types).
Only solution with mORMot is manual access to body with additional parameter tests?

#125 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-09 15:28:28

So, you recommend http.sys when available otherwise TWebSocketServerRest, right?
And THttpServer without http.sys should not be used.

#126 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-09 14:32:35

Interesting results...
HTTP server via TWebSocketServerRest (even without WebSocketsEnable) works faster than via THttpServer.
Actually, speed almost same as when http.sys used.

Client side both times same - JMeter.
Want screenshots? Or it is known normal behavior?

#127 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-09 12:07:45

Well, that not obvious, especially from this place:
fRestServer.MainFieldIDs(

Maybe possible somehow solve incompatibility deeper to allow execute same MainFieldIDs method for all server classes.
Or at least raise error to show that method not supported with selected server class...
But anyway, thank you for information) I have acceptable workaround.

#128 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-09 09:57:55

Calling MainFieldIDs() before CreateMissingTables() in your code is incorrect I'm afraid.

Opps, you right.
Anyway results are same, even with this code (test case updated):

procedure TForm1.Button1Click(Sender: TObject);
var
  fModel: TSQLModel;
  fRestServer: TSQLRestServer;
  IDs: TIDDynArray;
  Group: TSQLAuthGroup;
begin
  fModel := TSQLModel.Create([]);
  fRestServer := TSQLRestServerFullMemory.Create(fModel, True);
  fRestServer.ServiceDefine(TIT, [IT], sicSingle);
  fRestServer.CreateMissingTables();

  // DEBUG
  fRestServer.MainFieldIDs(TSQLAuthGroup, ['Admin', 'User'], IDs);
  if Length(IDs) = 0 then
    ShowMessage('why IDs = []? (((');
  // WHY IDs empty?
  Group := TSQLAuthGroup.CreateAndFillPrepare(fRestServer, '');
  try
    while Group.FillOne do
      ShowMessage(UTF8ToString(Group.Ident));
  finally
    Group.Free;
  end;
  // DEBUG

  fRestServer.Free;
  fModel.Free;
end;

#129 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-09 09:04:33

Well, here is super simple test case where issue persists.
Delphi XE6U1

(where is forum email notification settings? smile)

#130 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-08 18:38:59

I've committed updates to repository.

With temporary solution:

// ServiceFactoryServer.AllowByName([MethodAuthorizationSettings.MethodName], MethodAuthorizationSettings.AllowedGroups); // not work for some reason :(
// ServiceFactoryServer.DenyByName([MethodAuthorizationSettings.MethodName], MethodAuthorizationSettings.DeniedGroups);
for j := 0 to Length(MethodAuthorizationSettings.AllowedGroups) - 1 do
  ServiceFactoryServer.AllowByName([MethodAuthorizationSettings.MethodName], MethodAuthorizationSettings.AllowedGroups[j]);
for j := 0 to Length(MethodAuthorizationSettings.DeniedGroups)  - 1 do
  ServiceFactoryServer.DenyByName([MethodAuthorizationSettings.MethodName], MethodAuthorizationSettings.DeniedGroups[j]);

#131 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-08 17:45:55

Maybe, but anyway, i'm going furher now)

Authorization code almost done, but i can't understand why i get strange results that affect to ServiceFactoryServer.AllowByName method:

fRestServer.MainFieldIDs(TSQLAuthGroup, ['Administrators'], IDs); // IDs = [2]
fRestServer.MainFieldIDs(TSQLAuthGroup, ['Users'], IDs); // IDs = [1]
fRestServer.MainFieldIDs(TSQLAuthGroup, ['Administrators', 'Users'], IDs); // IDs = [], but why? ((((

I've double checked TSQLAuthGroup, there is only 3 groups each with different "Group.Ident" and ID ...

  // DEBUG {
  fRestServer.MainFieldIDs(TSQLAuthGroup, ['Administrators', 'Users'], IDs);
  if Length(IDs) = 0 then
    ShowMessage('why IDs = []? (((');
  // WHY IDs empty?
  Group := TSQLAuthGroup.CreateAndFillPrepare(fRestServer, '');
  try
    while Group.FillOne do
      ShowMessage(Group.Ident); 
      {'Users'; 'Administrators'; 'SomeoneElse'}
  finally
    Group.Free;
  end;
  // DEBUG }

#132 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-08 12:47:43

I will read about TSQLRecord properties, hope i will find solution there)

Yes, that works, thanks.

#133 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-08 11:30:59

No, i want fill roles and users using default classes.

Line: "User.GroupRights.Free" is the source of the issue.
But, without this line, memory leak appear.

#134 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-08 11:06:22

How to properly override fill TSQLAuthGroup and TSQLAuthUser?

procedure tRestServer.ApplyAuthorizationRules(ServiceFactoryServer: TServiceFactoryServer);
var
  User: TSQLAuthUser;
  Group: TSQLAuthGroup;
  SQLAccessRights: TSQLAccessRights;
  GroupID, UserID: TID;
begin

  { TSQLAuthGroup }
  // Clear default groups
  fRestServer.Delete(TSQLAuthGroup, '');
  // Prepare AccessRights
  SQLAccessRights.AllowRemoteExecute := [reService];
  // Create test group
  Group := TSQLAuthGroup.Create();
  Group.Ident := 'MyTestGroupAdmin';
  Group.SQLAccessRights := SQLAccessRights;
  Group.SessionTimeout := 10;
  // Save object to ORM
  GroupID := fRestServer.Add(Group, True);
  // Cleanup
  Group.Free;

  { TSQLAuthUser }
  // Clear default groups
  fRestServer.Delete(TSQLAuthUser, '');
  // Create test user
  User := TSQLAuthUser.Create();
  User.DisplayName := 'test';
  User.LogonName := 'test';
  User.PasswordPlain := 'test';
  User.GroupRights := TSQLAuthGroup.Create(fRestServer, GroupID);
  // Save object to ORM
  UserID := fRestServer.Add(User, True);
  // Cleanup
  User.GroupRights.Free;
  User.Free;

  // Test user data
  User := TSQLAuthUser.Create(fRestServer, UserID);
  if User.GroupRights.Ident = '' then
    ShowMessage('User group should not be empty! Something went wrong.');
  User.Free;

  // Apply Authorization Rules
  ServiceFactoryServer.AllowAll();
end;

I get server error: ESecurityException with message 'Invalid TAuthSession.Create(TSQLRestRoutingREST, TSQLAuthUser)'.
Seems, User.GroupRights (from TAuthSession.Create) is empty.

#135 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-08 08:59:06

Well, would be cool. Right now i use jmeter as benchmark tool with heavy load simulation.
File "JMeterTestPlan_NoAuthentication.jmx" with test plan for jmeter available in repository.
(in this build, log output should be disabled for best server performance).

#137 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-06-06 15:01:58

I've published GIT repository instead Google drive.

How to use TSQLRestServerAuthenticationURI scheme?
Is there any manual or demo related to this scheme?

Is there a simple way to disable automatic protocol downgrade or specify allowed protocols on server side?
I want prevent situations when server use websocket (binary + AES) while client use socket (or websocket JSON or websocket binary without AES).

#138 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-05-31 16:34:54

Ok, thanks!

Now i switched to interface based approach, it looks more like datasnap, include session management and authorization control.
Everything works great so far, i like mORMot REST smile
(test project updated, still not final build).

Interesting that DS allow serialize any custom class without custom serialization code...
But, i can live without this feature. Speed and security much more important.

#139 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-05-30 15:00:31

Sorry, not suppose that listed code is huge(
I can try make first message smaller, but not sure that it will not drop web server again, so, i leave it as is.

#140 Re: mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-05-30 14:05:53

How to create TSQLHttpServer with disabled listner and then activate or deactivate listner on the fly?

Related to 2.2: Is there a way to serialize objects more similar like in datasnap?
- without property declaration.
- with nested objects
- with arrays (string, integer, etc.)

Related to 2.3: JSONToObject(CustomResult, pointer(ObjectAsJSONstr), ObjectAsJSONstrValid); ObjectAsJSONstrValid = False.
Class:

  TCustomResult = class
  protected
    fResultCode: integer;
    fResultStr: string;
    fResultTimeStamp: TDateTime;
  published
    property ResultCode: integer read fResultCode write fResultCode;
    property ResultStr: string read fResultStr write fResultStr;
    property ResultTimeStamp: TDateTime read fResultTimeStamp write fResultTimeStamp;
  public
    constructor Create();
    destructor Destroy(); override;
  end;

  JSON:
 

{
	 "ClassName":"TCustomResult",
	 "ResultCode":999999,
	 "ResultStr":"Awesome!",
	 "ResultTimeStamp":"2016-05-30T10:40:00"
}

Related to 2.4: I know that i can access to body text via "Ctxt.Call.InBody", but how access to specified parameter when multiple json objects was passed via body data? Only manual parsing?
Something Like this one:
[
   {
      "param1":{
         "ClassName":"TCustomResult",
         "ResultCode":999999,
         "ResultStr":"Awesome!",
         "ResultTimeStamp":"2016-05-30T10:40:00"
      }
   },
   {
      "param2":{
         "ClassName":"TCustomResult",
         "ResultCode":90000,
         "ResultStr":"Wow!",
         "ResultTimeStamp":"2015-05-30T10:30:00"
      }
   }
]

#141 mORMot 1 » JSON RESTful web api from DataSnap to mORMot » 2016-05-30 11:00:11

George
Replies: 54

Hello!

Currently i use Datasnap for REST implementation.
After i've seen performance comparison between datasnap and mORMot, i want try reimplement same (or almost same) REST api as i've done with DataSnap before.
My target goals is to make test case that will include all features that was used with datasnap.

I'm pretty satisfied how server class module looks in DataSnap (clean business code, many background things works automatically "from the box").
So, here is part of my server class from datasnap:

type

  lsscAuth = (sscAuthNOTRequired, sscAuthRequired);

  TRestNewUser = class(TRestNewUser_session);

  TRestClientInfo = class
    Name: string;
    PreferredLanguage: string;
    sscAPITargetVersion: Double;
    sscLogin: string;
    sscPassword: string;
  end;

  TRestResult<T> = class
    MethodResult: T;
    Code: integer;
    Description: string;
    Comment: string;
  public
    procedure InitResult();
    procedure SetResult(pResCode: integer; pComment: string = ''); overload;
    procedure SetResult(Result: rResult); overload;
    function Success(): boolean;
    function GetDescription(OtherResCode: integer = -1): string;
    function GetReport(): string;
    constructor Create(MethodResult: T; RestClientInfo: TRestClientInfo; MethodName: string; SessionContext: TServerSessionContext); overload;
    constructor Create(MethodResult: T; RestClientInfo: TRestClientInfo; MethodName: string; SessionContext: TServerSessionContext;
      var UserAuthData: rRestUserAuthData); overload;
    destructor Destroy(); override;
  end;

  TGetAPIInfoResult = class
    APIVersion: Double;
    APINode: string;
    MethodsInfo: array of string;
    constructor Create;
    destructor Destroy; override;
  end;

  TRestMethodAttribute = class(TCustomAttribute)
    APIMinSupportedVersion: Double;
    sscAuth: lsscAuth;
  public
    constructor Create(MinSupportedVersion: Double; sscAuth: lsscAuth);
  end;

{$METHODINFO ON}

  TNode1 = class(TDataModule)
  private
    Settings: TSSCSettings;
    LeaveSessionAlive: boolean;
    SessionID: string;
    SessionContext: TServerSessionContext;
    class function GetMethodInfo(MethodName: string): TRestMethodAttribute;
    class function GetMethodsMinSupportedAPIVersions(): TStringList;
  published
    constructor Create(Settings: TSSCSettings); reintroduce;
    destructor Destroy(); override;
  public

    sscApiLogin: string;

    [TRestMethodAttribute(1.1, sscAuthNOTRequired)]
    function CreateSession(RestClientInfo: TRestClientInfo): TRestResult<string>;

    [TRestMethodAttribute(1.1, sscAuthNOTRequired)]
    function DestroySession(RestClientInfo: TRestClientInfo): TRestResult<string>;

    [TRestMethodAttribute(1.1, sscAuthNOTRequired)]
    function GetSessionID(RestClientInfo: TRestClientInfo): TRestResult<string>;

    [TRestMethodAttribute(1.1, sscAuthNOTRequired)]
    function GetAPIInfo(RestClientInfo: TRestClientInfo): TRestResult<TGetAPIInfoResult>;

    [TRestMethodAttribute(1.1, sscAuthNOTRequired)]
    function EchoString(RestClientInfo: TRestClientInfo; Value: string): TRestResult<string>;

    [TRestMethodAttribute(1.1, sscAuthNOTRequired)]
    function CheckUserExists(RestClientInfo: TRestClientInfo; UserID: string): TRestResult<boolean>;

    [TRestMethodAttribute(1.1, sscAuthNOTRequired)]
    function GetUserAuthorizationToken(RestClientInfo: TRestClientInfo; UserID: string): TRestResult<string>;

    [TRestMethodAttribute(1.1, sscAuthNOTRequired)]
    function RegisterNewUser(RestClientInfo: TRestClientInfo; NewUserData: TRestNewUser): TRestResult<string>;

    [TRestMethodAttribute(1.2, sscAuthRequired)]
    function SetRequestStatus(RestClientInfo: TRestClientInfo; RequestNumber, RequestStatusID: string; HTMLMessage: string = ''): TRestResult<string>;

    [TRestMethodAttribute(1.2, sscAuthRequired)]
    function AddRequestMessage(RestClientInfo: TRestClientInfo; RequestNumber, HTMLMessage: string; HiddenFromUser: boolean): TRestResult<string>;

  end;

{$METHODINFO OFF}

No interfaces, only classes and objects.
Same result "TRestResult" (for each method) with dynamic field "MethodResult" that can be represented as simple type like string or complicated object.
TRestResult has no properties. Method attributes contain additional method properties.
Datasnap convert input JSON to corresponding object automatically, as well as result type (even with arrays).

For example, GetAPIInfo method code:

// Returns API info
function TNode1.GetAPIInfo(RestClientInfo: TRestClientInfo): TRestResult<TGetAPIInfoResult>;
const
  MethodName = 'GetAPIInfo';
var
  SL: TStringList;
  i: integer;
begin
  // Here i initialize result type as common TRestResult class with field "MethodResult" as TGetAPIInfoResult.
  Result := TRestResult<TGetAPIInfoResult>.Create(TGetAPIInfoResult.Create, RestClientInfo, MethodName, SessionContext);
  try
    if Result.Success() then
      begin
        // Method body
        Result.MethodResult.APIVersion := const_APIVersion;
        Result.MethodResult.APINode := Self.ClassName;
        SL := GetMethodsMinSupportedAPIVersions();
        SetLength(Result.MethodResult.MethodsInfo, SL.Count);
        for i := 0 to SL.Count - 1 do
          Result.MethodResult.MethodsInfo[i] := SL.Strings[i];
        SL.Free;
      end;
  finally
    // Save log
    SessionContext.restADCAddToExecutionHistory(MethodName, RestClientInfo.Name, sscApiLogin, RestClientInfo.sscLogin, RestClientInfo.sscAPITargetVersion,
      Result.Code);
  end;
end;

Below, JSON input structure:

{
   "_parameters":[
      {
         "type":"NodeOne.TRestClientInfo",
         "id":1,
         "fields":{
            "Name":"Имя моего приложения",
            "PreferredLanguage":"Russian",
            "sscAPITargetVersion":"1.2",
            "sscLogin":"",
            "sscPassword":""
         }
      }
   ]
}

Below, JSON output structure:

{
   "result":[
      {
         "type":"NodeOne.TRestResult<NodeOne.TGetAPIInfoResult>",
         "id":1,
         "fields":{
            "MethodResult":{
               "type":"NodeOne.TGetAPIInfoResult",
               "id":2,
               "fields":{
                  "APIVersion":1.2,
                  "APINode":"TNode1 ",
                  "MethodsInfo":[
                     "GetAPIInfo=1,1",
                     "EchoString=1,1"
                  ]
               }
            },
            "Code":0,
            "Description":"No errors.",
            "Comment":""
         }
      }
   ]
}

So, my goals for mORMot test project are:

  • [done]1. Multi threaded processing.

  • [done]2. Method call via URL.

  • [done]2.1 Send parameters via url.

  • [partially done]2.2 Return custom object as JSON string.

  • [done]2.3 Send parameters as JSON via body.

  • [?]2.4 Send multiple parameters as JSON via body.

  • [under investigation]3. Server side session that allow store data between calls.

  • [under investigation]4. Authentication to allow general access to call methods.

  • [under investigation]4.1 Authorization to execute method.

  • [under investigation]5. HTTPS support.

  • [under investigation]5.1 Custom cert installation.

Current version 1.00 of mORMot test project can be downloaded here.
I will update file time to time. I use JMeter as client application, so JMeterTestPlan.jmx included.

Questions will appear below.

#142 Re: mORMot 1 » ORM + MSSql » 2016-04-05 12:39:30

Thanks!

Now i have only one question)

2. Documentation says, if some properties or classes was deleted, method "CreateMissingTables" will not update DB structure automatically.
I suppose, that i must implement table with model versions and after calling CreateMissingTables i should execute SQL statements that will update DB properly.
Am i right?

#143 mORMot 1 » ORM + MSSql » 2016-04-05 12:06:44

George
Replies: 3

Hello!

I've read a lot of documentation and trying to make test app to see how i can use mORMot in future projects.
Test application use MSSQL as DBMS via ODBC Driver 11 for SQL Server.
Simple class "TSQLUsers", and few basic methods (like read, add, delete and update) was implemented.
Everything works when i use one server application.
But if i start two or more instances and try to add records in same time, i get errors while insertion - primary key is not unique.

So, here is my questions:
1. Is there a way to safely use more than one ORM server with single database (in MSSQL)?
2. Documentation says, if some properties or classes was deleted, method "CreateMissingTables" will not update DB structure automatically.
I suppose, that i must implement table with model versions and after calling CreateMissingTables i should execute SQL statements that will update DB properly.
Am i right?

Can someone point me on answers if it is already posted somewhere.

UnitSQLConnection

unit UnitSQLConnection;

interface

uses
  SynLog,              // logging features
  mORMot,              // RESTful server & ORM
  SynSQLite3,
  mORMotSQLite3,       // SQLite3 engine as ORM core
  SynSQLite3Static,    // staticaly linked SQLite3 engine
  mORMotDB,            // ORM using external DB
  SynDB,               // external DB core
  SynDBODBC,           // external DB access via ODBC
  UnitSQLDataModel;    // data model unit

var
  DataBase: TSQLRestServerDB;

implementation

var
  aProps: TSQLDBConnectionProperties;

procedure ConnectToDataBase();
begin
  // set logging abilities
  SQLite3Log.Family.Level := LOG_VERBOSE;
  // SQLite3Log.Family.EchoToConsole := LOG_VERBOSE;
  SQLite3Log.Family.PerThreadLog := ptIdentifiedInOnFile;
  // ODBC driver
  aProps := TODBCConnectionProperties.Create('', 'Driver={ODBC Driver 11 for SQL Server}; Server=127.0.0.1; Database=test_mORMot_ORM; Uid=IISUSR; Pwd=password_here; MARS_Connection=Yes;', '', '');
  // get the shared data model
  Model := CreateTabbleModel();
  // use MSSQL database for all tables
  VirtualTableExternalRegisterAll(Model, aProps);
  // create the main mORMot server
  DataBase := TSQLRestServerDB.Create(Model, ':memory:', false); // authentication=false
  // optionally execute all MSSQL requests in a single thread
  DataBase.AcquireExecutionMode[execORMGet] := amBackgroundORMSharedThread;
  DataBase.AcquireExecutionMode[execORMWrite] := amBackgroundORMSharedThread;
  // create tables or fields if missing
  DataBase.CreateMissingTables();
end;

procedure PrepareForShutdown();
begin
  aProps.Free;
  Model.Free;
  DataBase.Free;
end;

initialization

ConnectToDataBase();

finalization

PrepareForShutdown();

end.

UnitSQLDataModel

unit UnitSQLDataModel;

interface

uses
  System.SysUtils,
  SynCommons,
  mORMot;

type
  TSQLUsers = class(TSQLRecord)
  private
    fName: RawUTF8;
    fSurname: RawUTF8;
    //fLastName: RawUTF8;
  published
    property Name: RawUTF8 read fName write fName;
    property Surname: RawUTF8 read fSurname write fSurname;
    //property LastName: RawUTF8 read fLastName write fLastName;
  end;

var
  Model: TSQLModel;

  function CreateTabbleModel: TSQLModel;

implementation

function CreateTabbleModel: TSQLModel;
begin
  result := TSQLModel.Create([TSQLUsers]);
end;

end.

Parts from UI unit

procedure TForm1.FillUserList();
var
  Users: TSQLUsers;
begin
  ListBoxUsers.Clear;
  EditUserName.Clear;
  EditUserSurname.Clear;
  Users := TSQLUsers.CreateAndFillPrepare(Database, '');
  ListBoxUsers.Items.BeginUpdate;
  while Users.FillOne() do
    ListBoxUsers.Items.Add(UTF8ToString(Users.Name) + '=' + IntToStr(Users.ID));
  ListBoxUsers.Items.EndUpdate;
end;

procedure TForm1.AddUser();
var
  User: TSQLUsers;
begin
  User := TSQLUsers.Create();
  User.Name := StringToUTF8(EditUserName.Text);
  User.Surname := StringToUTF8(EditUserSurname.Text);
  Database.Add(User, True);
end;

procedure TForm1.ButtonUserAddClick(Sender: TObject);
var
  i: integer;
begin
  for i := 1 to 10000 do
    AddUser();
  FillUserList();
end;

Board footer

Powered by FluxBB