示例#1
0
 /**
  * @coroutine
  *
  * @param string $data
  * @param float|int $timeout
  * @param bool $end
  *
  * @return \Generator
  *
  * @resolve int Number of bytes written to the stream.
  *
  * @throws \Icicle\Stream\Exception\UnwritableException If the stream is no longer writable.
  */
 protected function send(string $data, float $timeout = 0, bool $end = false) : \Generator
 {
     if (!$this->isWritable()) {
         throw new UnwritableException('The stream is no longer writable.');
     }
     $this->buffer->push($data);
     if (null !== $this->delayed && !$this->buffer->isEmpty()) {
         $delayed = $this->delayed;
         $this->delayed = null;
         $delayed->resolve($this->remove());
     }
     if ($end) {
         if ($this->buffer->isEmpty()) {
             $this->free();
         } else {
             $this->writable = false;
         }
     }
     if (0 !== $this->hwm && $this->buffer->getLength() > $this->hwm) {
         $awaitable = new Delayed($this->onCancelled = $this->onCancelled ?: function () {
             $this->free();
         });
         $this->queue->push($awaitable);
         if ($timeout) {
             $awaitable = $awaitable->timeout($timeout);
         }
         (yield $awaitable);
     }
     return strlen($data);
 }
示例#2
0
 /**
  * @coroutine
  *
  * Writes a value to the stream.
  *
  * The given value will be coerced to a string before being written. The resulting
  * string will be written to the internal buffer; if the buffer is full, the entire
  * buffer will be flushed to the stream.
  *
  * @param mixed $text A printable value that can be coerced to a string.
  *
  * @return \Generator
  *
  * @resolve int Number of bytes written to the buffer.
  *
  * @throws \Icicle\Awaitable\Exception\TimeoutException If the operation times out.
  * @throws \Icicle\Stream\Exception\UnwritableException If the stream is no longer writable.
  * @throws \Icicle\Stream\Exception\ClosedException If the stream is unexpectedly closed.
  */
 public function write(string $text) : \Generator
 {
     $length = strlen($text);
     $this->buffer->push($text);
     if ($this->autoFlush || $this->buffer->getLength() > $this->bufferSize) {
         yield from $this->flush();
     }
     return $length;
 }
示例#3
0
 /**
  * @coroutine
  *
  * @param int $maxSize Max frame size.
  * @param float|int $timeout
  *
  * @return \Generator
  *
  * @resolve \Icicle\WebSocket\Protocol\Rfc6455Frame
  *
  * @throws \Icicle\WebSocket\Exception\FrameException
  */
 public function read(int $maxSize, float $timeout = 0) : \Generator
 {
     $buffer = new Buffer();
     try {
         do {
             $buffer->push(yield from $this->socket->read(0, null, $timeout));
         } while ($buffer->getLength() < 2);
         $bytes = unpack('Cflags/Clength', $buffer->shift(2));
         $rsv = $bytes['flags'] & self::RSV_MASK;
         $opcode = $bytes['flags'] & self::OPCODE_MASK;
         $final = (bool) ($bytes['flags'] & self::FIN_MASK);
         $masked = (bool) ($bytes['length'] & self::MASK_FLAG_MASK);
         $size = $bytes['length'] & self::LENGTH_MASK;
         if ($masked === $this->masked) {
             throw new FrameException(sprintf('Received %s frame.', $masked ? 'masked' : 'unmasked'));
         }
         if ($size === self::TWO_BYTE_LENGTH_FLAG) {
             while ($buffer->getLength() < 2) {
                 $buffer->push(yield from $this->socket->read(0, null, $timeout));
             }
             $bytes = unpack('nlength', $buffer->shift(2));
             $size = $bytes['length'];
             if ($size < self::TWO_BYTE_LENGTH_FLAG) {
                 throw new FrameException('Frame format error.');
             }
         } elseif ($size === self::EIGHT_BYTE_LENGTH_FLAG) {
             while ($buffer->getLength() < 8) {
                 $buffer->push(yield from $this->socket->read(0, null, $timeout));
             }
             $bytes = unpack('Nhigh/Nlow', $buffer->shift(8));
             $size = $bytes['high'] << 32 | $bytes['low'];
             if ($size < self::TWO_BYTE_MAX_LENGTH) {
                 throw new FrameException('Frame format error.');
             }
         }
         if ($size > $maxSize) {
             throw new PolicyException('Frame size exceeded max allowed size.');
         }
         if ($masked) {
             while ($buffer->getLength() < self::MASK_LENGTH) {
                 $buffer->push(yield from $this->socket->read(0, null, $timeout));
             }
             $mask = $buffer->shift(self::MASK_LENGTH);
         }
         while ($buffer->getLength() < $size) {
             $buffer->push(yield from $this->socket->read(0, null, $timeout));
         }
         $data = $buffer->shift($size);
         if ($masked) {
             $data ^= str_repeat($mask, (int) (($size + self::MASK_LENGTH - 1) / self::MASK_LENGTH));
         }
     } finally {
         if (!$buffer->isEmpty()) {
             $this->socket->unshift((string) $buffer);
         }
     }
     return new Frame($opcode, $data, $rsv, $final);
 }
