You are not logged in.
Pages: 1
After updating mormot I've "EVariantDispatchError" in TDocVariant.DoFunction on FPC. I see that 'Add' function is disabled by {$ifndef FPC} directive with reason explained:
https://synopse.info/fossil/info/7bbf6c8e97992026.
Is it possible to partially restore that functions, eg by using more specific directive for known faulty scenarios? For me that functions worked well (win32, linux x86_64).
Regards
Latest mORMot with updated TSQLRest.OneFieldValues() solved the problem, Thx!
TestSQL3 passed 100%.
Regards
P.S. I've tried debug, but I don't get Lazarus debugger, inspected values are unclear and don't match final result (and lot of "GDB has encountered an internal error").
I've tried both Win and Linux.
FPC 3.2.0
Below simple program to reproduce:
program Test;
{$APPTYPE CONSOLE}
uses
{$I SynDprUses.inc} // use FastMM4 on older Delphi, or set FPC threads
SynCommons,
mORMot;
type
TSQLMyRec = class(TSQLRecord)
private
fGroupId: string;
fProp: string;
published
property GroupId: string read fGroupId write fGroupId;
property Prop: string read fProp write fProp;
end;
var
aModel: TSQLModel;
aRestServer: TSQLRestServer;
sTemp: RawUTF8;
begin
aModel := TSQLModel.Create([TSQLMyRec]);
try
aRestServer := TSQLRestServerFullMemory.Create(aModel);
try
aRestServer.AddSimple(TSQLMyRec, ['TA_FK', 'SomeValue']);
aRestServer.AddSimple(TSQLMyRec, ['TA_SPEDYTOR', 'SomeValue']);
writeln(aRestServer.RetrieveListJSON(TSQLMyRec, ''));
sTemp := aRestServer.OneFieldValues(TSQLMyRec, 'GroupId', FormatUTF8('Prop=:(%):', ['SomeValue']));
writeln(sTemp);
writeln;
writeln('Press [Enter] to close.');
readln;
finally
aRestServer.Free;
end;
finally
aModel.Free;
end;
end.
and output:
[
{"RowID":1,"GroupId":"TA_FK","Prop":"SomeValue"},
{"RowID":2,"GroupId":"TA_SPEDYTOR","Prop":"SomeValue"}]
TA_FK ,TA_SP
Press [Enter] to close.
Now I've problem with TSQLRest.OneFieldValues() in Lazarus, it returns corrupted result (in Delphi result is ok).
My tab (server FullMemory) has 2 rows:
writeln(aRestServer.RetrieveListJSON(TSQLMyRec, '')):
[
{"RowID":1,"GroupId":"TA_FK","Prop":"SomeValue"},
{"RowID":2,"GroupId":"TA_SPEDYTOR","Prop":"SomeValue"}
]
sRes := aRestServer.OneFieldValues(TSQLMyRec,'GroupId', FormatUTF8('Prop=:(%):', ['SomeValue'])):
Delphi writeln(sRes):
TA_FK,TA_SPEDYTOR
Lazarus writeln(sRes):
TA_FK ,TA_SP
Debbuger shows only 'TA_FK'. Lazarus Console shows unprintable characters between 'TA_FK' and ','
I finally used TSQLRest.OneFieldValues() with 1 condition (given, that my table contains small amount of records, and first condition cuts it to few).
DB is Firebird, but this query is not related with DB engine, it's TMyRest = class(TSQLRestServerFullMemory).
SQL executed internally by ExecuteList looks like this:
'SELECT RowID FROM MyRec WHERE FieldA=:(''some_value_a''): AND FieldB=:(''some_value_b''): LIMIT 1'
Hi
How to properly use this method with 'AND'? Example below returns nothing (there definitely exists a record that meets the conditions, example code with 1 condition works ok).
sRes := Self.OneFieldValue(TSQLMyRec,
'RowID',
'FieldA=? AND FieldB=?',
[sValueA, sValueB]);
Regards, Tomek
Yes, it can be either in User or Session (as stated in post title ), TSQLAuthUser.Data was the first choice.
Own session class did the job, thx.
If you really want ot use TSQLMyAuthUser custom properties, then use the ORM methods to update its values. But they won't be modified in the current session User instance.
Custom data for the user are supposed to be valid for current session only, won't be stored in db and may differ in next session. That's why TSQLAuthUser.Data seemed to be perfect for this purpose. Can you give an example of using TSQLAuthUser.Data?
As I wrote at original message it's actually working (with empty model):
TMyMethodsServer= class(TSQLRestServerFullMemory)
protected
private
public
published
procedure SetCustomProp(Ctxt: TSQLRestServerURIContext);
end;
procedure TMyMethodsServer.SetCustomProp(Ctxt: TSQLRestServerURIContext);
var
i: Integer;
begin
for i := 0 to fSessions.Count-1 do
with TAuthSession(fSessions.List[i]) do
if IDCardinal = Ctxt.Session then begin
(User as TMySQLAuthUser).MyPropert:= 'test';
Break;
end;
Ctxt.Results(['["ok"]']);
end;
The question is: Is this the right way to set this custom property or there is better (safe) way to do this?
Model is empty: TSQLModel.Create([]);
Setting Data property or custom property in GetUser() method was for test purposes. The goal is to set some custom data after successful login in a published server method, and then access this data in other server published methods.
Hi
I'm trying to store some custom data for logged user, which will persist as long as session is valid (method-based server).
I've tried to inherit TSQLAuthUser:
TMySQLAuthUser = class (TSQLAuthUser)
protected
fMyPropert: RawUTF8;
published
property MyPropert: RawUTF8 read fMyPropert write fMyPropert;
end;
but it appeared, that retrieving user with SessionGetUser returns a copy of user, so setting any property won't persist.
I've tried to retrieve user directly from Sessions.User and it actually work, but I'm afraid it is not safe way.
Is there any other reliable way to store some custom data related to logged user/session?
BTW I've tried to store it in TSQLAuthUser.Data, but it doesn't work at all - data set in Data at TMyAuth.GetUser are lost. Is it expected behaviour?
Regards, Tomek
Hi
I've mormot server on Linux with SSL enabled, but test request in Web browser fails with "Secure connection failed". Same server on Win works OK. Self-signed certificate generated with openssl.
Are there any other conditions that must be fulfilled to make it work?
Regards, Tomek
Hi
Adding https:// didn't actually work, but pointed me that aHttps wasn't set to true.
IgnoreSSLCertificateErrors did the job.
Thx
P.S. Maybe it would be worth to add few words to DOC about setting up the client?
https://synopse.info/files/html/Synopse … l#TITL_122
Hi
I've enabled SSL in mormot server for test purposes (with self-signed certificate). It works with web browser and Indy client, but TSQLHttpClient fails with winhttp.dll error 12002 - timeout. How to make TSQLHttpClient (or any other client class) to work with SSL?
Regards, Tomek
procedure TRemoteSrv.Test(Ctxt: TSQLRestServerURIContext);
var
vBody: RawJSON;
res: array [0..0] of TValuePUTF8Char;
sParam: RawUTF8;
begin
if Ctxt.Method = mPOST then begin
vBody := Ctxt.Call.InBody;
JSONDecode(vBody, ['Param1'], @res, true);
sParam := res[0].Value;
end;
Ctxt.Results(['Test: ' + sParam]);
end;
This is what you meant?
Yes, I read:
https://synopse.info/files/html/Synopse … ml#TITL_49
http://blog.synopse.info/post/2010/07/1 … phi-7-2010
and unless I missed something, all of it with corresponding samples are related with GET request and extracting parameters from URI.
Hi
How to easy process POST request in method-based service, i.e.:
- how to extract parameters from JSON payload?
- request Content type: text/plain or application/json?
- is it possible to use GetInputAsTDocVariant?
Regards, Tomek
Hi
I'm afraid I don't have enough experience in that matter
Actually I didn't even know, how to force server itself to push notification to clients (after receiving event from db). The only idea I had was to create client on server side, but I bet it can be accomplished much easier (I'm basing on chat example).
Regards, Tomek
Hi
Is it possible to use webSocket notifications functionality in CrossPlatform clients (Android)?
Regards, Tomek
Thank you all for tips and possible solutions.
The problem is, that server is made for third party client, and client side developers are quite reluctant to interpreting custom answers with http 200.
Regards, Tomek
Hi
I've tried, but it doesn't seem to work, it still returns 200:
function TRemoteSrv.Test(aParams: UTF8String): UTF8String;
begin
//Result := 'Test(' + aParams + ')';
//ServiceContext.Request.Error('Wrong param value', 400);
// CurrentServiceContextServer.... no "Error" method
CurrentServiceContext.Request.Error('Wrong param value', 400);
end;
Regards, Tomek
Hi
Is it possible to override mormot's response and return Http 400 "Bad request" in Interfaced-based service? (In case if we want to make additional validation of incoming parameters).
Regards, Tomek
Hi
I need to write a server for third party client, based on http basic authentication. So far it works only when /Auth is called first and subsequent calls include cookie: mORMot_session_signature=XXXXXXXX (retrieved from /Auth). Otherwise mORMot server returns 403 "Authentication Failed: Invalid signature (0)".
Is /Auth call necessary? Does it meet RFC standards?
Regards, Tomek
If there is a way to change Zeos behaviour with some options, how to set them through TSQLDBZEOSConnectionProperties?
Regards, Tomek
Perhaps a Zeos option to add to force UTF-8 work with Delphi?
How to do that?
(BTW which version of Delphi? Unicode?)
Delphi 10.1 Berlin
Regards, Tomek
Hi
Despite using UTF8 I have problems with polish diacritical characters. Problem exists in Delphi, in Lazarus is OK (both, Win and Linux).
Below is very simple test program illustrating problem. Table TEST_TABLE have 1 row: ID = 1, STR_FIELD = 'żyto'.
1 query gets value of STR_FIELD ('żyto') and 2 query is using this value to get ID of the same row. Result obviously should return ID=1 but is empty. Debugger shows that 1 query fetches 'ĹĽyto' intead of 'żyto'.
program TestZyto;
{$APPTYPE CONSOLE}
{$IFDEF FPC_CROSSCOMPILING}
{$IFDEF LINUX}
{$linklib libc_nonshared.a}
{$ENDIF}
{$ENDIF}
uses
{$I SynDprUses.inc} // use FastMM4 on older Delphi, or set FPC threads
SynDB,
SynDBZeos,
SynCrossPlatformJSON,
SynCommons,
SysUtils,
Classes,
IniFiles;
var
aProps: TSQLDBConnectionPropertiesThreadSafe;
aStmt: TSQLDBStatement;
sFBHost, sDBPath, sFBUser, sFBPass: string;
sResultJSON, sResultField, sResultFinal: UTF8String; //RawUTF8
jsonTable: TJSONTable;
//sl: TStringList;
begin
// ... db params from ini
aProps := TSQLDBZEOSConnectionProperties.Create(TSQLDBZEOSConnectionProperties.URI(dFIREBIRD, sFBHost),
sDBPath,
sFBUser,
sFBPass);
try
aProps.ThreadingMode := tmMainConnection;
aProps.MainConnection.Connect;
try
aStmt := aProps.NewThreadSafeStatement;
aStmt.Prepare('select STR_FIELD from TEST_TABLE where ID = 1', True);
aStmt.ExecutePrepared;
sResultJSON := aStmt.FetchAllAsJSON(True);
finally
aStmt.Free;
end;
jsonTable := TJSONTable.Create(sResultJSON);
try
if jsonTable.Step then
sResultField := jsonTable.Value['STR_FIELD'];
finally
jsonTable.Free;
end;
try
aStmt := aProps.NewThreadSafeStatement;
aStmt.Prepare('select ID from TEST_TABLE where STR_FIELD = ?', True);
aStmt.Bind([sResultField]);
aStmt.ExecutePrepared;
sResultFinal := aStmt.FetchAllAsJSON(True);
finally
aStmt.Free;
end;
writeln(sResultFinal);
{$ifdef MSWINDOWS}
readln;
{$endif}
finally
aProps.Free;
end;
end.
Regards, Tomek
In case someone else faced that problem:
ServiceFactoryServer instance has life timeout, for sicClientDriven, sicPerSession, sicPerUser or sicPerGroup modes. It's not dependent on Session timeout, default value is 30 minutes and can be set with SetTimeoutSec().
Regards, Tomek
You can use e.g. a TDocVariant:
function TNotifySrv.Test(const notify, alert: variant): RawJSON; var e: RawUTF8; begin e := notify.event; // it works! end;
It's a kind of magic
Thx once more.
Regards, Tomek
Thx! now it works like charm
Btw: what is the easiest way to extract fields from simple JSON? (eg "event" from Notify example above)
I'm using TJSONTable, but is seems to "heavy" for this purpose.
Regards, Tomek
Ok, now I understand how matching parameters and JSON values works.
But it works only for flat JSON, eg:
{"notify": "event_test", "alert": null}
For nested JSON objects
{"notify": {"event": "TEST", "timestamp": "1532530174.872"}, "alert": null}
it throws 406: "INotifySrv.Test failed on notify:UTF8String [missing or invalid value]"
Is it possible to receive nested JSON objects?
Thx for all answers
Regards, Tomek
It expects JSON content.
Either with brackets (one item per input argument), or as a JSON object (one field per argument).
I've tried JSON object, but service received empty input
POST /notify/NotifySrv.Test HTTP/1.1
Host: xx.xx.xx.xx:8092
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: */*
Accept-Language: pl,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Length: 75
Content-Type: text/plain;charset=UTF-8
Origin: null
Connection: keep-alive
{"notify": {"event": "TEST", "timestamp": "1532530174.872"}, "alert": null}
Hi
I'm trying to pass pure xml to interface based service with no success. It only works if xml is enclosed in brackets ["<?xml version="1.0" encoding="UTF-8" ?><notify>...</notify>"], otherwise it throws 406-bad input parameters.
Method is declared as follows:
function TNotifySrv.Test(aXml: UTF8String): UTF8String;
Test ajax request:
POST /notify/NotifySrv.Test HTTP/1.1
Host: xx.xx.xx.xx:8092
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: */*
Accept-Language: pl,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Length: 96
Content-Type: text/plain;charset=UTF-8
Origin: null
Connection: keep-alive
<?xml version="1.0" encoding="UTF-8" ?><notify>...</notify>
Regards, Tomek
I'm using CrossPlatform TSQLRestClientHTTP, and it hasn't SessionHeartbeatSeconds property.
When session timeout is set to low value, server throws 403-invalid signature - as expected.
There seems to be another validation for ClientDrivenId, which throws 401 before session expire.
Regards, Tomek
Hi
I've problem with session broken after about 30 minutes of inactivity. Session timeout for group User is set to default (60 min.), but after 30 min. delphi client gets:
401
"sicClientDriven instance not found or deprecated..."
Regards, Tomek
I'm not sure if it's Firebird issue. Auto-commit works fine in Firebird with any update/delete/insert, except this only case: insert with returning clause (ExpectResult := True).
Regards, Tomek
Firebird 2.5
Hello
I've insert statement with returning clause, it's executing fine but it's not committing to db (it's committed with client close).
fProps: TSQLDBConnectionPropertiesThreadSafe;
fProps.ThreadingMode := tmMainConnection;
function TMyService.Test: UTF8String;
var
aStmt: TSQLDBStatement;
sSql: string;
begin
sSql := 'insert into my_table(table_id, some_value)'
+ 'values (gen_id(gen_my_table, 1), ?)'
+ 'returning table_id';
try
aStmt := fProps.NewThreadSafeStatement;
aStmt.Prepare(sSql, True); // <-- ExpectResult := True
aStmt.BindTextS(1, 'abc');
aStmt.ExecutePrepared;
if aStmt <> nil then
Result := aStmt.FetchAllAsJSON(True);
finally
aStmt.Free;
end;
end;
Same statement without returning clause (with ExpectResult := False) commits immediately.
Regards, Tomek
I guess so. I'm using esmondb approach:
https://synopse.info/forum/viewtopic.ph … 513#p12513
extracting session ID's (after /Auth?UserName=user&Password=pass):
var data = JSON.parse(this.responseText);
var i = data.result.indexOf("+");
//console.log("data.result: " + data.result);
Client._instance.SessionID = parseInt(data.result.slice(0, i), 10);
//console.log("sessionID: " + Client._instance.SessionID);
Client._instance.SessionIDHexa8 = Client._instance.SessionID.toString(16);
while (Client._instance.SessionIDHexa8.length < 8) {
Client._instance.SessionIDHexa8 = '0' + Client._instance.SessionIDHexa8;
}
//console.log("SessionIDHexa8: " + Client._instance.SessionIDHexa8);
Client._instance.loggedIn = true;
Client._instance.SessionPrivateKey = Client._instance.crc32(Client._instance.PasswordHashHexa, Client._instance.crc32(data.result, 0));
and then signing each request:
Client.prototype.signUrl = function (url) {
if (Client._instance.loggedIn === true) {
var Tix, Nonce, s, ss, d = new Date();
Tix = d.getTime() - Client._instance.SessionTickCountOffset;
Nonce = Tix.toString(16);
while (Nonce.length < 8) {
Nonce = '0' + Nonce;
}
if (Nonce.length > 8) {
Nonce = Nonce.slice(Nonce.length - 8);
}
ss = Client._instance.crc32(url, Client._instance.crc32(Nonce, Client._instance.SessionPrivateKey)).toString(16);
while (ss.length < 8) {
ss = '0' + ss;
}
s = url.indexOf("?") === -1 ? url + '?session_signature=' : url + '&session_signature=';
return s + Client._instance.SessionIDHexa8 + Nonce + ss;
} else {
return url;
}
};
I was sure that if signature was incorrect i would receive 403.
Hi
I've server with services registered in sicClientDriven mode, and JavaScript client. Authentication works fine but services invoked within session don't see values shared in service object:
IRemoteSrv = interface(IInvokable)
['{9A60A8ED-CEB2-4E09-87D4-4A17F496E9F1}']
function SetValue(i: integer);
function Test(): UTF8String;
end;
TRemoteSrv = class(TInterfacedObjectWithCustomCreate, IRemoteSrv)
private
fSomeValue: string;
public
function SetValue();
function Test(): UTF8String;
end;
function TRemoteSrv.SetValue();
begin
fSomeValue := 'abc';
end;
function TRemoteSrv.Test(): UTF8String;
begin
Result := 'Result: ' + fSomeValue;
end;
JS calls:
XHR.open("GET", "http://localhost:8092/root/RemoteSRV.SetValue?session_signature=xxx"));
XHR.open("GET", "http://localhost:8092/root/RemoteSRV.Test?session_signature=xxx")); // returns 'Result: ';
Delphi client with ServiceRegisterClientDriven() works fine with the same server, returns 'Result: abc';
Regards, Tomek
I've found correct way to populate Call.OutBody without encoding issues.
I still need confirmation if it's right approach, and I don't understand why actually Call.OutStatus is hardcoded to HTTP_NOTIMPLEMENTED a this point.
(SynCrossPlatfromREST.pas line 3300)
try
fConnection.URI(Call,inType,fKeepAlive);
break; // do not retry on transmission success, or asynchronous request
except
on E: Exception do begin
Log(E);
fConnection.Free;
fConnection := nil;
if Length(Call.OutBody) = 0 then // added
Call.OutBody := TextToHttpBody(E.message);
Call.OutStatus := HTTP_NOTIMPLEMENTED; //?
if fForceTerminate then
break;
end; // will retry once (e.g. if connection broken)
end;
Regards, Tomek
I've another issue with exception handling, this time with client side exceptions.
When connection is broken for some reason I get "Error calling ServiceClassName.SomeService - returned status 501".
Root exception after unsuccessful request is: EWinHTTP: 'winhttp.dll error 12029 (cannot connect)' which is quite meaningful. But exception then is catched in try except block at
TSQLRestClientHTTP.InternalURI (SynCrossPlatfromREST.pas line 3304)
and reraised later with mentioned status 501.
I've tried to populate Call.OutBody with E.message at this point but with no success. I guess this is because of corrupted content of E.message: '(Ú蝰ӕ慌ՙ'#0#0'끸....'
try
fConnection.URI(Call,inType,fKeepAlive);
break; // do not retry on transmission success, or asynchronous request
except
on E: Exception do begin
Log(E);
fConnection.Free;
fConnection := nil;
SetLength(Call.OutBody, Length(E.message));
Move(pointer(E.message)^,pointer(Call.OutBody)^, Length(E.message));
Call.OutStatus := HTTP_NOTIMPLEMENTED;
if fForceTerminate then
break;
end; // will retry once (e.g. if connection broken)
end;
Regards, Tomek
Ok, thx for your interest.
Regards, Tomek
I did some research, and found this "additional info" in client response in Call.OutBody just before EServiceException is raised. It contains exactly root exception message from server.
I did temporary fix for myself in SynCrossPlatformREST.pas, TSQLRestClientURI.CallRemoteService (line 3061):
var sOutBody: UTF8String;
if Call.OutStatus<>HTTP_SUCCESS then begin
SetString(sOutBody, PUTF8Char(@Call.OutBody[0]), Length(Call.OutBody));
raise EServiceException.CreateFmt('Error calling %s.%s - returned status %d'
+ #13#10 + sOutBody,
[aCaller.fServiceName,aMethodName,Call.OutStatus]);
end;
I'm not sure if it won't break anything and I hope for more complex and decent solution
Regards, Tomek
Hi
Thx for your answer.
Actually I've already read those docs, but I'm not sure if I understand it correctly. It says:
By default, any Exception raised on the server side, within an interface-based service method, will be intercepted, and transmitted as an error to the client side, then a safe but somewhat obfuscated EInterfaceFactoryException will be raised on the client side, containing additional information serialized as JSON."
I'm looking only for this "additional information", I don't need full Exception object.
Standard TSQLHttpClient does have LastErrorMessage method to get it, cross platform TSQLRestClientHTTP does not.
Regards, Tomek
Hi
I'm using interface based services + cross platfrom client.
I've problem with exceptions handling in client app. If service fails I get only "Error calling ServiceClassName.SomeService - returned status 500".
Is it possbile to get any additional info from cross platform client TSQLRestClientHTTP the way TSQLHttpClient does (LastErrorMessage)?
Regards, Tomek
I'm new to mORMot as well as I've been reading the documentation and the posts of this forums for more than a year now and I'm fascinated what this framework does provide (very well done!!).
Installing mORMot was no big issue and the TestSQL3.exe is running smoothly but for one test scenario:
...
1.2. Low level types:
...
! - Encode decode JSON: 100 / 282,875 FAILED 133.68ms
...
...
Using mORMot 1.18.3310 ERTL FTS3
Running on Windows 7 64bit SP1 (6.1.7601) with code page 1252
TSQLite3LibraryStatic 3.16.0 with internal MM
Generated with: Delphi 7 compiler
Time elapsed for all tests: 83.30s
Tests performed at 04.01.2017 15:44:35
Total assertions failed for all test suits: 100 / 26,702,774
! Some tests FAILED: please correct the code.
I tracked this failure down to SynSelfTests.pas lines
6151: r := Random;
and
6197: CheckSame(Va,r);
In case when the decimal separator is set to "," instead of "." then line 6197 raises the failure as e.g. r = 0,0123456789 ("," as decimal separator) but Va = '0.0123456789'.
In case when the decimal separator is set to "." then line 6197 doesn't fail as r = 0.0123456789 ("." as decimal separator) and Va = '0.0123456789'
The same issue arises when using Delphi RAD Studio Berlin 10.1
As I'm usually using "," as decimal separator (and changing the decimal separator to "." is not an option) my question is how to circumvent this failure?
Thank you very much!
Paul
Pages: 1