You are not logged in.
Hi @ab, I made a modification but I think that exists better way. These are the steps involved:
The client side generate the parameters and invoke the service:
//2. Se inicia el cliente
lRestClient := TSQLHttpClient.Create('127.0.0.1', '8888', CreateModelDBService);
try
lRestClient.ServiceDefine([IDBService], sicClientDriven{sicShared});
if lRestClient.Services['DBService'].Get(lDBSvc) then
begin
lParams := _ArrFast(['INSERT_DIAL_DATA', // nombre de la tabla
'12345678', // teléfono
FormatDateTime('YYYYMMDDHHNNSS', IncMinute(Now, 3)),
'valor 1|valor 2|valor 3', // valores para los campos Vnn_TM
1, // cola
20]); // prioridad
lResp := lDBSvc.ExecSqlStm('INSDIALDAT', lParams, lResult);
Check(lResp = 1, 'No se actualizó la tabla');
end;
finally
lDBSvc := Nil;
lRestClient.Free;
TearDown;
end;The server side (excerpt) execute the query (definition is stored in a Firebird table):
function TDBService.ExecSqlStm(const aIDMDService: RawUTF8; const aParams: Variant; out aResult: RawUTF8): Integer;
var
...
lStm: TSQLDBStatement;
lParams: array of TVarRec;
...
begin
...
// convert aParams to lParams
TDocVariantData(aParams).InitJSON(aParams);
SetLength(lParams, TDocVariantData(aParams).Count);
for I := 0 to TDocVariantData(aParams).Count-1 do
VariantToVarRec(TDocVariantData(aParams).Values[i], lParams[i]);
// execute the statement
lStm.Execute(lSQLStmText, True, lParams);
...
end;Execute invoke the Bind procedure:
procedure TSQLDBStatement.Execute(const aSQL: RawUTF8;
ExpectResults: Boolean; const Params: array of const);
begin
Connection.InternalProcess(speActive);
try
Prepare(aSQL,ExpectResults);
Bind(Params);
ExecutePrepared;
finally
Connection.InternalProcess(speNonActive);
end;
end;And my problem is in Bind(Params):
procedure TSQLDBStatement.Bind(const Params: array of const;
IO: TSQLDBParamInOutType);
var i,c: integer;
begin
for i := 1 to high(Params)+1 do
with Params[i-1] do // bind parameter index starts at 1
case VType of
vtString: // expect WinAnsi String for ShortString
BindTextU(i,WinAnsiToUtf8(@VString^[1],ord(VString^[0])),IO);
vtAnsiString:
if VAnsiString=nil then
BindTextU(i,'',IO) else begin
c := PInteger(VAnsiString)^ and $00ffffff;
if c=JSON_BASE64_MAGIC then
BindBlob(i,Base64ToBin(PAnsiChar(VAnsiString)+3,length(RawUTF8(VAnsiString))-3)) else
if c=JSON_SQLDATE_MAGIC then
BindDateTime(i,Iso8601ToDateTimePUTF8Char(PUTF8Char(VAnsiString)+3,length(RawUTF8(VAnsiString))-3)) else
// expect UTF-8 content only for AnsiString, i.e. RawUTF8 variables
BindTextU(i,RawUTF8(VAnsiString),IO);
end;
vtPChar: BindTextP(i,PUTF8Char(VPChar),IO);
vtChar: BindTextU(i,RawUTF8(VChar),IO);
vtWideChar: BindTextU(i,RawUnicodeToUtf8(@VWideChar,1),IO);
vtPWideChar: BindTextU(i,RawUnicodeToUtf8(VPWideChar,StrLenW(VPWideChar)),IO);
vtWideString: BindTextW(i,WideString(VWideString),IO);
{$ifdef UNICODE}
vtUnicodeString: BindTextS(i,string(VUnicodeString),IO);
{$endif}
vtBoolean: Bind(i,integer(VBoolean),IO);
vtInteger: Bind(i,VInteger,IO);
vtInt64: Bind(i,VInt64^,IO);
vtCurrency: BindCurrency(i,VCurrency^,IO);
vtExtended: Bind(i,VExtended^,IO);
vtPointer:
if VPointer=nil then
BindNull(i,IO) else
raise ESQLDBException.CreateUTF8('Unexpected %.Bind() pointer',[self]);
vtVariant: BindVariant(i,VVariant^,false{true},IO); // *** false instead of true for the parameter IsDataBlob ***
else
raise ESQLDBException.CreateUTF8('%.BindArrayOfConst(Param=%,Type=%)',
[self,i,VType]);
end;
end;I had to modify the line // *** because all string values were converted as BLOB execution failing. Is there another solution ?
Thanks.
Thanks @ab, I now understand. And I this code works for me:
procedure TTestDBService.TestJSONUTF8;
var
lJSONUTF8: RawUTF8;
V: Variant;
begin
lJSONUTF8 := '{"Database":"\u201d\u00c9\u00c3\u00b6\u00b1\u00a2\u00a7\u00ad\u00a5\u00a4"}';
TDocVariantData(V).InitJSON(UTF8ToString(_JSON(lJSONUTF8)));
Check(V.Database='”Éö±¢§¥¤', 'Invalid database value');
end;Sorry for my bad english. Can you try this code ?
procedure TTestDBService.TestJSONUTF8;
var
lJSONUTF8: RawUTF8;
begin
lJSONUTF8 := '{"Database":"\u201d\u00c9\u00c3\u00b6\u00b1\u00a2\u00a7\u00ad\u00a5\u00a4"}';
Check(_JSON(lJSONUTF8) = '{"Database":"”Éö±¢§¥¤"}', 'Invalid database value');
end;Debug the SynCommons.WideCharToUtf8, you can see the erroneous conversion of the first char and the rest.
Thanks.
@ab, the WideCharUtf8 function is not working correctly, for example with 8221 decimal code from an UTF8 character. I convert a XML file with SuperObject, the XML file content no base64 characters (”,€, etc.), then SuperObject convert to Unicode with \u201D,\u20AC codes (following the example). I can see that ü --> ü can be converted correctly using only WideChar(aWideChar) in WideCharToUtf8.
My code converting the XML file to JSON using SuperXMLParser and SuperObject:
var
lConns: RawUTF8;
begin
lFileName := 'cm.daconnections';
lConns := XMLParseString(AnyTextFileToString(lFileName, True)).AsJSON;
fDBConnections := _JSON(lConns);
end;AnyTextFileToString load OK the file: Database=”Éö±¢§¥¤;UserID=`f`QON;Password=rp{|u‚z|qo;Server=‰‘£‘Ÿ£;
XMLParseString converto OK to JSON: Database=\u201d\u00c9\u00c3\u00b6\u00b1\u00a2\u00a7\u00ad\u00a5\u00a4;UserID=`f`QON;Password=\u0081rp{|u\u201az|qo;Server=\u2030\u2018\u00a3\u009d\u2018\u0178\u00a3;
But _JSON convert as: Database=â€Éö±¢§¥¤;UserID=`f`QON;Password=rp{|u‚z|qo;Server=‰‘£â€˜Å¸£;
The problem is with decimal over 2047: \u201d = 8221 = ” and you can see that the bad conversion is: \u201d = 8221 = â€.
Can you fix it ?
TIA.
To me the RootRedirect did not work when the URL already registered (with secSSL and without secSSL), check again registered URL list.
I found the Error but not the reason:
Http.AddUrlToUrlGroup results in Error 183 - ERROR_ALREADY_EXISTS what does this mean ?
That mean that your URL is already registered, probably with "http://...", you can see the registered URL with:
netsh http show urlacland you can delete the url with:
netsh http delete urlacl http://host:port/[URI]The [URI] is when the URL have an URI.
I hope it has been helpful.
The "exit" command always execute the finally section when is inside of the try ... finally.
GraphViz web site is updated, UML diagram sequence would be fantastic. UMLGraph http://www.umlgraph.org/doc/seq-intro.html is a good example but another textual representation could be better, for i.e:
TMyClass->TMyOtherClass.Method1(param. definitions) // call method without return
TMyClass<-TMyOtherClass.Method1(param. definitions) // return method
TMyClass<->TMyOtherClass.Method1(param. definitions) // call method with return
TMyClass->TMyOtherClass.Create(param. definitions) // create an instance
...
etc.
but you has made something about this.
Best regards.
I have to agree with @ab. If a JSON string is invalid is logical that the parse return an unmanageable data type, similar to SuperObject that return nil when the JSON string is invalid.
Regards.
@danielkuettner
ReadCommited, al least in Firebird, must be default configuration "out of box". With AutoCommit I prefer have the control on start-commit-rollback, but it's a matter of taste. Certainly the deadlocks should be rare. On the other hand when clearing the connection pool if the connection is in the pool mean that is not in use whereby the connection it is free of transactions and can be destroyed.
Best regards.
The most recommended transaction management is:
Transaction Isolation Level: read commited
Transaction parameters (no textual names):
- hard commit: true, commit retaining (soft commit) es very danger, degrading the Firebird engine.
- wait: in deadlock case (conflict with another transaction) the transaction (A) wait to another transaction (B) rollback their work, if the other transaction (B) commit their work then transaction (A) raise deadlock error, on the contrary transaction (A) commit their work.
- wait timeout N seconds: same wait but waiting the N seconds
- nowait: in deadlock case (conflict with another transaction) the transaction (A) raise immediatly deadlock error.
It is also highly recommended use:
DB.BeginTransaction;
try
...
DB.CommitTransaction;
except
DB.RollbackTransaction;
end;Same connection can be used in different threads but respecting the former (try ... except).
All this, is in my experience reading (Firebird gurus) and using Firebird. With other engines (MSSQL, Oracle, Informix, DB2, MySQL) using ADO and applying the same this works.
Best regards and sorry for by english.
I believe that would be fantastic have a "native" replication with mORMot. We are using http://www.symmetricds.org, is developed in Java and run as external service that generate tables and triggers inside the database to be replicated (Firebird 2.5). It also handles file replication and the schema DDL replication. With DDL schema replication I mean that when a table is created, the DDL can be put in a queue for replication and the other extreme receive the schema DDL and create the table. I think that DDL replication it should be taken into account. The SymmetricDS replication put all data for replication tables inside of each database, may be mORMot can use the SQLite3 database in a similar way, this allow that if database service is restarted the replication can continue with pending data for replication (SymmetricDS do it).
Best regards.
Hi Arnaud, I put the SynCrtSock.pas modified in GitHub and the pull request is https://github.com/synopse/mORMot/pull/5
Check and merge if you consider that modifications are right.
Best regards.
I know that is bad idea the global variable. Just I didn't want to have my own version of SynCrtSock.pas. I will add the parameter to TWinHTTP constructor and the class functions GET/POST/InternalRest/etc, if you want I put this in GitHub.
Bye.
I did that, but en class function TWinHTTPAPI.Get/Post ... call to class function TWinHTTPAPI.InternalRest:
class function TWinHttpAPI.InternalREST(const url,method,data,header: RawByteString): RawByteString;
var URI: TURI;
outHeaders: RawByteString;
begin
result := '';
with URI do
if From(url) then
try //*****************************************************
with self.Create(Server,Port,Https) do // --> new instance and fIgnoreSSLCertificates if False
try //*****************************************************
Request(Address,method,0,header,data,'',outHeaders,result);
finally
Free;
end;
except
result := '';
end;
end;By this I implemented the pseudo class property. I hope can you understand me.
Thanks.
Is not working because IgnoreSSLCertificates is a property of an object and is not transferred to created instance in class functions. I did make for my testing purpose in SynCrtSock.pas. The same is a dirty trick, I change the object property IgnoreSSLCertificates to class property.
TWinHTTP = class(TWinHttpAPI)
private
class function GetIgnoreSSLCertificates: Boolean;
class procedure SetIgnoreSSLCertificates(const Value: Boolean);
...
/// allows to ignore untrusted SSL certificates
// - similar to adding a security exception for a domain in the browser
property IgnoreSSLCertificates: boolean
read GetIgnoreSSLCertificates write SetIgnoreSSLCertificates;
end;after implementation (line 1350):
threadvar
TWinHTTP_IgnoreSSLCertificates: Boolean;
...
class function TWinHTTP.GetIgnoreSSLCertificates: Boolean;
begin
Result := TWinHTTP_IgnoreSSLCertificates;
end;
...
procedure TWinHTTP.InternalSendRequest(const aData: RawByteString);
var L: integer;
begin
if fHTTPS and GetIgnoreSSLCertificates then
if not WinHttpSetOption(fRequest, WINHTTP_OPTION_SECURITY_FLAGS,
@SECURITY_FLAT_IGNORE_CERTIFICATES, SizeOf(SECURITY_FLAT_IGNORE_CERTIFICATES)) then
RaiseLastModuleError(winhttpdll,EWinHTTP);
L := length(aData);
if not WinHttpSendRequest(fRequest, nil, 0, pointer(aData), L, L, 0) or
not WinHttpReceiveResponse(fRequest,nil) then
RaiseLastModuleError(winhttpdll,EWinHTTP);
end;
...
class procedure TWinHTTP.SetIgnoreSSLCertificates(const Value: Boolean);
begin
TWinHTTP_IgnoreSSLCertificates := Value;
end;
...And I removed the fIgnoreSSLCertificates.
I am sure that you will implement the best solution.
Thanks.
Pardon and my sincerest apologies.
I'll have into account its recommendations.
Thanks.
@ab, @mpv thanks for reply.
I'll investigate how use mORMot UTC with string datetime received from external gateway in the format aforementioned.
Thanks again.
Esteban
Hi, I work with datetime with the following format: YYYY-MM-DDTHH:NN:SS.zzzZ, being zzz=millisenconds and Z=timezone. When I use ISO8601ToDateTime function the last Z is ignored and the datetime is converted regardless the timezone. Then I wrote the following code wrapping the ISO8601ToDateTime function:
class function IMRUtils.ISO8601ToDateTime(const aValue: String): TDateTime;
var
lTimeZone: TTimeZoneInformation;
begin
Result := SynCommons.Iso8601ToDateTime(aValue);
if (aValue <> '') and (aValue[Length(aValue)] in ['Z', 'z']) then
begin
GetTimeZoneInformation(lTimeZone);
Result := IncHour(Result, (lTimeZone.Bias div -60));
end;
end;This works fine, but I want to know if the framework support timezone. if so, I could not find.
TIA.
Esteban.
I did mean when the exception inherits from EIBInterBaseError I recreate the connection and reexecute the statement. At this point I have the certainty that the SQL statement syntax and parameters is OK because the sql statement has been prepared and executed before. I keep the connection and sql statement objects for performance.
@AB
I understand you, but I mean that the background thread is another connection that when fail you know that there is a problem and you can take actions.
I have implemented a custom solution in Firebird, when a sql statement fail and the exception is not EIBinterbaseError (IBX) the connection is created and re execute the statement, but all this is specific to Firebird.
I am sure that any solution on your part will be optimal, the mORMot framework speaks for you.
Best regards.
@ab
I did not mean one ping per request, I did mean a thread running check SQL statement through of the configurable interval, and when the check fail, process/events can be invoked/fired. Of course after a successful check the engine may be shutdown and the next SQL statement fail.
Anyway I think all this should be done by Zeos framework (for example), is a database problem neither ORM or HTTP.
Best regards.
I have worked with frameworks that implement different workarounds to this problem. RemObjects (my very old version) use the timeout solution, mentioned by AB, when the connection reach the inactive time is destroyed and removed from the connection pool (if using). AFAIR the default value for timeout is the one minute.
Another solution is used by SymmetricDS (www.symmetricds.org). Consist in define the sql statement to verify if RDBMS engine (is per engine) is alive and interval check to execute the statement (one thread per engine). When the check fail actions can be taken. The statements usually are:
- Oracle: "select 1 from dual;"
- Firebird: "select 1 from rdb$database;"
- and so for each engine ...
This last solution allow know for sure there is a problem with the engine.
But the best solution are if the engine reports the problem through error codes.
Best regards.
Thank you very much !!!
Where I can found mozjs64.dll ? In Delphi XE3 on Windows 7 Home Premium 64 bits, sample 22 raise an access violation:
First chance exception at $5A0DA137. Exception class $C0000005 with message 'access violation at 0x5a0da137: read of address 0x00004a78'. Process JSHttpApiServer.exe (3680).
Works with Delphi 7 in same machine.
Thanks.
Hi AB, then, is there a way of validate and return all errors found to client with the current built-in validators? the client is not Delphi is Kendo UI Web. The code that I put in this thread works for I need, but if there is something more simple would be better.
Yes, I know and I used part of TSynFilterOrValidate, but when the errors are many the validation abort on the first fail.
I forgot put this code. In this code I can validate comparing to fields in the same record (maybe I can make the same inheriting from TSynFilterOrValidate ?):
TSQLCategory = class(TSQLRecord)
private
fDescription: RawUTF8;
fTime: TModTime;
fSQLCategory: TSQLCategory;
fLanguage: TSQLLanguage;
fSortOrder: Integer;
fStatus: TSQLStatus;
fImage: TSQLImage;
public
function ValidateAll(aRest: TSQLRest; const aFields: TSQLFieldBits=[0..MAX_SQLFIELDS-1]): string; override;
published
property ModTime: TModTime read fTime write fTime;
property Description: RawUTF8 read fDescription write fDescription;
property Language: TSQLLanguage read fLanguage write fLanguage;
property SortOrder: Integer read fSortOrder write fSortOrder;
property Image: TSQLImage read fImage write fImage;
property Status: TSQLStatus read fStatus write fStatus;
property Parent: TSQLCategory read fSQLCategory write fSQLCategory;
end;
implementation
{ TSQLCategory }
function TSQLCategory.ValidateAll(aRest: TSQLRest;
const aFields: TSQLFieldBits): string;
var
lParent: TSQLPropInfo;
lResultID: Integer;
lResultParent: RawUTF8;
lFieldName: PUTF8Char;
begin
Result := inherited;
lResultID := GetID;
lResultParent := '';
if (lResultID > 0) then
begin
lFieldName := 'Parent';
lParent := RecordProps.Fields.ByName(lFieldName);
if (lParent <> nil) then
lParent.GetValueVar(self, False, lResultParent, nil);
if (lResultID = StrToCurrency(Pointer(lResultParent))) then
with TTextWriter.CreateOwnedStream do
begin
if (Result <> '') then
AddShort(',');
AddShort('{"error":"');
AddJSONEscapeString('Category and subcategory cannot be the same');
AddShort('","fieldName":"');
AddJSONEscapeString('Parent');
AddShort('","fieldIndex":');
Add(lParent.PropertyIndex);
AddShort('}');
Result := Result + UTF8ToString(Text);
Free;
end;
end;
end;All errors detected reported once. I saw this behavior in Java Spring Framework.
TIA.
Esteban.
Hi,
First of all, here, in Argentina, we say that Carlos Gardel (tango singer legendary) "Cada día canta mejor/Every day sings better" and I can say that mORMot framework "Every day is better".
After a time I came back to use/try mORMot framework, and time ago I did make changes to the framework implementing server side validation. I want share this changes to know if this the best way and can be part of framework. I want server side validation thinking in web client application where the request (data, not authentication) can be changed despite the local validations (I know this is a paranoiac defensive programming) and I want return all errors where the error description and the field index are reported, in this way request round trip is saved when there are many errors. Also this information is used for display the error message and highlighting the field in the user interface.
Ok, these are the changes, all in mORMot.pas:
TSQLRecord = ...
...
protected
...
function Validate(aRest: TSQLRest; const aFields: array of RawUTF8; // just as reference in the code
aInvalidFieldIndex: PInteger=nil): string; overload;
//>>EMartin: Validate fields and return error list. Invoked from server side validate.
function ValidateAll(aRest: TSQLRest; const aFields: TSQLFieldBits=[0..MAX_SQLFIELDS-1]): string; overload; virtual;
//<<EMartin
...
end;
...
TSQLRestServer = ...
private
...
fPublishedMethods: TDynArrayHashed; // just as reference in the code
fServerSideValidate: Boolean; //EMartin
protected
...
function InternalUpdateEvent(aEvent: TSQLEvent; aTable: TSQLRecordClass; aID: integer; // just as reference in the code
aIsBlobFields: PSQLFieldBits): boolean; virtual;
///>>EMartin: this method will validate logic business rules on TSQLRecord from server side
function Validate(const aURI: TSQLRestServerURIContext): Boolean; virtual;
//<<EMartin
public
...
property SessionClass: TAuthSessionClass read fSessionClass write fSessionClass; // just as reference in the code
//>>EMartin: this property indicates whether execute TSQLValidate from server
// side
property ServerSideValidate: Boolean read fServerSideValidate write fServerSideValidate;
//<<EMartin
...
end;
...
implementation
...
//>>EMartin
function TSQLRecord.ValidateAll(aRest: TSQLRest; const aFields: TSQLFieldBits=[0..MAX_SQLFIELDS-1]): string;
var f, i: integer;
Value: RawUTF8;
Validate: TSynValidate;
ValidateRest: TSynValidateRest absolute Validate;
ErrMsg: String;
Writer: TTextWriter;
begin
result := '';
if (self=nil) or IsZero(aFields) then
// avoid GPF and handle case if no field was selected
exit;
Writer := TTextWriter.CreateOwnedStream;
with RecordProps do
for f := 0 to Fields.Count-1 do
if Fields.List[f].SQLFieldType in COPIABLE_FIELDS then begin
if (Filters<>nil) and (Filters[f]<>nil) then
for i := 0 to Filters[f].Count-1 do begin
Validate := TSynValidate(Filters[f].List[i]);
if Validate.InheritsFrom(TSynValidate) then begin
if Value='' then
Fields.List[f].GetValueVar(self,false,Value,nil);
if Validate.InheritsFrom(TSynValidateRest) then begin
// set additional parameters
ValidateRest.fProcessRec := self;
ValidateRest.fProcessRest := aRest;
end;
if not Validate.Process(f,Value,ErrMsg) then begin
// TSynValidate process failed -> add error to list
if ErrMsg='' then
// no custom message -> show a default message
ErrMsg := format(sValidationFailed,[
GetCaptionFromClass(Validate.ClassType)]);
with Writer do
begin
if (Text <> '') then
AddShort(',');
AddShort('{"error":"');
AddJSONEscapeString(ErrMsg);
AddShort('","fieldName":"');
AddJSONEscape(pointer(Fields.List[f].Name));
AddShort('","fieldIndex":');
Add(f);
AddShort('}');
end;
end;
end;
end;
Value := '';
end;
Writer.SetText(Value);
Writer.Free;
Result := UTF8ToString(Value);
end;
//<<EMartin
...
TSQLRestServerURIContext.ExecuteORMWrite ...
...
begin
...
// here, Table<>nil and TableIndex in [0..MAX_SQLTABLES-1]
if not (TableIndex in Call.RestAccessRights^.POST) then // check User
Call.OutStatus := HTML_NOTALLOWED else
if (Server.ServerSideValidate and not Server.Validate(Self)) then //EMartin Call.outBody will contain error messages
Call.OutStatus := HTML_BADREQUEST else //EMartin
...
// PUT ModelRoot/TableName/ID[/BlobFieldName] to update member/BLOB content
if not (TableIndex in Call.RestAccessRights^.PUT) then // check User
Call.OutStatus := HTML_NOTALLOWED else
if not Server.RecordCanBeUpdated(Table,ID,seUpdate,@CustomErrorMsg) then
Call.OutStatus := HTML_NOTMODIFIED else
if (Server.ServerSideValidate and not Server.Validate(Self)) then //EMartin Call.outBody will contain error messages
Call.OutStatus := HTML_BADREQUEST else begin //EMartin browser will treat as error
...
// ModelRoot/TableName/ID to delete a member
if not (TableIndex in Call.RestAccessRights^.DELETE) then // check User
Call.OutStatus := HTML_NOTALLOWED else
if not Server.RecordCanBeUpdated(Table,ID,seDelete,@CustomErrorMsg)
or (Server.ServerSideValidate and not Server.Validate(Self)) then //EMartin Call.outBody will contain error messages
Call.OutStatus := HTML_BADREQUEST else begin
...
end;
...
//>>EMartin
function TSQLRestServer.Validate(const aURI: TSQLRestServerURIContext): Boolean;
var
SQLRec: TSQLRecord;
Decoder: TJSONObjectDecoder;
I: Integer;
ModifiedFields: TSQLFieldBits;
lErrors: String;
begin
Result := True;
if (aURI.Call^.InBody = '') then
Exit;
SQLRec := aURI.Table.Create;
try
Retrieve(aURI.ID, SQLRec);
Decoder.Decode(aURI.Call^.InBody,nil,pNonQuoted);
for I := Low(Decoder.FieldNames) to High(Decoder.FieldNames) do
if (SQLRec.GetFieldValue(Decoder.FieldNames[i]) <> Decoder.FieldValues[i]) then
begin
SQLRec.SetFieldValue(Decoder.FieldNames[i], Pointer(Decoder.FieldValues[i]));
Include(ModifiedFields, I);
end;
if (ModifiedFields <> []) then
begin
lErrors := SQLRec.ValidateAll(Self, ModifiedFields);
Result := (lErrors = '');
if not Result then
with TTextWriter.CreateOwnedStream do
begin
AddShort('{"errors":[');
AddString(StringToUTF8(lErrors));
AddShort(']}');
SetText(aURI.Call^.OutBody);
Free;
end;
end;
finally
SQLRec.Free;
end;
end;
//<<EMartin
...
With
TSQLServerRest.ServerSideValidate := true;the server side validation is enabled and when validation fail this is the response:
{"errors":[{"error":"Expect at least 5 characters","fieldName":"Description","fieldIndex":1},{"error":"Category and subcategory cannot be the same","fieldName":"Parent","fieldIndex":6}]}I did make the changes with the revision 8d9f29394d60ee87.
That's all.
Best regards.
Esteban.
I've declared TSQLHttpServer.Request method as virtual, to allow easiest, like direct file sending to the clients.
See http://synopse.info/fossil/info/cf89e5be3a
This is a good idea.
Excellent, I'll update that revision.
Be warned that FileExists() can be slow, since it calls slow Windows API.
Having an in-memory hashed list of files, sounds like a better idea, about performance.
I know, I'll use GetAttributesEx API Windows initially and then I'll use a hashed list.
Thanks for all.
I solved the problem with a more simple modification, not the ideal (I touched mORMot source code) but it works perfectly. I modified in mORMotHttpServer.pas.TSQLHttpServer.Request adding the directive virtual. Then I implemented a custom class:
TSQLHttpServerWeb = class(TSQLHttpServer)
protected
function Request(const InURL, InMethod, InHeaders, InContent, InContentType: RawByteString;
out OutContent, OutContentType, OutCustomHeader: RawByteString): cardinal; override;
end;
...
function TSQLHttpServerWeb.Request(const InURL, InMethod, InHeaders, InContent,
InContentType: RawByteString; out OutContent, OutContentType,
OutCustomHeader: RawByteString): cardinal;
var
FileName: TFileName;
FN, lPath: RawUTF8;
begin
if not IdemPChar(pointer(InURL),'/W') then // W is the root
begin
result := 404;
exit;
end;
lPath := StringReplaceChars(UrlDecode(copy(InURL, 1, 3)),'/','\');
while (lPath <> '') and (lPath[1]='\') do
Delete(lPath, 1, 1);
FN := StringReplaceChars(UrlDecode(copy(InURL,4,maxInt)),'/','\');
while (FN<>'') and (FN[1]='\') do
delete(FN,1,1);
while (FN<>'') and (FN[length(FN)]='\') do
delete(FN,length(FN),1);
FileName := UTF8ToString(FN);
if FileExists(FileName) then
begin
OutContent := StringToUTF8(FileName);
OutContentType := HTTP_RESP_STATICFILE;
result := HTML_SUCCESS; // THttpApiServer.Execute will return 404 if not found
end
else
inherited; // invoke the TSQLHttpServer.Request for services.
end;Now, I will implement services using interfaces for retrieve/update data that will be invoked from client web page through REST.
Thank you very much for your quick answers.
Interface based services or method based services used to return web pages and supplementary files inside the page is not my preferred method of web server. For example this is the URL used for get a page:
http://localhost:888/service/GetFile?F=html\TVWebChat.htmlHow can I get the referenced files inside the page ? For example:
<script src="js/jquery-1.8.3.min.js" type="text/javascript"></script>This is I need, the exception of the interface based services is not a problem, I know that it works with normal purposes.
I tried Delphi On Rails (another nice framework) but mORMot has much more features.
Summarizing, how I do a TSQLRestServer serving static files and to use interface based services from a web client (I am using JQuery) ?
Ok, I did it works, but I am in the same point that with THttpApiServer, the referenced files inside web page (.css, .js, etc.) are not processed, that is, not sent to client browser and the displayed page is incomplete. Any other ideas ?
The other hand, I tried to use interfaced services but access violation in RecordClear function before invoke the implemented function service, sure I am missing something.
Thanks.
Yes, I did with this example to serve a file index.html, but after that I need request data through REST JSON, but THttpApiServer has not implemented the RESTFul JSON logic that has TSQLHttpServer. That is, TSQLHttpServer.Request method manage the GET/PUT/DELETE/etc. but not static files and THttpApiServer manage static files but not GET/PUT/DELETE/etc.
Thank.
Ok, I will rephrase my question. How, with a THttpSQLServer, I can serve a web page, that is, ie. an index.html ?
I tried this but the request never reach the OnRequest event that I implemented, only the first time.
Thanks.
PD: excellent framework and much better forum.
I have saw to both. I make with sample 09 serve a web page, but I don't know how invoke REST JSON from a web client against a THttpApiServer ? This server not process REST request and TSQLHttpServer yes.
Thanks.
Hi,
I am newbie with this great framework. My problem is that I want to use TSQLHttpServer for serving web pages and make use of the mORMot REST protocol. How I can implement the TSQLHttpServer serving web pages as well as REST protocol ? My idea is make with mORMot a full web server (not IIS/Apache). Any example will be wonderful ![]()
Apologies for my english and thanks in advance.
Esteban