#101 mORMot 1 » Mysql Access Violation » 2019-02-27 07:51:56

squirrel
Replies: 3

This is not a bug report or complaint, but a request for information from other that might have experienced something similar.  I'm really fishing for ideas on where to look, since lots of logging and debugging didn't really help so far.

I experience "random" access violations when running queries with parameters on Mysql.  The software runs at several customers on very different platforms (mostly Win7, 10, and server 2016) and versions of MySql (mostly either 5.5 or 5.7).  It sometimes happens after it has been running for days and sometimes after weeks. 

Initially I thought it might be a problem in the Odbc driver (again, this happens on a range of versions from ancient to the latest), so I switched from using TSQLDBConnectionProperties with the Odbc drivers to using TSQLDBFireDACConnectionProperties with the MySql client dll.  It looks like the switch to TSQLDBFireDACConnectionProperties did reduce the number of times this happens, but that is more a gut feel than based on data.

I was able to determine that this does not happen with select statements (TSQLDBConnectionProperties.Execute), just Inserts and Updates and always only when there are at least 10 parameters involved (TSQLDBConnectionProperties.ExecuteNoResult).  All the parameters are either strings or integers, no nulls or empty strings.  After the programs are restarted, those same queries with the same parameters all execute without problems for a few days.  What I have found with select statements though, is that when used with parameters via TSQLDBConnectionProperties , they sometimes return empty result sets where data should have been returned.  However, with TSQLDBFireDACConnectionProperties, those same queries always return the correct data.

It looks as like after the A/V happens, that specific connection is tainted, so maybe something got freed and an attempt is made to reuse it?  Maybe this happens while deleting old connections in the pool?  I've tried executing TSQLDBConnectionProperties.ClearConnectionPool after the A/V to attempt to force new connections to be made, but with no success.

So far, I have been unable to reliably replicate the issue.  Any advice from anybody that experienced something similar that could give an idea on maybe how to trigger it more often so I can debug it better would help a lot.

#102 Re: mORMot 1 » [GDIPages] Page of Total Pages » 2018-07-21 08:13:25

Suggestion to make the above request possible:  Add an optional parameter to AddPagesToFooterAt to specify from which page number the "Page x / y" footers should be added, eg:

// StartAtPageNo is the page from which this footer will be added - 1 is the first page, not 0   
procedure AddPagesToFooterAt(const PageText: string; XPos: integer;  YPosMultiplier: integer=1; const StartAtPageNo: integer = 1);

    fPagesToFooterTextStartPage := StartAtPageNo;

which can be used in EndDoc when adding the footers:

  if (n>0) and (fPagesToFooterText<>'') and (fPagesToFooterTextStartPage-1 <= n) then
    // add 'Page #/#' caption at the specified position
    for i := (fPagesToFooterTextStartPage-1) to n-1 do begin

#103 Re: mORMot 1 » [GDIPages] Page of Total Pages » 2018-07-20 13:46:18

Is it possible to specify on which pages AddPagesToFooterAt should apply or a starting page number from which it should be printed? When the pdf starts with a coverpage(s), it would be good to not print the footer on the first page.

#104 mORMot 1 » Execute interface function by name » 2018-06-25 10:35:02

squirrel
Replies: 1

On server-side, I sometimes need to execute interface functions. I know that, if the function name is known at design-time, I can get an instance of the interface and run it like so:

  Server.Service<ICalculator>.Add(10,20)

Is it possible to run Calculator.Add if the function name is only known at runtime (stored externally as a string)?  I already verify that the interface is registered and that the function exists.

InterfaceFactory.Methods[I].InterfaceDotMethodName

#105 Re: mORMot 1 » Enable compression on TSQLHttpServer » 2018-05-28 06:27:47

The Accept-Encoding in the response header from Mormot seem to always be Accept-Encoding: synlz,gzip.  But it doesn't look like that affects the outcome.  It looks like compression works fine for small strings, but that bigger strings are chunked and therefor not compressed.  I was (mistakenly) thinking that the chunking happens after compression.  Does this mean that compression specifically only work for small packages?  Wouldn't that negate the advantage of compression?  Is there a property I can set to always enforce compression if deflate is in the request headers?

I've created a test program using the latest nightly to ensure that none of my code affects the outcome.  Does it do anything wrong?

  TMyRestServer = class(TSQLRestServerFullMemory)
  published
    procedure Hello(Ctxt: TSQLRestServerURIContext);
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  aModel := TSQLModel.Create([],'my_test');
  aRestServer := TMyRestServer.Create(aModel);
  aHttpServer := TSQLHttpServer.Create('8080', [aRestServer]);
end;

procedure TMyRestServer.Hello(Ctxt: TSQLRestServerURIContext);
var
  data: RawUtf8;
begin
  data := StringFromFile('c:\sample_2mb.json');
  Ctxt.Returns(data) //not compressed, but chunked
  //Ctxt.Returns('{"hallo": "world"}'); //compressed
end;

#106 mORMot 1 » Enable compression on TSQLHttpServer » 2018-05-25 13:06:49

squirrel
Replies: 2

How do I ensure that the json responses from my interfaces are always sent compressed to ajax clients?

