wsupgrader.pp 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. unit wsupgrader;
  2. {$mode ObjFPC}{$H+}
  3. interface
  4. uses
  5. Classes, SysUtils, httpprotocol, httpdefs, fphttpserver, fpwebsocket, fpcustwsserver;
  6. Type
  7. { TCustomWebsocketUpgrader }
  8. TAllowUpgradeEvent = Procedure(Sender : TObject; aRequest : TRequest; var aAllow : Boolean) of object;
  9. TCustomWebsocketUpgrader = Class(TCustomWSServer)
  10. private
  11. FActive: Boolean;
  12. FOnAllowUpgrade: TAllowUpgradeEvent;
  13. FStrictProtocolCheck: Boolean;
  14. FUpgradeName: String;
  15. FWebServer: TFPCustomHttpServer;
  16. FHost: String;
  17. function GetHandshakeRequest(aRequest: TFPHTTPConnectionRequest): TWSHandShakeRequest;
  18. function GetUpgradeName: String;
  19. procedure SetHost(const AValue: String);
  20. procedure SetUpgradeName(const AValue: String);
  21. procedure SetWebServer(AValue: TFPCustomHttpServer);
  22. Protected
  23. // Override from custom server
  24. procedure SetActive(const aValue: Boolean); override;
  25. function GetActive: Boolean; override;
  26. Procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  27. // Start upgrader: register, create connection handler
  28. procedure StartUpgrader;
  29. // End upgrader: unregister, free connection handler
  30. procedure StopUpgrader;
  31. // Check callback for upgrader mechanism
  32. procedure DoCheck(aRequest: TFPHTTPConnectionRequest; var aHandlesUpgrade: Boolean); virtual;
  33. // Upgrade callback for upgrader mechanism
  34. procedure DoUpgrade(aConnection: TFPHTTPConnection; aRequest: TFPHTTPConnectionRequest); virtual;
  35. // Webserver that we must register with
  36. Property WebServer : TFPCustomHttpServer Read FWebServer Write SetWebServer;
  37. // If set, only this resource will be acceped for upgrade.
  38. Property Host : String Read FHost Write SetHost;
  39. // Name to use when registering upgrade mechanism. Defaults to Name.
  40. Property UpgradeName : String Read GetUpgradeName Write SetUpgradeName;
  41. // Check also Host and Sec-Websocket-Version
  42. Property StrictProtocolCheck : Boolean Read FStrictProtocolCheck Write FStrictProtocolCheck;
  43. // Called when upgrade request is processed. allow is initialized with check for websocket upgrade.
  44. Property OnAllowUpgrade : TAllowUpgradeEvent Read FOnAllowUpgrade Write FOnAllowUpgrade;
  45. Public
  46. Destructor Destroy; override;
  47. end;
  48. TWebsocketUpgrader = class(TCustomWebsocketUpgrader)
  49. Published
  50. Property Active; // Registers, unregisters
  51. Property WebServer;
  52. Property Host;
  53. Property Resource;
  54. Property StrictProtocolCheck;
  55. Property ThreadMode;
  56. Property WebSocketVersion;
  57. Property MessageWaitTime;
  58. Property Options;
  59. Property OnAllow;
  60. property OnMessageReceived;
  61. property OnDisconnect;
  62. property OnControlReceived;
  63. Property OnError;
  64. Property OutgoingFrameMask;
  65. Property OnAllowUpgrade;
  66. end;
  67. implementation
  68. Resourcestring
  69. SErrWebserverNotAssigned = 'Webserver not assigned';
  70. SErrNoUpgradeName = 'Upgradename not set. Set UpgradeName or Name';
  71. { TCustomWebsocketUpgrader }
  72. Function TCustomWebsocketUpgrader.GetActive : Boolean;
  73. begin
  74. Result:=FActive;
  75. end;
  76. procedure TCustomWebsocketUpgrader.SetActive(const AValue: Boolean);
  77. begin
  78. if FActive=AValue then Exit;
  79. If not Assigned(Webserver) then
  80. Raise EWebsocket.Create(SErrWebserverNotAssigned);
  81. If (UpgradeName='') then
  82. Raise EWebsocket.Create(SErrNoUpgradeName);
  83. if aValue then
  84. StartUpgrader
  85. else
  86. StopUpgrader;
  87. FActive:=AValue;
  88. end;
  89. Procedure TCustomWebsocketUpgrader.StartUpgrader;
  90. begin
  91. StartConnectionHandler;
  92. Webserver.RegisterUpdateHandler(UpgradeName,@DoCheck,@DoUpgrade)
  93. end;
  94. Procedure TCustomWebsocketUpgrader.StopUpgrader;
  95. begin
  96. Webserver.UnRegisterUpdateHandler(UpgradeName);
  97. ConnectionHandler.CloseConnections;
  98. WaitForConnections(10);
  99. FreeConnectionHandler;
  100. end;
  101. procedure TCustomWebsocketUpgrader.SetHost(const AValue: String);
  102. begin
  103. if Host=AValue then Exit;
  104. CheckInactive;
  105. Host:=AValue;
  106. end;
  107. function TCustomWebsocketUpgrader.GetUpgradeName: String;
  108. begin
  109. Result:=FUpgradeName;
  110. if Result='' then
  111. Result:=Name;
  112. end;
  113. procedure TCustomWebsocketUpgrader.DoCheck(aRequest: TFPHTTPConnectionRequest; var aHandlesUpgrade: Boolean);
  114. Var
  115. aKey,aVersion : String;
  116. begin
  117. aKey:=aRequest.GetFieldByName(SSecWebsocketKey);
  118. aVersion:=aRequest.GetFieldByName(SSecWebsocketVersion);
  119. // Connection: Upgrade is already checked before we get here
  120. aHandlesUpgrade:=SameText(aRequest.Method,'GET')
  121. and SameText(aRequest.GetHeader(hhUpgrade),'WebSocket')
  122. and (aKey<>'');
  123. if Host<>'' then
  124. aHandlesUpgrade:=aHandlesUpgrade and SameText(aRequest.GetHeader(hhHost),Host);
  125. if Resource<>'' then
  126. aHandlesUpgrade:=aHandlesUpgrade and aRequest.PathInfo.StartsWith(Resource,True);
  127. if StrictProtocolCheck and aHandlesUpgrade then
  128. aHandlesUpgrade:=((Host<>'') or (aRequest.GetHeader(hhHost)<>'')) // Check also Host present
  129. and (aVersion<>''); // and Sec-Websocket-Version
  130. if Assigned(OnAllowUpgrade) then
  131. OnAllowUpgrade(Self,aRequest,aHandlesUpgrade);
  132. end;
  133. Function TCustomWebsocketUpgrader.GetHandshakeRequest(aRequest: TFPHTTPConnectionRequest) : TWSHandShakeRequest;
  134. Var
  135. aHeaders : TStrings;
  136. H : THeader;
  137. N,V : String;
  138. I : Integer;
  139. begin
  140. Result:=Nil;
  141. aHeaders:=TStringList.Create;
  142. try
  143. aHeaders.NameValueSeparator:=':';
  144. for H:=Succ(Low(THeader)) to High(Theader) do
  145. begin
  146. V:=aRequest.GetHeader(H);
  147. if V<>'' then
  148. aHeaders.Add(HTTPHeaderNames[H]+': '+V);
  149. end;
  150. For I:=0 to aRequest.CustomHeaders.Count-1 do
  151. begin
  152. aRequest.CustomHeaders.GetNameValue(I,N,V);
  153. V:=Trim(V);
  154. if (N<>'') and (V<>'') then
  155. aHeaders.Add(N+': '+V);
  156. end;
  157. Result:=TWSHandShakeRequest.Create(aRequest.PathInfo,aHeaders);
  158. Finally
  159. aHeaders.Free;
  160. end;
  161. end;
  162. procedure TCustomWebsocketUpgrader.DoUpgrade(aConnection: TFPHTTPConnection; aRequest: TFPHTTPConnectionRequest);
  163. Var
  164. aHandShake : TWSHandShakeRequest;
  165. aConn : TWSServerConnection;
  166. begin
  167. aHandShake:=GetHandshakeRequest(aRequest);
  168. try
  169. aConn:=CreateWebsocketConnection(aConnection.Socket,Options);
  170. aConn.OnControl:=@DoControlReceived;
  171. aConn.OnMessageReceived:=@DoMessageReceived;
  172. aConn.OnDisconnect:=@DoDisconnect;
  173. aConn.OnHandshake:=OnConnectionHandshake;
  174. aConn.DoHandshake(aHandshake);
  175. Connections.Add(aConn);
  176. ConnectionHandler.HandleConnection(aConn,False);
  177. finally
  178. aHandshake.Free;
  179. end;
  180. end;
  181. destructor TCustomWebsocketUpgrader.Destroy;
  182. begin
  183. FActive:=False;
  184. inherited Destroy;
  185. end;
  186. procedure TCustomWebsocketUpgrader.SetUpgradeName(const AValue: String);
  187. begin
  188. if aValue=GetUpgradeName then
  189. exit;
  190. CheckInactive;
  191. FUpgradeName:=aValue;
  192. end;
  193. procedure TCustomWebsocketUpgrader.SetWebServer(AValue: TFPCustomHttpServer);
  194. begin
  195. if FWebServer=AValue then Exit;
  196. CheckInactive;
  197. if Assigned(FWebServer) then
  198. FWebServer.RemoveFreeNotification(Self);
  199. FWebServer:=AValue;
  200. if Assigned(FWebServer) then
  201. FWebServer.FreeNotification(Self);
  202. end;
  203. procedure TCustomWebsocketUpgrader.Notification(AComponent: TComponent; Operation: TOperation);
  204. begin
  205. inherited Notification(AComponent, Operation);
  206. if (Operation=opRemove) and (aComponent=FWebServer) then
  207. FWebServer:=Nil;
  208. end;
  209. end.