-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from sirn-se/subprotocol-middleware
SubprotocolHandler middleware
- Loading branch information
Showing
12 changed files
with
500 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
[Documentation](../Index.md) / [Middleware](../Middleware.md) / SubprotocolNegotiation | ||
|
||
# Websocket: SubprotocolNegotiation middleware | ||
|
||
This middlewares is included in library and can be added to provide additional functionality. | ||
|
||
It can be added to both Client and Server to help negotiate subprotocol to use. | ||
Note: This Middleware only negotiate protocols, it does NOT implement any subprotocols. | ||
|
||
## Client | ||
|
||
When used on Client, it will send a list of requested subprotocols to the Server. | ||
The Server is then expected to respond with the first requested subprotocol it supports, if any. | ||
The Client MUST expect Server to send messages according to selected subprotocol. | ||
|
||
```php | ||
$client->addMiddleware(new WebSocket\Middleware\SubprotocolNegotiation([ | ||
'subproto-1', | ||
'subproto-2', | ||
'subproto-3', | ||
])); | ||
$client->connect(); | ||
$selected_subprotocol = $this->client->getMeta('subprotocolNegotiation.selected'); | ||
``` | ||
|
||
## Server | ||
|
||
When added on Server, it should be defined with a list of subprotocols that Server support. | ||
When Client request subprotocols, it will select the first requested protocol available in the list. | ||
The ClienServert MUST expect Client to send messages according to selected subprotocol. | ||
|
||
```php | ||
$server->addMiddleware(new WebSocket\Middleware\SubprotocolNegotiation([ | ||
'subproto-1', | ||
'subproto-2', | ||
'subproto-3', | ||
])); | ||
$server->->onText(function (WebSocket\Server $server, WebSocket\Connection $connection, WebSocket\Message\Message $message) { | ||
$selected_subprotocol = $connection->getMeta('subprotocolNegotiation.selected'); | ||
})->start(); | ||
``` | ||
|
||
## Require option | ||
|
||
If second parameter is set to `true` a failed negotiation will close connection. | ||
|
||
* When used on Client, this will cause a `HandshakeException`. | ||
* When used on Server, server will respond with `426 Upgrade Required` HTTP error status. | ||
|
||
```php | ||
$client->addMiddleware(new WebSocket\Middleware\SubprotocolNegotiation([ | ||
'subproto-1', | ||
'subproto-2', | ||
'subproto-3', | ||
], true)); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
<?php | ||
|
||
/** | ||
* Copyright (C) 2014-2024 Textalk and contributors. | ||
* | ||
* This file is part of Websocket PHP and is free software under the ISC License. | ||
* License text: https://raw.githubusercontent.com/sirn-se/websocket-php/master/COPYING.md | ||
*/ | ||
|
||
namespace WebSocket\Middleware; | ||
|
||
use Psr\Log\{ | ||
LoggerAwareInterface, | ||
LoggerAwareTrait | ||
}; | ||
use WebSocket\Connection; | ||
use WebSocket\Exception\HandshakeException; | ||
use WebSocket\Http\{ | ||
Message, | ||
Request, | ||
Response, | ||
ServerRequest, | ||
}; | ||
use WebSocket\Trait\StringableTrait; | ||
|
||
/** | ||
* WebSocket\Middleware\CloseHandler class. | ||
* Handles close procedure. | ||
*/ | ||
class SubprotocolNegotiation implements LoggerAwareInterface, ProcessHttpOutgoingInterface, ProcessHttpIncomingInterface | ||
{ | ||
use LoggerAwareTrait; | ||
use StringableTrait; | ||
|
||
private $subprotocols; | ||
private $require; | ||
|
||
public function __construct(array $subprotocols, bool $require = false) | ||
{ | ||
$this->subprotocols = $subprotocols; | ||
$this->require = $require; | ||
} | ||
|
||
public function processHttpOutgoing(ProcessHttpStack $stack, Connection $connection, Message $message): Message | ||
{ | ||
if ($message instanceof Request) { | ||
// Outgoing requests on Client | ||
foreach ($this->subprotocols as $subprotocol) { | ||
$message = $message->withAddedHeader('Sec-WebSocket-Protocol', $subprotocol); | ||
} | ||
if ($supported = implode(', ', $this->subprotocols)) { | ||
$this->logger->debug("[subprotocol-negotiation] Requested subprotocols: {$supported}"); | ||
} | ||
} elseif ($message instanceof Response) { | ||
// Outgoing Response on Server | ||
if ($selected = $connection->getMeta('subprotocolNegotiation.selected')) { | ||
$message = $message->withHeader('Sec-WebSocket-Protocol', $selected); | ||
$this->logger->info("[subprotocol-negotiation] Selected subprotocol: {$selected}"); | ||
} elseif ($this->require) { | ||
// No matching subprotocol, fail handshake | ||
$message = $message->withStatus(426); | ||
} | ||
} | ||
return $stack->handleHttpOutgoing($message); | ||
} | ||
|
||
public function processHttpIncoming(ProcessHttpStack $stack, Connection $connection): Message | ||
{ | ||
$connection->setMeta('subprotocolNegotiation.selected', null); | ||
$message = $stack->handleHttpIncoming(); | ||
|
||
if ($message instanceof ServerRequest) { | ||
// Incoming requests on Server | ||
if ($requested = $message->getHeaderLine('Sec-WebSocket-Protocol')) { | ||
$this->logger->debug("[subprotocol-negotiation] Requested subprotocols: {$requested}"); | ||
} | ||
if ($supported = implode(', ', $this->subprotocols)) { | ||
$this->logger->debug("[subprotocol-negotiation] Supported subprotocols: {$supported}"); | ||
} | ||
foreach ($message->getHeader('Sec-WebSocket-Protocol') as $subprotocol) { | ||
if (in_array($subprotocol, $this->subprotocols)) { | ||
$connection->setMeta('subprotocolNegotiation.selected', $subprotocol); | ||
return $message; | ||
} | ||
} | ||
} elseif ($message instanceof Response) { | ||
// Incoming Response on Client | ||
if ($selected = $message->getHeaderLine('Sec-WebSocket-Protocol')) { | ||
$connection->setMeta('subprotocolNegotiation.selected', $selected); | ||
$this->logger->info("[subprotocol-negotiation] Selected subprotocol: {$selected}"); | ||
} elseif ($this->require) { | ||
// No matching subprotocol, close and fail | ||
$connection->close(); | ||
throw new HandshakeException("Could not resolve subprotocol.", $message); | ||
} | ||
} | ||
return $message; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.