Merge pull request #8 from ratchetphp/psr7-client-refactor

Client refactor
This commit is contained in:
Chris Boden 2016-02-08 22:20:33 -05:00
commit 71f10cb9ee
10 changed files with 120 additions and 127 deletions

View File

@ -1,84 +1,53 @@
<?php <?php
namespace Ratchet\RFC6455\Handshake; namespace Ratchet\RFC6455\Handshake;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
use GuzzleHttp\Psr7\Request;
class ClientNegotiator implements ClientNegotiatorInterface { class ClientNegotiator {
public $defaultHeaders = [ /**
'Connection' => 'Upgrade' * @var ResponseVerifier
, 'Cache-Control' => 'no-cache' */
, 'Pragma' => 'no-cache' private $verifier;
, 'Upgrade' => 'websocket'
, 'Sec-WebSocket-Version' => 13
, 'User-Agent' => "RatchetRFC/0.0.0"
];
/** @var Request */ /**
public $request; * @var \Psr\Http\Message\RequestInterface
*/
private $defaultHeader;
/** @var Response */ function __construct() {
public $response; $this->verifier = new ResponseVerifier;
/** @var ResponseVerifier */ $this->defaultHeader = new Request('GET', '', [
public $verifier; 'Connection' => 'Upgrade'
, 'Upgrade' => 'websocket'
private $websocketKey = ''; , 'Sec-WebSocket-Version' => $this->getVersion()
, 'User-Agent' => "RatchetRFC/0.0.0"
function __construct($path = null) ]);
{
if (!is_string($path)) $path = "/";
$request = new Request("GET", $path);
$request = $request->withUri(new Uri("ws://127.0.0.1:9001" . $path));
$this->request = $request;
$this->verifier = new ResponseVerifier();
$this->websocketKey = $this->generateKey();
} }
public function addRequiredHeaders() { public function generateRequest(UriInterface $uri) {
foreach ($this->defaultHeaders as $k => $v) { return $this->defaultHeader->withUri($uri)
// remove any header that is there now ->withHeader("Sec-WebSocket-Key", $this->generateKey());
$this->request = $this->request->withoutHeader($k);
$this->request = $this->request->withHeader($k, $v);
}
$this->request = $this->request->withoutHeader("Sec-WebSocket-Key");
$this->request = $this->request->withHeader("Sec-WebSocket-Key", $this->websocketKey);
$this->request = $this->request->withoutHeader("Host")
->withHeader("Host", $this->request->getUri()->getHost() . ":" . $this->request->getUri()->getPort());
} }
public function getRequest() { public function validateResponse(RequestInterface $request, ResponseInterface $response) {
$this->addRequiredHeaders(); return $this->verifier->verifyAll($request, $response);
return $this->request;
} }
public function getResponse() { public function generateKey() {
return $this->response;
}
public function validateResponse(Response $response) {
$this->response = $response;
return $this->verifier->verifyAll($this->getRequest(), $response);
}
protected function generateKey() {
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzyz1234567890+/='; $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzyz1234567890+/=';
$charRange = strlen($chars) - 1; $charRange = strlen($chars) - 1;
$key = ''; $key = '';
for ($i = 0;$i < 16;$i++) { for ($i = 0; $i < 16; $i++) {
$key .= $chars[mt_rand(0, $charRange)]; $key .= $chars[mt_rand(0, $charRange)];
} }
return base64_encode($key); return base64_encode($key);
} }
public function getVersion() {
return 13;
}
} }

View File

@ -1,11 +0,0 @@
<?php
namespace Ratchet\RFC6455\Handshake;
interface ClientNegotiatorInterface {
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
}

View File

