1
0
mirror of https://github.com/danog/byte-stream.git synced 2024-12-02 17:28:21 +01:00
byte-stream/lib/Message.php

148 lines
3.9 KiB
PHP
Raw Normal View History

2017-05-05 16:55:43 +02:00
<?php
namespace Amp\ByteStream;
use Amp\Coroutine;
use Amp\Deferred;
use Amp\Promise;
use Amp\Success;
/**
* Creates a buffered message from an InputStream. The message can be consumed in chunks using the read() API or it may
* be buffered and accessed in its entirety by waiting for the promise to resolve.
2017-05-05 16:55:43 +02:00
*
* Buffering Example:
*
* $stream = new Message($inputStream);
2017-05-05 16:55:43 +02:00
* $content = yield $stream;
*
* Streaming Example:
*
* $stream = new Message($inputStream);
2017-05-05 16:55:43 +02:00
*
* while (($chunk = yield $stream->read()) !== null) {
* // Immediately use $chunk, reducing memory consumption since the entire message is never buffered.
* }
*/
2017-05-11 01:20:34 +02:00
class Message implements InputStream, Promise {
/** @var InputStream */
private $source;
2017-05-05 16:55:43 +02:00
/** @var string */
private $buffer = "";
/** @var \Amp\Deferred|null */
private $pendingRead;
/** @var \Amp\Coroutine */
private $coroutine;
2017-05-11 01:04:10 +02:00
/** @var bool True if onResolve() has been called. */
private $buffering = false;
/** @var \Amp\Deferred|null */
private $backpressure;
2017-05-12 01:08:45 +02:00
/** @var bool True if the iterator has completed. */
private $complete = false;
2017-05-11 01:04:10 +02:00
2017-05-05 16:55:43 +02:00
/**
* @param InputStream $source An iterator that only emits strings.
2017-05-05 16:55:43 +02:00
*/
public function __construct(InputStream $source) {
$this->source = $source;
2017-05-05 16:55:43 +02:00
}
private function consume(): \Generator {
while (($chunk = yield $this->source->read()) !== null) {
$buffer = $this->buffer .= $chunk;
2017-05-11 01:04:10 +02:00
if ($buffer === "") {
continue; // Do not succeed reads with empty string.
} elseif ($this->pendingRead) {
2017-05-05 16:55:43 +02:00
$deferred = $this->pendingRead;
$this->pendingRead = null;
$this->buffer = "";
$deferred->resolve($buffer);
$buffer = ""; // Destroy last emitted chunk to free memory.
2017-05-11 01:04:10 +02:00
} elseif (!$this->buffering) {
$buffer = ""; // Destroy last emitted chunk to free memory.
2017-05-11 01:04:10 +02:00
$this->backpressure = new Deferred;
yield $this->backpressure->promise();
2017-05-05 16:55:43 +02:00
}
}
2017-05-12 01:08:45 +02:00
$this->complete = true;
2017-05-05 16:55:43 +02:00
if ($this->pendingRead) {
$deferred = $this->pendingRead;
$this->pendingRead = null;
2017-05-11 01:04:10 +02:00
$deferred->resolve($this->buffer !== "" ? $this->buffer : null);
2017-05-05 16:55:43 +02:00
$this->buffer = "";
}
return $this->buffer;
}
final public function read(): Promise {
2017-05-11 01:04:10 +02:00
if ($this->pendingRead) {
throw new PendingReadError;
2017-05-11 01:04:10 +02:00
}
if ($this->coroutine === null) {
$this->coroutine = new Coroutine($this->consume());
}
2017-05-05 16:55:43 +02:00
if ($this->buffer !== "") {
$buffer = $this->buffer;
$this->buffer = "";
2017-05-11 01:04:10 +02:00
if ($this->backpressure) {
$backpressure = $this->backpressure;
$this->backpressure = null;
$backpressure->resolve();
}
return new Success($buffer);
2017-05-05 16:55:43 +02:00
}
2017-05-12 01:08:45 +02:00
if ($this->complete) {
return new Success;
}
2017-05-05 16:55:43 +02:00
$this->pendingRead = new Deferred;
return $this->pendingRead->promise();
}
/**
* {@inheritdoc}
*/
final public function onResolve(callable $onResolved) {
2017-05-11 01:04:10 +02:00
$this->buffering = true;
if ($this->coroutine === null) {
$this->coroutine = new Coroutine($this->consume());
}
2017-05-11 01:04:10 +02:00
if ($this->backpressure) {
$backpressure = $this->backpressure;
$this->backpressure = null;
$backpressure->resolve();
}
2017-05-05 16:55:43 +02:00
$this->coroutine->onResolve($onResolved);
}
/**
* Exposes the source input stream.
*
* This might be required to resolve a promise with an InputStream, because promises in Amp can't be resolved with
* other promises.
*
* @return InputStream
*/
final public function getInputStream(): InputStream {
return $this->source;
}
2017-05-05 16:55:43 +02:00
}