You are not logged in.
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.
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 beginIs 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.
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].InterfaceDotMethodNameThe 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;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.0Response 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@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;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.
@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.
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",""]Thanks for the quick reply. Would something like this be possible using the wrappers available in SynOpenSSL?
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.pemDoes anybody have any suggestions or past experience that might help?
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.
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?
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.
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 descLooks perfect. Thanks!
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.
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).
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.soI 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');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; 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); 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?
@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?
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" ]
]
}Excellent! Thank you! ![]()
@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.
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" }
]
}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));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 collapsedHi 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?
@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"
}}
}Am I doing something wrong?
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)@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?
@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":[]}
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.
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?
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.
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.
@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.
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.
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.
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?
Perfect! Thanks! Will test the change tomorrow after downloading the nightly build.
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 perfectly2) 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.
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;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!
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}';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.