-
-
Notifications
You must be signed in to change notification settings - Fork 135
/
Copy pathmormot.rest.client.pas
3161 lines (2916 loc) · 123 KB
/
mormot.rest.client.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// REpresentation State Tranfer (REST) Types and Classes on Client side
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.rest.client;
{
*****************************************************************************
Client-Side REST Process
- Client Authentication and Authorization Logic
- TRestClientRoutingRest/TRestClientRoutingJsonRpc Routing Schemes
- TRestClientUri Base Class for Actual Clients
- TRestClientLibraryRequest after TRestServer.ExportServerGlobalLibraryRequest
- TInterfacedCallback/TBlockingCallback Classes
*****************************************************************************
}
interface
{$I ..\mormot.defines.inc}
uses
sysutils,
classes,
variants,
contnrs,
{$ifdef DOMAINRESTAUTH}
mormot.lib.sspi, // do-nothing units on non compliant OS
mormot.lib.gssapi,
{$endif DOMAINRESTAUTH}
mormot.core.base,
mormot.core.os,
mormot.core.buffers,
mormot.core.unicode,
mormot.core.text,
mormot.core.datetime,
mormot.core.variants,
mormot.core.data,
mormot.core.rtti,
mormot.crypt.core,
mormot.core.json,
mormot.core.threads,
mormot.core.perf,
mormot.crypt.secure,
mormot.core.log,
mormot.core.interfaces,
mormot.orm.base,
mormot.orm.core,
mormot.orm.rest,
mormot.soa.core,
mormot.soa.client,
mormot.db.core,
mormot.rest.core;
{ ************ Client Authentication and Authorization Logic }
type
TRestClientUri = class;
/// used by TRestClientUri.Uri() to let the client ask for an User name
// and password, in order to retry authentication to the server
// - should return TRUE if aUserName and aPassword both contain some entered
// values to be sent for remote secure authentication
// - should return FALSE if the user pressed cancel or the number of Retry
// reached a defined limit
// - here input/output parameters are defined as plain string, to match the
// type expected by the client's User Interface, via UI properties, or
// e.g. from TLoginForm as defined in mORMotUILogin.pas unit
TOnAuthentificationFailed = function(Retry: integer;
var aUserName, aPassword: string; out aPasswordHashed: boolean): boolean of object;
/// define how TRestClientAuthentication.ClientSetUser() should interpret
// the supplied password
// - passClear means that the password is not encrypted, e.g. as entered
// by the user in the login screen
// - passHashed means that the passwod is already hashed as in
// TAuthUser.PasswordHashHexa i.e. Sha256('salt'+Value)
// - passKerberosSpn indicates that the password is the Kerberos SPN domain
TRestClientSetUserPassword = (
passClear,
passHashed,
passKerberosSpn);
/// algorithms known by TRestClientAuthenticationSignedUri and
// TRestServerAuthenticationSignedUri to digitaly compute the
// session_signature parameter value for a given URI
// - by default, suaCRC32 will compute fast but not cryptographically secure
// ! crc32(crc32(privatesalt, timestamp, 8), url, urllen)
// - suaCRC32C and suaXXHASH are similar non-cryptographic alternatives
// - but you can select other stronger alternatives, which result will be
// reduced to 32-bit hexadecimal - suaMD5 will be the fastest cryptographic
// hash available on all platforms (but if SHA-NI is available), for enhanced
// security, by calling e.g.
// ! (aServer.AuthenticationRegister(TRestClientAuthenticationDefault) as
// ! TRestServerAuthenticationDefault).Algorithm := suaMD5;
// - suaSHA1, suaSHA256, suaSHA512 and suaSHA3 will be slower, and may provide
// additional level of trust, depending on your requirements: note that
// since the hash is reduced to 32-bit resolution, those may not provide
// higher security than suaMD5 or suaSHA1
// - note that SynCrossPlatformRest clients only implements suaCRC32 yet
TRestAuthenticationSignedUriAlgo = (
suaCRC32,
suaCRC32C,
suaXXHASH,
suaMD5,
suaSHA1,
suaSHA256,
suaSHA512,
suaSHA3);
/// function prototype for TRestClientAuthenticationSignedUri and
// TRestServerAuthenticationSignedUri computation of the session_signature
// parameter value
TOnRestAuthenticationSignedUriComputeSignature = function(
privatesalt: cardinal; timestamp, url: PAnsiChar; urllen: integer): cardinal of object;
/// abstract class used to implement client-side authentication
// - inherit from this class to implement expected authentication scheme
TRestClientAuthentication = class
protected
/// abstract method which will be called by ClientSetUser() to process the
// authentication step on the client side
// - at call, a TAuthUser instance will be supplied, with LogonName set
// with aUserName and PasswordHashHexa with a SHA-256 hash of aPassword
// - override with the expected method, returning the session key on success
class function ClientComputeSessionKey(Sender: TRestClientUri;
User: TAuthUser): RawUtf8; virtual; abstract;
/// is called by ClientComputeSessionKey() overriden method to execute the
// root/Auth service with the supplied parameters, then retrieve and
// decode the "result": session key and any other values (e.g. "version")
class function ClientGetSessionKey(Sender: TRestClientUri; User: TAuthUser;
const aNameValueParameters: array of const): RawUtf8; virtual;
public
/// class method to be used on client side to create a remote session
// - call this method instead of TRestClientUri.SetUser() if you need
// a custom authentication class
// - if saoUserByLogonOrID is defined in the server Options, aUserName may
// be a TAuthUser.ID and not a TAuthUser.LogonName
// - if passClear is used, you may specify aHashSalt and aHashRound,
// to enable Pbkdf2HmacSha256() use instead of plain Sha256(), and increase
// security on storage side (reducing brute force attack via rainbow tables)
// - will call the ModelRoot/Auth service, i.e. call TRestServer.Auth()
// published method to create a session for this user
// - returns true on success
class function ClientSetUser(Sender: TRestClientUri;
const aUserName, aPassword: RawUtf8;
aPasswordKind: TRestClientSetUserPassword = passClear;
const aHashSalt: RawUtf8 = ''; aHashRound: integer = 20000): boolean; virtual;
/// class method to be called on client side to sign an URI
// - used by TRestClientUri.Uri()
// - shall match the method as expected by RetrieveSession() virtual method
class procedure ClientSessionSign(Sender: TRestClientUri;
var Call: TRestUriParams); virtual; abstract;
end;
/// class-reference type (metaclass) used to define an authentication scheme
TRestClientAuthenticationClass = class of TRestClientAuthentication;
/// weak authentication scheme using URL-level parameter
TRestClientAuthenticationUri = class(TRestClientAuthentication)
public
/// class method to be called on client side to add the SessionID to the URI
// - append '&session_signature=SessionID' to the url
class procedure ClientSessionSign(Sender: TRestClientUri;
var Call: TRestUriParams); override;
end;
/// secure authentication scheme using URL-level digital signature
// - default suaCRC32 format of session_signature is
// !Hexa8(SessionID)+
// !Hexa8(Timestamp)+
// !Hexa8(crc32(SessionID + HexaSessionPrivateKey +
// ! Sha256('salt' + PassWord) + Hexa8(Timestamp) + url))
TRestClientAuthenticationSignedUri = class(TRestClientAuthenticationUri)
protected
// class functions implementing TRestAuthenticationSignedUriAlgo
class function ComputeSignatureCrc32(privatesalt: cardinal;
timestamp, url: PAnsiChar; urllen: integer): cardinal;
class function ComputeSignatureCrc32c(privatesalt: cardinal;
timestamp, url: PAnsiChar; urllen: integer): cardinal;
class function ComputeSignaturexxHash(privatesalt: cardinal;
timestamp, url: PAnsiChar; urllen: integer): cardinal;
class function ComputeSignatureMd5(privatesalt: cardinal;
timestamp, url: PAnsiChar; urllen: integer): cardinal;
class function ComputeSignatureSha1(privatesalt: cardinal;
timestamp, url: PAnsiChar; urllen: integer): cardinal;
class function ComputeSignatureSha256(privatesalt: cardinal;
timestamp, url: PAnsiChar; urllen: integer): cardinal;
class function ComputeSignatureSha512(privatesalt: cardinal;
timestamp, url: PAnsiChar; urllen: integer): cardinal;
class function ComputeSignatureSha3(privatesalt: cardinal;
timestamp, url: PAnsiChar; urllen: integer): cardinal;
public
/// retrieve the method to compute the session_signature=.... value
class function GetComputeSignature(
algo: TRestAuthenticationSignedUriAlgo): TOnRestAuthenticationSignedUriComputeSignature;
/// class method to be called on client side to sign an URI
// - generate the digital signature as expected by overridden RetrieveSession()
// - timestamp resolution is about 256 ms in the current implementation
class procedure ClientSessionSign(Sender: TRestClientUri;
var Call: TRestUriParams); override;
end;
/// mORMot secure RESTful authentication scheme
// - match TRestServerAuthenticationDefault class on server side
// - this scheme will use a password stored via safe SHA-256 hashing in the
// TAuthUser ORM table
TRestClientAuthenticationDefault = class(TRestClientAuthenticationSignedUri)
protected
/// class method used on client side to create a remote session
// - will call the ModelRoot/Auth service, i.e. call TRestServer.Auth()
// published method to create a session for this user: so
// TRestServerAuthenticationDefault should be registered on server side
// - User.LogonName and User.PasswordHashHexa will be checked
class function ClientComputeSessionKey(Sender: TRestClientUri;
User: TAuthUser): RawUtf8; override;
end;
/// mORMot weak RESTful authentication scheme
// - this method will authenticate with a given username, but no signature
// - match TRestServerAuthenticationNone class on server side
// - on client side, this scheme is not called by TRestClientUri.SetUser()
// method - so you have to write:
// ! TRestClientAuthenticationNone.ClientSetUser(Client,'User','');
TRestClientAuthenticationNone = class(TRestClientAuthenticationUri)
protected
/// class method used on client side to create a remote session
// - will call the ModelRoot/Auth service, i.e. call TRestClient.Auth()
// published method to create a session for this user: so
// TRestServerAuthenticationNone should be registered on server side
// - will check User.LogonName, but User.PasswordHashHexa will be ignored
class function ClientComputeSessionKey(Sender: TRestClientUri;
User: TAuthUser): RawUtf8; override;
end;
/// abstract class for implementing HTTP authentication
// - do not use this abstract class, but e.g. TRestClientAuthenticationHttpBasic
// - this class will transmit the session_signature as HTTP cookie, not at
// URI level, so is expected to be used only from browsers or old clients
TRestClientAuthenticationHttpAbstract = class(TRestClientAuthentication)
protected
/// should be overriden according to the HTTP authentication scheme
class function ComputeAuthenticateHeader(
const aUserName,aPasswordClear: RawUtf8): RawUtf8; virtual; abstract;
public
/// class method to be called on client side to sign an URI in Auth Basic
// resolution is about 256 ms in the current implementation
// - set "Cookie: mORMot_session_signature=..." HTTP header
class procedure ClientSessionSign(Sender: TRestClientUri;
var Call: TRestUriParams); override;
/// class method to be used on client side to create a remote session
// - call TRestClientAuthenticationHttpBasic.ClientSetUser() instead of
// TRestClientUri.SetUser(), and never the method of this abstract class
// - needs the plain aPassword, so aPasswordKind should be passClear
// - returns true on success
class function ClientSetUser(Sender: TRestClientUri;
const aUserName, aPassword: RawUtf8;
aPasswordKind: TRestClientSetUserPassword = passClear;
const aHashSalt: RawUtf8 = ''; aHashRound: integer = 20000): boolean; override;
/// class method to be used on client side to force the HTTP header for
// the corresponding HTTP authentication, without creating any remote session
// - call virtual protected method ComputeAuthenticateHeader()
// - here the password should be given as clear content
// - potential use case is to use a mORMot client through a HTTPS proxy,
// e.g. with TRestClientAuthenticationHttpBasic authentication
// - then you can use TRestClientAuthentication*.ClientSetUser() to
// define any another "mORMot only" authentication
// - this method is also called by the ClientSetUser() method of this class
// for a full client + server authentication via HTTP
// TRestClientAuthenticationHttp*.ClientSetUser()
class procedure ClientSetUserHttpOnly(Sender: TRestClientUri;
const aUserName, aPasswordClear: RawUtf8); virtual;
end;
/// authentication using HTTP Basic scheme
// - this protocol send both name and password as clear (just Base64 encoded)
// so should only be used over TLS / HTTPS, or for compatibility reasons
// - match TRestServerAuthenticationHttpBasic class on server side
// - will rely on TRestClientAuthenticationNone for authorization
// - on client side, this scheme is not called by TRestClientUri.SetUser()
// method - so you have to write:
// ! TRestClientAuthenticationHttpBasic.ClientSetUser(Client,'User','password');
// - for a remote proxy-only authentication (without creating any mORMot
// session), you can write:
// ! TRestClientAuthenticationHttpBasic.ClientSetUserHttpOnly(Client,'proxyUser','proxyPass');
TRestClientAuthenticationHttpBasic = class(TRestClientAuthenticationHttpAbstract)
protected
/// this overriden method returns "Authorization: Basic ...." HTTP header
class function ComputeAuthenticateHeader(
const aUserName,aPasswordClear: RawUtf8): RawUtf8; override;
end;
{$ifdef DOMAINRESTAUTH}
{ will use mormot.lib.sspi/gssapi units depending on the OS }
/// authentication of the current logged user using Kerberos or NTLM
// - calling the Security Support Provider Interface (SSPI) API on Windows,
// or GSSAPI on Linux
// - is able to authenticate the currently logged user on the client side,
// using either NTLM (Windows only) or Kerberos - it will allow to safely
// authenticate on a mORMot server without prompting the user to enter its
// password
// - match TRestServerAuthenticationSspi class on server side
// - if ClientSetUser() receives aUserName as '', aPassword should be either
// '' if you expect NTLM authentication to take place, or contain the SPN
// registration (e.g. 'mymormotservice/myserver.mydomain.tld') for Kerberos
// authentication
// - if ClientSetUser() receives aUserName as 'DomainName\UserName', then
// authentication will take place on the specified domain, with aPassword
// as plain password value
// - WARNING: on MacOS, the default system GSSAPI stack seems to create a
// session-wide token (like kinit), not a transient token in memory - you
// may prefer to load a proper libgssapi_krb5.dylib instead
TRestClientAuthenticationSspi = class(TRestClientAuthenticationSignedUri)
protected
class function ClientComputeSessionKey(Sender: TRestClientUri;
User: TAuthUser): RawUtf8; override;
end;
{$endif DOMAINRESTAUTH}
/// store the information about the current session
// - as set after a successful TRestClientUri.SetUser() method
TRestClientSession = record
{$ifdef HASINLINE}
private
{$endif HASINLINE}
// for internal use
Authentication: TRestClientAuthenticationClass;
IDHexa8: RawUtf8;
PrivateKey: cardinal;
Data: RawByteString;
LastTick64: Int64;
{$ifdef HASINLINE}
public
{$endif HASINLINE}
/// the current user as set by SetUser() method
// - contains nil if no User is currently authenticated
// - once authenticated, a TAuthUser instance is set, with its ID,
// LogonName, DisplayName, PasswordHashHexa and GroupRights (filled with a
// TAuthGroup ID casted as a pointer) properties - you can retrieve any
// optional binary data associated with this user via RetrieveBlobFields()
User: TAuthUser;
/// the current session ID as set after a successful SetUser() method call
// - equals 0 (CONST_AUTHENTICATION_SESSION_NOT_STARTED) if the session
// is not started yet - i.e. if SetUser() call failed
// - equals 1 (CONST_AUTHENTICATION_NOT_USED) if authentication mode
// is not enabled - i.e. after a fresh Create() without SetUser() call
ID: cardinal;
/// access to the low-level HTTP header used for authentication
// - you can force here your own header, e.g. a JWT as authentication bearer
// or as in TRestClientAuthenticationHttpAbstract.ClientSetUserHttpOnlyUser
// - used e.g. by TRestClientAuthenticationHttpBasic
HttpHeader: RawUtf8;
/// the remote server executable name, as retrieved after a SetUser() success
Server: RawUtf8;
/// the remote server version, as retrieved after a SetUser() success
Version: RawUtf8;
/// the remote server session tiemout in minutes, as retrieved after
// a SetUser() success
// - will be used to set SessionHeartbeatSeconds default
ServerTimeout: integer;
/// frequency of Callback/_ping_ calls to maintain session and services
// - will be used to call SessionRenewEvent at the specified period, so that
// the session and all sicClientDriven instances will be maintained on the
// server side as long as the client connection stands
// - equals half SessionServerTimeout or 25 minutes (if lower) by default -
// 25 minutes matches the default service timeout of 30 minutes
// - you may set 0 to disable this SOA-level heartbeat feature
HeartbeatSeconds: integer;
end;
// backward compatibility types redirections
{$ifndef PUREMORMOT2}
TSqlRestServerAuthenticationClientSetUserPassword = TRestClientSetUserPassword;
TSqlRestServerAuthenticationSignedUriAlgo = TRestAuthenticationSignedUriAlgo;
TSqlRestServerAuthenticationSignedUriComputeSignature =
TOnRestAuthenticationSignedUriComputeSignature;
// TRestServerAuthentication* classes have client-side only corresponding
// types named as TRestClientAuthentication*
{$endif PUREMORMOT2}
{ ************ TRestClientRoutingRest/TRestClientRoutingJsonRpc Routing Schemes }
//// used to customize TRestClientRouting.ClientSideInvoke process
TRestClientSideInvoke = set of (
csiAsOctetStream);
/// abstract Client side service routing
// - match TRestServerUriContext reciprocal class
// - never use this abstract class, but rather TRestClientRoutingRest or
// TRestClientRoutingJsonRpc classes
TRestClientRouting = class
public
/// at Client Side, compute URI and BODY according to the routing scheme
// - abstract implementation which is to be overridden
// - as input, method should be the method name to be executed,
// params should contain the incoming parameters as JSON CSV (without []),
// and clientDriven ID should contain the optional Client ID value
// - at output, should update the HTTP uri corresponding to the proper
// routing, and should return the corresponding HTTP body/headers within
// sent/head parameters
class procedure ClientSideInvoke(var uri: RawUtf8;
ctxt: TRestClientSideInvoke;
const method, params, clientDrivenID: RawUtf8;
out sent, head: RawUtf8); virtual; abstract;
/// at Client Side, adjust STATUS and BODY according to the routing scheme
// - this method does nothing by default
// - could be used to adapt to custom BODY output, e.g. on error handling
class procedure ClientSideInvoked(const uri: RawUtf8;
ctxt: TRestClientSideInvoke;
const method, params, clientDrivenID: RawUtf8;
var resp, head: RawUtf8; var status: integer); virtual;
/// could be overriden to notify advances routing features
// - default returns [] but TRestClientRoutingRest includes csiAsOctetStream
class function Supports: TRestClientSideInvoke; virtual;
end;
/// class used to define the Client side expected routing
// - match TRestServerUriContextClass reciprocal meta-class
// - most of the internal methods are declared as virtual, so it allows any
// kind of custom routing or execution scheme
// - TRestClientRoutingRest and TRestClientRoutingJsonRpc classes are provided
// in this unit, to allow RESTful and JSON/RPC protocols on Client side
// - you can retrieve the client class from the reciprocal server-side class
// using TRestServerUriContext.ClientRouting class method
TRestClientRoutingClass = class of TRestClientRouting;
/// client calling context using simple REST for interface-based services
// - match TRestServerRoutingRest reciprocal class
TRestClientRoutingRest = class(TRestClientRouting)
/// at Client Side, compute URI and BODY according to RESTful routing scheme
// - e.g. on input uri='root/Calculator', method='Add', params='1,2' and
// clientDrivenID='1234' -> on output uri='root/Calculator.Add/1234' and
// sent='[1,2]'
class procedure ClientSideInvoke(var uri: RawUtf8;
ctxt: TRestClientSideInvoke;
const method, params, clientDrivenID: RawUtf8;
out sent, head: RawUtf8); override;
/// overriden to include csiAsOctetStream
class function Supports: TRestClientSideInvoke; override;
end;
/// client calling context using simple REST for interface-based services
// - match TRestServerRoutingJsonRpc reciprocal class
TRestClientRoutingJsonRpc = class(TRestClientRouting)
/// at Client Side, compute URI and BODY according to JSON/RPC routing scheme
// - e.g. on input uri='root/Calculator', method='Add', params='1,2' and
// clientDrivenID='1234' -> on output uri='root/Calculator' and
// sent={"method":"Add","params":[1,2],"id":1234}
class procedure ClientSideInvoke(var uri: RawUtf8;
ctxt: TRestClientSideInvoke;
const method, params, clientDrivenID: RawUtf8;
out sent, head: RawUtf8); override;
end;
// backward compatibility types redirections
{$ifndef PUREMORMOT2}
TSqlRestServerUriContextClientInvoke = TRestClientSideInvoke;
{$endif PUREMORMOT2}
{ ************ TRestClientUri Base Class for Actual Clients }
/// called by TRestClientUri.Uri() when an error occurred
// - so that you may have a single entry point for all client-side issues
// - information will be available in Sender's LastErrorCode and
// LastErrorMessage properties
// - if the error comes from an Exception, it will be supplied as parameter
// - the REST context (if any) will be supplied within the Call parameter,
// and in this case Call^.OutStatus=HTTP_CLIENTERROR indicates a broken
// connection
TOnClientFailed = procedure(Sender: TRestClientUri; E: Exception;
Call: PRestUriParams) of object;
/// called by TRestClientUri.Uri() to notify some step
// - e.g. as OnError Event when an error Call.OutStatus was returned
// - return false to abort and fail the request
// - as OnError with 403, could return true to retry after changing e.g.
// Sender.SessionHttpHeader with a renewed JWT bearer
// - as OnBeforeCall or OnAfterCall, should return true to continue
TOnClientCall = function(Sender: TRestClientUri;
var Call: TRestUriParams): boolean of object;
/// signature e.g. of the TRestClientUri.OnSetUser event handler
TOnClientNotify = procedure(Sender: TRestClientUri) of object;
/// 31-bit positive identifier of an interface callback, as sent to the server
TRestClientCallbackID = type integer;
/// store information about registered interface callbacks
TRestClientCallbackItem = record
/// the identifier of the callback, as sent to the server side
// - computed from TRestClientUriCallbacks.fCurrentID counter
ID: TRestClientCallbackID;
/// set to TRUE if the instance was released from the server
ReleasedFromServer: boolean;
/// weak pointer typecast to the associated IInvokable variable
Instance: pointer;
//// information about the associated IInvokable
Factory: TInterfaceFactory;
end;
/// points to information about registered interface callbacks
PRestClientCallbackItem = ^TRestClientCallbackItem;
/// store the references to active interface callbacks on a REST Client
TRestClientCallbacks = class(TSynPersistent)
protected
fSafe: TLightLock; // very unlikely to have contention on client side
fCurrentID: integer; // thread-safe TRestClientCallbackID sequence generator
function UnRegisterByIndex(index: integer): boolean;
public
/// the associated REST instance
Owner: TRestClientUri;
/// how many callbacks are registered
Count: integer;
/// list of registered interface callbacks
List: array of TRestClientCallbackItem;
/// initialize the storage list
constructor Create(aOwner: TRestClientUri); reintroduce;
/// register a callback event interface instance from a new computed ID
function DoRegister(aInstance: pointer;
aFactory: TInterfaceFactory): TRestClientCallbackID; overload;
/// register a callback event interface instance from its supplied ID
procedure DoRegister(aID: TRestClientCallbackID; aInstance: pointer;
aFactory: TInterfaceFactory); overload;
/// delete all callback events from the internal list, as specified by its instance
// - note that the same IInvokable instance may be registered for several IDs
function UnRegister(aInstance: pointer): boolean; overload;
/// find the index of the ID in the internal list
// - warning: this method should be called within Safe.Lock/Safe.Unlock
function FindIndex(aID: TRestClientCallbackID): PtrInt;
/// find a matching callback
// - will call FindIndex(aItem.ID) within Safe.Lock/Safe.Unlock
// - returns TRUE if aItem.ID was found and aItem filled, FALSE otherwise
function FindEntry(var aItem: TRestClientCallbackItem): boolean;
/// find a matching entry
// - will call FindIndex(aID) within Safe.Lock/Safe.Unlock
// - returns TRUE if aID was found and aInstance/aFactory set, FALSE otherwise
function FindAndRelease(aID: TRestClientCallbackID): boolean;
end;
/// abstract REpresentational State Transfer (REST) client with URI
// - URI are standard Collection/Member implemented as ModelRoot/TableName/TableID
// - handle RESTful commands GET POST PUT DELETE LOCK UNLOCK
// - never call this abstract class, but inherit and override the
// InternalUri/InternalIsOpen/InternalOpen/InternalClose virtual abstract methods
// - do NOT use this abstract class, but one of its fully implemented children
TRestClientUri = class(TRest)
protected
fClient: IRestOrmClient;
fSession: TRestClientSession;
fComputeSignature: TOnRestAuthenticationSignedUriComputeSignature;
fOnConnected: TOnClientNotify;
fOnConnectionFailed: TOnClientFailed;
fOnIdle: TOnIdleSynBackgroundThread;
fOnBeforeCall, fOnAfterCall, fOnError: TOnClientCall;
fOnFailed: TOnClientFailed;
fOnAuthentificationFailed: TOnAuthentificationFailed;
fOnSetUser: TOnClientNotify;
fBackgroundThread: TSynBackgroundThreadEvent;
fConnectRetrySeconds: integer; // used by IsOpen
fMaximumAuthentificationRetry: integer;
fRetryOnceOnTimeout: boolean;
fServiceRoutingSupports: TRestClientSideInvoke;
fInternalState: set of (isDestroying, isInAuth, isClientError);
fLastErrorCode: integer;
fLastErrorMessage: RawUtf8;
fLastErrorException: ExceptClass;
fSafe: IAutoLocker; // to make the Uri() method thread-safe
fRemoteLogClass: TSynLog;
fRemoteLogThread: TObject; // private TRemoteLogThread
fRemoteLogOwnedByFamily: boolean;
fServicesRouting: TRestClientRoutingClass;
fServicePublishOwnInterfaces: RawUtf8;
fFakeCallbacks: TRestClientCallbacks;
{$ifdef OSWINDOWS}
fServiceNotificationMethodViaMessages: record
Wnd: HWND;
Msg: cardinal;
end;
{$endif OSWINDOWS}
procedure SetRoutingClass(aServicesRouting: TRestClientRoutingClass);
procedure SetSessionHeartbeatSeconds(timeout: integer);
function GetOnIdleBackgroundThreadActive: boolean;
procedure OnBackgroundProcess(Sender: TSynBackgroundThreadEvent;
ProcessOpaqueParam: pointer);
procedure SetLastException(E: Exception = nil; ErrorCode: integer = HTTP_BADREQUEST;
Call: PRestUriParams = nil);
function InternalRemoteLogSend(const aText: RawUtf8): boolean;
procedure InternalNotificationMethodExecute(var Ctxt: TRestUriParams); virtual;
// will call timestamp/info if the session has currently not been retrieved
function GetSessionVersion: RawUtf8;
// register the user session to the TRestClientUri instance
function SessionCreate(aAuth: TRestClientAuthenticationClass;
var aUser: TAuthUser; const aSessionKey: RawUtf8): boolean;
// call each fSession.HeartbeatSeconds delay
procedure SessionRenewEvent(Sender: TSynBackgroundTimer; const Msg: RawUtf8);
/// abstract methods to be implemented with a local, piped or HTTP/1.1 provider
// - you can specify some POST/PUT data in Call.OutBody (leave '' otherwise)
// - return the execution result in Call.OutStatus
// - for clients, RestAccessRights is never used
procedure InternalUri(var Call: TRestUriParams); virtual; abstract;
/// overridden protected method shall check if client is connected
function InternalIsOpen: boolean; virtual; abstract;
/// overridden protected method shall reopen the connectio
procedure InternalOpen; virtual; abstract;
/// overridden protected method shall force the connection to be closed,
// - a next call to IsOpen method shall re-open the connection
procedure InternalClose; virtual; abstract;
{$ifndef PUREMORMOT2}
// backward compatibility redirections to the homonymous IRestOrmClient methods
// see IRestOrmClient documentation for the proper use information
public
function Refresh(aID: TID; Value: TOrm; var Refreshed: boolean): boolean;
function List(const Tables: array of TOrmClass; const SqlSelect: RawUtf8 = 'RowID';
const SqlWhere: RawUtf8 = ''): TOrmTable;
function ListFmt(const Tables: array of TOrmClass;
const SqlSelect, SqlWhereFormat: RawUtf8; const Args: array of const): TOrmTable; overload;
function ListFmt(const Tables: array of TOrmClass;
const SqlSelect, SqlWhereFormat: RawUtf8; const Args, Bounds: array of const): TOrmTable; overload;
function TransactionBeginRetry(aTable: TOrmClass; Retries: integer = 10): boolean;
function BatchStart(aTable: TOrmClass;
AutomaticTransactionPerRow: cardinal = 0;
Options: TRestBatchOptions = []): boolean;
function BatchStartAny(AutomaticTransactionPerRow: cardinal;
Options: TRestBatchOptions = []): boolean;
function BatchAdd(Value: TOrm; SendData: boolean; ForceID: boolean = false;
const CustomFields: TFieldBits = []): integer;
function BatchUpdate(Value: TOrm; const CustomFields: TFieldBits = [];
DoNotAutoComputeFields: boolean = false): integer;
function BatchDelete(ID: TID): integer; overload;
function BatchDelete(Table: TOrmClass; ID: TID): integer; overload;
function BatchCount: integer;
function BatchSend(var Results: TIDDynArray): integer; overload;
procedure BatchAbort;
{$endif PUREMORMOT2}
public
/// initialize REST client instance
constructor Create(aModel: TOrmModel); override;
/// initialize REST client instance from a TSynConnectionDefinition
constructor RegisteredClassCreateFrom(aModel: TOrmModel;
aDefinition: TSynConnectionDefinition;
aServerHandleAuthentication: boolean); override;
/// release memory and close client connection
// - also unlock all still locked records by this client
destructor Destroy; override;
/// called by TRestOrm.Create overriden constructor to set fOrm from IRestOrm
procedure SetOrmInstance(aORM: TRestOrmParent); override;
/// save the TRestClientUri properties into a persistent storage object
// - CreateFrom() will expect Definition.UserName/Password to store the
// credentials which will be used by SetUser()
procedure DefinitionTo(Definition: TSynConnectionDefinition); override;
/// check if connected to the server, or try to (re)create the connection
// - convenient wrapper around InternalIsOpen and InternalOpen virtual methods
// - return TRUE on success, FALSE on any connection error
// - follows ConnectRetrySeconds property for optional retrial
// - calls OnConnected/OnConnectionFailed events if set
function IsOpen: boolean; virtual;
/// main method calling the remote Server via a RESTful command
// - redirect to the InternalUri() abstract method, which should be
// overridden for local, pipe, HTTP/1.1 or WebSockets actual communication
// - this method will sign the url with the appropriate digital signature
// according to the current SessionUser property
// - this method will retry the connection in case of authentication failure
// (i.e. if the session was closed by the remote server, for any reason -
// mostly a time out) if the OnAuthentificationFailed event handler is set
function Uri(const url, method: RawUtf8; Resp: PRawUtf8 = nil;
Head: PRawUtf8 = nil; SendData: PRawUtf8 = nil;
OutInternalState: PCardinal = nil): integer;
/// wrapper to the protected URI method to call a method on the server, using
// a ModelRoot/[TableName/[ID/]]MethodName RESTful GET request
// - returns the HTTP error code (e.g. 200/HTTP_SUCCESS on success)
// - this version will use a GET with supplied parameters (which will be
// encoded with the URL), and append the expected signature (if needed)
function CallBackGet(const aMethodName: RawUtf8;
const aNameValueParameters: array of const;
out aResponse: RawUtf8; aTable: TOrmClass = nil; aID: TID = 0;
aResponseHead: PRawUtf8 = nil): integer;
/// wrapper to the protected URI method to call a method on the server, using
// a ModelRoot/[TableName/[ID/]]MethodName RESTful GET request
// - returns the UTF-8 decoded JSON result (server must reply with one
// "result":"value" JSON object)
// - this version will use a GET with supplied parameters (which will be
// encoded with the URL), and append the expected signature (if needed)
function CallBackGetResult(const aMethodName: RawUtf8;
const aNameValueParameters: array of const;
aTable: TOrmClass = nil; aID: TID = 0): RawUtf8;
/// wrapper to the protected URI method to call a method on the server, using
// a ModelRoot/[TableName/[ID/]]MethodName RESTful PUT request
// - returns the HTTP error code (e.g. 200/HTTP_SUCCESS on success)
// - this version will use a PUT with the supplied raw UTF-8 data
function CallBackPut(const aMethodName, aSentData: RawUtf8;
out aResponse: RawUtf8; aTable: TOrmClass = nil; aID: TID = 0;
aResponseHead: PRawUtf8 = nil): integer;
/// wrapper to the protected URI method to call a method on the server, using
// a ModelRoot/[TableName/[ID/]]MethodName RESTful with any kind of request
// - returns the HTTP error code (e.g. 200/HTTP_SUCCESS on success)
// - for GET/PUT methods, you should better use CallBackGet/CallBackPut
function CallBack(method: TUriMethod; const aMethodName,aSentData: RawUtf8;
out aResponse: RawUtf8; aTable: TOrmClass = nil; aID: TID = 0;
aResponseHead: PRawUtf8 = nil): integer;
/// to be called before CallBack() if the client could ignore the answer
// - do nothing by default, but overriden e.g. in TRestHttpClientWebsockets
procedure CallbackNonBlockingSetHeader(out Header: RawUtf8); virtual;
/// access or initialize the internal IoC resolver, used for interface-based
// remote services, and more generaly any Services.Resolve() call
// - create and initialize the internal TServiceContainerClient if no
// service interface has been registered yet
// - may be used to inject some dependencies, which are not interface-based
// remote services, but internal IoC, without the ServiceRegister()
// or ServiceDefine() methods - e.g.
// ! aRest.ServiceContainer.InjectResolver([TInfraRepoUserFactory.Create(aRest)],true);
function ServiceContainer: TServiceContainer; override;
/// register one or several Services on the client side via their interfaces
// - this methods expects a list of interfaces to be registered to the client
// (e.g. [TypeInfo(IMyInterface)])
// - instance implementation pattern will be set by the appropriate parameter
// - will return true on success, false if registration failed (e.g. if any of
// the supplied interfaces is not correct or is not available on the server)
// - that is, server side will be called to check for the availability of
// each interface
// - you can specify an optional custom contract for the first interface
function ServiceRegister(const aInterfaces: array of PRttiInfo;
aInstanceCreation: TServiceInstanceImplementation = sicSingle;
const aContractExpected: RawUtf8 = ''): boolean; overload; virtual;
/// register a Service on the client side via its interface
// - this methods expects one interface to be registered to the client, as
// ! Client.ServiceRegister(TypeInfo(IMyInterface),sicShared);
// - instance implementation pattern will be set by the appropriate parameter
// - will return the corresponding fake class factory on success, nil if
// registration failed (e.g. if any of supplied interfaces is not correct or
// is not available on the server)
// - that is, server side will be called to check for the availability of
// each interface
// - you can specify an optional custom contract for the first interface
function ServiceRegister(aInterface: PRttiInfo;
aInstanceCreation: TServiceInstanceImplementation = sicSingle;
const aContractExpected: RawUtf8 = '';
aIgnoreAnyException: boolean = true): TServiceFactory; overload;
/// register and retrieve the sicClientDriven Service instance
// - will return TRUE on success, filling Obj output variable with the
// corresponding interface instance
// - will return FALSE on error
function ServiceRegisterClientDriven(aInterface: PRttiInfo; out Obj;
const aContractExpected: RawUtf8 = ''): boolean; overload;
/// register one or several Services on the client side via their interfaces
// - this method expects the interface(s) to have been registered previously:
// ! TInterfaceFactory.RegisterInterfaces([TypeInfo(IMyInterface),...]);
function ServiceDefine(const aInterfaces: array of TGuid;
aInstanceCreation: TServiceInstanceImplementation = sicSingle;
const aContractExpected: RawUtf8 = ''): boolean; overload;
/// register a Service on the client side via its interface
// - this method expects the interface to have been registered previously:
// ! TInterfaceFactory.RegisterInterfaces([TypeInfo(IMyInterface),...]);
function ServiceDefine(const aInterface: TGuid;
aInstanceCreation: TServiceInstanceImplementation = sicSingle;
const aContractExpected: RawUtf8 = '';
aIgnoreAnyException: boolean = true): TServiceFactoryClient; overload;
/// register and retrieve the sicClientDriven Service instance
// - this method expects the interface to have been registered previously:
// ! TInterfaceFactory.RegisterInterfaces([TypeInfo(IMyInterface),...]);
function ServiceDefineClientDriven(const aInterface: TGuid; out Obj;
const aContractExpected: RawUtf8 = ''): boolean;
/// register a sicShared Service instance communicating via JSON objects
// - will force SERVICE_CONTRACT_NONE_EXPECTED, ParamsAsJsonObject=true and
// ResultAsJsonObjectWithoutResult=true
// - may be used e.g. for accessing a sessionless public REST/JSON API, i.e.
// ! TRestServer.ServiceDefine(...).ResultAsJsonObjectWithoutResult := true
// - this method expects the interface to have been registered previously:
// ! TInterfaceFactory.RegisterInterfaces([TypeInfo(IMyInterface),...]);
// - aIgnoreAnyException may be set to TRUE if the server is likely
// to not propose this service, and any exception is to be catched
function ServiceDefineSharedApi(const aInterface: TGuid;
const aContractExpected: RawUtf8 = SERVICE_CONTRACT_NONE_EXPECTED;
aIgnoreAnyException: boolean = false): TServiceFactoryClient;
/// allow to notify a server the services this client may be actually capable
// - when this client will connect to a remote server to access its services,
// it will register its own services, supplying its TRestServer instance,
// and its corresponding public URI, within its '_contract_' internal call
// - it will allow automatic service discovery of Peer To Peer Servers,
// without the need of an actual centralized SOA catalog service: any
// client could retrieve an associated REST server for a given service,
// via the ServiceRetrieveAssociated method
property ServicePublishOwnInterfaces: RawUtf8
read fServicePublishOwnInterfaces write fServicePublishOwnInterfaces;
/// return all REST server URI associated to this client, for a given
// service name, the latest registered in first position
// - will lookup for the Interface name without the initial 'I', e.g.
// 'Calculator' for ICalculator - warning: research is case-sensitive
// - this methods is the reverse from ServicePublishOwnInterfaces: it allows
// to guess an associated REST server which may implement a given service
function ServiceRetrieveAssociated(const aServiceName: RawUtf8;
out URI: TRestServerUriDynArray): boolean; overload;
/// return all REST server URI associated to this client, for a given service
// - here the service is specified as its TGuid, e.g. IMyInterface
// - this method expects the interface to have been registered previously:
// ! TInterfaceFactory.RegisterInterfaces([TypeInfo(IMyInterface),...]);
// - the URI[] output array contains the matching server URIs, the latest
// registered in first position
// - this methods is the reverse from ServicePublishOwnInterfaces: it allows
// to guess an associated REST server which may implement a given service
function ServiceRetrieveAssociated(const aInterface: TGuid;
out URI: TRestServerUriDynArray): boolean; overload;
/// the routing class of the service remote request on client side
// - by default, contains TRestClientRoutingRest, i.e. an URI-based
// layout which is secure (since will use our RESTful authentication scheme),
// and also very fast
// - but TRestClientRoutingJsonRpc can e.g. be set (with
// TRestServerRoutingJsonRpc on server sides), if the client will rather
// use JSON/RPC alternative pattern
// - NEVER set the abstract TRestClientRouting class on this property
property ServicesRouting: TRestClientRoutingClass
read fServicesRouting write SetRoutingClass;
/// direct copy of ServicesRouting.Supports flags
property ServiceRoutingSupports: TRestClientSideInvoke
read fServiceRoutingSupports;
// internal methods used by mormot.soa.client
function FakeCallbackRegister(Sender: TServiceFactory;
const Method: TInterfaceMethod; const ParamInfo: TInterfaceMethodArgument;
ParamValue: pointer): TRestClientCallbackID; virtual;
function FakeCallbackUnregister(Factory: TInterfaceFactory;
FakeCallbackID: TRestClientCallbackID; Instance: pointer): boolean; virtual;
property FakeCallbacks: TRestClientCallbacks
read fFakeCallbacks;
/// you can call this method to call the remote URI root/Timestamp
// - this can be an handy way of testing the connection, since this method
// is always available, even without authentication
// - returns TRUE if the client time correction has been retrieved
// - returns FALSE on any connection error - check LastErrorMessage and
// LastErrorException to find out the exact connection error
function ServerTimestampSynchronize: boolean;
/// asynchronous call a 'RemoteLog' remote logging method on the server
// - as implemented by mORMot's LogView tool in server mode
// - to be used via ServerRemoteLogStart/ServerRemoteLogStop methods
// - a dedicated background thread will run the transmission process without
// blocking the main program execution, gathering log rows in chunks in case
// of high activity
// - map TOnTextWriterEcho signature, so that you will be able to set e.g.:
// ! TSqlLog.Family.EchoCustom := aClient.ServerRemoteLog;
function ServerRemoteLog(Sender: TEchoWriter; Level: TSynLogLevel;
const Text: RawUtf8): boolean; overload; virtual;
/// internal method able to emulate a call to TSynLog.Add.Log()
// - will compute timestamp and event text, than call the overloaded
// ServerRemoteLog() method
function ServerRemoteLog(Level: TSynLogLevel; const FormatMsg: RawUtf8;
const Args: array of const): boolean; overload;
/// start to send all logs to the server 'RemoteLog' method-based service
// - will associate the EchoCustom callback of the running log class to the
// ServerRemoteLog() method
// - if aClientOwnedByFamily is TRUE, this TRestClientUri instance
// lifetime will be managed by TSynLogFamily - which is mostly wished
// - if aClientOwnedByFamily is FALSE, you should manage this instance
// life time, and may call ServerRemoteLogStop to stop remote logging
// - warning: current implementation will disable all logging for this
// TRestClientUri instance, to avoid any potential concern (e.g. for
// multi-threaded process, or in case of communication error): you should
// therefore use this TRestClientUri connection only for the remote log
// server, e.g. via TRestHttpClientGeneric.CreateForRemoteLogging() - do
// not call ServerRemoteLogStart() from a high-level business client!
procedure ServerRemoteLogStart(aLogClass: TSynLogClass;
aClientOwnedByFamily: boolean);
/// stop sending all logs to the server 'RemoteLog' method-based service
// - do nothing if aClientOwnedByFamily was TRUE for ServerRemoteLogStart
procedure ServerRemoteLogStop;
/// authenticate an User to the current connected Server
// - will call the ModelRoot/Auth service, i.e. call TRestServer.Auth()
// published method to create a session for this user, with our secure
// TRestClientAuthenticationDefault authentication scheme
// - returns true on success
// - calling this method is optional, depending on your user right policy:
// your Server need to handle authentication
// - if saoUserByLogonOrID is defined in the server Options, aUserName may
// be a TAuthUser.ID integer value and not a TAuthUser.LogonName
// - on success, the SessionUser property map the logged user session on the
// server side
// - if aHashedPassword is TRUE, the aPassword parameter is expected to
// contain the already-hashed value, just as stored in PasswordHashHexa
// (i.e. Sha256('salt'+Value) as in TAuthUser.SetPasswordPlain method)
// - if SSPIAUTH conditional is defined, and aUserName='', a Windows
// authentication will be performed via TRestClientAuthenticationSspi -
// in this case, aPassword will contain the SPN domain for Kerberos
// (otherwise NTLM will be used), and table TAuthUser shall contain
// an entry for the logged Windows user, with the LoginName in form
// 'DomainName\UserName'
// - you can directly create the class method ClientSetUser() of a given
// TRestClientAuthentication inherited class, if neither
// TRestClientAuthenticationDefault nor TRestClientAuthenticationSspi
// match your need
function SetUser(const aUserName, aPassword: RawUtf8;
aHashedPassword: boolean = false): boolean;
/// clear session and call the /auth service on the server to notify shutdown
// - is called by Destroy and SetUser/ClientSetUser methods, so you should
// not have usually to call this method directly
procedure SessionClose;
/// internal method to retrieve the current Session TAuthUser.ID
function GetCurrentSessionUserID: TID; override;
/// customize the session_signature signing algorithm with a specific function
// - will be used by TRestServerAuthenticationSignedUri classes,
// e.g. TRestServerAuthenticationDefault instead of the algorithm
// specified by the server at session handshake
property ComputeSignature: TOnRestAuthenticationSignedUriComputeSignature
read fComputeSignature write fComputeSignature;
/// the current session information as set by a successful SetUser() call
property Session: TRestClientSession
read fSession;
/// the current user as set by SetUser() method
// - contains nil if no User is currently authenticated
property SessionUser: TAuthUser
read fSession.User;
/// access to the low-level HTTP header used for authentication
// - you can force here your own header, e.g. a JWT as authentication bearer
// or as in TRestClientAuthenticationHttpAbstract.ClientSetUserHttpOnlyUser
property SessionHttpHeader: RawUtf8
read fSession.HttpHeader write fSession.HttpHeader;
/// main access to the IRestOrmClient methods of this instance
property Client: IRestOrmClient
read fClient;
/// main access to the class implementing IRestOrm methods for this instance
// - used internally to avoid ORM: IRestOrm reference counting and
// enable inlining of most simple methods, if possible
function OrmInstance: TRestOrm;
{$ifdef HASINLINE}inline;{$endif}
{$ifdef OSWINDOWS}
/// set a HWND/WM_* pair to let interface-based services notification
// callbacks be processed safely in the main UI thread, via Windows messages
// - by default callbacks are executed in the transmission thread, e.g.
// the WebSockets client thread: using UI Synchronize() method may
// trigger some unexpected race conditions, e.g. when asynchronous
// notifications are received during a blocking REST command - this
// message-based mechanism will allow safe and easy notification for
// any UI client application
// - the associated ServiceNotificationMethodExecute() method shall be
// called in the client HWND TForm for the defined WM_* message
procedure ServiceNotificationMethodViaMessages(hWnd: HWND; Msg: cardinal);
/// event to be triggered when a WM_* message is received from
// the internal asynchronous notification system, to run the callback
// in the main UI thread
// - WM_* message identifier should have been set e.g. via the associated
// $ ServiceNotificationMethodViaMessages(Form.Handle, WM_USER)
// - message will be sent for any interface-based service method callback
// which expects no result (i.e. no out parameter nor function result),
// so is safely handled as asynchronous notification
// - is defines as a class procedure, since the underlying TRestClientUri
// instance has no impact here: a single WM_* handler is enough for
// several TRestClientUri instances
class procedure ServiceNotificationMethodExecute(var Msg: TMessage);
{$endif OSWINDOWS}
/// called by IsOpen when the raw connection is (re)established
property OnConnected: TOnClientNotify
read fOnConnected write fOnConnected;
/// called by IsOpen when it failed to connect
property OnConnectionFailed: TOnClientFailed
read fOnConnectionFailed write fOnConnectionFailed;
/// set a callback event to be executed in loop during remote blocking
// process, e.g. to refresh the UI during a somewhat long request
// - if not set, the request will be executed in the current thread,
// so may block the User Interface
// - you can assign a callback to this property, calling for instance
// Application.ProcessMessages, to execute the remote request in a
// background thread, but let the UI still be reactive: the
// TLoginForm.OnIdleProcess and OnIdleProcessForm methods of
// mORMotUILogin.pas will match this property expectations
property OnIdle: TOnIdleSynBackgroundThread
read fOnIdle write fOnIdle;
/// TRUE if the background thread is active, and OnIdle event is called
// during process
// - to be used e.g. to ensure no re-entrance from User Interface messages
property OnIdleBackgroundThreadActive: boolean
read GetOnIdleBackgroundThreadActive;
/// this Event is called in case of remote authentication failure
// - client software can ask the user to enter a password and user name
// - if no event is specified, the Uri() method will return directly
// an HTTP_FORBIDDEN "403 Forbidden" error code
property OnAuthentificationFailed: TOnAuthentificationFailed
read fOnAuthentificationFailed write fOnAuthentificationFailed;
/// this Event is called just before the raw InternalUri() method
// - the callback could return true to continue, or false to abort
property OnBeforeCall: TOnClientCall
read fOnBeforeCall write fOnBeforeCall;
/// this Event is called just after the raw InternalUri() method
// - the callback could return false to close the connection
property OnAfterCall: TOnClientCall
read fOnAfterCall write fOnAfterCall;
/// this Event is called if Uri() was not successful