added new socket manager, errorhandling and testing is not finished yet
This commit is contained in:
		
							
								
								
									
										135
									
								
								libs/React/Stream/Buffer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								libs/React/Stream/Buffer.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| <?php | ||||
|  | ||||
| namespace React\Stream; | ||||
|  | ||||
| use Evenement\EventEmitter; | ||||
| use React\EventLoop\LoopInterface; | ||||
|  | ||||
| /** @event full-drain */ | ||||
| class Buffer extends EventEmitter implements WritableStreamInterface | ||||
| { | ||||
|     public $stream; | ||||
|     public $listening = false; | ||||
|     public $softLimit = 2048; | ||||
|     private $writable = true; | ||||
|     private $loop; | ||||
|     private $data = ''; | ||||
|     private $lastError = array( | ||||
|         'number'  => 0, | ||||
|         'message' => '', | ||||
|         'file'    => '', | ||||
|         'line'    => 0, | ||||
|     ); | ||||
|  | ||||
|     public function __construct($stream, LoopInterface $loop) | ||||
|     { | ||||
|         $this->stream = $stream; | ||||
|         $this->loop = $loop; | ||||
|     } | ||||
|  | ||||
|     public function isWritable() | ||||
|     { | ||||
|         return $this->writable; | ||||
|     } | ||||
|  | ||||
|     public function write($data) | ||||
|     { | ||||
|         if (!$this->writable) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $this->data .= $data; | ||||
|  | ||||
|         if (!$this->listening) { | ||||
|             $this->listening = true; | ||||
|  | ||||
|             $this->loop->addWriteStream($this->stream, array($this, 'handleWrite')); | ||||
|         } | ||||
|  | ||||
|         $belowSoftLimit = strlen($this->data) < $this->softLimit; | ||||
|  | ||||
|         return $belowSoftLimit; | ||||
|     } | ||||
|  | ||||
|     public function end($data = null) | ||||
|     { | ||||
|         if (null !== $data) { | ||||
|             $this->write($data); | ||||
|         } | ||||
|  | ||||
|         $this->writable = false; | ||||
|  | ||||
|         if ($this->listening) { | ||||
|             $this->on('full-drain', array($this, 'close')); | ||||
|         } else { | ||||
|             $this->close(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function close() | ||||
|     { | ||||
|         $this->writable = false; | ||||
|         $this->listening = false; | ||||
|         $this->data = ''; | ||||
|  | ||||
|         $this->emit('close', [$this]); | ||||
|     } | ||||
|  | ||||
|     public function handleWrite() | ||||
|     { | ||||
|         if (!is_resource($this->stream)) { | ||||
|             $this->emit('error', array(new \RuntimeException('Tried to write to invalid stream.'), $this)); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         set_error_handler(array($this, 'errorHandler')); | ||||
|  | ||||
|         $sent = fwrite($this->stream, $this->data); | ||||
|  | ||||
|         restore_error_handler(); | ||||
|  | ||||
|         if (false === $sent) { | ||||
|             $this->emit('error', array( | ||||
|                 new \ErrorException( | ||||
|                     $this->lastError['message'], | ||||
|                     0, | ||||
|                     $this->lastError['number'], | ||||
|                     $this->lastError['file'], | ||||
|                     $this->lastError['line'] | ||||
|                 ), | ||||
|                 $this | ||||
|             )); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (0 === $sent && feof($this->stream)) { | ||||
|             $this->emit('error', array(new \RuntimeException('Tried to write to closed stream.'), $this)); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $len = strlen($this->data); | ||||
|         if ($len >= $this->softLimit && $len - $sent < $this->softLimit) { | ||||
|             $this->emit('drain', [$this]); | ||||
|         } | ||||
|  | ||||
|         $this->data = (string) substr($this->data, $sent); | ||||
|  | ||||
|         if (0 === strlen($this->data)) { | ||||
|             $this->loop->removeWriteStream($this->stream); | ||||
|             $this->listening = false; | ||||
|  | ||||
|             $this->emit('full-drain', [$this]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private function errorHandler($errno, $errstr, $errfile, $errline) | ||||
|     { | ||||
|         $this->lastError['number']  = $errno; | ||||
|         $this->lastError['message'] = $errstr; | ||||
|         $this->lastError['file']    = $errfile; | ||||
|         $this->lastError['line']    = $errline; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										59
									
								
								libs/React/Stream/BufferedSink.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								libs/React/Stream/BufferedSink.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| <?php | ||||
|  | ||||
| namespace React\Stream; | ||||
|  | ||||
| use React\Promise\Deferred; | ||||
| use React\Promise\PromisorInterface; | ||||
|  | ||||
| class BufferedSink extends WritableStream implements PromisorInterface | ||||
| { | ||||
|     private $buffer = ''; | ||||
|     private $deferred; | ||||
|  | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->deferred = new Deferred(); | ||||
|  | ||||
|         $this->on('pipe', array($this, 'handlePipeEvent')); | ||||
|         $this->on('error', array($this, 'handleErrorEvent')); | ||||
|     } | ||||
|  | ||||
|     public function handlePipeEvent($source) | ||||
|     { | ||||
|         Util::forwardEvents($source, $this, array('error')); | ||||
|     } | ||||
|  | ||||
|     public function handleErrorEvent($e) | ||||
|     { | ||||
|         $this->deferred->reject($e); | ||||
|     } | ||||
|  | ||||
|     public function write($data) | ||||
|     { | ||||
|         $this->buffer .= $data; | ||||
|         $this->deferred->progress($data); | ||||
|     } | ||||
|  | ||||
|     public function close() | ||||
|     { | ||||
|         if ($this->closed) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         parent::close(); | ||||
|         $this->deferred->resolve($this->buffer); | ||||
|     } | ||||
|  | ||||
|     public function promise() | ||||
|     { | ||||
|         return $this->deferred->promise(); | ||||
|     } | ||||
|  | ||||
|     public static function createPromise(ReadableStreamInterface $stream) | ||||
|     { | ||||
|         $sink = new static(); | ||||
|         $stream->pipe($sink); | ||||
|  | ||||
|         return $sink->promise(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										84
									
								
								libs/React/Stream/CompositeStream.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								libs/React/Stream/CompositeStream.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| <?php | ||||
|  | ||||
| namespace React\Stream; | ||||
|  | ||||
| use Evenement\EventEmitter; | ||||
|  | ||||
| class CompositeStream extends EventEmitter implements DuplexStreamInterface | ||||
| { | ||||
|     protected $readable; | ||||
|     protected $writable; | ||||
|     protected $pipeSource; | ||||
|  | ||||
|     public function __construct(ReadableStreamInterface $readable, WritableStreamInterface $writable) | ||||
|     { | ||||
|         $this->readable = $readable; | ||||
|         $this->writable = $writable; | ||||
|  | ||||
|         Util::forwardEvents($this->readable, $this, array('data', 'end', 'error', 'close')); | ||||
|         Util::forwardEvents($this->writable, $this, array('drain', 'error', 'close', 'pipe')); | ||||
|  | ||||
|         $this->readable->on('close', array($this, 'close')); | ||||
|         $this->writable->on('close', array($this, 'close')); | ||||
|  | ||||
|         $this->on('pipe', array($this, 'handlePipeEvent')); | ||||
|     } | ||||
|  | ||||
|     public function handlePipeEvent($source) | ||||
|     { | ||||
|         $this->pipeSource = $source; | ||||
|     } | ||||
|  | ||||
|     public function isReadable() | ||||
|     { | ||||
|         return $this->readable->isReadable(); | ||||
|     } | ||||
|  | ||||
|     public function pause() | ||||
|     { | ||||
|         if ($this->pipeSource) { | ||||
|             $this->pipeSource->pause(); | ||||
|         } | ||||
|  | ||||
|         $this->readable->pause(); | ||||
|     } | ||||
|  | ||||
|     public function resume() | ||||
|     { | ||||
|         if ($this->pipeSource) { | ||||
|             $this->pipeSource->resume(); | ||||
|         } | ||||
|  | ||||
|         $this->readable->resume(); | ||||
|     } | ||||
|  | ||||
|     public function pipe(WritableStreamInterface $dest, array $options = array()) | ||||
|     { | ||||
|         Util::pipe($this, $dest, $options); | ||||
|  | ||||
|         return $dest; | ||||
|     } | ||||
|  | ||||
|     public function isWritable() | ||||
|     { | ||||
|         return $this->writable->isWritable(); | ||||
|     } | ||||
|  | ||||
|     public function write($data) | ||||
|     { | ||||
|         return $this->writable->write($data); | ||||
|     } | ||||
|  | ||||
|     public function end($data = null) | ||||
|     { | ||||
|         $this->writable->end($data); | ||||
|     } | ||||
|  | ||||
|     public function close() | ||||
|     { | ||||
|         $this->pipeSource = null; | ||||
|  | ||||
|         $this->readable->close(); | ||||
|         $this->writable->close(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								libs/React/Stream/DuplexStreamInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								libs/React/Stream/DuplexStreamInterface.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <?php | ||||
|  | ||||
| namespace React\Stream; | ||||
|  | ||||
| interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface | ||||
| { | ||||
| } | ||||
							
								
								
									
										42
									
								
								libs/React/Stream/ReadableStream.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								libs/React/Stream/ReadableStream.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| <?php | ||||
|  | ||||
| namespace React\Stream; | ||||
|  | ||||
| use Evenement\EventEmitter; | ||||
|  | ||||
| class ReadableStream extends EventEmitter implements ReadableStreamInterface | ||||
| { | ||||
|     protected $closed = false; | ||||
|  | ||||
|     public function isReadable() | ||||
|     { | ||||
|         return !$this->closed; | ||||
|     } | ||||
|  | ||||
|     public function pause() | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     public function resume() | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     public function pipe(WritableStreamInterface $dest, array $options = array()) | ||||
|     { | ||||
|         Util::pipe($this, $dest, $options); | ||||
|  | ||||
|         return $dest; | ||||
|     } | ||||
|  | ||||
|     public function close() | ||||
|     { | ||||
|         if ($this->closed) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $this->closed = true; | ||||
|         $this->emit('end', array($this)); | ||||
|         $this->emit('close', array($this)); | ||||
|         $this->removeAllListeners(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										20
									
								
								libs/React/Stream/ReadableStreamInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								libs/React/Stream/ReadableStreamInterface.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <?php | ||||
|  | ||||
| namespace React\Stream; | ||||
|  | ||||
| use Evenement\EventEmitterInterface; | ||||
|  | ||||
| /** | ||||
|  * @event data | ||||
|  * @event end | ||||
|  * @event error | ||||
|  * @event close | ||||
|  */ | ||||
| interface ReadableStreamInterface extends EventEmitterInterface | ||||
| { | ||||
|     public function isReadable(); | ||||
|     public function pause(); | ||||
|     public function resume(); | ||||
|     public function pipe(WritableStreamInterface $dest, array $options = array()); | ||||
|     public function close(); | ||||
| } | ||||
							
								
								
									
										141
									
								
								libs/React/Stream/Stream.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								libs/React/Stream/Stream.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | ||||
| <?php | ||||
|  | ||||
| namespace React\Stream; | ||||
|  | ||||
| use Evenement\EventEmitter; | ||||
| use React\EventLoop\LoopInterface; | ||||
| use InvalidArgumentException; | ||||
|  | ||||
| class Stream extends EventEmitter implements DuplexStreamInterface | ||||
| { | ||||
|     public $bufferSize = 4096; | ||||
|     public $stream; | ||||
|     protected $readable = true; | ||||
|     protected $writable = true; | ||||
|     protected $closing = false; | ||||
|     protected $loop; | ||||
|     protected $buffer; | ||||
|  | ||||
|     public function __construct($stream, LoopInterface $loop) | ||||
|     { | ||||
|         $this->stream = $stream; | ||||
|         if (!is_resource($this->stream) || get_resource_type($this->stream) !== "stream") { | ||||
|              throw new InvalidArgumentException('First parameter must be a valid stream resource'); | ||||
|         } | ||||
|  | ||||
|         stream_set_blocking($this->stream, 0); | ||||
|  | ||||
|         $this->loop = $loop; | ||||
|         $this->buffer = new Buffer($this->stream, $this->loop); | ||||
|  | ||||
|         $this->buffer->on('error', function ($error) { | ||||
|             $this->emit('error', array($error, $this)); | ||||
|             $this->close(); | ||||
|         }); | ||||
|  | ||||
|         $this->buffer->on('drain', function () { | ||||
|             $this->emit('drain', array($this)); | ||||
|         }); | ||||
|  | ||||
|         $this->resume(); | ||||
|     } | ||||
|  | ||||
|     public function isReadable() | ||||
|     { | ||||
|         return $this->readable; | ||||
|     } | ||||
|  | ||||
|     public function isWritable() | ||||
|     { | ||||
|         return $this->writable; | ||||
|     } | ||||
|  | ||||
|     public function pause() | ||||
|     { | ||||
|         $this->loop->removeReadStream($this->stream); | ||||
|     } | ||||
|  | ||||
|     public function resume() | ||||
|     { | ||||
|         if ($this->readable) { | ||||
|             $this->loop->addReadStream($this->stream, array($this, 'handleData')); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function write($data) | ||||
|     { | ||||
|         if (!$this->writable) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         return $this->buffer->write($data); | ||||
|     } | ||||
|  | ||||
|     public function close() | ||||
|     { | ||||
|         if (!$this->writable && !$this->closing) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $this->closing = false; | ||||
|  | ||||
|         $this->readable = false; | ||||
|         $this->writable = false; | ||||
|  | ||||
|         $this->emit('end', array($this)); | ||||
|         $this->emit('close', array($this)); | ||||
|         $this->loop->removeStream($this->stream); | ||||
|         $this->buffer->removeAllListeners(); | ||||
|         $this->removeAllListeners(); | ||||
|  | ||||
|         $this->handleClose(); | ||||
|     } | ||||
|  | ||||
|     public function end($data = null) | ||||
|     { | ||||
|         if (!$this->writable) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $this->closing = true; | ||||
|  | ||||
|         $this->readable = false; | ||||
|         $this->writable = false; | ||||
|  | ||||
|         $this->buffer->on('close', function () { | ||||
|             $this->close(); | ||||
|         }); | ||||
|  | ||||
|         $this->buffer->end($data); | ||||
|     } | ||||
|  | ||||
|     public function pipe(WritableStreamInterface $dest, array $options = array()) | ||||
|     { | ||||
|         Util::pipe($this, $dest, $options); | ||||
|  | ||||
|         return $dest; | ||||
|     } | ||||
|  | ||||
|     public function handleData($stream) | ||||
|     { | ||||
|         $data = fread($stream, $this->bufferSize); | ||||
|  | ||||
|         $this->emit('data', array($data, $this)); | ||||
|  | ||||
|         if (!is_resource($stream) || feof($stream)) { | ||||
|             $this->end(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function handleClose() | ||||
|     { | ||||
|         if (is_resource($this->stream)) { | ||||
|             fclose($this->stream); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function getBuffer() | ||||
|     { | ||||
|         return $this->buffer; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										33
									
								
								libs/React/Stream/ThroughStream.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								libs/React/Stream/ThroughStream.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| <?php | ||||
|  | ||||
| namespace React\Stream; | ||||
|  | ||||
| class ThroughStream extends CompositeStream | ||||
| { | ||||
|     public function __construct() | ||||
|     { | ||||
|         $readable = new ReadableStream(); | ||||
|         $writable = new WritableStream(); | ||||
|  | ||||
|         parent::__construct($readable, $writable); | ||||
|     } | ||||
|  | ||||
|     public function filter($data) | ||||
|     { | ||||
|         return $data; | ||||
|     } | ||||
|  | ||||
|     public function write($data) | ||||
|     { | ||||
|         $this->readable->emit('data', array($this->filter($data), $this)); | ||||
|     } | ||||
|  | ||||
|     public function end($data = null) | ||||
|     { | ||||
|         if (null !== $data) { | ||||
|             $this->readable->emit('data', array($this->filter($data), $this)); | ||||
|         } | ||||
|  | ||||
|         $this->writable->end($data); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										45
									
								
								libs/React/Stream/Util.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								libs/React/Stream/Util.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| <?php | ||||
|  | ||||
| namespace React\Stream; | ||||
|  | ||||
| // TODO: move to a trait | ||||
|  | ||||
| class Util | ||||
| { | ||||
|     public static function pipe(ReadableStreamInterface $source, WritableStreamInterface $dest, array $options = array()) | ||||
|     { | ||||
|         // TODO: use stream_copy_to_stream | ||||
|         // it is 4x faster than this | ||||
|         // but can lose data under load with no way to recover it | ||||
|  | ||||
|         $dest->emit('pipe', array($source)); | ||||
|  | ||||
|         $source->on('data', function ($data) use ($source, $dest) { | ||||
|             $feedMore = $dest->write($data); | ||||
|  | ||||
|             if (false === $feedMore) { | ||||
|                 $source->pause(); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         $dest->on('drain', function () use ($source) { | ||||
|             $source->resume(); | ||||
|         }); | ||||
|  | ||||
|         $end = isset($options['end']) ? $options['end'] : true; | ||||
|         if ($end && $source !== $dest) { | ||||
|             $source->on('end', function () use ($dest) { | ||||
|                 $dest->end(); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static function forwardEvents($source, $target, array $events) | ||||
|     { | ||||
|         foreach ($events as $event) { | ||||
|             $source->on($event, function () use ($event, $target) { | ||||
|                 $target->emit($event, func_get_args()); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										40
									
								
								libs/React/Stream/WritableStream.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								libs/React/Stream/WritableStream.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| <?php | ||||
|  | ||||
| namespace React\Stream; | ||||
|  | ||||
| use Evenement\EventEmitter; | ||||
|  | ||||
| class WritableStream extends EventEmitter implements WritableStreamInterface | ||||
| { | ||||
|     protected $closed = false; | ||||
|  | ||||
|     public function write($data) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     public function end($data = null) | ||||
|     { | ||||
|         if (null !== $data) { | ||||
|             $this->write($data); | ||||
|         } | ||||
|  | ||||
|         $this->close(); | ||||
|     } | ||||
|  | ||||
|     public function isWritable() | ||||
|     { | ||||
|         return !$this->closed; | ||||
|     } | ||||
|  | ||||
|     public function close() | ||||
|     { | ||||
|         if ($this->closed) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $this->closed = true; | ||||
|         $this->emit('end', array($this)); | ||||
|         $this->emit('close', array($this)); | ||||
|         $this->removeAllListeners(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								libs/React/Stream/WritableStreamInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								libs/React/Stream/WritableStreamInterface.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| <?php | ||||
|  | ||||
| namespace React\Stream; | ||||
|  | ||||
| use Evenement\EventEmitterInterface; | ||||
|  | ||||
| /** | ||||
|  * @event drain | ||||
|  * @event error | ||||
|  * @event close | ||||
|  * @event pipe | ||||
|  */ | ||||
| interface WritableStreamInterface extends EventEmitterInterface | ||||
| { | ||||
|     public function isWritable(); | ||||
|     public function write($data); | ||||
|     public function end($data = null); | ||||
|     public function close(); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user