
Added the ability to turn UTF-8 encoding checks off to increase performance Separated encoding checks into its own set of classes Encoding checks now use mbstring or iconv depending on availability
226 lines
6.4 KiB
PHP
226 lines
6.4 KiB
PHP
<?php
|
|
namespace Ratchet\WebSocket;
|
|
use Ratchet\MessageComponentInterface;
|
|
use Ratchet\ConnectionInterface;
|
|
use Ratchet\WebSocket\Version;
|
|
use Ratchet\WebSocket\Encoding\ToggleableValidator;
|
|
use Guzzle\Http\Message\Response;
|
|
|
|
/**
|
|
* The adapter to handle WebSocket requests/responses
|
|
* This is a mediator between the Server and your application to handle real-time messaging through a web browser
|
|
* @link http://ca.php.net/manual/en/ref.http.php
|
|
* @link http://dev.w3.org/html5/websockets/
|
|
*/
|
|
class WsServer implements MessageComponentInterface {
|
|
/**
|
|
* Buffers incoming HTTP requests returning a Guzzle Request when coalesced
|
|
* @var HttpRequestParser
|
|
* @note May not expose this in the future, may do through facade methods
|
|
*/
|
|
public $reqParser;
|
|
|
|
/**
|
|
* Manage the various WebSocket versions to support
|
|
* @var VersionManager
|
|
* @note May not expose this in the future, may do through facade methods
|
|
*/
|
|
public $versioner;
|
|
|
|
/**
|
|
* Decorated component
|
|
* @var Ratchet\MessageComponentInterface|WsServerInterface
|
|
*/
|
|
protected $_decorating;
|
|
|
|
/**
|
|
* @var SplObjectStorage
|
|
*/
|
|
protected $connections;
|
|
|
|
/**
|
|
* For now, array_push accepted subprotocols to this array
|
|
* @deprecated
|
|
* @temporary
|
|
*/
|
|
protected $acceptedSubProtocols = array();
|
|
|
|
/**
|
|
* @var Ratchet\WebSocket\Encoding\ValidatorInterface
|
|
*/
|
|
protected $validator;
|
|
|
|
/**
|
|
* Flag if we have checked the decorated component for sub-protocols
|
|
* @var boolean
|
|
*/
|
|
private $isSpGenerated = false;
|
|
|
|
/**
|
|
* @param Ratchet\MessageComponentInterface Your application to run with WebSockets
|
|
*/
|
|
public function __construct(MessageComponentInterface $component) {
|
|
$this->reqParser = new HttpRequestParser;
|
|
$this->versioner = new VersionManager;
|
|
$this->validator = new ToggleableValidator;
|
|
|
|
$this->versioner
|
|
->enableVersion(new Version\RFC6455($this->validator))
|
|
->enableVersion(new Version\HyBi10($this->validator))
|
|
->enableVersion(new Version\Hixie76)
|
|
;
|
|
|
|
$this->_decorating = $component;
|
|
$this->connections = new \SplObjectStorage;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function onOpen(ConnectionInterface $conn) {
|
|
$conn->WebSocket = new \StdClass;
|
|
$conn->WebSocket->established = false;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function onMessage(ConnectionInterface $from, $msg) {
|
|
if (true !== $from->WebSocket->established) {
|
|
try {
|
|
if (null === ($request = $this->reqParser->onMessage($from, $msg))) {
|
|
return;
|
|
}
|
|
} catch (\OverflowException $oe) {
|
|
return $this->close($from, 413);
|
|
}
|
|
|
|
if (!$this->versioner->isVersionEnabled($request)) {
|
|
return $this->close($from);
|
|
}
|
|
|
|
$from->WebSocket->request = $request;
|
|
$from->WebSocket->version = $this->versioner->getVersion($request);
|
|
|
|
$response = $from->WebSocket->version->handshake($request);
|
|
$response->setHeader('X-Powered-By', \Ratchet\VERSION);
|
|
|
|
// This needs to be refactored later on, incorporated with routing
|
|
if ('' !== ($agreedSubProtocols = $this->getSubProtocolString($request->getTokenizedHeader('Sec-WebSocket-Protocol', ',')))) {
|
|
$response->setHeader('Sec-WebSocket-Protocol', $agreedSubProtocols);
|
|
}
|
|
|
|
$from->send((string)$response);
|
|
|
|
if (101 != $response->getStatusCode()) {
|
|
return $from->close();
|
|
}
|
|
|
|
$upgraded = $from->WebSocket->version->upgradeConnection($from, $this->_decorating);
|
|
|
|
$this->connections->attach($from, $upgraded);
|
|
|
|
$upgraded->WebSocket->established = true;
|
|
|
|
return $this->_decorating->onOpen($upgraded);
|
|
}
|
|
|
|
$from->WebSocket->version->onMessage($this->connections[$from], $msg);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function onClose(ConnectionInterface $conn) {
|
|
if ($this->connections->contains($conn)) {
|
|
$decor = $this->connections[$conn];
|
|
$this->connections->detach($conn);
|
|
|
|
$this->_decorating->onClose($decor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
|
if ($conn->WebSocket->established) {
|
|
$this->_decorating->onError($this->connections[$conn], $e);
|
|
} else {
|
|
$conn->close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disable a specific version of the WebSocket protocol
|
|
* @param int Version ID to disable
|
|
* @return WsServer
|
|
*/
|
|
public function disableVersion($versionId) {
|
|
$this->versioner->disableVersion($versionId);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Toggle weather to check encoding of incoming messages
|
|
* @param bool
|
|
* @return WsServer
|
|
*/
|
|
public function setEncodingChecks($opt) {
|
|
$this->validator->on = (boolean)$opt;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param string
|
|
* @return boolean
|
|
*/
|
|
public function isSubProtocolSupported($name) {
|
|
if (!$this->isSpGenerated) {
|
|
if ($this->_decorating instanceof WsServerInterface) {
|
|
$this->acceptedSubProtocols = array_flip($this->_decorating->getSubProtocols());
|
|
}
|
|
|
|
$this->isSpGenerated = true;
|
|
}
|
|
|
|
return array_key_exists($name, $this->acceptedSubProtocols);
|
|
}
|
|
|
|
/**
|
|
* @param Traversable
|
|
* @return string
|
|
*/
|
|
protected function getSubProtocolString(\Traversable $requested = null) {
|
|
if (null === $requested) {
|
|
return '';
|
|
}
|
|
|
|
$string = '';
|
|
|
|
foreach ($requested as $sub) {
|
|
if ($this->isSubProtocolSupported($sub)) {
|
|
$string .= $sub . ',';
|
|
}
|
|
}
|
|
|
|
return substr($string, 0, -1);
|
|
}
|
|
|
|
/**
|
|
* Close a connection with an HTTP response
|
|
* @param Ratchet\ConnectionInterface
|
|
* @param int HTTP status code
|
|
*/
|
|
protected function close(ConnectionInterface $conn, $code = 400) {
|
|
$response = new Response($code, array(
|
|
'Sec-WebSocket-Version' => $this->versioner->getSupportedVersionString()
|
|
, 'X-Powered-By' => \Ratchet\VERSION
|
|
));
|
|
|
|
$conn->send((string)$response);
|
|
$conn->close();
|
|
}
|
|
} |