@ -1,29 +1,29 @@
<?php <?php
namespace Ratchet\RFC6455\Handshake; namespace Ratchet\RFC6455\Handshake;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
class ResponseVerifier { class ResponseVerifier {
public function verifyAll(Request $request, Response $response) { public function verifyAll(RequestInterface $request, ResponseInterface $response) {
$passes = 0; $passes = 0;
$passes += (int)$this->verifyStatus($response->getStatusCode()); $passes += (int)$this->verifyStatus($response->getStatusCode());
$passes += (int)$this->verifyUpgrade($response->getHeader('Upgrade')); $passes += (int)$this->verifyUpgrade($response->getHeader('Upgrade'));
$passes += (int)$this->verifyConnection($response->getHeader('Connection')); $passes += (int)$this->verifyConnection($response->getHeader('Connection'));
$passes += (int)$this->verifySecWebSocketAccept( $passes += (int)$this->verifySecWebSocketAccept(
$response->getHeader('Sec-WebSocket-Accept'), $response->getHeader('Sec-WebSocket-Accept')
$request->getHeader('sec-websocket-key') , $request->getHeader('Sec-WebSocket-Key')
); );
$passes += (int)$this->verifySubProtocol(
$request->getHeader('Sec-WebSocket-Protocol')
, $response->getHeader('Sec-WebSocket-Protocol')
);
return (4 == $passes); return (5 === $passes);
} }
public function verifyStatus($status) { public function verifyStatus($status) {
return ($status == 101); return ((int)$status === 101);
} }
public function verifyUpgrade(array $upgrade) { public function verifyUpgrade(array $upgrade) {
@ -38,10 +38,15 @@ class ResponseVerifier {
return ( return (
1 === count($swa) && 1 === count($swa) &&
1 === count($key) && 1 === count($key) &&
$swa[0] == $this->sign($key[0])); $swa[0] === $this->sign($key[0])
);
} }
public function sign($key) { public function sign($key) {
return base64_encode(sha1($key . Negotiator::GUID, true)); return base64_encode(sha1($key . NegotiatorInterface::GUID, true));
}
public function verifySubProtocol(array $requestHeader, array $responseHeader) {
return 0 === count($responseHeader) || count(array_intersect($responseHeader, $requestHeader)) > 0;
} }
} }

View File

@ -1,7 +1,7 @@
<?php <?php
use GuzzleHttp\Psr7\Uri;
use React\Promise\Deferred; use React\Promise\Deferred;
use Ratchet\RFC6455\Messaging\Protocol\Frame; use Ratchet\RFC6455\Messaging\Protocol\Frame;
use Ratchet\RFC6455\Messaging\Protocol\Message;
require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/../bootstrap.php';
@ -48,8 +48,8 @@ function getTestCases() {
$deferred = new Deferred(); $deferred = new Deferred();
$factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred) { $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred) {
$cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator("/getCaseCount"); $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator();
$cnRequest = $cn->getRequest(); $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001/getCaseCount'));
$rawResponse = ""; $rawResponse = "";
$response = null; $response = null;
@ -57,7 +57,7 @@ function getTestCases() {
/** @var \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer $ms */ /** @var \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer $ms */
$ms = null; $ms = null;
$stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context) { $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) {
if ($response === null) { if ($response === null) {
$rawResponse .= $data; $rawResponse .= $data;
$pos = strpos($rawResponse, "\r\n\r\n"); $pos = strpos($rawResponse, "\r\n\r\n");
@ -66,7 +66,7 @@ function getTestCases() {
$rawResponse = substr($rawResponse, 0, $pos + 4); $rawResponse = substr($rawResponse, 0, $pos + 4);
$response = \GuzzleHttp\Psr7\parse_response($rawResponse); $response = \GuzzleHttp\Psr7\parse_response($rawResponse);
if (!$cn->validateResponse($response)) { if (!$cn->validateResponse($cnRequest, $response)) {
$stream->end(); $stream->end();
$deferred->reject(); $deferred->reject();
} else { } else {
@ -105,15 +105,15 @@ function runTest($case)
$deferred = new Deferred(); $deferred = new Deferred();
$factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred, $casePath, $case) { $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred, $casePath, $case) {
$cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator($casePath); $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator();
$cnRequest = $cn->getRequest(); $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $casePath));
$rawResponse = ""; $rawResponse = "";
$response = null; $response = null;
$ms = null; $ms = null;
$stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context) { $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) {
if ($response === null) { if ($response === null) {
$rawResponse .= $data; $rawResponse .= $data;
$pos = strpos($rawResponse, "\r\n\r\n"); $pos = strpos($rawResponse, "\r\n\r\n");
@ -122,7 +122,7 @@ function runTest($case)
$rawResponse = substr($rawResponse, 0, $pos + 4); $rawResponse = substr($rawResponse, 0, $pos + 4);
$response = \GuzzleHttp\Psr7\parse_response($rawResponse); $response = \GuzzleHttp\Psr7\parse_response($rawResponse);
if (!$cn->validateResponse($response)) { if (!$cn->validateResponse($cnRequest, $response)) {
$stream->end(); $stream->end();
$deferred->reject(); $deferred->reject();
} else { } else {
@ -155,8 +155,8 @@ function createReport() {
$factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred) { $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred) {
$reportPath = "/updateReports?agent=" . AGENT . "&shutdownOnComplete=true"; $reportPath = "/updateReports?agent=" . AGENT . "&shutdownOnComplete=true";
$cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator($reportPath); $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator();
$cnRequest = $cn->getRequest(); $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $reportPath));
$rawResponse = ""; $rawResponse = "";
$response = null; $response = null;
@ -164,7 +164,7 @@ function createReport() {
/** @var \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer $ms */ /** @var \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer $ms */
$ms = null; $ms = null;
$stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context) { $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) {
if ($response === null) { if ($response === null) {
$rawResponse .= $data; $rawResponse .= $data;
$pos = strpos($rawResponse, "\r\n\r\n"); $pos = strpos($rawResponse, "\r\n\r\n");
@ -173,7 +173,7 @@ function createReport() {
$rawResponse = substr($rawResponse, 0, $pos + 4); $rawResponse = substr($rawResponse, 0, $pos + 4);
$response = \GuzzleHttp\Psr7\parse_response($rawResponse); $response = \GuzzleHttp\Psr7\parse_response($rawResponse);
if (!$cn->validateResponse($response)) { if (!$cn->validateResponse($cnRequest, $response)) {
$stream->end(); $stream->end();
$deferred->reject(); $deferred->reject();
} else { } else {

View File

@ -1,13 +1,14 @@
{ {
"options": {"failByDrop": false}, "options": {
"outdir": "./reports/servers", "failByDrop": false
}
"servers": [{ , "outdir": "./reports/servers"
"agent": "RatchetRFC/0.1.0", , "servers": [{
"url": "ws://localhost:9001", "agent": "RatchetRFC/0.1.0"
"options": {"version": 18} , "url": "ws://localhost:9001"
}], , "options": {"version": 18}
"cases": ["*"], }]
"exclude-cases": ["12.*","13.*"], , "cases": ["*"]
"exclude-agent-cases": {} , "exclude-cases": ["6.4.*", "12.*","13.*"]
, "exclude-agent-cases": {}
} }

View File

@ -1,10 +1,10 @@
{ {
"url": "ws://127.0.0.1:9001" "url": "ws://127.0.0.1:9001"
, "options": { , "options": {
"failByDrop": false "failByDrop": false
} }
, "outdir": "./reports/clients" , "outdir": "./reports/clients"
, "cases": ["*"] , "cases": ["*"]
, "exclude-cases": ["12.*", "13.*"] , "exclude-cases": ["6.4.*", "12.*", "13.*"]
, "exclude-agent-cases": {} , "exclude-agent-cases": {}
} }

View File

@ -1,11 +1,9 @@
<?php <?php
namespace Ratchet\RFC6455\Test\Unit\Handshake; namespace Ratchet\RFC6455\Test\Unit\Handshake;
use Ratchet\RFC6455\Handshake\RequestVerifier; use Ratchet\RFC6455\Handshake\RequestVerifier;
/** /**
* covers Ratchet\WebSocket\Version\RFC6455\HandshakeVerifier * @covers Ratchet\RFC6455\Handshake\RequestVerifier
*/ */
class RequestVerifierTest extends \PHPUnit_Framework_TestCase { class RequestVerifierTest extends \PHPUnit_Framework_TestCase {
/** /**

View File

@ -0,0 +1,34 @@
<?php
namespace Ratchet\RFC6455\Test\Unit\Handshake;
use Ratchet\RFC6455\Handshake\ResponseVerifier;
/**
* @covers Ratchet\WebSocket\Version\RFC6455\ResponseVerifier
*/
class ResponseVerifierTest extends \PHPUnit_Framework_TestCase {
/**
* @var ResponseVerifier
*/
protected $_v;
public function setUp() {
$this->_v = new ResponseVerifier;
}
public static function subProtocolsProvider() {
return [
[true, ['a'], ['a']]
, [true, ['b', 'a'], ['c', 'd', 'a']]
, [false, ['a', 'b', 'c'], ['d']]
, [true, [], []]
, [true, ['a', 'b'], []]
];
}
/**
* @dataProvider subProtocolsProvider
*/
public function testVerifySubProtocol($expected, $response, $request) {
$this->assertEquals($expected, $this->_v->verifySubProtocol($response, $request));
}
}

View File

@ -1,10 +1,9 @@
<?php <?php
namespace Ratchet\RFC6455\Test\Unit\Messaging\Protocol; namespace Ratchet\RFC6455\Test\Unit\Messaging\Protocol;
use Ratchet\RFC6455\Messaging\Protocol\Frame; use Ratchet\RFC6455\Messaging\Protocol\Frame;
/** /**
* @covers Ratchet\RFC6455\Messaging\Protocol\Frame
* @todo getMaskingKey, getPayloadStartingByte don't have tests yet * @todo getMaskingKey, getPayloadStartingByte don't have tests yet
* @todo Could use some clean up in general, I had to rush to fix a bug for a deadline, sorry. * @todo Could use some clean up in general, I had to rush to fix a bug for a deadline, sorry.
*/ */

View File

@ -1,12 +1,10 @@
<?php <?php
namespace Ratchet\RFC6455\Test\Unit\Messaging\Protocol; namespace Ratchet\RFC6455\Test\Unit\Messaging\Protocol;
use Ratchet\RFC6455\Messaging\Protocol\Frame; use Ratchet\RFC6455\Messaging\Protocol\Frame;
use Ratchet\RFC6455\Messaging\Protocol\Message; use Ratchet\RFC6455\Messaging\Protocol\Message;
/** /**
* covers Ratchet\WebSocket\Version\RFC6455\Message * @covers Ratchet\RFC6455\Messaging\Protocol\Message
*/ */
class MessageTest extends \PHPUnit_Framework_TestCase { class MessageTest extends \PHPUnit_Framework_TestCase {
/** @var Message */ /** @var Message */