Cleanup
Application stack working! Existing unit tests fixed Implemented HyBi-10 unframing
This commit is contained in:
parent
7514ce8e85
commit
51d0516aa3
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Ratchet\Server\Command;
|
||||
namespace Ratchet\Command;
|
||||
use Ratchet\SocketCollection;
|
||||
|
||||
class CloseConnection implements CommandInterface {
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Ratchet\Server\Command;
|
||||
namespace Ratchet\Command;
|
||||
use Ratchet\SocketCollection;
|
||||
|
||||
interface CommandInterface {
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Ratchet\Server\Command;
|
||||
namespace Ratchet\Command;
|
||||
use Ratchet\SocketCollection;
|
||||
|
||||
class Null implements CommandInterface {
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Ratchet\Server\Command;
|
||||
namespace Ratchet\Command;
|
||||
use Ratchet\SocketCollection;
|
||||
|
||||
/**
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Ratchet\Server\Command;
|
||||
namespace Ratchet\Command;
|
||||
use Ratchet\SocketCollection;
|
||||
|
||||
/**
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Ratchet\Server\Command;
|
||||
namespace Ratchet\Command;
|
||||
use Ratchet\SocketCollection;
|
||||
|
||||
class SendMessage implements CommandInterface {
|
@ -8,6 +8,8 @@ use Ratchet\ReceiverInterface;
|
||||
|
||||
/**
|
||||
* @link http://ca.php.net/manual/en/ref.http.php
|
||||
* @todo Make sure this works both ways (client/server) as stack needs to exist on client for framing
|
||||
* @todo Clean up Client/Version stuff. This should be a factory making single instances of Version classes, implement chain of reponsibility for version - client should implement an interface?
|
||||
*/
|
||||
class WebSocket implements ProtocolInterface {
|
||||
/**
|
||||
@ -25,6 +27,11 @@ class WebSocket implements ProtocolInterface {
|
||||
*/
|
||||
protected $_app;
|
||||
|
||||
protected $_versions = array(
|
||||
'HyBi10' => null
|
||||
, 'Hixie76' => null
|
||||
);
|
||||
|
||||
public function __construct(ReceiverInterface $application) {
|
||||
$this->_lookup = new \SplObjectStorage;
|
||||
$this->_app = $application;
|
||||
@ -62,15 +69,29 @@ class WebSocket implements ProtocolInterface {
|
||||
}
|
||||
|
||||
public function onRecv(SocketInterface $from, $msg) {
|
||||
$client = $this->_lookup[$from];
|
||||
$client = $this->_lookup[$from];
|
||||
if (true !== $client->isHandshakeComplete()) {
|
||||
$headers = $this->getHeaders($msg);
|
||||
$header = $client->doHandshake($this->getVersion($headers));
|
||||
|
||||
// remove client, get protocol, do handshake, return, etc
|
||||
|
||||
$headers = $this->getHeaders($msg);
|
||||
$response = $client->setVersion($this->getVersion($headers))->doHandshake($headers);
|
||||
|
||||
$header = '';
|
||||
foreach ($response as $key => $val) {
|
||||
if (!empty($key)) {
|
||||
$header .= "{$key}: ";
|
||||
}
|
||||
|
||||
$header .= "{$val}\r\n";
|
||||
}
|
||||
$header .= "\r\n";
|
||||
// $header = implode("\r\n", $response) . "\r\n";
|
||||
|
||||
// $from->write($header, strlen($header));
|
||||
$to = new \Ratchet\SocketCollection;
|
||||
$to->enqueue($from);
|
||||
$cmd = new \Ratchet\Server\Command\SendMessage($to);
|
||||
$cmd = new \Ratchet\Command\SendMessage($to);
|
||||
$cmd->setMessage($header);
|
||||
|
||||
// call my decorated onRecv()
|
||||
@ -80,6 +101,20 @@ $this->_server->log('Returning handshake: ' . $header);
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
try {
|
||||
$msg = $client->getVersion()->unframe($msg);
|
||||
if (is_array($msg)) { // temporary
|
||||
$msg = $msg['payload'];
|
||||
}
|
||||
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
$to = new \Ratchet\SocketCollection;
|
||||
$to->enqueue($from);
|
||||
$cmd = new \Ratchet\Command\Close($to);
|
||||
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
return $this->_app->onRecv($from, $msg);
|
||||
}
|
||||
|
||||
@ -98,6 +133,7 @@ $this->_server->log('Returning handshake: ' . $header);
|
||||
/**
|
||||
* @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 http_parse_headers($http_message);
|
||||
@ -109,7 +145,11 @@ $this->_server->log('Returning handshake: ' . $header);
|
||||
protected function getVersion(array $headers) {
|
||||
if (isset($headers['Sec-Websocket-Version'])) { // HyBi
|
||||
if ($headers['Sec-Websocket-Version'] == '8') {
|
||||
return new Version\Hybi10($headers);
|
||||
if (null === $this->_versions['HyBi10']) {
|
||||
$this->_versions['HyBi10'] = new Version\Hybi10;
|
||||
}
|
||||
|
||||
return $this->_versions['HyBi10'];
|
||||
}
|
||||
} elseif (isset($headers['Sec-Websocket-Key2'])) { // Hixie
|
||||
}
|
||||
|
14
lib/Ratchet/Protocol/WebSocket/AppInterface.php
Normal file
14
lib/Ratchet/Protocol/WebSocket/AppInterface.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Ratchet\Protocol\Websocket;
|
||||
use Ratchet\ReceiverInterface;
|
||||
|
||||
/**
|
||||
* @todo App interfaces this (optionally) if is meant for WebSocket
|
||||
* @todo WebSocket checks if instanceof AppInterface, if so uses getSubProtocol() when doing handshake
|
||||
*/
|
||||
interface AppInterface extends ReceiverInterface {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function getSubProtocol();
|
||||
}
|
@ -13,13 +13,30 @@ class Client {
|
||||
*/
|
||||
protected $_hands_shook = false;
|
||||
|
||||
public function doHandshake(VersionInterface $version) {
|
||||
$key = $version->sign();
|
||||
// $tosend['Sec-WebSocket-Accept'] = $key;
|
||||
/**
|
||||
* @param VersionInterface
|
||||
* @return Client
|
||||
*/
|
||||
public function setVersion(VersionInterface $version) {
|
||||
$this->_version = $version;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return VersionInterface
|
||||
*/
|
||||
public function getVersion() {
|
||||
return $this->_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array
|
||||
* @return array
|
||||
*/
|
||||
public function doHandshake(array $headers) {
|
||||
$this->_hands_shook = true;
|
||||
|
||||
return "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {$key}\r\nSec-WebSocket-Protocol: test\r\n\r\n";
|
||||
return $this->_version->handshake($headers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,24 +2,23 @@
|
||||
namespace Ratchet\Protocol\WebSocket\Version;
|
||||
|
||||
class Hixie76 implements VersionInterface {
|
||||
protected $_headers = array();
|
||||
public function handshake(array $headers) {
|
||||
}
|
||||
|
||||
public function __construct(array $headers) {
|
||||
public function unframe($message) {
|
||||
}
|
||||
|
||||
public function frame($message) {
|
||||
}
|
||||
|
||||
public function sign($key) {
|
||||
}
|
||||
|
||||
/**
|
||||
* What was I doing here?
|
||||
* @param Headers
|
||||
* @return string
|
||||
*/
|
||||
public function concatinateKeyString($headers) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
public function sign($key) {
|
||||
|
||||
}
|
||||
}
|
@ -4,17 +4,109 @@ namespace Ratchet\Protocol\WebSocket\Version;
|
||||
class Hybi10 implements VersionInterface {
|
||||
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||
|
||||
protected $_headers = array();
|
||||
/**
|
||||
*/
|
||||
public function handshake(array $headers) {
|
||||
$key = $this->sign($headers['Sec-Websocket-Key']);
|
||||
|
||||
public function __construct(array $headers) {
|
||||
$this->_headers = $headers;
|
||||
return array(
|
||||
'' => 'HTTP/1.1 101 Switching Protocols'
|
||||
, 'Upgrade' => 'websocket'
|
||||
, 'Connection' => 'Upgrade'
|
||||
, 'Sec-WebSocket-Accept' => $this->sign($headers['Sec-Websocket-Key'])
|
||||
// , 'Sec-WebSocket-Protocol' => ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unframe a message received from the client
|
||||
* Thanks to @lemmingzshadow for the code on decoding a HyBi-10 frame
|
||||
* @link https://github.com/lemmingzshadow/php-websocket
|
||||
* @param string
|
||||
* @return string
|
||||
* @throws UnexpectedValueException
|
||||
*/
|
||||
public function unframe($message) {
|
||||
$data = $message;
|
||||
$payloadLength = '';
|
||||
$mask = '';
|
||||
$unmaskedPayload = '';
|
||||
$decodedData = array();
|
||||
|
||||
// estimate frame type:
|
||||
$firstByteBinary = sprintf('%08b', ord($data[0]));
|
||||
$secondByteBinary = sprintf('%08b', ord($data[1]));
|
||||
$opcode = bindec(substr($firstByteBinary, 4, 4));
|
||||
$isMasked = ($secondByteBinary[0] == '1') ? true : false;
|
||||
$payloadLength = ord($data[1]) & 127;
|
||||
|
||||
// close connection if unmasked frame is received:
|
||||
if($isMasked === false) {
|
||||
throw new \UnexpectedValueException('Masked byte is false');
|
||||
}
|
||||
|
||||
switch($opcode) {
|
||||
// text frame:
|
||||
case 1:
|
||||
$decodedData['type'] = 'text';
|
||||
break;
|
||||
|
||||
// connection close frame:
|
||||
case 8:
|
||||
$decodedData['type'] = 'close';
|
||||
break;
|
||||
|
||||
// ping frame:
|
||||
case 9:
|
||||
$decodedData['type'] = 'ping';
|
||||
break;
|
||||
|
||||
// pong frame:
|
||||
case 10:
|
||||
$decodedData['type'] = 'pong';
|
||||
break;
|
||||
|
||||
default:
|
||||
// Close connection on unknown opcode:
|
||||
throw new UnexpectedValueException('Unknown opcode');
|
||||
break;
|
||||
}
|
||||
|
||||
if($payloadLength === 126) {
|
||||
$mask = substr($data, 4, 4);
|
||||
$payloadOffset = 8;
|
||||
} elseif($payloadLength === 127) {
|
||||
$mask = substr($data, 10, 4);
|
||||
$payloadOffset = 14;
|
||||
} else {
|
||||
$mask = substr($data, 2, 4);
|
||||
$payloadOffset = 6;
|
||||
}
|
||||
|
||||
$dataLength = strlen($data);
|
||||
|
||||
if($isMasked === true) {
|
||||
for($i = $payloadOffset; $i < $dataLength; $i++) {
|
||||
$j = $i - $payloadOffset;
|
||||
$unmaskedPayload .= $data[$i] ^ $mask[$j % 4];
|
||||
}
|
||||
$decodedData['payload'] = $unmaskedPayload;
|
||||
} else {
|
||||
$payloadOffset = $payloadOffset - 4;
|
||||
$decodedData['payload'] = substr($data, $payloadOffset);
|
||||
}
|
||||
|
||||
return $decodedData;
|
||||
}
|
||||
|
||||
public function sign($key = null) {
|
||||
if (null === $key) {
|
||||
$key = $this->_headers['Sec-Websocket-Key'];
|
||||
}
|
||||
/**
|
||||
* @todo Complete this method
|
||||
*/
|
||||
public function frame($message) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function sign($key) {
|
||||
return base64_encode(sha1($key . static::GUID, 1));
|
||||
}
|
||||
}
|
@ -3,13 +3,31 @@ namespace Ratchet\Protocol\WebSocket\Version;
|
||||
|
||||
interface VersionInterface {
|
||||
/**
|
||||
* Perform the handshake and return the response headers
|
||||
* @param array
|
||||
* @return array
|
||||
*/
|
||||
function __construct(array $headers);
|
||||
function handshake(array $headers);
|
||||
|
||||
/**
|
||||
* Get a framed message as per the protocol and return the decoded message
|
||||
* @param string
|
||||
* @return string
|
||||
* @todo Return a frame object with message, type, masked?
|
||||
*/
|
||||
function unframe($message);
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
function sign($header);
|
||||
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);
|
||||
}
|
@ -3,6 +3,9 @@ namespace Ratchet;
|
||||
use Ratchet\Server;
|
||||
use Ratchet\SocketObserver;
|
||||
|
||||
/**
|
||||
* @todo Should probably move this into \Ratchet\Server namespace
|
||||
*/
|
||||
interface ReceiverInterface extends SocketObserver {
|
||||
/**
|
||||
* @return string
|
||||
|
@ -10,7 +10,7 @@ use Ratchet\Logging\NullLogger;
|
||||
* @todo Move SocketObserver methods to separate class, create, wrap class in __construct
|
||||
* @todo Currently passing Socket object down the decorated chain - should be sending reference to it instead; Receivers do not interact with the Socket directly, they do so through the Command pattern
|
||||
*/
|
||||
class Server implements SocketObserver {
|
||||
class Server implements SocketObserver, \IteratorAggregate {
|
||||
/**
|
||||
* The master socket, receives all connections
|
||||
* @type Socket
|
||||
|
@ -2,15 +2,16 @@
|
||||
namespace Ratchet\Tests\Protocol;
|
||||
use Ratchet\Protocol\WebSocket;
|
||||
use Ratchet\Tests\Mock\Socket;
|
||||
use Ratchet\Tests\Mock\Application;
|
||||
|
||||
/**
|
||||
* @covers Ratchet\Protocol\WebSocket
|
||||
*/
|
||||
class ServerTest extends \PHPUnit_Framework_TestCase {
|
||||
class WebSocketTest extends \PHPUnit_Framework_TestCase {
|
||||
protected $_ws;
|
||||
|
||||
public function setUp() {
|
||||
$this->_ws = new WebSocket();
|
||||
$this->_ws = new WebSocket(new Application);
|
||||
}
|
||||
|
||||
public function testServerImplementsServerInterface() {
|
||||
|
@ -11,10 +11,12 @@ use Ratchet\Tests\Mock\ArrayLogger;
|
||||
class ServerTest extends \PHPUnit_Framework_TestCase {
|
||||
protected $_catalyst;
|
||||
protected $_server;
|
||||
protected $_app;
|
||||
|
||||
public function setUp() {
|
||||
$this->_catalyst = new Socket;
|
||||
$this->_server = new Server($this->_catalyst, new TestApp);
|
||||
$this->_app = new TestApp;
|
||||
$this->_server = new Server($this->_catalyst, $this->_app);
|
||||
}
|
||||
|
||||
protected function getPrivateProperty($class, $name) {
|
||||
@ -36,7 +38,7 @@ class ServerTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
public function testPassedLoggerIsSetInConstruct() {
|
||||
$logger = new ArrayLogger;
|
||||
$server = new Server(new Socket(), $logger);
|
||||
$server = new Server(new Socket(), $this->_app, $logger);
|
||||
|
||||
$this->assertSame($logger, $this->getPrivateProperty($server, '_log'));
|
||||
}
|
||||
@ -70,8 +72,7 @@ class ServerTest extends \PHPUnit_Framework_TestCase {
|
||||
}
|
||||
|
||||
public function testBindToInvalidAddress() {
|
||||
$this->markTestIncomplete();
|
||||
return;
|
||||
return $this->markTestIncomplete();
|
||||
|
||||
$app = new TestApp();
|
||||
|
||||
|
@ -3,6 +3,7 @@ namespace Ratchet\Tests;
|
||||
use Ratchet\Tests\Mock\FakeSocket as Socket;
|
||||
use Ratchet\Socket as RealSocket;
|
||||
use Ratchet\Tests\Mock\Protocol;
|
||||
use Ratchet\Tests\Mock\Application as TestApp;
|
||||
|
||||
/**
|
||||
* @covers Ratchet\Socket
|
||||
@ -51,14 +52,14 @@ class SocketTest extends \PHPUnit_Framework_TestCase {
|
||||
}
|
||||
|
||||
public function testConstructionFromProtocolInterfaceConfig() {
|
||||
$protocol = new Protocol();
|
||||
$protocol = new Protocol(new TestApp);
|
||||
$socket = Socket::createFromConfig($protocol);
|
||||
|
||||
$this->assertInstanceOf('\\Ratchet\\Socket', $socket);
|
||||
}
|
||||
|
||||
public function testCreationFromConfigOutputMatchesInput() {
|
||||
$protocol = new Protocol();
|
||||
$protocol = new Protocol(new TestApp);
|
||||
$socket = Socket::createFromConfig($protocol);
|
||||
$config = $protocol::getDefaultConfig();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user