MessageStreamer refactor

Remove notion of context and nested callbacks
Each connection will create an instance of MessageParser to hold message/frame state
This commit is contained in:
Chris Boden 2015-12-22 20:16:55 -05:00
parent 06263cd9a5
commit 3c3588fc8b
6 changed files with 116 additions and 120 deletions

View File

@ -99,9 +99,9 @@ class Negotiator implements NegotiatorInterface {
return new Response(101, array_merge($headers, [
'Upgrade' => 'websocket'
, 'Connection' => 'Upgrade'
, 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0])
, 'X-Powered-By' => 'Ratchet'
, 'Connection' => 'Upgrade'
, 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0])
, 'X-Powered-By' => 'Ratchet'
]));
}

View File

@ -0,0 +1,24 @@
<?php
namespace Ratchet\RFC6455\Messaging\Protocol;
class CloseFrameChecker {
private $validCloseFrames = [];
public function __construct() {
$this->validCloseCodes = [
Frame::CLOSE_NORMAL,
Frame::CLOSE_GOING_AWAY,
Frame::CLOSE_PROTOCOL,
Frame::CLOSE_BAD_DATA,
Frame::CLOSE_BAD_PAYLOAD,
Frame::CLOSE_POLICY,
Frame::CLOSE_TOO_BIG,
Frame::CLOSE_MAND_EXT,
Frame::CLOSE_SRV_ERR,
];
}
public function __invoke($val) {
return ($val >= 3000 && $val <= 4999) || in_array($val, $this->validCloseCodes);
}
}

View File

@ -22,22 +22,6 @@ class Message implements \IteratorAggregate, MessageInterface {
return count($this->_frames);
}
public function offsetExists($index) {
return $this->_frames->offsetExists($index);
}
public function offsetGet($index) {
return $this->_frames->offsetGet($index);
}
public function offsetSet($index, $newval) {
throw new \DomainException('Frame access in messages is read-only');
}
public function offsetUnset($index) {
unset($this->_frames[$index]);
}
/**
* {@inheritdoc}
*/

View File

