-
-
Notifications
You must be signed in to change notification settings - Fork 135
/
Copy pathmormot.net.server.pas
8630 lines (8155 loc) · 335 KB
/
mormot.net.server.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
/// HTTP/HTTPS Server Classes
// - 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.net.server;
{
*****************************************************************************
HTTP/UDP Server Classes
- Abstract UDP Server
- Custom URI Routing using an efficient Radix Tree
- Shared Server-Side HTTP Process
- THttpServerSocket/THttpServer HTTP/1.1 Server
- THttpPeerCache Local Peer-to-peer Cache
- THttpApiServer HTTP/1.1 Server Over Windows http.sys Module
- THttpApiWebSocketServer Over Windows http.sys Module
*****************************************************************************
}
interface
{$I ..\mormot.defines.inc}
uses
sysutils,
classes,
mormot.core.base,
mormot.core.os,
mormot.core.os.security,
mormot.core.data,
mormot.core.threads,
mormot.core.unicode,
mormot.core.text,
mormot.core.buffers,
mormot.core.rtti,
mormot.core.json,
mormot.core.datetime,
mormot.core.zip,
mormot.core.log,
mormot.core.search,
mormot.net.sock,
mormot.net.http,
{$ifdef USEWININET}
mormot.lib.winhttp,
{$endif USEWININET}
mormot.lib.sspi, // void unit on POSIX
mormot.lib.gssapi, // void unit on Windows
mormot.net.client,
mormot.crypt.core,
mormot.crypt.secure;
{ ******************** Abstract UDP Server }
type
EUdpServer = class(ENetSock);
/// work memory buffer of the maximum size of UDP frame (64KB)
TUdpFrame = array[word] of byte;
/// pointer to a memory buffer of the maximum size of UDP frame
PUdpFrame = ^TUdpFrame;
/// abstract UDP server thread
TUdpServerThread = class(TLoggedThread)
protected
fSock: TNetSocket;
fSockAddr: TNetAddr;
fExecuteMessage: RawUtf8;
fFrame: PUdpFrame;
fReceived: integer;
function GetIPWithPort: RawUtf8;
procedure AfterBind; virtual;
/// will loop for any pending UDP frame, and execute FrameReceived method
procedure DoExecute; override;
// this is the main processing method for all incoming frames
procedure OnFrameReceived(len: integer; var remote: TNetAddr); virtual; abstract;
procedure OnIdle(tix64: Int64); virtual; // called every 512 ms at most
procedure OnShutdown; virtual; abstract;
public
/// initialize and bind the server instance, in non-suspended state
constructor Create(LogClass: TSynLogClass;
const BindAddress, BindPort, ProcessName: RawUtf8;
TimeoutMS: integer); reintroduce;
/// finalize the processing thread
destructor Destroy; override;
published
property IPWithPort: RawUtf8
read GetIPWithPort;
property Received: integer
read fReceived;
end;
const
/// the UDP frame content as sent by TUdpServerThread.Destroy
UDP_SHUTDOWN: RawUtf8 = 'shutdown';
{ ******************** Custom URI Routing using an efficient Radix Tree }
type
/// one HTTP method supported by TUriRouter
// - only supports RESTful GET/POST/PUT/DELETE/OPTIONS/HEAD by default
// - each method would have its dedicated TUriTree parser in TUriRouter
TUriRouterMethod = (
urmGet,
urmPost,
urmPut,
urmDelete,
urmOptions,
urmHead,
urmPatch);
/// the HTTP methods supported by TUriRouter
TUriRouterMethods = set of TUriRouterMethod;
/// context information, as cloned by TUriTreeNode.Split()
TUriTreeNodeData = record
/// the Rewrite() URI text
ToUri: RawUtf8;
/// [pos1,len1,valndx1,pos2,len2,valndx2,...] trios from ToUri content
ToUriPosLen: TIntegerDynArray;
/// the size of all ToUriPosLen[] static content
ToUriStaticLen: integer;
/// the URI method to be used after ToUri rewrite
ToUriMethod: TUriRouterMethod;
/// the HTTP error code for a Rewrite() with an integer ToUri (e.g. '404')
ToUriErrorStatus: {$ifdef CPU32} word {$else} cardinal {$endif};
/// the callback registered by Run() for this URI
Execute: TOnHttpServerRequest;
/// an additional pointer value, assigned to Ctxt.RouteOpaque of Execute()
ExecuteOpaque: pointer;
end;
/// implement a Radix Tree node to hold one URI registration
TUriTreeNode = class(TRadixTreeNodeParams)
protected
function LookupParam(Ctxt: TObject; Pos: PUtf8Char; Len: integer): boolean;
override;
procedure RewriteUri(Ctxt: THttpServerRequestAbstract);
public
/// all context information, as cloned by Split()
Data: TUriTreeNodeData;
/// overriden to support the additional Data fields
function Split(const Text: RawUtf8): TRadixTreeNode; override;
end;
/// implement a Radix Tree to hold all registered URI for a given HTTP method
TUriTree = class(TRadixTreeParams)
public
/// access to the root node of this tree
function Root: TUriTreeNode;
{$ifdef HASINLINE}inline;{$endif}
end;
/// exception class raised during TUriRouter.Rewrite/Run registration
EUriRouter = class(ERadixTree);
/// store per-method URI multiplexing Radix Tree in TUriRouter
// - each HTTP method would have its dedicated TUriTree parser in TUriRouter
TUriRouterTree = array[urmGet .. high(TUriRouterMethod)] of TUriTree;
/// efficient server-side URI routing for THttpServerGeneric
// - Process() is done with no memory allocation for a static route,
// using a very efficient Radix Tree for path lookup, over a thread-safe
// non-blocking URI parsing with values extractions for rewrite or execution
// - here are some numbers from TNetworkProtocols._TUriTree on my laptop:
// $ 1000 URI lookups in 37us i.e. 25.7M/s, aver. 37ns
// $ 1000 URI static rewrites in 80us i.e. 11.9M/s, aver. 80ns
// $ 1000 URI parametrized rewrites in 117us i.e. 8.1M/s, aver. 117ns
// $ 1000 URI static execute in 91us i.e. 10.4M/s, aver. 91ns
// $ 1000 URI parametrized execute in 162us i.e. 5.8M/s, aver. 162ns
TUriRouter = class(TObjectRWLightLock)
protected
fTree: TUriRouterTree;
fTreeOptions: TRadixTreeOptions;
fEntries: array[urmGet .. high(TUriRouterMethod)] of integer;
fTreeNodeClass: TRadixTreeNodeClass;
procedure Setup(aFrom: TUriRouterMethod; const aFromUri: RawUtf8;
aTo: TUriRouterMethod; const aToUri: RawUtf8;
const aExecute: TOnHttpServerRequest; aExecuteOpaque: pointer);
public
/// initialize this URI routing engine
constructor Create(aNodeClass: TRadixTreeNodeClass;
aOptions: TRadixTreeOptions = []); reintroduce;
/// finalize this URI routing engine
destructor Destroy; override;
/// register an URI rewrite with optional <param> place holders
// - <param> will be replaced in aToUri
// - if aToUri is an '200'..'599' integer, it will return it as HTTP error
// - otherwise, the URI will be rewritten into aToUri, e.g.
// $ Rewrite(urmGet, '/info', urmGet, 'root/timestamp/info');
// $ Rewrite(urmGet, '/path/from/<from>/to/<to>', urmPost,
// $ '/root/myservice/convert?from=<from>&to=<to>'); // for IMyService.Convert
// $ Rewrite(urmGet, '/index.php', '400'); // to avoid fuzzing
// $ Rewrite(urmGet, '/*', '/static/*' // '*' synonymous to '<path:path>'
procedure Rewrite(aFrom: TUriRouterMethod; const aFromUri: RawUtf8;
aTo: TUriRouterMethod; const aToUri: RawUtf8);
/// just a wrapper around Rewrite(urmGet, aFrom, aToMethod, aTo)
// - e.g. Route.Get('/info', 'root/timestamp/info');
// - e.g. Route.Get('/user/<id>', '/root/userservice/new?id=<id>'); will
// rewrite internally '/user/1234' URI as '/root/userservice/new?id=1234'
// - e.g. Route.Get('/user/<int:id>', '/root/userservice/new?id=<id>');
// to ensure id is a real integer before redirection
// - e.g. Route.Get('/admin.php', '403');
// - e.g. Route.Get('/*', '/static/*'); with '*' synonymous to '<path:path>'
procedure Get(const aFrom, aTo: RawUtf8;
aToMethod: TUriRouterMethod = urmGet); overload;
/// just a wrapper around Rewrite(urmPost, aFrom, aToMethod, aTo)
// - e.g. Route.Post('/doconvert', '/root/myservice/convert');
procedure Post(const aFrom, aTo: RawUtf8;
aToMethod: TUriRouterMethod = urmPost); overload;
/// just a wrapper around Rewrite(urmPut, aFrom, aToMethod, aTo)
// - e.g. Route.Put('/domodify', '/root/myservice/update', urmPost);
procedure Put(const aFrom, aTo: RawUtf8;
aToMethod: TUriRouterMethod = urmPut); overload;
/// just a wrapper around Rewrite(urmPatch, aFrom, aToMethod, aTo)
// - e.g. Route.Patch('/domodify', '/root/myservice/update', urmPatch);
procedure Patch(const aFrom, aTo: RawUtf8;
aToMethod: TUriRouterMethod = urmPatch); overload;
/// just a wrapper around Rewrite(urmDelete, aFrom, aToMethod, aTo)
// - e.g. Route.Delete('/doremove', '/root/myservice/delete', urmPost);
procedure Delete(const aFrom, aTo: RawUtf8;
aToMethod: TUriRouterMethod = urmDelete); overload;
/// just a wrapper around Rewrite(urmOptions, aFrom, aToMethod, aTo)
// - e.g. Route.Options('/doremove', '/root/myservice/Options', urmPost);
procedure Options(const aFrom, aTo: RawUtf8;
aToMethod: TUriRouterMethod = urmOptions); overload;
/// just a wrapper around Rewrite(urmHead, aFrom, aToMethod, aTo)
// - e.g. Route.Head('/doremove', '/root/myservice/Head', urmPost);
procedure Head(const aFrom, aTo: RawUtf8;
aToMethod: TUriRouterMethod = urmHead); overload;
/// assign a TOnHttpServerRequest callback with a given URI
// - <param> place holders will be parsed and available in callback
// as Ctxt['param'] default property or Ctxt.RouteInt64/RouteEquals methods
// - could be used e.g. for standard REST process as
// $ Route.Run([urmGet], '/user/<user>/pic', DoUserPic) // retrieve a list
// $ Route.Run([urmGet, urmPost, urmPut, urmDelete],
// $ '/user/<user>/pic/<id>', DoUserPic) // CRUD picture access
procedure Run(aFrom: TUriRouterMethods; const aFromUri: RawUtf8;
const aExecute: TOnHttpServerRequest; aExecuteOpaque: pointer = nil);
/// just a wrapper around Run([urmGet], aUri, aExecute) registration method
// - e.g. Route.Get('/plaintext', DoPlainText);
procedure Get(const aUri: RawUtf8; const aExecute: TOnHttpServerRequest;
aExecuteOpaque: pointer = nil); overload;
/// just a wrapper around Run([urmPost], aUri, aExecute) registration method
procedure Post(const aUri: RawUtf8; const aExecute: TOnHttpServerRequest;
aExecuteOpaque: pointer = nil); overload;
/// just a wrapper around Run([urmPut], aUri, aExecute) registration method
procedure Put(const aUri: RawUtf8; const aExecute: TOnHttpServerRequest;
aExecuteOpaque: pointer = nil); overload;
/// just a wrapper around Run([urmPatch], aUri, aExecute) registration method
procedure Patch(const aUri: RawUtf8; const aExecute: TOnHttpServerRequest;
aExecuteOpaque: pointer = nil); overload;
/// just a wrapper around Run([urmDelete], aUri, aExecute) registration method
procedure Delete(const aUri: RawUtf8; const aExecute: TOnHttpServerRequest;
aExecuteOpaque: pointer = nil); overload;
/// just a wrapper around Run([urmOptions], aUri, aExecute) registration method
procedure Options(const aUri: RawUtf8; const aExecute: TOnHttpServerRequest;
aExecuteOpaque: pointer = nil); overload;
/// just a wrapper around Run([urmHead], aUri, aExecute) registration method
procedure Head(const aUri: RawUtf8; const aExecute: TOnHttpServerRequest;
aExecuteOpaque: pointer = nil); overload;
/// assign the published methods of a class instance to their URI via RTTI
// - the signature of each method should match TOnHttpServerRequest
// - the method name is used for the URI, e.g. Instance.user as '/user',
// with exact case matching, and replacing _ in the method name by '-', e.g.
// Instance.cached_query as '/cached-query'
procedure RunMethods(RouterMethods: TUriRouterMethods; Instance: TObject;
const Prefix: RawUtf8 = '/');
/// perform URI parsing and rewrite/execution within HTTP server Ctxt members
// - should return 0 to continue the process, on a HTTP status code to abort
// if the request has been handled by a TOnHttpServerRequest callback
// - this method is thread-safe
function Process(Ctxt: THttpServerRequestAbstract): integer;
/// search for a given URI match
// - could be used e.g. in OnBeforeBody() to quickly reject an invalid URI
// - this method is thread-safe
function Lookup(const aUri, aUriMethod: RawUtf8): TUriTreeNode;
/// erase all previous registrations, optionally for a given HTTP method
// - currently, there is no way to delete a route once registered, to
// optimize the process thread-safety: use Clear then re-register
procedure Clear(aMethods: TUriRouterMethods = [urmGet .. high(TUriRouterMethod)]);
/// access to the internal per-method TUriTree instance
// - some Tree[] may be nil if the HTTP method has not been registered yet
// - used only for testing/validation purpose
property Tree: TUriRouterTree
read fTree;
/// how the TUriRouter instance should be created
// - should be set before calling Run/Rewrite registration methods
property TreeOptions: TRadixTreeOptions
read fTreeOptions write fTreeOptions;
published
/// how many GET rules have been registered
property Gets: integer
read fEntries[urmGet];
/// how many POST rules have been registered
property Posts: integer
read fEntries[urmPost];
/// how many PUT rules have been registered
property Puts: integer
read fEntries[urmPut];
/// how many PATCH rules have been registered
property Patchs: integer
read fEntries[urmPatch];
/// how many DELETE rules have been registered
property Deletes: integer
read fEntries[urmDelete];
/// how many HEAD rules have been registered
property Heads: integer
read fEntries[urmHead];
/// how many OPTIONS rules have been registered
property Optionss: integer
read fEntries[urmOptions];
end;
const
/// convert TUriRouterMethod into its standard HTTP text
// - see UriMethod() function for the reverse conversion
URIROUTERMETHOD: array[TUriRouterMethod] of RawUtf8 = (
'GET', // urmGet
'POST', // urmPost
'PUT', // urmPut
'DELETE', // urmDelete
'OPTIONS', // urmOptions
'HEAD', // urmHead
'PATCH'); // urmPatch
/// quickly recognize most HTTP text methods into a TUriRouterMethod enumeration
// - may replace cascaded IsGet() IsPut() IsPost() IsDelete() function calls
// - see URIROUTERMETHOD[] constant for the reverse conversion
function UriMethod(const Text: RawUtf8; out Method: TUriRouterMethod): boolean;
/// check if the supplied text contains only valid characters for a root URI
// - excluding the parameters, i.e. rejecting the ? and % characters
// - but allowing <param> place holders as recognized by TUriRouter
function IsValidUriRoute(p: PUtf8Char): boolean;
{ ******************** Shared Server-Side HTTP Process }
type
/// exception raised during HTTP process
EHttpServer = class(ESynException);
{$M+} // to have existing RTTI for published properties
THttpServerGeneric = class;
{$M-}
/// 32-bit sequence value used to identify one asynchronous connection
// - will start from 1, and increase during the server live-time
// - THttpServerConnectionID may be retrieved from nginx reverse proxy
// - used e.g. for Server.AsyncResponse() delayed call with HTTP_ASYNCRESPONSE
TConnectionAsyncHandle = type integer;
/// a dynamic array of TConnectionAsyncHandle identifiers
TConnectionAsyncHandleDynArray = array of TConnectionAsyncHandle;
/// a generic input/output structure used for HTTP server requests
// - URL/Method/InHeaders/InContent properties are input parameters
// - OutContent/OutContentType/OutCustomHeader are output parameters
THttpServerRequest = class(THttpServerRequestAbstract)
protected
fServer: THttpServerGeneric;
fConnectionAsyncHandle: TConnectionAsyncHandle;
fErrorMessage: string;
fTempWriter: TJsonWriter; // reused between SetOutJson() calls
{$ifdef USEWININET}
fHttpApiRequest: PHTTP_REQUEST;
function GetFullUrl: SynUnicode;
{$endif USEWININET}
public
/// initialize the context, associated to a HTTP server instance
constructor Create(aServer: THttpServerGeneric;
aConnectionID: THttpServerConnectionID; aConnectionThread: TSynThread;
aConnectionAsyncHandle: TConnectionAsyncHandle;
aConnectionFlags: THttpServerRequestFlags;
aConnectionOpaque: PHttpServerConnectionOpaque); virtual;
/// could be called before Prepare() to reuse an existing instance
procedure Recycle(
aConnectionID: THttpServerConnectionID; aConnectionThread: TSynThread;
aConnectionAsyncHandle: TConnectionAsyncHandle;
aConnectionFlags: THttpServerRequestFlags;
aConnectionOpaque: PHttpServerConnectionOpaque);
/// finalize this execution context
destructor Destroy; override;
/// prepare one reusable HTTP State Machine for sending the response
function SetupResponse(var Context: THttpRequestContext;
CompressGz, MaxSizeAtOnce: integer): PRawByteStringBuffer;
/// just a wrapper around fErrorMessage := FormatString()
procedure SetErrorMessage(const Fmt: RawUtf8; const Args: array of const);
/// serialize a given value as JSON into OutContent and OutContentType fields
// - this function returns HTTP_SUCCESS
function SetOutJson(Value: pointer; TypeInfo: PRttiInfo): cardinal; overload;
{$ifdef HASINLINE} inline; {$endif}
/// serialize a given TObject as JSON into OutContent and OutContentType fields
// - this function returns HTTP_SUCCESS
function SetOutJson(Value: TObject): cardinal; overload;
{$ifdef HASINLINE} inline; {$endif}
/// low-level initialization of the associated TJsonWriter instance
// - will reset and reuse an TJsonWriter associated to this execution context
// - as called by SetOutJson() overloaded methods using RTTI
// - a local TTextWriterStackBuffer should be provided as temporary buffer
function TempJsonWriter(var temp: TTextWriterStackBuffer): TJsonWriter;
{$ifdef HASINLINE} inline; {$endif}
/// an additional custom parameter, as provided to TUriRouter.Setup
function RouteOpaque: pointer; override;
/// return the low-level internal handle for Server.AsyncResponse() delayed call
// - to be used in conjunction with a HTTP_ASYNCRESPONSE internal status code
// - raise an EHttpServer exception if async responses are not available
function AsyncHandle: TConnectionAsyncHandle;
/// the associated server instance
// - may be a THttpServer or a THttpApiServer class
property Server: THttpServerGeneric
read fServer;
/// optional error message which will be used by SetupResponse
property ErrorMessage: string
read fErrorMessage write fErrorMessage;
{$ifdef USEWININET}
/// for THttpApiServer, input parameter containing the caller full URL
property FullUrl: SynUnicode
read GetFullUrl;
/// for THttpApiServer, points to a PHTTP_REQUEST structure
property HttpApiRequest: PHTTP_REQUEST
read fHttpApiRequest;
{$endif USEWININET}
end;
/// meta-class of HTTP server requests instances
THttpServerRequestClass = class of THttpServerRequest;
/// available HTTP server options
// - some THttpServerGeneric classes may have only partial support of them
// - hsoHeadersUnfiltered will store all headers, not only relevant (i.e.
// include raw Content-Length, Content-Type and Content-Encoding entries)
// - hsoHeadersInterning triggers TRawUtf8Interning to reduce memory usage
// - hsoNoStats will disable low-level statistic counters
// - hsoNoXPoweredHeader excludes 'X-Powered-By: mORMot 2 synopse.info' header
// - hsoCreateSuspended won't start the server thread immediately
// - hsoLogVerbose could be used to debug a server in production
// - hsoIncludeDateHeader will let all answers include a Date: ... HTTP header
// - hsoEnableTls enables TLS support for THttpServer socket server, using
// Windows SChannel API or OpenSSL - call WaitStarted() to set the certificates
// - hsoBan40xIP will reject any IP for a few seconds after a 4xx error code
// is returned (but 401/403) - only implemented by socket servers for now
// - either hsoThreadCpuAffinity or hsoThreadSocketAffinity could be set: to
// force thread affinity to one CPU logic core, or CPU HW socket; see
// TNotifiedThread corresponding methods - not available on http.sys
// - hsoReusePort will set SO_REUSEPORT on POSIX, allowing to bind several
// THttpServerGeneric on the same port, either within the same process, or as
// separated processes (e.g. to set process affinity to one CPU HW socket)
// - hsoThreadSmooting will change the TAsyncConnections.ThreadPollingWakeup()
// algorithm to focus the process on the first threads of the pool - by design,
// this will disable both hsoThreadCpuAffinity and hsoThreadSocketAffinity
// - hsoEnablePipelining enable HTTP pipelining (unsafe) on THttpAsyncServer
// - hsoEnableLogging enable an associated THttpServerGeneric.Logger instance
// - hsoTelemetryCsv and hsoTelemetryJson will enable CSV or JSON consolidated
// per-minute metrics logging via an associated THttpServerGeneric.Analyzer
THttpServerOption = (
hsoHeadersUnfiltered,
hsoHeadersInterning,
hsoNoXPoweredHeader,
hsoNoStats,
hsoCreateSuspended,
hsoLogVerbose,
hsoIncludeDateHeader,
hsoEnableTls,
hsoBan40xIP,
hsoThreadCpuAffinity,
hsoThreadSocketAffinity,
hsoReusePort,
hsoThreadSmooting,
hsoEnablePipelining,
hsoEnableLogging,
hsoTelemetryCsv,
hsoTelemetryJson);
/// how a THttpServerGeneric class is expected to process incoming requests
THttpServerOptions = set of THttpServerOption;
/// abstract parent class to implement a HTTP server
// - do not use it, but rather THttpServer/THttpAsyncServer or THttpApiServer
THttpServerGeneric = class(TNotifiedThread)
protected
fShutdownInProgress, fFavIconRouted: boolean;
fOptions: THttpServerOptions;
fDefaultRequestOptions: THttpRequestOptions;
fRoute: TUriRouter;
/// optional event handlers for process interception
fOnRequest: TOnHttpServerRequest;
fOnBeforeBody: TOnHttpServerBeforeBody;
fOnBeforeRequest: TOnHttpServerRequest;
fOnAfterRequest: TOnHttpServerRequest;
fOnAfterResponse: TOnHttpServerAfterResponse;
fMaximumAllowedContentLength: Int64;
fCurrentConnectionID: integer; // 31-bit NextConnectionID sequence
/// set by RegisterCompress method
fCompress: THttpSocketCompressRecDynArray;
fCompressAcceptEncoding: RawUtf8;
fServerName: RawUtf8;
fRequestHeaders: RawUtf8; // pre-computed headers with ServerName
fCallbackSendDelay: PCardinal;
fCallbackOutgoingCount: PCardinal; //TODO
fRemoteIPHeader, fRemoteIPHeaderUpper: RawUtf8;
fRemoteConnIDHeader, fRemoteConnIDHeaderUpper: RawUtf8;
fOnSendFile: TOnHttpServerSendFile;
fFavIcon: RawByteString;
fRouterClass: TRadixTreeNodeClass;
fLogger: THttpLogger;
fAnalyzer: THttpAnalyzer;
function GetApiVersion: RawUtf8; virtual; abstract;
procedure SetRouterClass(aRouter: TRadixTreeNodeClass);
procedure SetServerName(const aName: RawUtf8); virtual;
procedure SetOptions(opt: THttpServerOptions);
procedure SetOnRequest(const aRequest: TOnHttpServerRequest); virtual;
procedure SetOnBeforeBody(const aEvent: TOnHttpServerBeforeBody); virtual;
procedure SetOnBeforeRequest(const aEvent: TOnHttpServerRequest); virtual;
procedure SetOnAfterRequest(const aEvent: TOnHttpServerRequest); virtual;
procedure SetOnAfterResponse(const aEvent: TOnHttpServerAfterResponse); virtual;
procedure SetMaximumAllowedContentLength(aMax: Int64); virtual;
procedure SetRemoteIPHeader(const aHeader: RawUtf8); virtual;
procedure SetRemoteConnIDHeader(const aHeader: RawUtf8); virtual;
function GetHttpQueueLength: cardinal; virtual; abstract;
procedure SetHttpQueueLength(aValue: cardinal); virtual; abstract;
function GetConnectionsActive: cardinal; virtual; abstract;
function DoBeforeRequest(Ctxt: THttpServerRequest): cardinal;
{$ifdef HASINLINE}inline;{$endif}
function DoAfterRequest(Ctxt: THttpServerRequest): cardinal;
{$ifdef HASINLINE}inline;{$endif}
function NextConnectionID: integer; // 31-bit internal sequence
procedure ParseRemoteIPConnID(const Headers: RawUtf8;
var RemoteIP: RawUtf8; var RemoteConnID: THttpServerConnectionID);
{$ifdef HASINLINE}inline;{$endif}
procedure AppendHttpDate(var Dest: TRawByteStringBuffer); virtual;
function GetFavIcon(Ctxt: THttpServerRequestAbstract): cardinal;
public
/// initialize the server instance
constructor Create(const OnStart, OnStop: TOnNotifyThread;
const ProcessName: RawUtf8; ProcessOptions: THttpServerOptions); reintroduce; virtual;
/// release all memory and handlers used by this server
destructor Destroy; override;
/// specify URI routes for internal URI rewrites or callback execution
// - rules registered here will be processed before main Request/OnRequest
// - URI rewrites allow to extend the default routing, e.g. from TRestServer
// - callbacks execution allow efficient server-side processing with parameters
// - static routes could be defined e.g. Route.Get('/', '/root/default')
// - <param> place holders could be defined for proper URI rewrite
// e.g. Route.Post('/user/<id>', '/root/userservice/new?id=<id>') will
// rewrite internally '/user/1234' URI as '/root/userservice/new?id=1234'
// - could be used e.g. for standard REST process via event callbacks with
// Ctxt['user'] or Ctxt.RouteInt64('id') parameter extraction in DoUserPic:
// $ Route.Run([urmGet], '/user/<user>/pic', DoUserPic) // retrieve a list
// $ Route.Run([urmGet, urmPost, urmPut, urmDelete],
// $ '/user/<user>/pic/<id>', DoUserPic) // CRUD picture access
// - warning: with the THttpApiServer, URIs will be limited by the actual
// root URI registered at http.sys level - there is no such limitation with
// the socket servers, which bind to a port, so handle all URIs on it
function Route: TUriRouter;
/// thread-safe replace the TUriRouter instance
// - returns the existing instance: caller should keep it for a few seconds
// untouched prior to Free it, to let finish any pending background process
function ReplaceRoute(another: TUriRouter): TUriRouter;
/// will route a GET to /favicon.ico to the given .ico file content
// - if none is supplied, the default Synopse/mORMot icon is used
// - if '' is supplied, /favicon.ico will return a 404 error status
// - warning: with THttpApiServer, may require a proper URI registration
procedure SetFavIcon(const FavIconContent: RawByteString = 'default');
/// override this function to customize your http server
// - InURL/InMethod/InContent properties are input parameters
// - OutContent/OutContentType/OutCustomHeader are output parameters
// - result of the function is the HTTP error code (200 if OK, e.g.),
// - OutCustomHeader is available to handle Content-Type/Location
// - if OutContentType is STATICFILE_CONTENT_TYPE (i.e. '!STATICFILE'),
// then OutContent is the UTF-8 filename of a file to be sent directly
// to the client via http.sys or NGINX's X-Accel-Redirect; the
// OutCustomHeader should contain the eventual 'Content-type: ....' value
// - default implementation is to call the OnRequest event (if existing),
// and will return HTTP_NOTFOUND if OnRequest was not set
// - warning: this process must be thread-safe (can be called by several
// threads simultaneously, but with a given Ctxt instance for each)
function Request(Ctxt: THttpServerRequestAbstract): cardinal; virtual;
/// send a request back to the client, if the connection has been upgraded
// e.g. to WebSockets
// - InURL/InMethod/InContent properties are input parameters
// (InContentType is ignored)
// - OutContent/OutContentType/OutCustomHeader are output parameters
// - Ctxt.ConnectionID should be set, so that the method could know
// which connnection is to be used - returns HTTP_NOTFOUND (404) if unknown
// - result of the function is the HTTP error code (200 if OK, e.g.)
// - warning: this void implementation will raise an EHttpServer exception -
// inherited classes should override it, e.g. as in TWebSocketServerRest
function Callback(Ctxt: THttpServerRequest; aNonBlocking: boolean): cardinal; virtual;
/// send an asynchronous response to the client, when a slow process (e.g.
// DB request) has been executed
// - warning: this void implementation will raise an EHttpServer exception -
// inherited classes should override it, e.g. as in THttpAsyncServer
procedure AsyncResponse(Connection: TConnectionAsyncHandle;
const Content, ContentType: RawUtf8; Status: cardinal = HTTP_SUCCESS); virtual;
/// send an asynchronous (JSON by default) response to the client
procedure AsyncResponseFmt(Connection: TConnectionAsyncHandle;
const ContentFmt: RawUtf8; const Args: array of const;
const ContentType: RawUtf8 = JSON_CONTENT_TYPE;
Status: cardinal = HTTP_SUCCESS);
/// send an asynchronous RTTI-serialized JSON response to the client
procedure AsyncResponseJson(Connection: TConnectionAsyncHandle;
Value: pointer; TypeInfo: PRttiInfo; Status: cardinal = HTTP_SUCCESS);
/// send an asynchronous text error response to the client
procedure AsyncResponseError(Connection: TConnectionAsyncHandle;
const Message: RawUtf8; Status: cardinal = HTTP_SERVERERROR);
/// will register a compression algorithm
// - used e.g. to compress on the fly the data, with standard gzip/deflate
// or custom (synlz) protocols
// - you can specify a minimal size (in bytes) before which the content won't
// be compressed (1024 by default, corresponding to a MTU of 1500 bytes)
// - the first registered algorithm will be the prefered one for compression
// within each priority level (the lower aPriority first)
procedure RegisterCompress(aFunction: THttpSocketCompress;
aCompressMinSize: integer = 1024; aPriority: integer = 10); virtual;
/// you can call this method to prepare the HTTP server for shutting down
procedure Shutdown;
/// allow to customize the Route() implementation Radix Tree node class
// - if not set, will use TUriTreeNode as defined in this unit
// - raise an Exception if set twice, or after Route() is called
property RouterClass: TRadixTreeNodeClass
read fRouterClass write SetRouterClass;
/// main event handler called by the default implementation of the
// virtual Request method to process a given request
// - OutCustomHeader will handle Content-Type/Location
// - if OutContentType is STATICFILE_CONTENT_TYPE (i.e. '!STATICFILE'),
// then OutContent is the UTF-8 filename of a file to be sent directly
// to the client via http.sys or NGINX's X-Accel-Redirect; the
// OutCustomHeader should contain the eventual 'Content-type: ....' value
// - warning: this process must be thread-safe (can be called by several
// threads simultaneously)
property OnRequest: TOnHttpServerRequest
read fOnRequest write SetOnRequest;
/// event handler called just before the body is retrieved from the client
// - should return HTTP_SUCCESS=200 to continue the process, or an HTTP
// error code to reject the request immediately, and close the connection
property OnBeforeBody: TOnHttpServerBeforeBody
read fOnBeforeBody write SetOnBeforeBody;
/// event handler called after HTTP body has been retrieved, before OnRequest
// - may be used e.g. to return a HTTP_ACCEPTED (202) status to client and
// continue a long-term job inside the OnRequest handler in the same thread;
// or to modify incoming information before passing it to main business logic,
// (header preprocessor, body encoding etc...)
// - if the handler returns > 0 server will send a response immediately,
// unless return code is HTTP_ACCEPTED (202), then OnRequest will be called
// - warning: this handler must be thread-safe (could be called from several
// threads), and is NOT called before Route() callbacks execution
property OnBeforeRequest: TOnHttpServerRequest
read fOnBeforeRequest write SetOnBeforeRequest;
/// event handler called after request is processed but before response
// is sent back to client
// - main purpose is to apply post-processor, not part of request logic
// - if handler returns value > 0 it will override the OnRequest response code
// - warning: this handler must be thread-safe (could be called from several
// threads), and is NOT called after Route() callbacks execution
property OnAfterRequest: TOnHttpServerRequest
read fOnAfterRequest write SetOnAfterRequest;
/// event handler called after response is sent back to client
// - main purpose is to apply post-response analysis, logging, etc...
// - warning: this handler must be thread-safe (could be called from several
// threads), and IS called after Route() callbacks execution
property OnAfterResponse: TOnHttpServerAfterResponse
read fOnAfterResponse write SetOnAfterResponse;
/// event handler called after each working Thread is just initiated
// - called in the thread context at first place in THttpServerGeneric.Execute
property OnHttpThreadStart: TOnNotifyThread
read fOnThreadStart write fOnThreadStart;
/// event handler called when a working Thread is terminating
// - called in the corresponding thread context
// - the TThread.OnTerminate event will be called within a Synchronize()
// wrapper, so it won't fit our purpose
// - to be used e.g. to call CoUnInitialize from thread in which CoInitialize
// was made, for instance via a method defined as such:
// ! procedure TMyServer.OnHttpThreadTerminate(Sender: TThread);
// ! begin // TSqlDBConnectionPropertiesThreadSafe
// ! fMyConnectionProps.EndCurrentThread;
// ! end;
// - is used e.g. by TRest.EndCurrentThread for proper multi-threading
property OnHttpThreadTerminate: TOnNotifyThread
read fOnThreadTerminate write SetOnTerminate;
/// reject any incoming request with a body size bigger than this value
// - default to 0, meaning any input size is allowed
// - returns HTTP_PAYLOADTOOLARGE = 413 error if "Content-Length" incoming
// header overflow the supplied number of bytes
property MaximumAllowedContentLength: Int64
read fMaximumAllowedContentLength write SetMaximumAllowedContentLength;
/// custom event handler used to send a local file for STATICFILE_CONTENT_TYPE
// - see also NginxSendFileFrom() method
property OnSendFile: TOnHttpServerSendFile
read fOnSendFile write fOnSendFile;
/// defines request/response internal queue length
// - default value if 1000, which sounds fine for most use cases
// - for THttpApiServer, will return 0 if the system does not support HTTP
// API 2.0 (i.e. under Windows XP or Server 2003)
// - for THttpServer or THttpAsyncServer, will shutdown any incoming accepted
// socket if the internal number of pending requests exceed this limit
// - increase this value if you don't have any load-balancing in place, and
// in case of e.g. many 503 HTTP answers or if many "QueueFull" messages
// appear in HTTP.sys log files (normally in
// C:\Windows\System32\LogFiles\HTTPERR\httperr*.log) - may appear with
// thousands of concurrent clients accessing at once the same server -
// see @http://msdn.microsoft.com/en-us/library/windows/desktop/aa364501
// - you can use this property with a reverse-proxy as load balancer, e.g.
// with nginx configured as such:
// $ location / {
// $ proxy_pass http://balancing_upstream;
// $ proxy_next_upstream error timeout invalid_header http_500 http_503;
// $ proxy_connect_timeout 2;
// $ proxy_set_header Host $host;
// $ proxy_set_header X-Real-IP $remote_addr;
// $ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
// $ proxy_set_header X-Conn-ID $connection
// $ }
// see https://synopse.info/forum/viewtopic.php?pid=28174#p28174
property HttpQueueLength: cardinal
read GetHttpQueueLength write SetHttpQueueLength;
/// returns the number of current HTTP connections
// - may not include HTTP/1.0 short-living connections
property ConnectionsActive: cardinal
read GetConnectionsActive;
/// TRUE if the inherited class is able to handle callbacks
// - only TWebSocketServer/TWebSocketAsyncServer have this ability by now
function CanNotifyCallback: boolean;
{$ifdef HASINLINE}inline;{$endif}
/// the value of a custom HTTP header containing the real client IP
// - by default, the RemoteIP information will be retrieved from the socket
// layer - but if the server runs behind some proxy service, you should
// define here the HTTP header name which indicates the true remote client
// IP value, mostly as 'X-Real-IP' or 'X-Forwarded-For'
property RemoteIPHeader: RawUtf8
read fRemoteIPHeader write SetRemoteIPHeader;
/// the value of a custom HTTP header containing the real client connection ID
// - by default, Ctxt.ConnectionID information will be retrieved from our
// socket layer - but if the server runs behind some proxy service, you should
// define here the HTTP header name which indicates the real remote connection,
// for example as 'X-Conn-ID', setting in nginx config:
// $ proxy_set_header X-Conn-ID $connection
property RemoteConnIDHeader: RawUtf8
read fRemoteConnIDHeader write SetRemoteConnIDHeader;
published
/// the Server name, UTF-8 encoded, e.g. 'mORMot2 (Linux)'
// - will be served as "Server: ..." HTTP header
// - for THttpApiServer, when called from the main instance, will propagate
// the change to all cloned instances, and included in any HTTP API 2.0 log
property ServerName: RawUtf8
read fServerName write SetServerName;
/// the associated process name
property ProcessName: RawUtf8
read fProcessName write fProcessName;
/// returns the API version used by the inherited implementation
property ApiVersion: RawUtf8
read GetApiVersion;
/// allow to customize this HTTP server instance
// - some inherited classes may have only partial support of those options
property Options: THttpServerOptions
read fOptions write SetOptions;
/// read access to the URI router, as published property (e.g. for logs)
// - use the Route function to actually setup the routing
// - may be nil if Route has never been accessed, i.e. no routing was set
property Router: TUriRouter
read fRoute;
/// access to the HTTP logger initialized with hsoEnableLogging option
// - you can customize the logging process via Logger.Format,
// Logger.DestFolder, Logger.DefaultRotate, Logger.DefaultRotateFiles
// properties and Logger.DefineHost() method
// - equals nil if hsoEnableLogging was not set in the constructor
property Logger: THttpLogger
read fLogger;
/// access to the HTTP analyzer initialized with hsoTelemetryCsv or
// hsoTelemetryJson options
// - you can customize this process via Analyzer.DestFolder
property Analyzer: THttpAnalyzer
read fAnalyzer;
end;
const
/// used to compute the request ConnectionFlags from the socket TLS state
HTTP_TLS_FLAGS: array[{tls=}boolean] of THttpServerRequestFlags = (
[],
[hsrHttps, hsrSecured]);
/// used to compute the request ConnectionFlags from connection: upgrade header
HTTP_UPG_FLAGS: array[{tls=}boolean] of THttpServerRequestFlags = (
[],
[hsrConnectionUpgrade]);
/// used to compute the request ConnectionFlags from HTTP/1.0 command
HTTP_10_FLAGS: array[{http10=}boolean] of THttpServerRequestFlags = (
[],
[hsrHttp10]);
/// some pre-computed CryptCertOpenSsl[caaRS256].New key for Windows
// - the associated password is 'pass'
// - as used e.g. by THttpServerSocketGeneric.WaitStartedHttps
function PrivKeyCertPfx: RawByteString;
/// initialize a server-side TLS structure with a self-signed algorithm
// - as used e.g. by THttpServerSocketGeneric.WaitStartedHttps
// - if OpenSSL is available and UsePreComputed is false, will
// generate a temporary pair of key files via
// Generate(CU_TLS_SERVER, '127.0.0.1', nil, 3650) with a random password
// - if UsePreComputed=true or on pure SChannel, will use the PrivKeyCertPfx
// pre-computed constant
// - you should eventually call DeleteFile(Utf8ToString(TLS.CertificateFile))
// and DeleteFile(Utf8ToString(TLS.PrivateKeyFile)) to delete the two temp files
procedure InitNetTlsContextSelfSignedServer(var TLS: TNetTlsContext;
Algo: TCryptAsymAlgo = caaRS256; UsePreComputed: boolean = false);
/// used by THttpServerGeneric.SetFavIcon to return a nice /favicon.ico
function FavIconBinary: RawByteString;
type
/// define how GetMacAddress() makes its sorting choices
// - used e.g. for THttpPeerCacheSettings.InterfaceFilter property
// - mafEthernetOnly will only select TMacAddress.Kind = makEthernet
// - mafLocalOnly will only select makEthernet or makWifi adapters
// - mafRequireBroadcast won't return any TMacAddress with Broadcast = ''
// - mafIgnoreGateway won't put the TMacAddress.Gateway <> '' first
// - mafIgnoreKind and mafIgnoreSpeed will ignore Kind or Speed properties
TMacAddressFilter = set of (
mafEthernetOnly,
mafLocalOnly,
mafRequireBroadcast,
mafIgnoreGateway,
mafIgnoreKind,
mafIgnoreSpeed);
/// pickup the most suitable network according to some preferences
// - will sort GetMacAddresses() results according to its Kind and Speed
// to select the most suitable local interface e.g. for THttpPeerCache
function GetMainMacAddress(out Mac: TMacAddress;
Filter: TMacAddressFilter = []): boolean; overload;
/// get a network interface from its TMacAddress main fields
// - search is case insensitive for TMacAddress.Name and Address fields or as
// exact IP, and eventually as IP bitmask pattern (e.g. 192.168.1.255)
function GetMainMacAddress(out Mac: TMacAddress;
const InterfaceNameAddressOrIP: RawUtf8;
UpAndDown: boolean = false): boolean; overload;
{ ******************** THttpServerSocket/THttpServer HTTP/1.1 Server }
type
/// results of THttpServerSocket.GetRequest virtual method
// - grClosed is returned if the socket was disconnected/closed by the client
// - grException is returned if any exception occurred during the process
// - grOversizedPayload is returned when MaximumAllowedContentLength is reached
// - grRejected on invalid input, or when OnBeforeBody returned not 200
// - grIntercepted is returned e.g. from OnHeaderParsed as valid result
// - grTimeout is returned when HeaderRetrieveAbortDelay is reached
// - grHeaderReceived is returned for GetRequest({withbody=}false)
// - grBodyReceived is returned for GetRequest({withbody=}true)
// - grWwwAuthenticate is returned if GetRequest() did send a 401 response
// - grUpgraded indicates that this connection was upgraded e.g. as WebSockets
// - grBanned is triggered by the hsoBan40xIP option
THttpServerSocketGetRequestResult = (
grClosed,
grException,
grOversizedPayload,
grRejected,
grIntercepted,
grTimeout,
grHeaderReceived,
grBodyReceived,
grWwwAuthenticate,
grUpgraded,
grBanned);
{$M+} // to have existing RTTI for published properties
THttpServer = class;
{$M-}
/// Socket API based HTTP/1.1 server class used by THttpServer Threads
THttpServerSocket = class(THttpSocket)
protected
fRemoteConnectionID: THttpServerConnectionID;
fServer: THttpServer;
fKeepAliveClient: boolean;
fAuthorized: THttpServerRequestAuthentication;
fRequestFlags: THttpServerRequestFlags;
fAuthSec: cardinal;
fConnectionOpaque: THttpServerConnectionOpaque; // two PtrUInt tags
fResponseHeader: RawUtf8;
// from TSynThreadPoolTHttpServer.Task
procedure TaskProcess(aCaller: TSynThreadPoolWorkThread); virtual;
function TaskProcessBody(aCaller: TSynThreadPoolWorkThread;
aHeaderResult: THttpServerSocketGetRequestResult): boolean;
public
/// create the socket according to a server
// - will register the THttpSocketCompress functions from the server
// - once created, caller should call AcceptRequest() to accept the socket
// - if TLS is enabled, ensure server certificates are initialized once
constructor Create(aServer: THttpServer); reintroduce;
/// main object function called after aClientSock := Accept + Create:
// - get Command, Method, URL, Headers and Body (if withBody is TRUE)
// - get sent data in Content (if withBody=true and ContentLength<>0)
// - returned enumeration will indicates the processing state
function GetRequest(withBody: boolean;
headerMaxTix: Int64): THttpServerSocketGetRequestResult; virtual;
/// access to the internal two PtrUInt tags of this connection
// - may be nil behind a reverse proxy (i.e. Server.RemoteConnIDHeader<>'')
function GetConnectionOpaque: PHttpServerConnectionOpaque;
{$ifdef HASINLINE} inline; {$endif}
/// contains the method ('GET','POST'.. e.g.) after GetRequest()
property Method: RawUtf8
read Http.CommandMethod;
/// contains the URL ('/' e.g.) after GetRequest()
property URL: RawUtf8
read Http.CommandUri;
/// true if the client is HTTP/1.1 and 'Connection: Close' is not set
// - default HTTP/1.1 behavior is "keep alive", unless 'Connection: Close'
// is specified, cf. RFC 2068 page 108: "HTTP/1.1 applications that do not
// support persistent connections MUST include the "close" connection option
// in every message"
property KeepAliveClient: boolean
read fKeepAliveClient write fKeepAliveClient;
/// the recognized connection ID, after a call to GetRequest()
// - identifies either the raw connection on the current server, or
// the custom header value as set by a local proxy, e.g.
// THttpServerGeneric.RemoteConnIDHeader='X-Conn-ID' for nginx
property RemoteConnectionID: THttpServerConnectionID
read fRemoteConnectionID;
/// the associated HTTP Server instance - may be nil
property Server: THttpServer
read fServer;
end;
/// HTTP response Thread as used by THttpServer Socket API based class
// - Execute procedure get the request and calculate the answer, using
// the thread for a single client connection, until it is closed
// - you don't have to overload the protected THttpServerResp Execute method:
// override THttpServer.Request() function or, if you need a lower-level access
// (change the protocol, e.g.) THttpServer.Process() method itself
THttpServerResp = class(TSynThread)
protected
fConnectionID: THttpServerConnectionID;
fServer: THttpServer;
fServerSock: THttpServerSocket;
fClientSock: TNetSocket;
fClientSin: TNetAddr;
/// main thread loop: read request from socket, send back answer
procedure Execute; override;
public
/// initialize the response thread for the corresponding incoming socket
// - this version will get the request directly from an incoming socket
constructor Create(aSock: TNetSocket; const aSin: TNetAddr;
aServer: THttpServer); reintroduce; overload;
/// initialize the response thread for the corresponding incoming socket
// - this version will handle KeepAlive, for such an incoming request
constructor Create(aServerSock: THttpServerSocket; aServer: THttpServer);
reintroduce; overload; virtual;
/// called by THttpServer.Destroy on existing connections
// - set Terminate and close the socket
procedure Shutdown; virtual;
/// the associated socket to communicate with the client
property ServerSock: THttpServerSocket
read fServerSock;
/// the associated main HTTP server instance
property Server: THttpServer
read fServer;
/// the unique identifier of this connection
property ConnectionID: THttpServerConnectionID
read fConnectionID;
end;
/// metaclass of HTTP response Thread
THttpServerRespClass = class of THttpServerResp;
/// a simple Thread Pool, used for fast handling HTTP requests of a THttpServer
// - will handle multi-connection with less overhead than creating a thread
// for each incoming request
// - will create a THttpServerResp response thread, if the incoming request is
// identified as HTTP/1.1 keep alive, or HTTP body length is bigger than 1 MB
TSynThreadPoolTHttpServer = class(TSynThreadPool)
protected
fServer: THttpServer;
fBigBodySize: integer;
fMaxBodyThreadCount: integer;
{$ifndef USE_WINIOCP}
function QueueLength: integer; override;
{$endif USE_WINIOCP}
// here aContext is a THttpServerSocket instance
procedure Task(aCaller: TSynThreadPoolWorkThread;
aContext: pointer); override;
procedure TaskAbort(aContext: pointer); override;
public
/// initialize a thread pool with the supplied number of threads
// - Task() overridden method processs the HTTP request set by Push()