rfc6455/WsServer.php
Chris Boden cb6f580d02 Guzzle 3.6 conformance
Updated inheritance class to match API change
Use new Guzzle Header class when searching for values
Lock Ratchet into using Guzzle 3.6 to future-proof
2013-06-09 11:10:22 -04:00

238 lines
6.8 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
*/
protected $_decorating;
/**
* @var \SplObjectStorage
*/
protected $connections;
/**
* For now, array_push accepted subprotocols to this array
* @deprecated
* @temporary
*/
protected $acceptedSubProtocols = array();
/**
* UTF-8 validator
* @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 $component Your application to run with WebSockets
* If you want to enable sub-protocols have your component implement WsServerInterface as well
*/
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) {
return $from->WebSocket->version->onMessage($this->connections[$from], $msg);
}
if (isset($from->WebSocket->request)) {
$from->WebSocket->request->getBody()->write($msg);
} else {
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);
}
try {
$response = $from->WebSocket->version->handshake($from->WebSocket->request);
} catch (\UnderflowException $e) {
return;
}
if (null !== ($subHeader = $from->WebSocket->request->getHeader('Sec-WebSocket-Protocol'))) {
if ('' !== ($agreedSubProtocols = $this->getSubProtocolString($subHeader->normalize()))) {
$response->setHeader('Sec-WebSocket-Protocol', $agreedSubProtocols);
}
}
$response->setHeader('X-Powered-By', \Ratchet\VERSION);
$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);
}
/**
* {@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 $versionId 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|null $requested
* @return string
*/
protected function getSubProtocolString(\Traversable $requested = null) {
if (null === $requested) {
return '';
}
$result = array();
foreach ($requested as $sub) {
if ($this->isSubProtocolSupported($sub)) {
$result[] = $sub;
}
}
return implode(',', $result);
}
/**
* Close a connection with an HTTP response
* @param \Ratchet\ConnectionInterface $conn
* @param int $code HTTP status code
* @return void
*/
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();
}
}