[Server][WebSocket] Performance
Switched IoServer::factory to use React factory (libevent stable) Bit operations in Frame for performance gains Added performance tests back to fuzzing config
This commit is contained in:
parent
288f74a9f1
commit
188e9f04ce
@ -4,7 +4,6 @@ use Ratchet\MessageComponentInterface;
|
|||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
use React\EventLoop\LoopInterface;
|
use React\EventLoop\LoopInterface;
|
||||||
use React\Socket\ServerInterface;
|
use React\Socket\ServerInterface;
|
||||||
use React\EventLoop\StreamSelectLoop;
|
|
||||||
use React\EventLoop\Factory as LoopFactory;
|
use React\EventLoop\Factory as LoopFactory;
|
||||||
use React\Socket\Server as Reactor;
|
use React\Socket\Server as Reactor;
|
||||||
|
|
||||||
@ -56,7 +55,7 @@ class IoServer {
|
|||||||
* @return Ratchet\Server\IoServer
|
* @return Ratchet\Server\IoServer
|
||||||
*/
|
*/
|
||||||
public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0') {
|
public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0') {
|
||||||
$loop = new StreamSelectLoop;
|
$loop = LoopFactory::create();
|
||||||
$socket = new Reactor($loop);
|
$socket = new Reactor($loop);
|
||||||
$socket->listen($port, $address);
|
$socket->listen($port, $address);
|
||||||
|
|
||||||
|
@ -41,15 +41,35 @@ class Frame implements FrameInterface {
|
|||||||
* Number of bytes in the payload (as per framing protocol)
|
* Number of bytes in the payload (as per framing protocol)
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
protected $_pay_len_def = -1;
|
protected $defPayLen = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the frame is coalesced this is true
|
||||||
|
* This is to prevent doing math every time ::isCoaleced is called
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
|
private $isCoalesced = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unpacked first byte of the frame
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $firstByte = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unpacked second byte of the frame
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $secondByte = -1;
|
||||||
|
|
||||||
public function __construct($payload = null, $final = true, $opcode = 1) {
|
public function __construct($payload = null, $final = true, $opcode = 1) {
|
||||||
if (null === $payload) {
|
if (null === $payload) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$raw = (int)(boolean)$final . sprintf('%07b', (int)$opcode);
|
$firstByte = chr(($final ? 128 : 0) + $opcode);
|
||||||
|
|
||||||
|
$raw = '';
|
||||||
$plLen = strlen($payload);
|
$plLen = strlen($payload);
|
||||||
if ($plLen <= 125) {
|
if ($plLen <= 125) {
|
||||||
$raw .= sprintf('%08b', $plLen);
|
$raw .= sprintf('%08b', $plLen);
|
||||||
@ -59,7 +79,7 @@ class Frame implements FrameInterface {
|
|||||||
$raw .= sprintf('%08b', 127) . sprintf('%064b', $plLen);
|
$raw .= sprintf('%08b', 127) . sprintf('%064b', $plLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->addBuffer(static::encode($raw) . $payload);
|
$this->addBuffer($firstByte . static::encode($raw) . $payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,6 +106,10 @@ class Frame implements FrameInterface {
|
|||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function isCoalesced() {
|
public function isCoalesced() {
|
||||||
|
if (true === $this->isCoalesced) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$payload_length = $this->getPayloadLength();
|
$payload_length = $this->getPayloadLength();
|
||||||
$payload_start = $this->getPayloadStartingByte();
|
$payload_start = $this->getPayloadStartingByte();
|
||||||
@ -93,26 +117,38 @@ class Frame implements FrameInterface {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->bytesRecvd >= $payload_length + $payload_start;
|
$this->isCoalesced = $this->bytesRecvd >= $payload_length + $payload_start;
|
||||||
|
|
||||||
|
return $this->isCoalesced;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function addBuffer($buf) {
|
public function addBuffer($buf) {
|
||||||
|
$len = strlen($buf);
|
||||||
|
|
||||||
$this->data .= $buf;
|
$this->data .= $buf;
|
||||||
$this->bytesRecvd += strlen($buf);
|
$this->bytesRecvd += $len;
|
||||||
|
|
||||||
|
if ($this->firstByte === -1 && $this->bytesRecvd !== 0) {
|
||||||
|
$this->firstByte = ord($this->data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->secondByte === -1 && $this->bytesRecvd >= 2) {
|
||||||
|
$this->secondByte = ord($this->data[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function isFinal() {
|
public function isFinal() {
|
||||||
if ($this->bytesRecvd < 1) {
|
if (-1 === $this->firstByte) {
|
||||||
throw new \UnderflowException('Not enough bytes received to determine if this is the final frame in message');
|
throw new \UnderflowException('Not enough bytes received to determine if this is the final frame in message');
|
||||||
}
|
}
|
||||||
|
|
||||||
return 128 === (ord($this->data[0]) & 128);
|
return 128 === ($this->firstByte & 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -120,11 +156,11 @@ class Frame implements FrameInterface {
|
|||||||
* @throws UnderflowException
|
* @throws UnderflowException
|
||||||
*/
|
*/
|
||||||
public function getRsv1() {
|
public function getRsv1() {
|
||||||
if ($this->bytesRecvd < 1) {
|
if (-1 === $this->firstByte) {
|
||||||
throw new \UnderflowException('Not enough bytes received to determine reserved bit');
|
throw new \UnderflowException('Not enough bytes received to determine reserved bit');
|
||||||
}
|
}
|
||||||
|
|
||||||
return 64 === (ord($this->data[0]) & 64);
|
return 64 === ($this->firstByte & 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -132,11 +168,11 @@ class Frame implements FrameInterface {
|
|||||||
* @throws UnderflowException
|
* @throws UnderflowException
|
||||||
*/
|
*/
|
||||||
public function getRsv2() {
|
public function getRsv2() {
|
||||||
if ($this->bytesRecvd < 1) {
|
if (-1 === $this->firstByte) {
|
||||||
throw new \UnderflowException('Not enough bytes received to determine reserved bit');
|
throw new \UnderflowException('Not enough bytes received to determine reserved bit');
|
||||||
}
|
}
|
||||||
|
|
||||||
return 32 === (ord($this->data[0]) & 32);
|
return 32 === ($this->firstByte & 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,22 +180,22 @@ class Frame implements FrameInterface {
|
|||||||
* @throws UnderflowException
|
* @throws UnderflowException
|
||||||
*/
|
*/
|
||||||
public function getRsv3() {
|
public function getRsv3() {
|
||||||
if ($this->bytesRecvd < 1) {
|
if (-1 === $this->firstByte) {
|
||||||
throw new \UnderflowException('Not enough bytes received to determine reserved bit');
|
throw new \UnderflowException('Not enough bytes received to determine reserved bit');
|
||||||
}
|
}
|
||||||
|
|
||||||
return 16 == (ord($this->data[0]) & 16);
|
return 16 == ($this->firstByte & 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function isMasked() {
|
public function isMasked() {
|
||||||
if ($this->bytesRecvd < 2) {
|
if (-1 === $this->secondByte) {
|
||||||
throw new \UnderflowException("Not enough bytes received ({$this->bytesRecvd}) to determine if mask is set");
|
throw new \UnderflowException("Not enough bytes received ({$this->bytesRecvd}) to determine if mask is set");
|
||||||
}
|
}
|
||||||
|
|
||||||
return 128 === (ord($this->data[1]) & 128);
|
return 128 === ($this->secondByte & 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -214,9 +250,9 @@ class Frame implements FrameInterface {
|
|||||||
|
|
||||||
$this->unMaskPayload();
|
$this->unMaskPayload();
|
||||||
|
|
||||||
$byte = sprintf('%08b', ord($this->data[1]));
|
$this->secondByte = $this->secondByte | 128;
|
||||||
|
$this->data[1] = chr($this->secondByte);
|
||||||
|
|
||||||
$this->data = substr_replace($this->data, static::encode(substr_replace($byte, '1', 0, 1)), 1, 1);
|
|
||||||
$this->data = substr_replace($this->data, $maskingKey, $this->getNumPayloadBytes() + 1, 0);
|
$this->data = substr_replace($this->data, $maskingKey, $this->getNumPayloadBytes() + 1, 0);
|
||||||
|
|
||||||
$this->bytesRecvd += static::MASK_LENGTH;
|
$this->bytesRecvd += static::MASK_LENGTH;
|
||||||
@ -231,15 +267,19 @@ class Frame implements FrameInterface {
|
|||||||
* @return Frame
|
* @return Frame
|
||||||
*/
|
*/
|
||||||
public function unMaskPayload() {
|
public function unMaskPayload() {
|
||||||
|
if (!$this->isCoalesced()) {
|
||||||
|
throw new \UnderflowException('Frame must be coalesced before applying mask');
|
||||||
|
}
|
||||||
|
|
||||||
if (!$this->isMasked()) {
|
if (!$this->isMasked()) {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
$maskingKey = $this->getMaskingKey();
|
$maskingKey = $this->getMaskingKey();
|
||||||
|
|
||||||
$byte = sprintf('%08b', ord($this->data[1]));
|
$this->secondByte = $this->secondByte & ~128;
|
||||||
|
$this->data[1] = chr($this->secondByte);
|
||||||
|
|
||||||
$this->data = substr_replace($this->data, static::encode(substr_replace($byte, '0', 0, 1)), 1, 1);
|
|
||||||
$this->data = substr_replace($this->data, '', $this->getNumPayloadBytes() + 1, static::MASK_LENGTH);
|
$this->data = substr_replace($this->data, '', $this->getNumPayloadBytes() + 1, static::MASK_LENGTH);
|
||||||
|
|
||||||
$this->bytesRecvd -= static::MASK_LENGTH;
|
$this->bytesRecvd -= static::MASK_LENGTH;
|
||||||
@ -255,7 +295,7 @@ class Frame implements FrameInterface {
|
|||||||
* @throws UnderflowException If using the payload but enough hasn't been buffered
|
* @throws UnderflowException If using the payload but enough hasn't been buffered
|
||||||
* @return string The masked string
|
* @return string The masked string
|
||||||
*/
|
*/
|
||||||
protected function applyMask($maskingKey, $payload = null) {
|
public function applyMask($maskingKey, $payload = null) {
|
||||||
if (null === $payload) {
|
if (null === $payload) {
|
||||||
if (!$this->isCoalesced()) {
|
if (!$this->isCoalesced()) {
|
||||||
throw new \UnderflowException('Frame must be coalesced to apply a mask');
|
throw new \UnderflowException('Frame must be coalesced to apply a mask');
|
||||||
@ -276,11 +316,11 @@ class Frame implements FrameInterface {
|
|||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getOpcode() {
|
public function getOpcode() {
|
||||||
if ($this->bytesRecvd < 1) {
|
if (-1 === $this->firstByte) {
|
||||||
throw new \UnderflowException('Not enough bytes received to determine opcode');
|
throw new \UnderflowException('Not enough bytes received to determine opcode');
|
||||||
}
|
}
|
||||||
|
|
||||||
return bindec(substr(sprintf('%08b', ord($this->data[0])), 4, 4));
|
return ($this->firstByte & ~240);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -289,11 +329,11 @@ class Frame implements FrameInterface {
|
|||||||
* @throws UnderflowException If the buffer doesn't have enough data to determine this
|
* @throws UnderflowException If the buffer doesn't have enough data to determine this
|
||||||
*/
|
*/
|
||||||
protected function getFirstPayloadVal() {
|
protected function getFirstPayloadVal() {
|
||||||
if ($this->bytesRecvd < 2) {
|
if (-1 === $this->secondByte) {
|
||||||
throw new \UnderflowException('Not enough bytes received');
|
throw new \UnderflowException('Not enough bytes received');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ord($this->data[1]) & 127;
|
return $this->secondByte & 127;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -301,7 +341,7 @@ class Frame implements FrameInterface {
|
|||||||
* @throws UnderflowException
|
* @throws UnderflowException
|
||||||
*/
|
*/
|
||||||
protected function getNumPayloadBits() {
|
protected function getNumPayloadBits() {
|
||||||
if ($this->bytesRecvd < 2) {
|
if (-1 === $this->secondByte) {
|
||||||
throw new \UnderflowException('Not enough bytes received');
|
throw new \UnderflowException('Not enough bytes received');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,15 +378,12 @@ class Frame implements FrameInterface {
|
|||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getPayloadLength() {
|
public function getPayloadLength() {
|
||||||
if ($this->_pay_len_def !== -1) {
|
if ($this->defPayLen !== -1) {
|
||||||
return $this->_pay_len_def;
|
return $this->defPayLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
$length_check = $this->getFirstPayloadVal();
|
$this->defPayLen = $this->getFirstPayloadVal();
|
||||||
|
if ($this->defPayLen <= 125) {
|
||||||
if ($length_check <= 125) {
|
|
||||||
$this->_pay_len_def = $length_check;
|
|
||||||
|
|
||||||
return $this->getPayloadLength();
|
return $this->getPayloadLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,12 +392,13 @@ class Frame implements FrameInterface {
|
|||||||
throw new \UnderflowException('Not enough data buffered to determine payload length');
|
throw new \UnderflowException('Not enough data buffered to determine payload length');
|
||||||
}
|
}
|
||||||
|
|
||||||
$strings = array();
|
$len = 0;
|
||||||
for ($i = 2; $i < $byte_length + 1; $i++) {
|
for ($i = 2; $i <= $byte_length; $i++) {
|
||||||
$strings[] = ord(substr($this->data, $i, 1));
|
$len <<= 8;
|
||||||
|
$len += ord($this->data[$i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->_pay_len_def = bindec(vsprintf(str_repeat('%08b', $byte_length - 1), $strings));
|
$this->defPayLen = $len;
|
||||||
|
|
||||||
return $this->getPayloadLength();
|
return $this->getPayloadLength();
|
||||||
}
|
}
|
||||||
@ -381,10 +419,10 @@ class Frame implements FrameInterface {
|
|||||||
throw new \UnderflowException('Can not return partial message');
|
throw new \UnderflowException('Can not return partial message');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->isMasked()) {
|
|
||||||
$payload = $this->applyMask($this->getMaskingKey());
|
|
||||||
} else {
|
|
||||||
$payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength());
|
$payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength());
|
||||||
|
|
||||||
|
if ($this->isMasked()) {
|
||||||
|
$payload = $this->applyMask($this->getMaskingKey(), $payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
|
@ -9,6 +9,6 @@
|
|||||||
]
|
]
|
||||||
|
|
||||||
, "cases": ["*"]
|
, "cases": ["*"]
|
||||||
, "exclude-cases": ["9.*", "1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"]
|
, "exclude-cases": ["1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"]
|
||||||
, "exclude-agent-cases": {}
|
, "exclude-agent-cases": {}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user