public function on(string $event, Closure $closure) { if (!isset($this->listeners[$event])) { $this->listeners[$event] = []; } $this->listeners[$event][] = Coroutine\wrap($this->wrap($closure)); }
public function __construct() { $this->timerCallback = Coroutine\wrap(function (Timer $timer) : \Generator { $key = $timer->getData(); yield from $this->wait($key); // Wait if key is locked. // Delete only if value has not been changed. if (isset($this->timers[$key]) && $timer === $this->timers[$key]) { yield from $this->delete($key); } }); }
/** * @param float $interval * @param int $maxSize * @param int $maxFrames * * @return \Icicle\Observable\Observable */ private function createObservable(float $interval, int $maxSize, int $maxFrames) : Observable { return new Emitter(function (callable $emit) use($interval, $maxSize, $maxFrames) : \Generator { /** @var \Icicle\WebSocket\Protocol\Rfc6455Frame[] $frames */ $frames = []; $size = 0; $ping = Loop\periodic($interval, Coroutine\wrap(function () use(&$pong, &$expected) { try { yield from $this->ping($expected = base64_encode(random_bytes(self::PING_DATA_LENGTH))); if (null === $pong) { $pong = Loop\timer($this->timeout, Coroutine\wrap(function () { yield from $this->close(Close::VIOLATION); $this->transporter->close(); })); $pong->unreference(); } else { $pong->again(); } } catch (\Throwable $exception) { $this->transporter->close(); } })); $ping->unreference(); try { while ($this->transporter->isOpen()) { /** @var \Icicle\WebSocket\Protocol\Rfc6455Frame $frame */ $frame = (yield from $this->transporter->read($maxSize - $size)); $ping->again(); switch ($type = $frame->getType()) { case Frame::CLOSE: // Close connection. if (!$this->closed) { // Respond with close frame if one has not been sent. yield from $this->close(Close::NORMAL); } $data = $frame->getData(); if (2 > strlen($data)) { return new Close(Close::NO_STATUS); } $bytes = unpack('ncode', substr($data, 0, 2)); $data = (string) substr($data, 2); if (!preg_match('//u', $data)) { throw new DataException('Invalid UTF-8 data received.'); } return new Close($bytes['code'], $data); case Frame::PING: // Respond with pong frame. yield from $this->pong($frame->getData()); continue; case Frame::PONG: // Cancel timeout set by sending ping frame. // Only stop timer if ping sent and pong data matches ping data. if (null !== $pong && $frame->getData() === $expected) { $pong->stop(); } continue; case Frame::CONTINUATION: $count = count($frames); if (0 === $count) { throw new ProtocolException('Received orphan continuation frame.'); } $frames[] = $frame; $size += $frame->getSize(); if (!$frame->isFinal()) { if ($count + 1 >= $maxFrames) { throw new PolicyException('Too many frames in message.'); } continue; } $data = ''; for ($i = $count; $i >= 0; --$i) { $frame = $frames[$i]; $data = $frame->getData() . $data; } $frames = []; $size = 0; $type = $frame->getType(); if ($type === Frame::TEXT && !preg_match('//u', $data)) { throw new DataException('Invalid UTF-8 data received.'); } yield from $emit(new Message($data, $type === Frame::BINARY)); continue; case Frame::TEXT: case Frame::BINARY: if (!empty($frames)) { throw new ProtocolException('Expected continuation data frame.'); } if (!$frame->isFinal()) { $size = $frame->getSize(); $frames[] = $frame; continue; } $data = $frame->getData(); if ($type === Frame::TEXT && !preg_match('//u', $data)) { throw new DataException('Invalid UTF-8 data received.'); } yield from $emit(new Message($data, $type === Frame::BINARY)); continue; default: throw new ProtocolException('Received unrecognized frame type.'); } } } catch (ConnectionException $exception) { $close = new Close($exception->getReasonCode(), $exception->getMessage()); } catch (SocketException $exception) { $close = new Close(Close::ABNORMAL, $exception->getMessage()); } catch (StreamException $exception) { $close = new Close(Close::ABNORMAL, $exception->getMessage()); } catch (TimeoutException $exception) { $close = new Close(Close::GOING_AWAY, $exception->getMessage()); } finally { $ping->stop(); if (null !== $pong) { $pong->stop(); } } if (!isset($close)) { $close = new Close(Close::ABNORMAL, 'Peer unexpectedly disconnected.'); } if ($this->isOpen()) { yield from $this->close($close->getCode()); } return $close; }); }