#301 mORMot 1 » TSQLDBStatement.Bind » 2015-02-22 12:52:59

EMartin
Replies: 5

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.

#302 Re: mORMot 1 » multipart/form-data not implemented yet » 2015-02-19 15:03:09

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;

#303 Re: mORMot 1 » multipart/form-data not implemented yet » 2015-02-19 00:55:53

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.

#304 Re: mORMot 1 » multipart/form-data not implemented yet » 2015-02-18 21:40:29

@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.

#305 Re: mORMot 1 » secSSL and RootRedirect » 2015-01-29 15:04:44

To me the RootRedirect did not work when the URL already registered (with secSSL and without secSSL), check again registered URL list.

#306 Re: mORMot 1 » secSSL how to connect ? » 2015-01-29 13:29:02

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 urlacl

and 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.

#307 Re: mORMot 1 » OnHttpThreadTerminate event » 2015-01-20 19:58:54

The "exit" command always execute the finally section when is inside of the try ... finally.

#308 Re: SynProject » GraphiViz? » 2015-01-19 22:15:37

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.

#309 Re: mORMot 1 » [JSON] New behavior of _json » 2015-01-07 18:22:25

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.

#310 Re: mORMot 1 » ZEOS + Firebird + Update-Problem » 2014-11-26 20:21:35

@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.

#311 Re: mORMot 1 » ZEOS + Firebird + Update-Problem » 2014-11-26 14:43:56

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.

#312 Re: mORMot 1 » Data replication in a multiple master environment » 2014-11-14 11:21:07

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.

#313 Re: mORMot 1 » Debugging with TWinHTTP and SSL certificates » 2014-10-21 22:01:55

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.

#314 Re: mORMot 1 » Debugging with TWinHTTP and SSL certificates » 2014-10-21 20:35:03

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.

#315 Re: mORMot 1 » Debugging with TWinHTTP and SSL certificates » 2014-10-21 17:23:28

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.

#316 Re: mORMot 1 » Debugging with TWinHTTP and SSL certificates » 2014-10-21 15:21:24

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.

#317 Re: mORMot 1 » Debugging with TWinHTTP and SSL certificates » 2014-10-21 13:47:02

Pardon and my sincerest apologies.

I'll have into account its recommendations.

Thanks.

#318 Re: mORMot 1 » ISO-8601 timezone » 2014-09-26 18:36:33

@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

#319 mORMot 1 » ISO-8601 timezone » 2014-09-25 21:03:56

EMartin
Replies: 8

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.

#320 Re: mORMot 1 » ZEOS + Firebird + Update-Problem » 2014-09-02 16:00:05

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.

#321 Re: mORMot 1 » ZEOS + Firebird + Update-Problem » 2014-09-02 15:49:12

@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.

#322 Re: mORMot 1 » ZEOS + Firebird + Update-Problem » 2014-09-02 13:46:04

@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.

#323 Re: mORMot 1 » ZEOS + Firebird + Update-Problem » 2014-09-02 10:43:05

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.

#325 Re: mORMot 1 » Thread-safety of mORMot » 2013-11-27 12:15:24

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.

#326 Re: mORMot 1 » Server side validation returning all errors » 2013-11-27 11:03:40

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.

#327 Re: mORMot 1 » Server side validation returning all errors » 2013-11-26 15:15:00

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.

#328 mORMot 1 » Server side validation returning all errors » 2013-11-26 12:46:21

EMartin
Replies: 4

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.

#329 Re: mORMot 1 » Using TSQLHttpServer with RESTFull/Web Server » 2013-01-18 16:52:04

Administrator wrote:

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.

Administrator wrote:

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.

#330 Re: mORMot 1 » Using TSQLHttpServer with RESTFull/Web Server » 2013-01-18 10:28:52

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.

#331 Re: mORMot 1 » Using TSQLHttpServer with RESTFull/Web Server » 2013-01-17 21:44:32

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.html

How 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) ?

#332 Re: mORMot 1 » Using TSQLHttpServer with RESTFull/Web Server » 2013-01-17 20:34:53

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.

#333 Re: mORMot 1 » Using TSQLHttpServer with RESTFull/Web Server » 2013-01-17 16:32:26

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.

#334 Re: mORMot 1 » Using TSQLHttpServer with RESTFull/Web Server » 2013-01-17 16:12:37

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.

#335 Re: mORMot 1 » Using TSQLHttpServer with RESTFull/Web Server » 2013-01-17 15:53:36

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.

#336 mORMot 1 » Using TSQLHttpServer with RESTFull/Web Server » 2013-01-17 15:25:54

EMartin
Replies: 19

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 smile

   Apologies for my english and thanks in advance.

Esteban

Board footer

Powered by FluxBB