Hixie-76 protocol

Implemented WebSocket Hixie-76 protocol
This commit is contained in:
Chris Boden 2011-11-01 14:10:12 -04:00
parent 7c5c5ed6ce
commit 2d7774fd65
5 changed files with 122 additions and 51 deletions

View File

@ -61,19 +61,21 @@ class WebSocket implements ProtocolInterface {
public function onRecv(SocketInterface $from, $msg) {
$client = $this->_clients[$from];
if (true !== $client->isHandshakeComplete()) {
$response = $client->setVersion($this->getVersion($msg))->doHandshake($msg);
$headers = $this->getHeaders($msg);
$response = $client->setVersion($this->getVersion($headers))->doHandshake($headers);
if (is_array($response)) {
$header = '';
foreach ($response as $key => $val) {
if (!empty($key)) {
$header .= "{$key}: ";
}
$header = '';
foreach ($response as $key => $val) {
if (!empty($key)) {
$header .= "{$key}: ";
$header .= "{$val}\r\n";
}
$header .= "{$val}\r\n";
$header .= "\r\n";
} else {
$header = $response;
}
$header .= "\r\n";
$to = new \Ratchet\SocketCollection;
$to->enqueue($from);
@ -133,30 +135,66 @@ class WebSocket implements ProtocolInterface {
}
/**
* @param string
* @return array
* @todo Consider strtolower all the header keys...right now PHP Changes Sec-WebSocket-X to Sec-Websocket-X...this could change
* @todo Put in fallback code if http_parse_headers is not a function
* @param array of HTTP headers
* @return Version\VersionInterface
*/
protected function getHeaders($http_message) {
return http_parse_headers($http_message);
protected function getVersion($message) {
$headers = $this->getHeaders($message);
if (isset($headers['Sec-Websocket-Version'])) { // HyBi
if ($headers['Sec-Websocket-Version'] == '8') {
return $this->versionFactory('HyBi10');
}
} elseif (isset($headers['Sec-Websocket-Key2'])) { // Hixie
return $this->versionFactory('Hixie76');
}
throw new \UnexpectedValueException('Could not identify WebSocket protocol');
}
/**
* @return Version\VersionInterface
*/
protected function getVersion(array $headers) {
if (isset($headers['Sec-Websocket-Version'])) { // HyBi
if ($headers['Sec-Websocket-Version'] == '8') {
if (null === $this->_versions['HyBi10']) {
$this->_versions['HyBi10'] = new Version\HyBi10;
}
return $this->_versions['HyBi10'];
}
} elseif (isset($headers['Sec-Websocket-Key2'])) { // Hixie
protected function versionFactory($version) {
if (null === $this->_versions[$version]) {
$ns = __CLASS__ . "\\Version\\{$version}";
$this->_version[$version] = new $ns;
}
throw new \UnexpectedValueException('Could not identify WebSocket protocol');
return $this->_version[$version];
}
/**
* @param string
* @return array
* @todo Consider strtolower all the header keys...right now PHP Changes Sec-WebSocket-X to Sec-Websocket-X...this could change
*/
protected function getHeaders($http_message) {
return function_exists('http_parse_headers') ? http_parse_headers($http_message) : $this->http_parse_headers($http_message);
}
/**
* This is a fallback method for http_parse_headers as not all php installs have the HTTP module present
* @internal
*/
protected function http_parse_headers($http_message) {
$retVal = array();
$fields = explode("br", preg_replace("%(<|/\>|>)%", "", nl2br($header)));
foreach ($fields as $field) {
if (preg_match('%^(GET|POST|PUT|DELETE|PATCH)(\s)(.*)%', $field, $matchReq)) {
$retVal["Request Method"] = $matchReq[1];
$retVal["Request Url"] = $matchReq[3];
} elseif (preg_match('/([^:]+): (.+)/m', $field, $match) ) {
$match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1])));
if (isset($retVal[$match[1]])) {
$retVal[$match[1]] = array($retVal[$match[1]], $match[2]);
} else {
$retVal[$match[1]] = trim($match[2]);
}
}
}
return $retVal;
}
}

View File

@ -33,10 +33,10 @@ class Client {
}
/**
* @param array
* @return array
* @param string
* @return array|string
*/
public function doHandshake(array $headers) {
public function doHandshake($headers) {
$this->_hands_shook = true;
return $this->_version->handshake($headers);

View File

@ -3,26 +3,56 @@ namespace Ratchet\Protocol\WebSocket\Version;
/**
* The Hixie76 is currently implemented by Safari
* Not yet complete
* Handshake from Andrea Giammarchi (http://webreflection.blogspot.com/2010/06/websocket-handshake-76-simplified.html)
* @link http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
*/
class Hixie76 implements VersionInterface {
public function handshake(array $headers) {
/**
* @return string
*/
public function handshake($message) {
$buffer = $message;
$resource = $host = $origin = $key1 = $key2 = $protocol = $code = $handshake = null;
preg_match('#GET (.*?) HTTP#', $buffer, $match) && $resource = $match[1];
preg_match("#Host: (.*?)\r\n#", $buffer, $match) && $host = $match[1];
preg_match("#Sec-WebSocket-Key1: (.*?)\r\n#", $buffer, $match) && $key1 = $match[1];
preg_match("#Sec-WebSocket-Key2: (.*?)\r\n#", $buffer, $match) && $key2 = $match[1];
preg_match("#Sec-WebSocket-Protocol: (.*?)\r\n#", $buffer, $match) && $protocol = $match[1];
preg_match("#Origin: (.*?)\r\n#", $buffer, $match) && $origin = $match[1];
preg_match("#\r\n(.*?)\$#", $buffer, $match) && $code = $match[1];
return "HTTP/1.1 101 WebSocket Protocol Handshake\r\n".
"Upgrade: WebSocket\r\n"
. "Connection: Upgrade\r\n"
. "Sec-WebSocket-Origin: {$origin}\r\n"
. "Sec-WebSocket-Location: ws://{$host}{$resource}\r\n"
. ($protocol ? "Sec-WebSocket-Protocol: {$protocol}\r\n" : "")
. "\r\n"
. $this->_createHandshakeThingy($key1, $key2, $code)
;
}
public function unframe($message) {
return substr($message, 1, strlen($message) - 2);
}
public function frame($message) {
return chr(0) . $message . chr(255);
}
public function sign($key) {
protected function _doStuffToObtainAnInt32($key) {
return preg_match_all('#[0-9]#', $key, $number) && preg_match_all('# #', $key, $space) ?
implode('', $number[0]) / count($space[0]) :
''
;
}
/**
* What was I doing here?
* @param Headers
* @return string
*/
public function concatinateKeyString($headers) {
protected function _createHandshakeThingy($key1, $key2, $code) {
return md5(
pack('N', $this->_doStuffToObtainAnInt32($key1))
. pack('N', $this->_doStuffToObtainAnInt32($key2))
. $code
, true);
}
}

View File

@ -8,8 +8,13 @@ namespace Ratchet\Protocol\WebSocket\Version;
class HyBi10 implements VersionInterface {
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
public function handshake(array $headers) {
$key = $this->sign($headers['Sec-Websocket-Key']);
/**
* @return array
* @todo Use the WebSocket::http_parse_headers wrapper instead of native function (how to get to method is question)
*/
public function handshake($message) {
$headers = http_parse_headers($message);
$key = $this->sign($headers['Sec-Websocket-Key']);
return array(
'' => 'HTTP/1.1 101 Switching Protocols'
@ -178,6 +183,12 @@ class HyBi10 implements VersionInterface {
return $frame;
}
/**
* Used when doing the handshake to encode the key, verifying client/server are speaking the same language
* @param string
* @return string
* @internal
*/
public function sign($key) {
return base64_encode(sha1($key . static::GUID, 1));
}

View File

@ -8,10 +8,10 @@ namespace Ratchet\Protocol\WebSocket\Version;
interface VersionInterface {
/**
* Perform the handshake and return the response headers
* @param array
* @return array
* @param string
* @return array|string
*/
function handshake(array $headers);
function handshake($message);
/**
* Get a framed message as per the protocol and return the decoded message
@ -26,12 +26,4 @@ interface VersionInterface {
* @return string
*/
function frame($message);
/**
* Used when doing the handshake to encode the key, verifying client/server are speaking the same language
* @param string
* @return string
* @internal
*/
function sign($key);
}