You are not logged in.
Pages: 1
Sorry, errata corrige: the problem occurs when the property is SINGLE, not DOUBLE.
With DOUBLE it works flawlessly.
Thank you.
The same code.
Compiled with the 32 bit compiler calling ObjectToJSON generates this JSON:
{
"MyVal" : 0.234500005841255,
}
Instead when compiled with the 64 bit compiler, it produces this JSON:
{
"MyVal" : 5.2E-0315,
}
Stumbled upon a weird issue.
I have a class like this (omitting useless fields):
TMyClass = class(TPersistent)
private
fMyVal: Double;
published
property MyVal: Double read fMyVal write fMyVal;
end;
Say I assign a number with a lot of decimals here:
MyObj.MyVal := 0.234500005841255;
Then ObjectToJSON produces the following output (encoded in scientific notation, no double quotes, and WRONG number by the way):
{
"MyVal" : 5.2E-0315,
}
Then I call:
fMyCollection.Insert(LGeneratedJSON, []);
After inserting it into my MongoDB, I run RoboMongo to see what the document in MongoDB looks like.
And here's what I see:
{
"MyVal" : "5.2E-0315",
}
See? MongoDB received or considered that as a String value (added double quotes).
For such reason when I query my MongoDB and then call JSONToObject back again, I get a ACCESS VIOLATION.
VERY IMPORTANT NOTE: The above behavior ONLY happens if you compile at 64 bit, the 32 bit generated EXE behaves properly.
I'll try to be brief, and on the spot.
So, this is what I have:
var
fVFSystems: TObjectDictionary<string, TVFSContainer>;
I also call these 2 methods, to make sure mORMot knows the necessary classes:
TJSONSerializer.RegisterClassForJSON(TVFSContainer);
TJSONSerializer.RegisterClassForJSON(TObjectDictionary<string, TVFSContainer>);
Then I call:
MyJSONString := ObjectToJSON(fVFSystems, [woHumanReadable]);
And yet, after the above call, MyJSONString still contains an empty JSON { }.
What am I doing wrong?
Anyone willing to pick up this proposal?
Marius, I agree. And if you look at the source code you will actually find my contributions.
But this is a "delicate" addition, hence my request to the higher level person.
No answer to this? I thought it was a good idea...
Hello,
currently the TMongoClient class only features one constructor, defined like this:
constructor Create(const Host: RawUTF8; Port: Integer=MONGODB_DEFAULTPORT;
const SecondaryHostCSV: RawUTF8=''; const SecondaryPortCSV: RawUTF8=''); overload;
But, even though it's very easy to use, the above is somewhat limited.
Why not adding an overloaded constructor like the following:
constructor Create(const MongoDBConnString: RawUTF8); overload;
That would allow an expert user to create the TMongoClient object by passing a standard MongoDB connection string, as specified here:
http://docs.mongodb.org/manual/referenc … on-string/
You'd be able to do beautiful things like this:
fClient := TMongoClient.Create('mongodb://db1.example.net,db2.example.net:2500/?replicaSet=test&connectTimeoutMS=300000');
I know that the HTTP Basic authentication is inherently weak, in fact Sabbiolina is forcing it through a TLS channel. Yet it still remains fairly weak for other reasons.
But it's compatible. If mORMot had supported a more secure - yet highly compatible - authentication scheme, like OAuth(2) for instance, we would have gone that way.
mORMot's native auth scheme, with its two-phase challenge, is strong, but it's a pain if you need to create an API to be accessed by apps written in PHP, Python, Ruby, Java, C# (etc...) because no one wants to implement the client-side of mORMot's auth scheme when the languages they use already have very reliable and mature OAuth client libraries they could leverage upon.
On top of that consider when you have zero control over the backend DB, and you have to abide to restrictions (no SQLite for example) imposed by either the software or the politics behind it... then you need a framework flexible enough to let you go around these hurdles.
Summarizing: OAuth(2) is really a must if we don't want to pretend that every developer in the world uses Delphi/mORMot... in the meantime - waiting for OAuth(2) - a HTTP Basic Auth inside a TLS channel with some more flexibility would be good enough (temporarily).
Changing the way it is stored on the server would not help.
Again, since your library is a framework, it is not good to assume all your users are developing NEW software (and thus have the ability to structure the back-end DB as you suggest).
When you have to integrate new parts in a very large existing software, sometimes you have to "make do".
So, let's say that we're dealing with an existing DB, where salts and password-hashes are already stored that way, in separate fields... I do believe Sabbiolina's solution here would make your library flexible enough to handle such case with a minimal amount of lines of code (as opposed to reinventing the wheel, and subclassing a class that can only be subclassed inside mORMot.pas due to the large use of protected members). Wouldn't you agree?
Pardon me for the intrusion, I used to teach Operating Systems Security at the University back in my country of origin, and I agree with Sabbiolina on the fact that a fixed salt is a pretty weak solution.
In fact your authentication scheme is so peculiar that it's pretty easy to "guesstimate" the REST service was built with mORMot, and your library is open source, therefore it would take seconds to an attacker to download your source code and realize the salt is constant. Using such information it would be fairly easy to bruteforce the back-end DB directly.
On top of that: a constant salt and no salt at all is basically the same thing from a cryptoanalytical strandpoint, as 2 salted SHA256's of the same password will result in the exact same has code:
- SHA256 of "saltMyPassword" is identical to SHA256 of "saltMyPassword", whereas
- SHA256 of "1234MyPassword" is not the same as SHA256 of "5678MyPassword"
In the latter case, in fact, because of the 2 different salts, the hash codes of the same password would be different, de facto preventing most linear and differential crypto-analysis attacks.
Hello!
Thank you... but... deriving from your base (user) class would be possible if it was a new software, where you don't have to deal with legacy classes.
Not to mention that deriving would still require the modification of mORMot.pas, given the amount of protected members you use.
But when you are integrating mORMot into an existing 8-year-old code, you need enough flexibility to use your old classes (TMyUserProfile) as changing them would require way too much work to justify the effort.
So the changes that Sabbiolina and I made were thought/designed to give mORMot this kind of flexibility.
Wouldn't you agree?
If I invert the parameters and use them the way you wrote, it doesn't compile.
That's why I inverted them.
The compiler says: there is no overloaded version of that function that can be called with these parameters
(maybe I have to update to the latest mORMot nightly build? I downloaded the latest only few days ago...)
Interestingly enough...
This one works:
ruQ := '{"TimeStamp":{"$lte":{"$date":"2015-03-21T20:19:36"}}}';
fPreBListColl.Remove(BSONVariant(ruQ));
But this one does not work:
fPreBListColl.RemoveFmt('{"TimeStamp":{"$lte":{"$date":?}}}',[aDateTimeVariable],[]);
Have you run the project "as administrator" at least once?
Mmmm, nope... built the query using "strict mode" JSON notation, as per the MongoDB extended JSON official document. And it still doesn't work.
Since the "offset" is not clarified by the MongoDB extended JSON notation document, I tried several variations of it. None of them works.
ruQ := '{"TimeStamp":{"$lte":{"$date":"2015-03-21T20:19:36.000Z"}}}';
fPreBListColl.Remove(ruQ);
ruQ := '{"TimeStamp":{"$lte":{"$date":"2015-03-21T20:19:36.000+0"}}}';
fPreBListColl.Remove(ruQ);
ruQ := '{"TimeStamp":{"$lte":{"$date":"2015-03-21T20:19:36.000+0000"}}}';
fPreBListColl.Remove(ruQ);
So... the first query works because RoboMongo sends it to MongoDB in "shell mode", and instead fBListColl.Remove expects a query written in "strict mode"?
Is that what you mean? I'll give it a try...
Here's what puzzles me. I use RoboMongo (or MongoVue) to connect to my MongoDB server, and manually type this query:
db.smblacklist.remove({"TimeStamp":{$lte:new ISODate("2015-03-21T18:10:27.000Z")}, "Persistence":"Temporary"})
And the above query works like a charm. All documents with a timestamp less than the isodate I specify (and with Persistence=Temporary) are deleted.
Good! Then I would expect the following Delphi code to work just the same:
ruQ := '{"TimeStamp":{$lte:new ISODate("2015-03-21T18:10:27.000Z")}, "Persistence":"Temporary"}';
fBListColl.Remove(ruQ);
But it doesn't. The query is executed without errors or exceptions... but NOTHING gets deleted from the database.
I really am puzzled. What am I doing wrong?
Thank you, the fix works.
I will also have a look at TSynPersistent and TSynAutoCreateFields as you suggested.
Ok, I've prepared a test project to show you the behavior. It would require too much posting of code here... if you see the example I prepared you'll understand in 2 seconds. How do I send you the app? There's no "attach file" feature in this forum. Is it ok if I send it to you via email?
If so, please, tell me which email address should I use, as the email feature of this forum also doesn't allow sending attachments.
Thank you.
Ok... I'll prepare an example.
Please consider the following line of code:
JSONToObject(Self, @json[1], Result, nil, [j2oIgnoreUnknownProperty]);
If I ADD a property to my object, and such property is NOT in my JSON string, it works as expected, it just ignores the property that wasn't found in the JSON string.
But if I REMOVE a property from my object, and such field still exists in my JSON string... then my object will be deserialized only in part. All properties of my object that follow the property missing from the JSON string will not be set to the values found in the JSON string.
I would expect j2oIgnoreUnknownProperty to work in a bi-directional way: whatever is missing on either side (object or JSON) is ignored, but the process continues and every other field after the missing one will still be read from the JSON and set into the object.
What am I missing?
Hello,
I am trying to build my own custom authentication method for my REST server.
I have created my own class (TSQLRestServerAuthenticationHttpToken) derived from yours, and on the server side I register it like this:
Model:=TSQLModel.Create([Tc2dAuthUser,TSQLauthGroup]);
// create the main mORMot server
RestSrv:=TSQLRestServerDB.Create(Model,':memory:',false); // authentication=false
try
RestSrv.CreateMissingTables; // create tables or fields if missing
restSrv.AuthenticationRegister(TSQLRestServerAuthenticationHttpToken);
But on the client side I doesn't seem to be able to specify which auth scheme the server is expected to use. In fact:
amodel := TSQLModel.Create([TSQLAuthUser],collname);
aClient := TSQLHttpClientWinHTTP.Create(addr,SERVER_PORT,aModel,false);
aclient.AuthScheme //// this one can only be one of the schemes defined by you
if not aClient.SetUser('Admin','synopse',false) then
In fact the AuthScheme property can only be:
THttpRequestAuthentication = (wraNone,wraBasic,wraDigest,wraNegotiate);
And then based upon the value given to AuthScheme you pick one of your pre-defined auth schemes for the client side.
How can I tell the client to use my own auth scheme on the client side?
Thank you.
Are you going to commit my changes into your source code?
Here's where you lose it:
function TMongoConnection.GetJSONAndFree(Query: TMongoRequestQuery; Mode: TMongoJSONMode): RawUTF8;
var W: TTextWriter;
ReturnAsJSONArray: boolean;
begin
ReturnAsJSONArray := Query.NumberToReturn>1;
W := TTextWriter.CreateOwnedStream;
try
if ReturnAsJSONArray then
W.Add('[');
if Mode=modMongoShell then
GetRepliesAndFree(Query,ReplyJSONExtended,W) else
GetRepliesAndFree(Query,ReplyJSONStrict,W);
W.CancelLastComma;
if ReturnAsJSONArray then
W.Add(']');
W.SetText(result);
if (result='') or (result='[]') or (result='{}') then
result := 'null';
finally
W.Free;
end;
end;
In this function you only check if Mode is equal to modMongoShell or not. Instead of that if..then there should be a:
case Mode of
modNoMongo: GetRepliesAndFree(Query,ReplyJSONNoMongo,W);
modMongoStrict: GetRepliesAndFree(Query,ReplyJSONStrict,W);
modMongoShell: GetRepliesAndFree(Query,ReplyJSONExtended,W);
end;
The function ReplyJSONNoMongo was not existing, so I created it:
procedure TMongoConnection.ReplyJSONNoMongo(Request: TMongoRequest;
const Reply: TMongoReplyCursor; var Opaque);
var W: TTextWriter absolute Opaque;
begin
Reply.FetchAllToJSON(W,modNoMongo,false);
W.Add(',');
end;
With these changes everything works perfectly.
Nope. I was reporting it to you cause that's what I did and I expected it to work... but that's the result I got.
Any clues?
Unless I'm very wrong here... something doesn't work as I thought.
Here's the method I'm using:
rutf := fStatsColl.FindJSON('', '', 1, 0, [], modNoMongo);
And after calling this method, here's the contents of the rutf variable:
'{"_id":"54F79A32F6A1078C08487DE7","FirstStart":{"$date":"2015-03-06T19:03:35"}, ...........
See? The $date Mongo-date-field identifier is still there.
I concur! Just tested it and without any changes in my code it works flawlessly and it's even faster than v2.6.
Wait... I'm not sure I understand here. This is what I do:
- I use ObjectToJSON with [woDateTimeWithMagic] to create a JSON that I can insert into MongoDB
- Later on I query the MongoDB and use the JSONtoObject to restore the object I read from MongoDB into a new object
So of course the JSON is in Mongo format, because I receive such JSON by querying the MongoDB...
How can I do that and correctly convert dates back and forth?
Actually it does work, but.... now seems like its counterpart JSONToObject doesn't interpret those dates correctly when I READ the object back from my MongoDB and I want to assign it back to my native object.
I think TJSONToObjectOption needs a [j2oDateTimeWithMagic] option as well... otherwise I can store my objects in MongoDB, but I cannot re-load them from the DB back into my original Delphi objects...
I'll try it. Thank you.
Awesome project this mORMot! Kudos!!
Hello there!
Suppose you have an object that looks like this:
TIPRec = class(TPersistent)
private
f_id: RawUTF8;
fIPAddr: AnsiString;
fTimeStamp: TDateTime;
public
constructor Create;
destructor Destroy; override;
published
property _id: RawUTF8 read f_id write f_id;
property IPAddr: AnsiString read fIPAddr write fIPAddr;
property TimeStamp: TDateTime read fTimeStamp write fTimeStamp;
end;
In my code I call ObjectToJSON to serialize it to JSON and then insert it into my MongoDB like this:
var
rutf: RawUTF8;
item: TIPRec;
begin
item := TIPRec.Create;
try
try
item.IPAddr := IP;
item.TimeStamp := now;
//--
rutf := ObjectToJSON(item, [woHumanReadable]);;
fIPCollection.Insert(rutf, []);
Result := true;
except
Result := false;
end;
finally
FreeAndNil(item);
end;
end;
After inserting the record I open RoboMongo to check if the insertion was successful, and actually the document is there in my collection.... but... the TimeStamp field (which was of type TDateTime) is now of type STRING. Therefore I cannot query it as I would query a date field.
I suspect that ObjectToJSON doesn't serialize TDateTime fields in a MongoDB-friendly way. Is there any way to accomplish a JSON serialization of my object that would tell MongoDB to consider TDateTime fields as per what they really are?
THANK YOU!
Just my 2 cents: we've built a REST service with mORMot and a Delphi client to consume it (which works very well) but not a day goes by without a customer asking us if they can consume our REST service via their own web apps (written in PHP, Ruby, Python, ...) and the first thing they ask is OAuth2 support, as the client-side of it comes hassle-free in practically any web application programming language.
I know that, with a little bit of effort, they could implement the current mORMot auth scheme in PHP, Python (etc) but the argument is "why should we do it, when there is a standard that the world is already using (OAuth2) and that our development tools already support amazingly well?"
So, in my humble opinion, OAuth2 support is more than a nice-to-have feature... it's a very desirable one.
Thank you!
That's perfect. Thank you!
Hello,
I was trying to set the IgnoreSSLCertificateErrors property of the TWinHTTPAPI object from within a TSQLHttpClientWinHTTP, but I encountered a problem.
Here's the code I wrote:
aClient := TSQLHttpClientWinHTTP.Create(addr, SERVER_PORT, aModel, true);
aClient.WinAPI.IgnoreSSLCertificateErrors:=true; // ERROR because aClient.WinAPI is still nil here
Examining your code I have noticed that TSQLHttpClientWinHTTP.Create does not create its WinAPI internal object, such object is created by the InternalCheckOpen method.
As a temporary solution I have modified the code of your TSQLHttpClientWinHTTP.Create constructor as follows:
constructor TSQLHttpClientWinGeneric.Create(const aServer, aPort: AnsiString;
aModel: TSQLModel; aHttps: boolean; const aProxyName, aProxyByPass: AnsiString;
SendTimeout,ReceiveTimeout: DWORD);
begin
inherited Create(aServer,aPort,aModel);
fHttps := aHttps;
fProxyName := aProxyName;
fProxyByPass := aProxyByPass;
fSendTimeout := SendTimeout;
fReceiveTimeout := ReceiveTimeout;
InternalCheckOpen; // added this line
end;
But it's not a clean solution. I think that propagating/adding the IgnoreSSLCertificateErrors property also to the TSQLHttpClientWinHTTP class would be the best way to achieve the goal.
What do you think?
Feature?
There is no definition of '' in JSON.
In fact, '' is no valid JSON content, whereas 'null' is a null JSON object.
So unassigned sounds definitively better than null.
I have to agree with Sabbiolina here.
If you pass a WRONGLY-formatted JSON string (or anything that cannot be decoded as JSON) it would be better to return a NULL variant, not an empty variant.
Not only because breaking backwards compatibility is a bad practice (would require coders to go over everything they already wrote in the past), but also because if what I pass as an argument to that function is not a JSON then it's much more correct to return NULL rather than empty.
NULL tells the story: "hey dude that was NOT a JSON"
EMPTY is dangerously bi-valent: I could have EMPTY if I pass a valid empty JSON or an invalid one... and then how do I tell the difference?
I most definitely vote to bring back the old behavior: whatever invalid parameters, just return a NULL variant.
Thank you.
I actually did. Together with a friend of mine we had already made those changes, tested them, and we were getting ready to commit them for your evaluation... but once again you've been faster, so we will just stick to your official changes.
By the way, let me share my appreciation for mORMot once again, it's truly a well-thought-out and well-designed library. My sincere congratulations!
Thank you. By debugging the code line by line I came to the same conclusion, and I was about to propose the same change to the code, so that both types of "setters" could be supported. But you've been faster than me. You have amazing reaction times! KUDOS! And thanks!
Errata, please in the above code use the following code in place of TForm1.Button2Click and see what happens:
procedure TForm1.Button1Click(Sender: TObject);
var
json: RawUTF8;
i: integer;
obj: TMyObj;
res: boolean;
begin
obj := TMyObj.Create;
for i := 0 to 1999 do // ObjectToJSON, let's call it 2000 times
json := ObjectToJSON(obj, [woHumanReadable]);
for i := 0 to 499 do // now let's call JSONToObject only 500 times
JSONToObject(obj, @json[1], res, nil, [j2oIgnoreUnknownProperty]);
(* the above only leaks 1 time *)
for i := 0 to 1999 do // now ObjectToJSON and JSONToObject 2000 times in the same cycle
begin
json := ObjectToJSON(obj, [woHumanReadable]);
JSONToObject(obj, @json[1], res, nil, [j2oIgnoreUnknownProperty]);
end;
(* the above leaks 2000 times *)
FreeAndNil(obj);
MessageDlg('Done. Now close the program to see 2000 memory leaks... one per each iteration of JSONToObject.', mtInformation, [mbOK], 0);
end;
I have found a strange memory leak that apparently affects the ObjectToJSON function. To confirm I have created a short program that compares the ObjectToJSON function with another commonly used object serializer (OmniXML). Even though one function writes a JSON string and the other one writes an XML string, they both do the same thing: they READ the object to be serialized and WRITE a string (regardless of the format of such string).
Here's the code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
Label2: TLabel;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TMyObj = class(TPersistent)
private
fSomething: TStringList;
function GetSomething: TStringList;
procedure SetSomething(Value: TStringList);
public
constructor Create;
destructor Destroy; override;
published
property Something: TStringList read GetSomething write SetSomething;
end;
var
Form1: TForm1;
implementation
uses
SynCommons, mORMot, OmniXML, OmniXMLPersistent, OmniXML_Types;
{$R *.dfm}
constructor TMyObj.Create;
begin
inherited Create;
fSomething := TStringList.Create;
fSomething.Add('First string');
fSomething.Add('Second string');
end;
destructor TMyObj.Destroy;
begin
FreeAndNil(fSomething);
inherited Destroy;
end;
function TMyObj.GetSomething: TStringList;
begin
Result := fSomething;
end;
procedure TMyObj.SetSomething(Value: TStringList);
begin
fSomething.Assign(Value);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
json: RawUTF8;
i: integer;
obj: TMyObj;
res: boolean;
begin
obj := TMyObj.Create;
for i := 0 to 1999 do
begin
// mORMot serializer, requires to register TCollections descendants, and
// leaks memory (even just with TStringList properties)
json := ObjectToJSON(obj, [woHumanReadable]);
end;
FreeAndNil(obj);
MessageDlg('Done. Now close the program to see 2000 memory leaks... one per each iteration of JSONToObject.', mtInformation, [mbOK], 0);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
obj: TMyObj;
i: integer;
xml: XmlString;
begin
obj := TMyObj.Create;
for i := 0 to 1999 do
begin
// OmniXML serialization causes no leaks... and it even works with TCollection child objects
// without the need to register them...
TOmniXMLWriter.SaveXML(obj, xml, pfAttributes, ofIndent);
end;
FreeAndNil(obj);
end;
end.
Now, one may argue that the property setter for the TStringList should be like this:
procedure TMyObj.SetSomething(Value: TStringList);
begin
fSomething.Free;
fSomething := Value;
end;
But everywhere in the Delphi code itself, and on pretty much all Delphi programming books that I've read, they use the non-destructive assign method:
procedure TMyObj.SetSomething(Value: TStringList);
begin
fSomething.Assign(Value);
end;
On top of that, and on a more general scale, I can see that other serializers (like OmniXML) work like a charm with the Assign-based property setter, and don't leak memory. Also, as a side note, OmniXML serializes collections without the need to register them with the serializer (but that might be a side-effect of the greater verbosity of XML versus the data-centric nature of JSON).
Pages: 1