示例#4
0
 /**
  * @coroutine
  *
  * Reads and parses characters from the stream according to a format.
  *
  * The format string is of the same format as `sscanf()`.
  *
  * @param string $format The parse format.
  * @param float|int $timeout Number of seconds until the returned promise is rejected with a TimeoutException
  *     if no data is received. Use 0 for no timeout.
  *
  * @return \Generator
  *
  * @resolve array An array of parsed values.
  *
  * @throws \Icicle\Awaitable\Exception\TimeoutException If the operation times out.
  * @throws \Icicle\Stream\Exception\UnreadableException If the stream is no longer readable.
  * @throws \Icicle\Stream\Exception\ClosedException If the stream is unexpectedly closed.
  *
  * @see http://php.net/sscanf
  */
 public function scan(string $format, float $timeout = 0) : \Generator
 {
     // Read from the stream chunk by chunk, attempting to satisfy the format
     // string each time until the format successfully parses or the end of
     // the stream is reached.
     while (true) {
         $result = sscanf((string) $this->buffer, $format . '%n');
         $length = $result ? array_pop($result) : null;
         // If the format string was satisfied, consume the used characters and
         // return the parsed results.
         if ($length !== null && $length < $this->buffer->getLength()) {
             $this->buffer->shift($length);
             return $result;
         }
         // Read more into the buffer if possible.
         if ($this->stream->isReadable()) {
             $this->buffer->push(yield from $this->stream->read(0, null, $timeout));
         } else {
             // Format string can't be satisfied.
             return [];
         }
     }
 }
示例#5
0
 /**
  * {@inheritdoc}
  *
  * @throws \Icicle\Http\Exception\MessageException If an invalid chunk length is found.
  */
 protected function send(string $data, float $timeout = 0, bool $end = false) : \Generator
 {
     $this->buffer->push($data);
     $data = '';
     while (!$this->buffer->isEmpty()) {
         if (0 === $this->length) {
             // Read chunk length.
             if (false === ($position = $this->buffer->search("\r\n"))) {
                 return yield from parent::send($data, $timeout, $end);
             }
             $length = rtrim($this->buffer->remove($position + 2), "\r\n");
             if ($position = strpos($length, ';')) {
                 $length = substr($length, 0, $position);
             }
             if (!preg_match('/^[a-f0-9]+$/i', $length)) {
                 yield from parent::send('', $timeout, true);
                 throw new MessageException(Response::BAD_REQUEST, 'Invalid chunk length.');
             }
             $this->length = hexdec($length) + 2;
             if (2 === $this->length) {
                 // Termination chunk.
                 $end = true;
             }
         }
         if (2 < $this->length) {
             // Read chunk.
             $buffer = $this->buffer->remove($this->length - 2);
             $this->length -= strlen($buffer);
             $data .= $buffer;
         }
         if (2 >= $this->length) {
             // Remove \r\n after chunk.
             $this->length -= strlen($this->buffer->remove($this->length));
         }
     }
     return yield from parent::send($data, $timeout, $end);
 }
示例#6
0
 /**
  * @param \Icicle\Stream\Structures\Buffer $buffer
  * @param \Icicle\Socket\Socket $socket
  * @param float|int $timeout
  *
  * @return \Generator
  *
  * @throws \Icicle\Http\Exception\MessageException
  * @throws \Icicle\Http\Exception\ParseException
  */
 protected function readHeaders(Buffer $buffer, Socket $socket, float $timeout = 0) : \Generator
 {
     $size = 0;
     $headers = [];
     do {
         while (false === ($position = $buffer->search("\r\n"))) {
             if ($buffer->getLength() >= $this->maxSize) {
                 throw new MessageException(Response::REQUEST_HEADER_TOO_LARGE, sprintf('Message header exceeded maximum size of %d bytes.', $this->maxSize));
             }
             $buffer->push(yield from $socket->read(0, null, $timeout));
         }
         $length = $position + 2;
         $line = $buffer->shift($length);
         if (2 === $length) {
             return $headers;
         }
         $size += $length;
         $parts = explode(':', $line, 2);
         if (2 !== count($parts)) {
             throw new ParseException('Found header without colon.');
         }
         list($name, $value) = $parts;
         $name = Message\decode($name);
         $value = Message\decode(trim($value));
         // No check for case as Message class will automatically combine similarly named headers.
         if (!isset($headers[$name])) {
             $headers[$name] = [$value];
         } else {
             $headers[$name][] = $value;
         }
     } while ($size < $this->maxSize);
     throw new MessageException(Response::REQUEST_HEADER_TOO_LARGE, sprintf('Message header exceeded maximum size of %d bytes.', $this->maxSize));
 }
示例#7
0
 /**
  * {@inheritdoc}
  */
 public function getLength() : int
 {
     return $this->buffer->getLength();
 }