123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- unit wsupgrader;
- {$mode ObjFPC}{$H+}
- interface
- uses
- Classes, SysUtils, httpprotocol, httpdefs, fphttpserver, fpwebsocket, fpcustwsserver;
- Type
- { TCustomWebsocketUpgrader }
- TAllowUpgradeEvent = Procedure(Sender : TObject; aRequest : TRequest; var aAllow : Boolean) of object;
- TCustomWebsocketUpgrader = Class(TCustomWSServer)
- private
- FActive: Boolean;
- FOnAllowUpgrade: TAllowUpgradeEvent;
- FStrictProtocolCheck: Boolean;
- FUpgradeName: String;
- FWebServer: TFPCustomHttpServer;
- FHost: String;
- function GetHandshakeRequest(aRequest: TFPHTTPConnectionRequest): TWSHandShakeRequest;
- function GetUpgradeName: String;
- procedure SetHost(const AValue: String);
- procedure SetUpgradeName(const AValue: String);
- procedure SetWebServer(AValue: TFPCustomHttpServer);
- Protected
- // Override from custom server
- procedure SetActive(const aValue: Boolean); override;
- function GetActive: Boolean; override;
- Procedure Notification(AComponent: TComponent; Operation: TOperation); override;
- // Start upgrader: register, create connection handler
- procedure StartUpgrader;
- // End upgrader: unregister, free connection handler
- procedure StopUpgrader;
- // Check callback for upgrader mechanism
- procedure DoCheck(aRequest: TFPHTTPConnectionRequest; var aHandlesUpgrade: Boolean); virtual;
- // Upgrade callback for upgrader mechanism
- procedure DoUpgrade(aConnection: TFPHTTPConnection; aRequest: TFPHTTPConnectionRequest); virtual;
- // Webserver that we must register with
- Property WebServer : TFPCustomHttpServer Read FWebServer Write SetWebServer;
- // If set, only this resource will be acceped for upgrade.
- Property Host : String Read FHost Write SetHost;
- // Name to use when registering upgrade mechanism. Defaults to Name.
- Property UpgradeName : String Read GetUpgradeName Write SetUpgradeName;
- // Check also Host and Sec-Websocket-Version
- Property StrictProtocolCheck : Boolean Read FStrictProtocolCheck Write FStrictProtocolCheck;
- // Called when upgrade request is processed. allow is initialized with check for websocket upgrade.
- Property OnAllowUpgrade : TAllowUpgradeEvent Read FOnAllowUpgrade Write FOnAllowUpgrade;
- Public
- Destructor Destroy; override;
- end;
- TWebsocketUpgrader = class(TCustomWebsocketUpgrader)
- Published
- Property Active; // Registers, unregisters
- Property WebServer;
- Property Host;
- Property Resource;
- Property StrictProtocolCheck;
- Property ThreadMode;
- Property WebSocketVersion;
- Property MessageWaitTime;
- Property Options;
- Property OnAllow;
- property OnMessageReceived;
- property OnDisconnect;
- property OnControlReceived;
- Property OnError;
- Property OutgoingFrameMask;
- Property OnAllowUpgrade;
- end;
- implementation
- Resourcestring
- SErrWebserverNotAssigned = 'Webserver not assigned';
- SErrNoUpgradeName = 'Upgradename not set. Set UpgradeName or Name';
- { TCustomWebsocketUpgrader }
- Function TCustomWebsocketUpgrader.GetActive : Boolean;
- begin
- Result:=FActive;
- end;
- procedure TCustomWebsocketUpgrader.SetActive(const AValue: Boolean);
- begin
- if FActive=AValue then Exit;
- If not Assigned(Webserver) then
- Raise EWebsocket.Create(SErrWebserverNotAssigned);
- If (UpgradeName='') then
- Raise EWebsocket.Create(SErrNoUpgradeName);
- if aValue then
- StartUpgrader
- else
- StopUpgrader;
- FActive:=AValue;
- end;
- Procedure TCustomWebsocketUpgrader.StartUpgrader;
- begin
- StartConnectionHandler;
- Webserver.RegisterUpdateHandler(UpgradeName,@DoCheck,@DoUpgrade)
- end;
- Procedure TCustomWebsocketUpgrader.StopUpgrader;
- begin
- Webserver.UnRegisterUpdateHandler(UpgradeName);
- ConnectionHandler.CloseConnections;
- WaitForConnections(10);
- FreeConnectionHandler;
- end;
- procedure TCustomWebsocketUpgrader.SetHost(const AValue: String);
- begin
- if Host=AValue then Exit;
- CheckInactive;
- Host:=AValue;
- end;
- function TCustomWebsocketUpgrader.GetUpgradeName: String;
- begin
- Result:=FUpgradeName;
- if Result='' then
- Result:=Name;
- end;
- procedure TCustomWebsocketUpgrader.DoCheck(aRequest: TFPHTTPConnectionRequest; var aHandlesUpgrade: Boolean);
- Var
- aKey,aVersion : String;
- begin
- aKey:=aRequest.GetFieldByName(SSecWebsocketKey);
- aVersion:=aRequest.GetFieldByName(SSecWebsocketVersion);
- // Connection: Upgrade is already checked before we get here
- aHandlesUpgrade:=SameText(aRequest.Method,'GET')
- and SameText(aRequest.GetHeader(hhUpgrade),'WebSocket')
- and (aKey<>'');
- if Host<>'' then
- aHandlesUpgrade:=aHandlesUpgrade and SameText(aRequest.GetHeader(hhHost),Host);
- if Resource<>'' then
- aHandlesUpgrade:=aHandlesUpgrade and aRequest.PathInfo.StartsWith(Resource,True);
- if StrictProtocolCheck and aHandlesUpgrade then
- aHandlesUpgrade:=((Host<>'') or (aRequest.GetHeader(hhHost)<>'')) // Check also Host present
- and (aVersion<>''); // and Sec-Websocket-Version
- if Assigned(OnAllowUpgrade) then
- OnAllowUpgrade(Self,aRequest,aHandlesUpgrade);
- end;
- Function TCustomWebsocketUpgrader.GetHandshakeRequest(aRequest: TFPHTTPConnectionRequest) : TWSHandShakeRequest;
- Var
- aHeaders : TStrings;
- H : THeader;
- N,V : String;
- I : Integer;
- begin
- Result:=Nil;
- aHeaders:=TStringList.Create;
- try
- aHeaders.NameValueSeparator:=':';
- for H:=Succ(Low(THeader)) to High(Theader) do
- begin
- V:=aRequest.GetHeader(H);
- if V<>'' then
- aHeaders.Add(HTTPHeaderNames[H]+': '+V);
- end;
- For I:=0 to aRequest.CustomHeaders.Count-1 do
- begin
- aRequest.CustomHeaders.GetNameValue(I,N,V);
- V:=Trim(V);
- if (N<>'') and (V<>'') then
- aHeaders.Add(N+': '+V);
- end;
- Result:=TWSHandShakeRequest.Create(aRequest.PathInfo,aHeaders);
- Finally
- aHeaders.Free;
- end;
- end;
- procedure TCustomWebsocketUpgrader.DoUpgrade(aConnection: TFPHTTPConnection; aRequest: TFPHTTPConnectionRequest);
- Var
- aHandShake : TWSHandShakeRequest;
- aConn : TWSServerConnection;
- begin
- aHandShake:=GetHandshakeRequest(aRequest);
- try
- aConn:=CreateWebsocketConnection(aConnection.Socket,Options);
- aConn.OnControl:=@DoControlReceived;
- aConn.OnMessageReceived:=@DoMessageReceived;
- aConn.OnDisconnect:=@DoDisconnect;
- aConn.OnHandshake:=OnConnectionHandshake;
- aConn.DoHandshake(aHandshake);
- Connections.Add(aConn);
- ConnectionHandler.HandleConnection(aConn,False);
- finally
- aHandshake.Free;
- end;
- end;
- destructor TCustomWebsocketUpgrader.Destroy;
- begin
- FActive:=False;
- inherited Destroy;
- end;
- procedure TCustomWebsocketUpgrader.SetUpgradeName(const AValue: String);
- begin
- if aValue=GetUpgradeName then
- exit;
- CheckInactive;
- FUpgradeName:=aValue;
- end;
- procedure TCustomWebsocketUpgrader.SetWebServer(AValue: TFPCustomHttpServer);
- begin
- if FWebServer=AValue then Exit;
- CheckInactive;
- if Assigned(FWebServer) then
- FWebServer.RemoveFreeNotification(Self);
- FWebServer:=AValue;
- if Assigned(FWebServer) then
- FWebServer.FreeNotification(Self);
- end;
- procedure TCustomWebsocketUpgrader.Notification(AComponent: TComponent; Operation: TOperation);
- begin
- inherited Notification(AComponent, Operation);
- if (Operation=opRemove) and (aComponent=FWebServer) then
- FWebServer:=Nil;
- end;
- end.
|