You are not logged in.
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 ![]()
This is the pattern we use in production, with great benefit.
And you not experience any data inconsistencies?
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 ![]()
Maybe that help: https://github.com/gabr42/OmniThreadLibrary
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)
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 ![]()
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?)
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?
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?
Hello!
I found that my test project was included in ThirdPartyDemos, which is not bad ![]()
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).
Yes, you right. Thanks.
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..
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.
Code above is server side, not client.
Should i use anyway RetrieveBlob?
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.
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.
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?
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"]}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?
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/Yep, that works)
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
...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"
}
}
]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 ![]()
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 ![]()
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?
So, you recommend http.sys when available otherwise TWebSocketServerRest, right?
And THttpServer without http.sys should not be used.
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?
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.
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;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]);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 }I will read about TSQLRecord properties, hope i will find solution there)
Yes, that works, thanks.
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.
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.
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).
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 ![]()
(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.
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.
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"
}
}
]
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.
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?
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;