@ -1,7 +1,7 @@
<?php
namespace Ratchet\RFC6455\Messaging\Protocol;
interface MessageInterface extends DataInterface, \Traversable, \ArrayAccess, \Countable {
interface MessageInterface extends DataInterface, \Traversable, \Countable {
/**
* @param FrameInterface $fragment
* @return MessageInterface

View File

@ -1,6 +1,7 @@
<?php
namespace Ratchet\RFC6455\Messaging\Streaming;
use Ratchet\RFC6455\Encoding\ValidatorInterface;
use Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker;
use Ratchet\RFC6455\Messaging\Protocol\MessageInterface;
use Ratchet\RFC6455\Messaging\Protocol\FrameInterface;
use Ratchet\RFC6455\Messaging\Protocol\Message;
@ -12,23 +13,50 @@ class MessageStreamer {
*/
private $validator;
/**
* @var \Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker
*/
private $closeFrameChecker;
/**
* @var callable
*/
private $exceptionFactory;
/**
* @var \Ratchet\RFC6455\Messaging\Protocol\Message
*/
private $messageBuffer = null;
/**
* @var \Ratchet\RFC6455\Messaging\Protocol\Frame
*/
private $frameBuffer;
/**
* @var callable
*/
private $onMessage;
/**
* @var callable
*/
private $onControl;
/**
* @var bool
*/
private $checkForMask;
/**
* @var array
*/
private $validCloseCodes;
function __construct(ValidatorInterface $encodingValidator, $expectMask = true) {
$this->validator = $encodingValidator;
function __construct(
ValidatorInterface $encodingValidator,
CloseFrameChecker $frameChecker,
callable $onMessage,
callable $onControl = null,
$expectMask = true
) {
$this->validator = $encodingValidator;
$this->closeFrameChecker = $frameChecker;
$this->checkForMask = (bool)$expectMask;
$exception = new \UnderflowException;
@ -36,104 +64,67 @@ class MessageStreamer {
return $exception;
};
$this->noop = function() {};
$this->validCloseCodes = [
Frame::CLOSE_NORMAL,
Frame::CLOSE_GOING_AWAY,
Frame::CLOSE_PROTOCOL,
Frame::CLOSE_BAD_DATA,
Frame::CLOSE_BAD_PAYLOAD,
Frame::CLOSE_POLICY,
Frame::CLOSE_TOO_BIG,
Frame::CLOSE_MAND_EXT,
Frame::CLOSE_SRV_ERR,
];
$this->onMessage = $onMessage;
$this->onControl = $onControl ?: function() {};
}
/**
* @param $data
* @param mixed $context
* @param MessageInterface $message
* @param callable(MessageInterface) $onMessage
* @param callable(FrameInterface) $onControl
* @return MessageInterface
* @param string $data
* @return null
*/
public function onData($data, MessageInterface $message = null, callable $onMessage, callable $onControl = null, $context = null) {
$overflow = '';
public function onData($data) {
$this->messageBuffer ?: $this->messageBuffer = $this->newMessage();
$this->frameBuffer ?: $this->frameBuffer = $this->newFrame();
$onControl ?: $this->noop;
$message ?: $message = $this->newMessage();
$this->frameBuffer->addBuffer($data);
if (!$this->frameBuffer->isCoalesced()) {
return;
}
$prevFrame = null;
$frameCount = count($message);
$onMessage = $this->onMessage;
$onControl = $this->onControl;
if ($frameCount > 0) {
$frame = $message[$frameCount - 1];
$this->frameBuffer = $this->frameCheck($this->frameBuffer);
if ($frame->isCoalesced()) {
$prevFrame = $frame;
$frame = $this->newFrame();
$message->addFrame($frame);
$frameCount++;
} elseif ($frameCount > 1) {
$prevFrame = $message[$frameCount - 2];
$overflow = $this->frameBuffer->extractOverflow();
$this->frameBuffer->unMaskPayload();
$opcode = $this->frameBuffer->getOpcode();
if ($opcode > 2) {
$onControl($this->frameBuffer);
if (Frame::OP_CLOSE === $opcode) {
return;
}
} else {
$frame = $this->newFrame();
$message->addFrame($frame);
$frameCount++;
$this->messageBuffer->addFrame($this->frameBuffer);
}
$frame->addBuffer($data);
if ($frame->isCoalesced()) {
$frame = $this->frameCheck($frame, $prevFrame);
$this->frameBuffer = null;
$opcode = $frame->getOpcode();
if ($opcode > 2) {
$onControl($frame, $context);
unset($message[$frameCount - 1]);
$overflow = $frame->extractOverflow();
if (strlen($overflow) > 0) {
$message = $this->onData($overflow, $message, $onMessage, $onControl, $context);
}
return $message;
}
$overflow = $frame->extractOverflow();
$frame->unMaskPayload();
}
if ($message->isCoalesced()) {
$msgCheck = $this->checkMessage($message);
if ($this->messageBuffer->isCoalesced()) {
$msgCheck = $this->checkMessage($this->messageBuffer);
if (true !== $msgCheck) {
$onControl($this->newCloseFrame($msgCheck), $context);
return $this->newMessage();
$onControl($this->newCloseFrame($msgCheck));
} else {
$onMessage($this->messageBuffer);
}
$onMessage($message, $context);
$message = $this->newMessage();
$this->messageBuffer = null;
}
if (strlen($overflow) > 0) {
$this->onData($overflow, $message, $onMessage, $onControl, $context);
$this->onData($overflow); // PHP doesn't do tail recursion :(
}
return $message;
}
/**
* Check a frame and previous frame in a message; returns the frame that should be dealt with
* Check a frame to be added to the current message buffer
* @param \Ratchet\RFC6455\Messaging\Protocol\FrameInterface|FrameInterface $frame
* @param \Ratchet\RFC6455\Messaging\Protocol\FrameInterface|FrameInterface $previousFrame
* @return \Ratchet\RFC6455\Messaging\Protocol\FrameInterface|FrameInterface
*/
public function frameCheck(FrameInterface $frame, FrameInterface $previousFrame = null) {
public function frameCheck(FrameInterface $frame) {
if (false !== $frame->getRsv1() ||
false !== $frame->getRsv2() ||
false !== $frame->getRsv3()
@ -170,7 +161,8 @@ class MessageStreamer {
list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2)));
}
if (!$this->isValidCloseCode($closeCode)) {
$checker = $this->closeFrameChecker;
if (!$checker($closeCode)) {
return $this->newCloseFrame(Frame::CLOSE_PROTOCOL);
}
@ -191,11 +183,11 @@ class MessageStreamer {
return $frame;
}
if (Frame::OP_CONTINUE === $frame->getOpcode() && null === $previousFrame) {
if (Frame::OP_CONTINUE == $frame->getOpcode() && 0 == count($this->messageBuffer)) {
return $this->newCloseFrame(Frame::CLOSE_PROTOCOL);
}
if (null !== $previousFrame && Frame::OP_CONTINUE != $frame->getOpcode()) {
if (count($this->messageBuffer) > 0 && Frame::OP_CONTINUE != $frame->getOpcode()) {
return $this->newCloseFrame(Frame::CLOSE_PROTOCOL);
}
@ -237,8 +229,4 @@ class MessageStreamer {
public function newCloseFrame($code) {
return $this->newFrame(pack('n', $code), true, Frame::OP_CLOSE);
}
public function isValidCloseCode($val) {
return ($val >= 3000 && $val <= 4999) || in_array($val, $this->validCloseCodes);
}
}

View File

@ -10,10 +10,10 @@ $socket = new \React\Socket\Server($loop);
$server = new \React\Http\Server($socket);
$encodingValidator = new \Ratchet\RFC6455\Encoding\Validator;
$closeFrameChecker = new \Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker;
$negotiator = new \Ratchet\RFC6455\Handshake\Negotiator($encodingValidator);
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer($encodingValidator);
$server->on('request', function (\React\Http\Request $request, \React\Http\Response $response) use ($negotiator, $ms) {
$server->on('request', function (\React\Http\Request $request, \React\Http\Response $response) use ($negotiator, $encodingValidator, $closeFrameChecker) {
$psrRequest = new \GuzzleHttp\Psr7\Request($request->getMethod(), $request->getPath(), $request->getHeaders());
$negotiatorResponse = $negotiator->handshake($psrRequest);
@ -31,21 +31,21 @@ $server->on('request', function (\React\Http\Request $request, \React\Http\Respo
return;
}
$msg = null;
$request->on('data', function($data) use ($ms, $response, &$msg) {
$msg = $ms->onData($data, $msg, function(MessageInterface $msg, \React\Http\Response $conn) {
$conn->write($msg->getContents());
}, function(FrameInterface $frame, \React\Http\Response $conn) use ($ms) {
switch ($frame->getOpCode()) {
case Frame::OP_CLOSE:
$conn->end($frame->getContents());
$parser = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer($encodingValidator, $closeFrameChecker, function(MessageInterface $message) use ($response) {
$response->write($message->getContents());
}, function(FrameInterface $frame) use ($response, &$parser) {
switch ($frame->getOpCode()) {
case Frame::OP_CLOSE:
$response->end($frame->getContents());
break;
case Frame::OP_PING:
$conn->write($ms->newFrame($frame->getPayload(), true, Frame::OP_PONG)->getContents());
case Frame::OP_PING:
$response->write($parser->newFrame($frame->getPayload(), true, Frame::OP_PONG)->getContents());
break;
}
}, $response);
}
});
$request->on('data', [$parser, 'onData']);
});
$socket->listen(9001, '0.0.0.0');
$loop->run();