-
-
Notifications
You must be signed in to change notification settings - Fork 181
Expand file tree
/
Copy pathmormot.net.ws.server.pas
More file actions
572 lines (509 loc) · 21.8 KB
/
Copy pathmormot.net.ws.server.pas
File metadata and controls
572 lines (509 loc) · 21.8 KB
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
/// WebSockets Server-Side Process
// - 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.ws.server;
{
*****************************************************************************
WebSockets Bidirectional Server
- TWebSocketProcessServer Processing Class
- TWebSocketServerRest Bidirectional REST Server
- Socket.IO / Engine.IO Server Protocol over WebSockets
*****************************************************************************
}
interface
{$I ..\mormot.defines.inc}
uses
sysutils,
classes,
mormot.core.base,
mormot.core.os,
mormot.core.unicode,
mormot.core.text,
mormot.core.data,
mormot.core.log,
mormot.core.threads,
mormot.core.rtti,
mormot.core.json,
mormot.core.buffers,
mormot.core.interfaces,
mormot.crypt.core,
mormot.crypt.ecc,
mormot.crypt.secure, // IProtocol definition
mormot.net.sock,
mormot.net.http,
mormot.net.client,
mormot.net.server,
mormot.net.ws.core;
{ ******************** TWebSocketProcessServer Processing Class }
type
/// implements WebSockets process as used on server side
TWebSocketProcessServer = class(TWebCrtSocketProcess)
protected
function ComputeContext(
out RequestProcess: TOnHttpServerRequest): THttpServerRequestAbstract; override;
end;
/// meta-class of WebSockets process as used on server side
TWebSocketProcessServerClass = class of TWebSocketProcessServer;
{ ******************** TWebSocketServerRest Bidirectional REST Server }
/// socket maintained for each connection to the HTTP/WebSockets server
// - this class contains additional parameters used to maintain the
// WebSockets execution context in overriden TWebSocketServer.Process method
TWebSocketServerSocket = class(THttpServerSocket)
protected
fProcess: TWebSocketProcessServer; // set once upgraded
public
/// ensure focConnectionClose is done before closing the connection
procedure Close; override;
/// finalize this socket and its associated TWebSocketProcessServer
destructor Destroy; override;
/// push a notification to the client
function NotifyCallback(Ctxt: THttpServerRequest;
aMode: TWebSocketProcessNotifyCallback): cardinal; virtual;
/// the Sec-WebSocket-Protocol application protocol currently involved
// - TWebSocketProtocolJson or TWebSocketProtocolBinary in the mORMot context
// - could be nil if the connection is in standard HTTP/1.1 mode
function WebSocketProtocol: TWebSocketProtocol;
{$ifdef HASINLINE} inline; {$endif}
/// low-level WebSocket protocol processing instance
property WebSocketProcess: TWebSocketProcessServer
read fProcess;
end;
/// callback signature to notify TWebSocketServer connections
TOnWebSocketServerEvent = procedure(Sender: TWebSocketServerSocket) of object;
/// main HTTP/WebSockets server Thread using the standard Sockets API (e.g. WinSock)
// - once upgraded to WebSockets from the client, this class is able to serve
// any Sec-WebSocket-Protocol application content
TWebSocketServer = class(THttpServer)
protected
fWebSocketConnections: TSynObjectListLocked; // of TWebSocketProcessServer
fProtocols: TWebSocketProtocolList;
fSettings: TWebSocketProcessSettings;
fProcessClass: TWebSocketProcessServerClass;
fOnWSUpgraded: TOnWebSocketProtocolUpgraded;
fOnWSClose: TOnWebSocketProtocolClosed;
fOnWSConnect, fOnWSDisconnect: TOnWebSocketServerEvent;
function DoUpgrade(Protocol: TWebSocketProtocol): integer; virtual;
procedure DoConnect(Context: TWebSocketServerSocket); virtual;
procedure DoDisconnect(Context: TWebSocketServerSocket); virtual;
/// validate the WebSockets handshake, then call Context.fProcess.ProcessLoop()
function WebSocketProcessUpgrade(ClientSock: THttpServerSocket): integer; virtual;
/// overriden method which will recognize the WebSocket protocol handshake,
// then run the whole bidirectional communication in its calling thread
// - here ConnectionThread is a THttpServerResp, and ClientSock.Headers
// and ConnectionUpgrade properties should be checked for the handshake
procedure Process(ClientSock: THttpServerSocket;
ConnectionID: THttpServerConnectionID; ConnectionThread: TSynThread); override;
public
/// create a Server Thread, binded and listening on a port
// - this constructor will raise a EHttpServer exception if binding failed
// - expects the port to be specified as string, e.g. '1234'; you can
// optionally specify a server address to bind to, e.g. '1.2.3.4:1234'
// - due to the way how WebSockets works, one thread will be created
// for any incoming connection
// - note that this constructor will not register any protocol, so is
// useless until you execute Protocols.Add()
// - in the current implementation, the ServerThreadPoolCount parameter will
// use two threads by default to handle shortliving HTTP/1.0 "connection: close"
// requests, and one thread will be maintained per keep-alive/websockets client
// - by design, the KeepAliveTimeOut value is ignored with this server
// once it has been upgraded to WebSockets
constructor Create(const aPort: RawUtf8; const OnStart, OnStop: TOnNotifyThread;
const ProcessName: RawUtf8; ServerThreadPoolCount: integer = 2;
KeepAliveTimeOut: integer = 30000; ProcessOptions: THttpServerOptions = [];
aLog: TSynLogClass = nil); override;
/// close the server
destructor Destroy; override;
/// will send a given frame to all connected clients
// - expect aFrame.opcode to be either focText or focBinary
// - will call TWebSocketProcess.Outgoing.Push for asynchronous sending
procedure WebSocketBroadcast(const aFrame: TWebSocketFrame); overload;
/// will send a given frame to clients matching the supplied connection IDs
// - expect aFrame.opcode to be either focText or focBinary
// - WebSocketBroadcast(nil) will broadcast to all running websockets
// - will call TWebSocketProcess.Outgoing.Push for asynchronous sending
procedure WebSocketBroadcast(const aFrame: TWebSocketFrame;
const aClientsConnectionID: THttpServerConnectionIDDynArray); overload;
/// give access to the underlying WebSockets connection from its ID
function IsActiveWebSocket(
ConnectionID: THttpServerConnectionID): TWebSocketProcessServer;
/// allow to customize the WebSockets processing
// - apply to all protocols on this server instance
// - those parameters are accessed by reference from existing connections,
// so you should better not modify them once the server started
function Settings: PWebSocketProcessSettings;
{$ifdef HASINLINE}inline;{$endif}
/// allow to customize the WebSockets processing classes
property ProcessClass: TWebSocketProcessServerClass
read fProcessClass write fProcessClass;
/// how many WebSockets connections are currently maintained
function WebSocketConnections: integer;
/// access to the protocol list handled by this server
property WebSocketProtocols: TWebSocketProtocolList
read fProtocols;
/// event triggerred when a new connection upgrade has been upgraded
// - allow e.g. to verify a JWT bearer before returning the WS 101 response
property OnWebSocketUpgraded: TOnWebSocketProtocolUpgraded
read fOnWSUpgraded write fOnWSUpgraded;
/// event triggerred when a new connection upgrade has been done
// - just before the main processing WebSockets frames processing loop
property OnWebSocketConnect: TOnWebSocketServerEvent
read fOnWSConnect write fOnWSConnect;
/// event triggerred when a connection is about to be closed
// - when the main processing WebSockets frames processing loop finishes
property OnWebSocketDisconnect: TOnWebSocketServerEvent
read fOnWSDisconnect write fOnWSDisconnect;
/// same as OnWebSocketDisconnect, but using TWebSocketProtocol as parameter
property OnWebSocketClose: TOnWebSocketProtocolClosed
read fOnWSClose write fOnWSClose;
end;
/// main HTTP/WebSockets server Thread using the standard Sockets API (e.g. WinSock)
// - once upgraded to WebSockets from the client, this class is able to serve
// our proprietary Sec-WebSocket-Protocol: 'synopsejson' or 'synopsebin'
// application content, managing regular REST client-side requests and
// also server-side push notifications
// - once in 'synopse*' mode, the Request() method will be trigerred from
// any incoming WebSocket frame from the client, and the OnCallback event
// will be available to push a WebSocket frame from the server to the client
TWebSocketServerRest = class(TWebSocketServer)
public
/// create a Server Thread, binded and listening on a port, with our
// 'synopsebin' and optionally 'synopsejson' modes
// - if aWebSocketsURI is '', any URI would potentially upgrade; you can
// specify an URI to limit the protocol upgrade to a single resource
// - TWebSocketProtocolBinary will always be registered by this constructor
// - aWebSocketsEncryptionKey format follows TWebSocketProtocol.SetEncryptKey,
// so could be e.g. 'password#xxxxxx.private' or 'a=mutual;e=aesctc128;p=34a2..'
// to use TEcdheProtocol, or a plain password for TProtocolAes
// - if aWebSocketsAjax is TRUE, it will also register TWebSocketProtocolJson
// so that AJAX applications would be able to connect to this server
// - warning: WaitStarted should be called after Create() to check for
// for actual port binding in the background thread
constructor Create(const aPort: RawUtf8; const OnStart, OnStop: TOnNotifyThread;
const aProcessName, aWebSocketsURI, aWebSocketsEncryptionKey: RawUtf8;
aWebSocketsAjax: boolean = false; aLog: TSynLogClass = nil); reintroduce; overload;
/// defines the WebSockets protocols to be used for this Server
// - i.e. 'synopsebin' and optionally 'synopsejson' modes
// - if aWebSocketsURI is '', any URI would potentially upgrade; you can
// specify an URI to limit the protocol upgrade to a single resource
// - TWebSocketProtocolBinary will always be registered by this constructor
// - aWebSocketsEncryptionKey format follows TWebSocketProtocol.SetEncryptKey
// - if aWebSocketsAjax is TRUE, it will also register TWebSocketProtocolJson
// so that AJAX applications would be able to connect to this server
function WebSocketsEnable(const aWebSocketsURI,
aWebSocketsEncryptionKey: RawUtf8; aWebSocketsAjax: boolean = false;
aWebSocketsBinaryOptions: TWebSocketProtocolBinaryOptions =
[pboSynLzCompress]): pointer; override;
/// server can send a request back to the client, when the connection has
// been upgraded to WebSocket
// - InURL/InMethod/InContent properties are input parameters (InContentType
// is ignored)
// - OutContent/OutContentType/OutCustomHeader are output parameters
// - CallingThread should be set to the client's Ctxt.CallingThread
// value, so that the method could know which connnection is to be used -
// it will return HTTP_NOTFOUND (404) if the connection is unknown
// - result of the function is the HTTP error code (200 if OK, e.g.)
function Callback(Ctxt: THttpServerRequest; aNonBlocking: boolean): cardinal;
override;
end;
{ ******************** Socket.IO / Engine.IO Server Protocol over WebSockets }
implementation
{ ******************** TWebSocketProcessServer Processing Class }
{ TWebSocketProcessServer }
function TWebSocketProcessServer.ComputeContext(
out RequestProcess: TOnHttpServerRequest): THttpServerRequestAbstract;
var
server: THttpServer;
begin
server := (fSocket as TWebSocketServerSocket).Server;
result := THttpServerRequest.Create(
server, fProtocol.ConnectionID, fOwnerThread, {asynchandle=}0,
fProtocol.ConnectionFlags + HTTP_TLS_FLAGS[fSocket.TLS.Enabled],
fProtocol.ConnectionOpaque);
RequestProcess := server.Request;
end;
{ ******************** TWebSocketProcessServer Processing Class }
{ TWebSocketServer }
constructor TWebSocketServer.Create(const aPort: RawUtf8;
const OnStart, OnStop: TOnNotifyThread; const ProcessName: RawUtf8;
ServerThreadPoolCount, KeepAliveTimeOut: integer;
ProcessOptions: THttpServerOptions; aLog: TSynLogClass);
begin
// override with custom processing classes
fSocketClass := TWebSocketServerSocket;
fProcessClass := TWebSocketProcessServer;
// initialize protocols and connections
fCallbackSendDelay := @fSettings.SendDelay;
fWebSocketConnections := TSynObjectListLocked.Create({owned=}false);
fProtocols := TWebSocketProtocolList.Create;
fProtocols.OnUpgraded := DoUpgrade;
fSettings.SetDefaults;
fSettings.HeartbeatDelay := 20000;
if hsoLogVerbose in ProcessOptions then
fSettings.SetFullLog;
if ServerThreadPoolCount > 4 then
ServerThreadPoolCount := 4; // don't loose threads for nothing
// start the server
inherited Create(aPort, OnStart, OnStop, ProcessName,
ServerThreadPoolCount, KeepAliveTimeOut, ProcessOptions, aLog);
end;
function TWebSocketServer.WebSocketProcessUpgrade(
ClientSock: THttpServerSocket): integer;
var
protocol: TWebSocketProtocol;
resp: RawUtf8;
sock: TWebSocketServerSocket;
begin
sock := ClientSock as TWebSocketServerSocket;
// validate the WebSockets upgrade handshake
sock.KeepAliveClient := false; // close connection with WebSockets
result := fProtocols.ServerUpgrade(sock.Http, sock.RemoteIP,
sock.RemoteConnectionID, @sock.fConnectionOpaque, protocol, resp);
if result = HTTP_SUCCESS then
if not sock.TrySndLow(pointer(resp), length(resp)) then
begin
protocol.Free;
result := HTTP_BADREQUEST;
end;
if result <> HTTP_SUCCESS then
begin
// notify upgrade failure to client and close connection
FormatUtf8('HTTP/1.0 % WebSocket Upgrade Error'#13#10 +
'Connection: Close'#13#10#13#10, [result], resp);
sock.TrySndLow(pointer(resp), length(resp));
exit;
end;
// if we reached here, we switched/upgraded to WebSockets bidir frames
sock.fProcess := fProcessClass.Create(
ClientSock, protocol, {ownerthread=}nil, @fSettings, fProcessName);
fWebSocketConnections.Add(sock.fProcess);
try
// run main blocking loop in this connection-dedicated thread
DoConnect(sock);
sock.fProcess.ProcessLoop; // will return on closing
finally
DoDisconnect(sock);
fWebSocketConnections.Remove(sock.fProcess);
FreeAndNilSafe(sock.fProcess); // notify end of WebSockets
end;
end;
function TWebSocketServer.DoUpgrade(Protocol: TWebSocketProtocol): integer;
begin
if Assigned(fOnWSUpgraded) then
result := fOnWSUpgraded(Protocol)
else
result := HTTP_SUCCESS; // continue
end;
procedure TWebSocketServer.DoConnect(Context: TWebSocketServerSocket);
begin
if Assigned(fOnWSConnect) then
fOnWSConnect(Context);
end;
procedure TWebSocketServer.DoDisconnect(Context: TWebSocketServerSocket);
begin
if Assigned(fOnWSDisconnect) then
try
fOnWSDisconnect(Context);
except // ignore any external callback error during shutdown
end;
if Assigned(fOnWSClose) then
try
fOnWSClose(Context.fProcess.Protocol);
finally
end;
end;
procedure TWebSocketServer.Process(ClientSock: THttpServerSocket;
ConnectionID: THttpServerConnectionID; ConnectionThread: TSynThread);
var
err: integer;
begin
if (hfConnectionUpgrade in ClientSock.Http.HeaderFlags) and
ClientSock.KeepAliveClient and
(ClientSock.Method <> '') and
IsGet(ClientSock.Method) and
PropNameEquals(ClientSock.Http.Upgrade, 'websocket') then
begin
// upgrade and run fProcess.ProcessLoop
err := WebSocketProcessUpgrade(ClientSock);
if err <> HTTP_SUCCESS then
WebSocketLog.Add.Log(sllTrace,
'Process: WebSocketProcessUpgrade failed as %', [err], self);
end
else
inherited Process(ClientSock, ConnectionID, ConnectionThread);
end;
destructor TWebSocketServer.Destroy;
begin
inherited Destroy; // close any pending connection
fWebSocketConnections.Free;
fProtocols.Free;
end;
function TWebSocketServer.Settings: PWebSocketProcessSettings;
begin
result := @fSettings;
end;
function TWebSocketServer.WebSocketConnections: integer;
begin
result := fWebSocketConnections.Count;
end;
type
PWebSocketProcessServer = ^TWebSocketProcessServer;
function FastFindConnection(c: PWebSocketProcessServer; n: integer;
id: THttpServerConnectionID): TWebSocketProcessServer;
begin
if n > 0 then
repeat
result := c^;
if result.fConnectionID = id then // brute force search
exit;
inc(c);
dec(n);
until n = 0;
result := nil;
end;
function TWebSocketServer.IsActiveWebSocket(
ConnectionID: THttpServerConnectionID): TWebSocketProcessServer;
begin
result := nil;
if Terminated or
(ConnectionID = 0) then
exit;
fWebSocketConnections.Safe.ReadOnlyLock;
try
result := FastFindConnection(pointer(fWebSocketConnections.List),
fWebSocketConnections.Count, ConnectionID);
finally
fWebSocketConnections.Safe.ReadOnlyUnLock;
end;
if (result <> nil) and
(result.State <> wpsRun) then
result := nil;
end;
procedure TWebSocketServer.WebSocketBroadcast(const aFrame: TWebSocketFrame);
begin
WebSocketBroadcast(aFrame, nil);
end;
procedure TWebSocketServer.WebSocketBroadcast(const aFrame: TWebSocketFrame;
const aClientsConnectionID: THttpServerConnectionIDDynArray);
var
i, len, ids: integer;
ws: PWebSocketProcessServer;
tix: cardinal;
temp: TWebSocketFrame; // local copy since SendFrame() modifies the payload
sorted: TSynTempBuffer;
begin
if Terminated or
not (aFrame.opcode in [focText, focBinary]) then
exit;
ids := length(aClientsConnectionID);
if ids > 0 then
begin
sorted.Init(pointer(aClientsConnectionID), ids * 8);
QuickSortInt64(sorted.buf, 0, ids - 1);
end;
dec(ids); // WebSocketBroadcast(nil) -> ids<0 -> broadcast all
temp.opcode := aFrame.opcode;
temp.content := aFrame.content;
len := length(aFrame.payload);
tix := GetTickSec;
fWebSocketConnections.Safe.ReadOnlyLock;
try
ws := pointer(fWebSocketConnections.List);
for i := 1 to fWebSocketConnections.Count do
begin
if (ws^.State = wpsRun) and
// broadcast all
((ids < 0) or
// branchless O(log(n)) asm on x86_64
(FastFindInt64Sorted(sorted.buf, ids, ws^.Protocol.ConnectionID) >= 0)) then
begin
FastSetRawByteString(temp.payload, pointer(aFrame.payload), len);
ws^.Outgoing.Push(temp, tix); // non blocking asynchronous sending
end;
inc(ws);
end;
finally
fWebSocketConnections.Safe.ReadOnlyUnLock;
if ids >= 0 then
sorted.Done;
end;
end;
{ TWebSocketServerSocket }
procedure TWebSocketServerSocket.Close;
begin
if (fProcess <> nil) and
not fProcess.ConnectionCloseWasSent then
begin
WebSocketLog.Add.Log(sllTrace, 'Close: send focConnectionClose', self);
fProcess.Shutdown({waitforpong=}true); // notify client with focConnectionClose
end;
inherited Close;
end;
destructor TWebSocketServerSocket.Destroy;
begin
inherited Destroy;
FreeAndNilSafe(fProcess);
end;
function TWebSocketServerSocket.NotifyCallback(Ctxt: THttpServerRequest;
aMode: TWebSocketProcessNotifyCallback): cardinal;
begin
if fProcess = nil then
result := HTTP_NOTFOUND
else
result := fProcess.NotifyCallback(Ctxt, aMode);
end;
function TWebSocketServerSocket.WebSocketProtocol: TWebSocketProtocol;
begin
if (Self = nil) or
(fProcess = nil) then
result := nil
else
result := fProcess.Protocol;
end;
{ TWebSocketServerRest }
constructor TWebSocketServerRest.Create(const aPort: RawUtf8;
const OnStart, OnStop: TOnNotifyThread; const aProcessName, aWebSocketsURI,
aWebSocketsEncryptionKey: RawUtf8; aWebSocketsAjax: boolean; aLog: TSynLogClass);
begin
Create(aPort, OnStart, OnStop, aProcessName, {threadpool=}2, {keepalive=}30000,
{options=}[], aLog);
WebSocketsEnable(aWebSocketsURI, aWebSocketsEncryptionKey, aWebSocketsAjax);
end;
function TWebSocketServerRest.WebSocketsEnable(
const aWebSocketsURI, aWebSocketsEncryptionKey: RawUtf8;
aWebSocketsAjax: boolean;
aWebSocketsBinaryOptions: TWebSocketProtocolBinaryOptions): pointer;
begin
fProtocols.AddOnce(TWebSocketProtocolBinary.Create(
aWebSocketsURI, {server=}true, aWebSocketsEncryptionKey,
@fSettings, aWebSocketsBinaryOptions));
if aWebSocketsAjax then
fProtocols.AddOnce(TWebSocketProtocolJson.Create(aWebSocketsURI));
result := @fSettings;
end;
function TWebSocketServerRest.Callback(Ctxt: THttpServerRequest;
aNonBlocking: boolean): cardinal;
var
connection: TWebSocketProcessServer;
mode: TWebSocketProcessNotifyCallback;
begin
if aNonBlocking then // see TInterfacedObjectFakeServer.CallbackInvoke
mode := wscNonBlockWithoutAnswer
else
mode := wscBlockWithAnswer;
if Ctxt = nil then
connection := nil
else
begin
connection := IsActiveWebSocket(Ctxt.ConnectionID);
WebSocketLog.Add.Log(LOG_TRACEERROR[connection = nil],
'Callback(%) % on ConnectionID=%',
[Ctxt.Url, ToText(mode)^, Ctxt.ConnectionID], self);
end;
if connection <> nil then
// this request is a websocket, on a non broken connection
result := connection.NotifyCallback(Ctxt, mode)
else
result := HTTP_NOTFOUND;
end;
{ ******************** Socket.IO / Engine.IO Server Protocol over WebSockets }
end.