fHttpServer.RegisterCompress(CompressGZip) do execute as well as THttpSocketCompress.func
However, when looking at the headers returned from my Mormot server, Content-Encoding is never set, and the transfered data size in the webbrowser is exactly the same as the uncompressed size.  Monitoring traffic confirmed that the 3mb json strings are sent uncompressed to the web browsers.  Is there an additional property that needs to be set, or is this an IIS issue?

Request header:

Accept:application/json, text/javascript, */*; q=0.01
Accept-Encoding	:gzip, deflate
Connection	:keep-alive
Content-Length:290
Content-Type:application/json; charset=utf-8
User-Agent:Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/60.0

Response header:

Accept-Encoding	:synlz,gzip
Access-Control-Allow-Methods:POST, PUT, GET, DELETE, LOCK, OPTIONS
Access-Control-Allow-Origin	:*
Access-Control-Expose-Headers:content-length,location,server-internalstate
Access-Control-Max-Age:1728000
Content-Type:application/json; charset=UTF-8
Server:mORMot (Windows) Microsoft-HTTPAPI/2.0
Server-InternalState:14
Transfer-Encoding:chunked
X-Powered-By:mORMot 1.18 synopse.info

#107 mORMot 1 » Suggestion for expanding StringFromFile » 2017-11-14 05:34:35

squirrel
Replies: 0

@ab

Would it be possible to expand the StringFromFile function to allow getting parts of a file?  This would be very handy when serving big files via http with RangeRequests.  Maybe something similar with defaults which would allow the current usage to continue working unaffected:

  function StringFromFile(const FileName: TFileName; const StartPos: Int64 = 0; const Count: Int64 = 0): RawByteString;
  var F: THandle;
      Size, BytesToCopy, BytesCopied: integer;
  begin
    result := '';
    if (FileName='') or (StartPos < 0) or (Count < 0) then
      exit;
    F := FileOpenSequentialRead(FileName);
    if PtrInt(F)>=0 then
    begin
      Size := GetFileSize(F,nil);
      if Size>0 then
      begin
        BytesToCopy := Size - StartPos;
        if (Count > 0) and (BytesToCopy >= Count) then //count = 0 should always return the entire file/rest of the file
          BytesToCopy := Count;

        SetLength(result, BytesToCopy);
        if StartPos > 0 then
          FileSeek(F, StartPos, 0);
        BytesCopied := FileRead(F,pointer(result)^,BytesToCopy);
        if BytesCopied < 0 then //FileRead failed
          result := ''
        else
        if BytesCopied < BytesToCopy then //less bytes were left to read than anticipated
          SetLength(result, BytesCopied);
      end;
      FileClose(F);
    end;
  end;

#108 Re: mORMot 1 » ISQLDBRows and memory » 2017-10-19 12:47:14

Thanks igors.

I assume you mean something like this:

procedure TForm1.Button1Click(Sender: TObject);
  procedure checkDB;
  var
    qry: RawUTF8;
    res: ISQLDBRows;
  begin
    qry := 'SELECT count(*) cnt FROM sysdatabases WHERE name = ?';
    res := ODBCConnection.Execute(qry, ['my_db']);
    if Res.Step and (Res['cnt'] > 0) then
      Label1.Caption := 'DB Found: ' + FloatToStr(Res['cnt']);
  end;
begin
  CheckDB;
end;

But it looks as if it does leak if calling Button1Click multiple times.  In the example above, even the query stays in memory.

#109 mORMot 1 » ISQLDBRows and memory » 2017-10-19 12:13:27

squirrel
Replies: 21

@Ab, please forgive me for asking this, I know you've tackled this one a few times. I have searched the forums and documentation.

What is the proper way to clear ISQLDBRows after use?  I noticed at #TITL_195 that we should just assign nil to it, however, that seem to produce a memory leak.  Am I doing it incorrectly?

Sample code:

procedure TForm1.Button1Click(Sender: TObject);
var
  qry: RawUTF8;
  res: ISQLDBRows;
begin
  qry := 'SELECT count(*) cnt FROM sysdatabases WHERE name = ?';
  res := ODBCConnection.Execute(qry, ['my_db']);
  //res._Release; //this removes it from memory and prevents memory leaks, but throws AV if calling container function repeatedly
  res := nil; //this leaves it in memory and creates a memory leak
end;

Finally, are there any uninit functions that must be called before Freeing TSQLDBConnectionProperties?  Just calling ODBCConnection.Free seem to leak as well.

#110 mORMot 1 » Different Json results between Delphi 7 and XE3 » 2017-07-13 07:39:35

squirrel
Replies: 1

Are there any conditional defines that must be set for Delphi 7?  I noticed that the results for TDocVariantData.ToJSON() differs between Delphi 7 and XE3.

Sample code:

procedure TForm1.FormCreate(Sender: TObject);
type
  TListItem = record
    Name: string;
    Value: integer;
  end;
  TListItems = Array of TListItem;
var
  Source: TDocVariantData;
  Data: TListItems;
  DataStr: RawUtf8;
begin
  SetLength(Data, 2);
  Data[0].Name := 'Name1';  Data[0].Value := 0;
  Data[1].Name := 'Name2';  Data[1].Value := 1;

  Source.InitFromTypeInfo(Data, TypeInfo(TListItems), true, []);
  DataStr := source.ToJSON();
  Memo1.Lines.Add(DataStr);
end;

In XE3, this produces:

[{"Name":"Name1","Value":0},{"Name":"Name2","Value":1}]

In Delphi 7, this produces:

["Name1",""]

#111 Re: mORMot 1 » Openssl RSA Decrypt » 2017-07-07 14:06:06

Thanks for the quick reply.  Would something like this be possible using the wrappers available in SynOpenSSL?

#112 mORMot 1 » Openssl RSA Decrypt » 2017-07-07 13:47:50

squirrel
Replies: 4

Is there a method in SynCrypto which provides for decrypting a RSAutl string?  I have the private key, but can't find anything that provides the same result in delphi as using:

openssl rsautl -decrypt -inkey keyfile.pem

Does anybody have any suggestions or past experience that might help?

#113 Re: mORMot 1 » Export / Import tables » 2017-05-31 07:52:48

I fully agree that there are advantages to a purely logical design.

However, the requirements of this task specify that a human readable file be created which can be stored offsite on a backup medium with no access to electricity.  That leaves me with either a .sql or a .json file.  Due to the custom nature of the specifications, just creating a sql dump file is not ideal.  So I've decided to create a json file per table which contains the table's field definitions (GetFields), index defs (GetIndexes), and data (FetchAllAsJSON(false)).

Restoring the structure seems very doable (SqlCreate, SQLAddIndex).  I'm not too sure about restoring the data.

#114 Re: mORMot 1 » Export / Import tables » 2017-05-30 13:43:58

This is similar to a disaster recovery/offsite backup scenario.  So there will likely only be old backup files to use, no running services.  It is not intended to be used as a live transfer of data.  Or am I understanding your suggestion incorrectly?

#115 mORMot 1 » Export / Import tables » 2017-05-30 09:35:13

squirrel
Replies: 5

I can use a comment / idea to point me in the right direction.

The need is to backup and restore both the structure and data of some tables - not an entire database.  The exact structure of the tables will not be known to me, since this might include client-side customizations.  I also do not know in advance whether the table will be on Sql Server or Mysql.  So I thought of extracting and storing the data in a db neutral format and being able to either generate sql statements for the required platform as needed, or directly creating onto the required platform as needed. (yes I know there are 3rd party backup tools which can do this, but this request is only part of the main task)

The table structure I can get using TSQLDBConnectionProperties.GetFields and TSQLDBConnectionProperties.GetIndexes and save this to a file and the data I can get from a Select statement.  (select statement will be custom, since some sensitive data must sometimes be excluded, ie leaving password fields empty).  Both the structure and data can be stored as either json or sql. This leaves me with needing to be able to restore this custom backup.  I've seen several areas dealing with batch inserts.

1) Using TSQLDBConnectionProperties.SQLCreate, I can generate the create statement without the indexes and then add the indexes one by one using SQLAddIndex. Is this correct or is there a more suitable way of storing and retrieving the table structure?
2) Which of the batch insert options are most suitable for such restores?  My limitation here is that some of the tables have multi-field primary keys, so depending on the existence of a primary key called id is not an option.
3) Is there a way of generating standard create/insert sql scripts from these structures as a fallback?  TSQLRestStorageExternal.JSONDecodedPrepareToSQL?

Any suggestions / ideas / guidance will be welcome.

#116 mORMot 1 » Sql Query on Virtual Tables » 2017-03-13 12:29:45

squirrel
Replies: 1

Is it possible to run a sql query on a virtual table?
for example

Select A, max(B) myCustomMaxFieldname from VirtualTable4 group by A order by myCustomMaxFieldname desc

#117 Re: mORMot 1 » HTML Status code » 2016-08-26 10:35:00

Looks perfect.  Thanks!

#118 mORMot 1 » HTML Status code » 2016-08-26 09:11:16

squirrel
Replies: 5

Would it be possible to add Html status code 206 (Partial Content) to the list of html status codes as well as to StatusCodeIsSuccess?  This is needed when manually serving files such as audio and video files.

I  added the following to mormot.pas to test:

 /// HTML Status Code for "Partial Content"
  HTML_PARTIALCONTENT = 206;

changed StatusCodeIsSuccess so:

function StatusCodeIsSuccess(Code: integer): boolean;
begin
  case Code of
  HTML_SUCCESS, HTML_NOCONTENT, HTML_CREATED, HTML_PARTIALCONTENT,
  HTML_NOTMODIFIED, HTML_TEMPORARYREDIRECT:
    result := true;
  else
    result := false;
  end;
end;

Also, if anybody has experience with serving audio files to Safari, advice and suggestions would be helpful.  I'm sure I'm missing either a status code or header, but serving the audio files through mormot works with all the browsers except Safari, which sees it as a live broadcast.

#119 Re: mORMot 1 » Http Basic Authentication » 2016-07-03 07:38:53

Does anybody have an example of using TSQLRestServerAuthenticationHttpBasic that works?  I need to use basic authentication and look up the username and password from my own list (not orm).

#120 Re: mORMot 1 » Linux Odbc » 2016-06-09 10:35:19

Would it be possible to do a Linux implimentation of ODBCInstalledDriversList?  The odbc drivers are listed in /etc/odbcinst.ini and the odbc dsns are listed in /etc/odbc.ini

Example drivers ini file:

[MySql 5.1]
Description		= MySql 5.1
Driver			= /usr/lib/x86_64-linux-gnu/odbc/libmyodbc.so
Driver64		= /usr/lib/x86_64-linux-gnu/odbc/libmyodbc.so
Setup		= /usr/lib/x86_64-linux-gnu/odbc/libodbcmyS.so
Setup64		= /usr/lib/x86_64-linux-gnu/odbc/libodbcmyS.so

I did a very basic implimentation of ODBCInstalledDriversList for Linux, but getting the correct version numbers might be tricky:

function ODBCInstalledDriversList(const aIncludeVersion: Boolean; out aDrivers: TStrings): Boolean;
const
  OdbcInstLocation = '/etc/odbcinst.ini';
var
  Ini: TIniFile;
begin
  Result := FileExists(OdbcInstLocation);
  if Result then begin
    Ini := TIniFile.Create(OdbcInstLocation);
    if not Assigned(aDrivers) then
      aDrivers := TStringList.Create;
    Ini.ReadSections(aDrivers);
    Ini.Free;
  end;
end;

I found a problem using the ODBCInstalledDriversList function in Windows 10.  The OpenKey would always return false even when running as administrator, however replacing it with OpenKeyReadOnly works 100%:

    result := OpenKeyReadOnly('Software\ODBC\ODBCINST.INI\ODBC Drivers') or
              OpenKeyReadOnly('Software\ODBC\ODBCINST.INI');

#121 Re: mORMot 1 » Linux Odbc » 2016-06-09 10:00:06

I changed it like this and it seamed to work.  Not sure how different versions should be handled.  It is conceivable that someone might be using libodbc.so.2 and not the libodbc.so.1 that worked for me.

constructor TODBCLib.Create;
var P: PPointer;
    i: integer;
    dllname: string;
begin
  {$IFDEF LINUX}
    dllname := 'libodbc.so.1';
  {$ELSE}
    dllname := 'odbc32.dll';
  {$ENDIF}
  fHandle := SafeLoadLibrary(dllname);
  if fHandle=0 then
    raise EODBCException.Create('Unable to find ODBC Client Interface (' + dllname + ')');
  P := @@AllocEnv;
  for i := 0 to High(ODBC_ENTRIES) do begin
    P^ := GetProcAddress(fHandle,ODBC_ENTRIES[i]);
    if P^=nil then begin
      FreeLibrary(fHandle);
      fHandle := 0;
      raise EODBCException.CreateFmt('Invalid ' + dllname + ': missing %s',[ODBC_ENTRIES[i]]);
    end;
    inc(P);
  end;
end;   

#122 mORMot 1 » Linux Odbc » 2016-06-09 08:42:31

squirrel
Replies: 5

Is it possible to use Mormot's Odbc connection in Linux?  It looks like TODBCLib.Create tries to load a Windows library, odbc32.dll which is not possible in linux.  Or is there a different directive which must be set to enable this on Linux FPC?

aConnection := TODBCConnectionProperties.Create('',ConnectionString,'',DBPassword); 

#123 mORMot 1 » Http Basic Authentication » 2016-05-31 12:47:40

squirrel
Replies: 3

When using Basic Authentication, it looks as if Mormot only accepts the Authorized header details when a call to /auth is made.  Any calls to other functions just check if the signature is included, but never the Authorized header.

TSQLRestServerAuthenticationHttpBasic.RetrieveSession only checks the the username and password in the header if there is a valid session, making it a bit pointless to include the Authorized header in the first place.
Is this correct / by design?  Does this mean that the Authorized header can only be used when calling /auth and not to authenticate on the fly?

Is there a setting I can set to ensure that the authentication is always done when the Authorized header is included?

#124 Re: mORMot 1 » Expand or Collapse Json » 2016-03-17 08:45:36

@ab I noticed that TDocVariantData.InitFromTypeInfo works as expected for TDateTime values, but when sending a TDate type, it is treated as the underlying float value.  Is this correct?

#125 Re: mORMot 1 » Expand or Collapse Json » 2016-03-15 11:41:37

I've added a simple parameter which would allow this function to return datasets as datasets, ie with fieldnames and rows:

function TDocVariantData.ToNonExpandedJSON(const SplitRows: boolean = false): RawUTF8;
var fields: TRawUTF8DynArray;
    fieldsCount: integer;
    W: TTextWriter;
    r,f: integer;
    row: PDocVariantData;
begin
  if VKind<>dvArray then begin
    result := '';
    exit;
  end;
  if VCount=0 then begin
    result := '[]';
    exit;
  end;
  with _Safe(VValue[0])^ do
    if VKind=dvObject then begin
      fields := VName;
      fieldsCount := VCount;
    end else
      fieldsCount := 0;
  if fieldsCount=0 then
    raise EDocVariant.Create('ToNonExpandedJSON: Value[0] is not an object');
  W := DefaultTextWriterJSONClass.CreateOwnedStream;
  try
    if SplitRows then
      W.Add('{"fieldCount":%,"rowCount":%,"fields":[',[fieldsCount,VCount])
    else
      W.Add('{"fieldCount":%,"rowCount":%,"values":[',[fieldsCount,VCount]);
    for f := 0 to fieldsCount-1 do begin
      W.Add('"');
      W.AddJSONEscape(pointer(fields[f]));
      W.Add('"',',');
    end;
    if SplitRows then
    begin
      W.CancelLastComma;
      W.Add('],"values":[',[]);
    end;
    for r := 0 to VCount-1 do begin
      if SplitRows then
        W.Add('[',[]);
      row := _Safe(VValue[r]);
      if (r>0) and ((row^.VKind<>dvObject) or (row^.VCount<>fieldsCount)) then
        raise EDocVariant.CreateUTF8('ToNonExpandedJSON: Value[%] not object',[r]);
      for f := 0 to fieldsCount-1 do
        if (r>0) and not IdemPropNameU(row^.VName[f],fields[f]) then
          raise EDocVariant.CreateUTF8('ToNonExpandedJSON: Value[%] field=% expected=%',
            [r,row^.VName[f],fields[f]]) else begin
          W.AddVariant(row^.VValue[f],twJSONEscape);
          W.Add(',');
        end;
      if SplitRows then
      begin
        W.CancelLastComma;
        W.Add('],',[]);
      end;
    end;
    W.CancelLastComma;
    W.Add(']','}');
    W.SetText(result);
  finally
    W.Free;
  end;
end;

It will return the nonexpanded json like this:

{
	fieldCount: 3,
	rowCount: 2,
	fields: 
	    [ 
		"DateData", "IntData", "StrData"
  	    ],
	values: 
	    [	
		[  "2016-03-14T13:23:25",  1,  "Hello"  ],
		[  "2016-03-14T13:23:45",  2,  "Bye"  ]
  	    ]
}

#127 Re: mORMot 1 » Expand or Collapse Json » 2016-03-15 08:13:45

@mpv: Yes that might be the case when working on own projects, but I provide data for third parties.  They expect some standards, such as that a record only contain data for 1 record and not all the fields of all the records as one big list.

#128 Re: mORMot 1 » Expand or Collapse Json » 2016-03-14 11:29:24

Thanks, I'll take a look at TTextWriter again.

Since the json data is usually used to display information in tables, I have to process every dataset at client side into a more recognisable "table" structure, which is a slow process in javascript.

It would be useful to be able to provide a format or template to a function such as JsonReformat.  When the data have to be displayed in a table or shared with third parties, this structure is very logical, since the fieldnames are not part of the data and every row is a set on its own:

{
	fieldCount: 3,
	rowCount: 2,
	fieldNames: 
	    [ 
		"DateData", "IntData", "StrData"
  	    ],
	values: 
	    [	
		{  "2016-03-14T13:23:25",  1,  "Hello"  },
		{  "2016-03-14T13:23:45",  2,  "Bye"  }
  	    ]
}

#129 Re: mORMot 1 » Expand or Collapse Json » 2016-03-14 08:31:25

It feels a bit clumsy, but this should copy the data to allow exporting to the same json format as when expanded=false is used.  Does anyone have suggestions as to how this can be improved?

function CollapseJSON(const data: RawUTF8): RawUTF8;
var
  Source, Target: TDocVariantData;
  TargetValues, valueList : variant;
  fieldCnt, rowCnt, I, J: integer;
begin
  Source.InitJSON(data);
  Target.InitFast;
  TargetValues := _ArrFast([]);
  fieldCnt := 0;
  rowCnt := Source.Count;

  if rowCnt > 0 then
  begin
    valueList := Source.Value[0];
    fieldCnt := DocVariantData(valueList)^.Count;
    Target.Value['fieldCount'] := fieldCnt;
    for I := 0 to fieldCnt -1 do
      DocVariantData(TargetValues)^.AddItem(DocVariantData(valueList)^.Names[i]);

    for I := 0 to rowCnt -1 do
    begin
      valueList := Source.Value[i];
      for J := 0 to fieldCnt -1 do
        DocVariantData(TargetValues)^.AddItem(DocVariantData(valueList)^.Value[J]);
    end;
  end;
  Target.AddValue('values', TargetValues);
  Target.Value['rowCount'] := rowCnt;

  Result := Target.ToJSON;
end;

function RecordToCollapsedJSON(const data; const RecTypeInfo: pointer): RawUTF8;
var
  Source: TDocVariantData;
begin
  Source.InitFromTypeInfo(data, RecTypeInfo, true, []);
  result := CollapseJSON(Source.ToJSON);
end;

Using the initial example, this can then be used like so:

  result := RecordToCollapsedJSON(mytype, TypeInfo(TMyTypeArray));

#130 mORMot 1 » Expand or Collapse Json » 2016-03-11 07:39:55

squirrel
Replies: 19

ISQLDBROWS.FetchAllAsJSON gives the option to specifiy the format of the json as either expanded or not.  Is there a function or way to expand or collapse a rawutf8 dataset so that it matches the same formats as returned by either ISQLDBROWS.FetchAllAsJSON(true) or ISQLDBROWS.FetchAllAsJSON(false)?

I am asking if its possible to go both ways, but the initial need would be to collapse data returned using ISQLDBROWS.FetchAllAsJSON(true) so that it matches the format of ISQLDBROWS.FetchAllAsJSON(false).  This is to ensure that only one format is consistantly used.

The issue comes in when using an array of record in TdocVariant and trying to export it as json.  That only produces expanded format.

type
  TMyType = record
    DateData: TDateTime;
    IntData: Integer;
    StrData: string;
  end;
  TMyTypeArray= array of TMyType;
var 
  doc: TDocVariantData; 
  jsonResult: RawUTF8;
  myType: TMyTypeArray;
begin
    [...] //fill myType with values and do processing
  doc.InitFromTypeInfo(mytype, TypeInfo(TMyTypeArray), true, []);
  jsonResult:= doc.ToJSON; //this is expanded, and must be collapsed

#131 Re: mORMot 1 » Manipulate data to server before send it to client » 2016-03-08 13:52:03

Hi ab, I would like to know something similar to the above question.

I use TSQLDBConnectionProperties.Execute to return ISQLDBRows and then send ISQLDBRows.FetchAllAsJSON to my 3rd party clients.

There are 2 scenarios where I need to change the data before sending it as a result, and I want to ensure that the same format as FetchAllAsJSON is always used:
  1) I must step through the data and change the content of some fields (such as hide sensitive values)
  2) I must remove some records from the set or add additional records to the result set.

What would be the preferred way of doing this?  The queries are very complicated and the data require additional work before being sent as a JSON result.  Do I send the ISQLDBRows to a memory table, or is there a way of directly making changes to the resulting dataset?

#132 Re: mORMot 1 » Database connection exception handling » 2016-02-22 04:42:08

@ab

It looks like the DatabaseNameSafe property contains the database password again.  Could this be a regression, or is there a property I need to set to ensure that it is not included? I've noticed that the TSQLDBConnectionProperties.Password is readonly, so it is possible that the reason the password is included, is because I am unable to set that value.

Example of the problem:

"DatabaseNameSafe": "DRIVER=MySQL ODBC 5.1 Driver;Server=localhost;DATABASE=test_db;USER=root;PASSWORD=testdbrootpwd;PORT=3306;Option=3",

Example of exception generated when I add a space in mytable name to force an odbc error to generate:

{
"errorCode":500,
"error":
{"EODBCException":{
	"ClassName":"EODBCException",
	"Address":"05868B40",
	"Statement": {
		"ClassName":"TODBCStatement",
		"Address":"04F1F2F0",
		"SQL": "SELECT * FROM my table ORDER BY ID desc LIMIT 50",
		"SQLWithInlinedParams": "SELECT * FROM my table ORDER BY ID desc LIMIT 50",
		"CurrentRow": 0,
		"TotalRowsRetrieved": 0,
		"Connection": {
			"ClassName":"TODBCConnection",
			"Address":"04EDAF90",
			"DBMSName": "MySQL",
			"DBMSVersion": "5.1.34-community",
			"DriverName": "myodbc5.dll",
			"Connected": true,
			"ServerTimeStampAtConnection": "2016-02-22T06:29:52",
			"TotalConnectionCount": 1,
			"TransactionCount": 0,
			"InTransaction": false,
			"RollbackOnDisconnect": true,
			"LastErrorMessage": "",
			"LastErrorWasAboutConnection": false,
			"Properties": {
				"ClassName":"TODBCConnectionProperties",
				"Address":"04EABD30",
				"Engine": "ODBC",
				"ServerName": "",
				"DatabaseNameSafe": "DRIVER=MySQL ODBC 5.1 Driver;Server=localhost;DATABASE=test_db;USER=root;PASSWORD=testdbrootpwd;PORT=3306;Option=3",
				"UserID": "root",
				"DBMS": "dMySQL",
				"DBMSEngineName": "MySQL",
				"BatchSendingAbilities": ["cCreate"],
				"BatchMaxSentAtOnce": 4096,
				"LoggedSQLMaxSize": 0,
				"LogSQLStatementOnException": false,
				"ForcedSchemaName": "",
				"UseCache": true,
				"RollbackOnDisconnect": true,
				"StoreVoidStringAsNull": false
			}
		}
	},
	"Message": "TODBCStatement - TODBCLib error: [42000] [MySQL][ODBC 5.1 Driver][mysqld-5.1.34-community]You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'table ORDER BY starttime desc LIMIT 50' at line 1 (1064)\r\n"
}}
}

#134 Re: mORMot 1 » Serve files compiled into the exe » 2016-01-28 10:16:04

Thanks, that sounds exactly like what I need, but I'm not sure how to make it work.  The server runs locally on an intranet server called demoserver on port 8080.

Without redirecting, I am able to access a file using either http://localhost:8080/root/files/login.html or http://demoserver:8080/root/files/login.html
These same urls still work with redirecting, but the Ctxt.URIBlobFieldname changes to include the root/files/ which it didn't before
However, even with redirection, I am unable to access the files function using http://localhost:8080/login.html
It doesnt enter the files method and returns a 503 Service Unavailable error

Using the documentation, I tried all of the following

aHttpServer.DomainHostRedirect('localhost', 'root/files');
aHttpServer.DomainHostRedirect('localhost:8080', 'root/files');
aHttpServer.DomainHostRedirect('demoserver', 'root/files');
aHttpServer.DomainHostRedirect('demoserver:8080', 'root/files');

using

aHttpServer.RootRedirectToURI('root/files');

causes a visit to http://localhost:8080/login.html to give a 404 error (not generated by mormot) while visiting http://localhost:8080/root/files/login.html still works

Additional info: My model and server is created using   

aModel := TSQLModel.Create([],'root');
aHttpServer := TSQLHttpServer.Create(IntToStr(8080), [aRestServer],'+',useHttpApiRegisteringURI)

#135 Re: mORMot 1 » Serve files compiled into the exe » 2016-01-28 08:19:32

@ab  Would it be possible to use this method to serve files without explicitly calling the Files function?  Similarly, is it possible to have an empty root?

So that for example this url: http://localhost:8080/root/files/myfile.html
would become: http://localhost:8080/myfile.html

I assume that one way of using the files method as default would be to register it as an error handler for Error 400s, which would allow me to use custom pages for errors such as 500, 404 etc

Any advice?

#136 Re: mORMot 1 » Serve files compiled into the exe » 2015-12-17 07:12:59

@ab: The method based service you provided works perfectly and I am currently using it to serve .js files without issue.  I currently use ServiceMethodByPassAuthentication to ensure these files can be served before log in.

I now need to be able to serve some files only to authenticated users.  I tried 2 approaches without success and would be very happy if you can give some advice.

First I copied the working method above, gave it a new name and then didn't bypass authentication for it.  Requests to this method always results in 403 Forbidden.
Secondly as an alternative, I added it to one of my interfaces (which I prefer since it keeps related code together).  But I'm not sure how to impliment it / what the return type must be.  This just always returns {"result":[]}

#137 Re: mORMot 1 » Authentication on both user and ajax application level » 2015-12-01 13:19:22

Usually I would agree that a username and password is enough.  But sometimes it is important to ensure that 3rd party Applications can not use some parts of your code, regardless of whether they have the admin username and password.

#138 Re: mORMot 1 » Authentication on both user and ajax application level » 2015-12-01 09:35:53

It looks like it basically boils down to: anything my web application can do/call can be done/called by any 3rd party as well.  So leave the system configuration far away from html - do that as a standard exe on the server.  Doesn't look like there is a way to create the session knowing that it was created via a specific call or application.  Still, any ideas will be welcome, if something like this can be done, it opens a lot of doors.  Custom nonce based on referer for a specific file only, maybe?

#139 Re: mORMot 1 » Authentication on both user and ajax application level » 2015-12-01 08:14:45

Thanks, I'll take a look at Fingerprintjs.  I suppose if Fingerprintjs can give me the real url of the request page (Referer in header can be spoofed) I can make an assumption on serverside and either allow it or not.
Already using https.  I'm still worried that any 3rd party developer can open my client and with Firebug get the url of for example myapp/login_funcs.js and just use the same (instead of publicapi/login_funcs.js) to get access to the full api.

You are right that code can be changed live in browers.  By readonly I mean that an administrator can't override the JS with old / custom versions without either replacing the exe (full set) or rerouting the urls.  Being able to release a full set of code together as one version is fantastic.

#140 mORMot 1 » Authentication on both user and ajax application level » 2015-12-01 04:12:11

squirrel
Replies: 11

Hi guys

I have written an interface based server with functions that can be used as an api by ajax clients.  I've also created a few helper javascript files which are compiled into the server exe and served to these ajax clients to make integrating into our product easier.  One of these scripts is used to do the login encryption and session handling.

Now I need to write our own ajax client which will use this same api.  However, there are a number of system configuration type functions which my own ajax client must be able to call, but not 3rd party clients.  The limitation here must not only be which user can access these functions, but also which application can.  As with the api js files, I intend on including all the html and js files for this ajax client compiled in as a resource and served as files with mormot.  This helps a lot in ensuring that the js files are "readonly" and that they are all at a version that is compatible with the api.

Do you have any suggestions what would be the most secure way of ensuring that only a certain ajax application can make certain calls?  If, for example, I send an extra parameter with our login to identify our app, that parameter will be clearly visible in the web browser console, enabling 3rd parties to use the same parameter.  If I package it as a seperate exe containing just these files to be served and let that exe interface with our api server, third parties can still just look at the calls our client makes to authenticate and do the same by using this server as an api instead of the real api server.

Not sure if I properly explain the idea here.  If anybody have any ideas or suggestions or questions, please let me know?  This will be deployed on third party servers which I will not have any access to.

#141 Re: mORMot 1 » Serve files compiled into the exe » 2015-10-09 04:46:08

@daniel: I fully agree about client side, opening firebug and making changes to loaded code is childsplay.  But the issue here is server side when you distribute software to sites that you do not administer yourself.  In this scenario, you want the js files to always be at the same version as the exe (updated together) and not give the local site admins the option to permanently change the js files on server side to impact an sla. 

Admins are supposed to be the responsible ones, but that does not change reality.  When you deal with partial upgrades or custom changes done by someone else's admins and it impacts your application's stability, you want to package as much together as possible.

#142 Re: mORMot 1 » Serve files compiled into the exe » 2015-10-08 12:40:00

This is excellent!  Works like a dream! Thanks ab!

For future users, resource names are not allowed to contain a fullstop . So I name the resources the same as the filename, but just exclude the fullstop in the resource name.  Then when loading the resource, instead of the filename, I use:

  ResourceToRawByteString(ReplaceStr(fn, '.', ''), PChar(10), blob);

and leave the rest as-is.

#143 Re: mORMot 1 » Serve files compiled into the exe » 2015-10-08 12:11:46

Thanks, this looks exactly like what I need.  Were there any recent changes to UTF8ToString and ReturnBlob?  The compiler doesn't expect the fn parameter as well in these 2 functions.

#144 mORMot 1 » Serve files compiled into the exe » 2015-10-08 11:29:11

squirrel
Replies: 13

Is it possible to serve files/resources that are compiled into the executable? 

I am busy creating a bunch of javascript files which will be used to access the interfaces defined in the mormot service.  Ideally I would like these files to be accessible similar to http://mormotserver:888/root/files/usermanagement.js. But I do not want them in a folder on the disk where anyone can edit them and claim I provided them in a different state than I did.  Having them compiled in as a resource also ensures that these files are always at the same version as the rest of the executable.

I use mormot authentication and they would have to be available to unauthenticated users, since the login pages will need them.

Does anybody have any practical ideas or pointers on how to achieve this?

#145 Re: mORMot 1 » Database connection exception handling » 2015-10-07 11:19:30

Perfect! Thanks!  Will test the change tomorrow after downloading the nightly build.

#146 Re: mORMot 1 » Database connection exception handling » 2015-10-07 08:57:26

1) Thanks, the change causes SQLDriverFullString to no longer appear in the error.  The DatabaseName is logged in full.  I must include the password in the connectionstring, because excluding it means that it is not used.  It looks like the other parameters are not used when the database name is a connection string.

  ConnectionStr := 'DRIVER=MySQL ODBC 5.1 Driver;Server=localhost;DATABASE=test_db;USER=root;PORT=3306;Option=3';
  aConnection := TODBCConnectionProperties.Create('',ConnectionStr,'root','testpwd'); //exception: Access denied for user 'root'@'localhost' (using password:NO) (1045)

  ConnectionStr := 'DRIVER=MySQL ODBC 5.1 Driver;Server=localhost;DATABASE=test_db;USER=root;PASSWORD=testpwd;PORT=3306;Option=3';
  aConnection := TODBCConnectionProperties.Create('',ConnectionStr,'',''); //works perfectly

2) Thanks, this looks perfect

3) This should do the trick to keep the connection alive.  If the mysql server was restarted between these disconnects and reconnects, this will reconnect.  But will I then still get the connection error when running a query if the connection dropped between these heartbeats?


@DigRiver: I would love to use the newer versions of FireDac, but we are on XE3 with no option of upgrading.  Firedac on XE3 is not stable enough for production use.

#147 mORMot 1 » Database connection exception handling » 2015-10-07 05:56:31

squirrel
Replies: 8

Hi ab

Would you be able to assist me with the following?  I am using TODBCConnectionProperties to connect to a Mysql database.

1) Is it possible to override the contents of what the EODBCException posts with the 500 server error?  Everything that is currently sent is useful information and I would like that to stay, but this line is included, which is scary, since it gives the user all the required information to connect to the database:

"SQLDriverFullString": "DRIVER={MySQL ODBC 5.1 Driver};Server=localhost;DATABASE=test_db;USER=root;PASSWORD
=testpwd;PORT=3306;Option=3;"
"DatabaseName": "DRIVER=MySQL ODBC 5.1 Driver;Server=localhost;DATABASE=test_db;USER=root;PASSWORD
=testpwd;PORT=3306;Option=3",

2) Would it be possible to redirect all exception logs to my own logging system, so that alarms can be raised for some errors / events?

3) After leaving the server running and unused the whole night, I got the error message this morning that Mysql has gone away.  I assume that the connection was terminated after not being used for a while.  Is there an auto reconnect option, or is this the way to do before running a query:

if not connection.MainConnection.Connected then
    connection.MainConnection.Connect;

#148 Re: mORMot 1 » Dynamic named array as input » 2015-10-05 13:50:38

I suspect the browser had a cached version of the js file where the value 100 was "100" ie as a string.  It is working now after clearing caches.  Thanks ab!

#149 Re: mORMot 1 » Dynamic named array as input » 2015-10-05 12:42:21

Looks like the parsing is very fragile.  Adding more parameters to the function highlights that some exact format is required, even though it succeeds in browser parsing.

Changing the function to:

function TStaff.FilterStaff(const filters: TFilterList; Limit: integer): TFilterList;

and the datastring to

var dataString = '{"filters":[{"name":"name1","value":"value1","comparetype":">"},{"name":"name2","value":"value2","comparetype":"="}], "Limit":"100"}';

gives the 406 same issue again, but the json validates successfully on sites such as jsonschema.

Using this format, works, provided that the correct type (integer vs string) is passed.  Note that this json doesnt validate on sites such as jsonschema.net:

var dataString = '{"filters":[{"name":"name1","value":"value1","comparetype":">"},{"name":"name2","value":"value2","comparetype":"="}]}, {"Limit":"100"}';

This works for integer Limit parameter:

var dataString = '{"filters":[{"name":"name1","value":"value1","comparetype":">"},{"name":"name2","value":"value2","comparetype":"="}]}, {"Limit":100}';

#150 Re: mORMot 1 » Dynamic named array as input » 2015-10-05 12:21:20

Problem found!  packed record did not make a difference, but ab put me on the right track.  For future ajax users, send the json as string, not object.  To correct the example above, I changed

var dataString = {"filters":[{"name":"name1","value":"value1","comparetype":">"},{"name":"name2","value":"value2","comparetype":"="}]};

to

var dataString = '{"filters":[{"name":"name1","value":"value1","comparetype":">"},{"name":"name2","value":"value2","comparetype":"="}]}';

However this messes up appending the session signature when using GET.  So Post is forced.

In TServiceMethodExecute.ExecuteJson's case statement, Par^ detects the first character as f and not as { when parsing.  Adding the quotes resolved it.

Board footer

Powered by FluxBB