예제 #1
0
 public function on(string $event, Closure $closure)
 {
     if (!isset($this->listeners[$event])) {
         $this->listeners[$event] = [];
     }
     $this->listeners[$event][] = Coroutine\wrap($this->wrap($closure));
 }
예제 #2
0
 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);
         }
     });
 }
예제 #3
0
 /**
  * @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;
     });
 }