ES
games/xencenter-xenadmin-xcp-jumphp / src / Xapi / Fake / FakeHttpStream.php · commit fb1b7c9
<?phpnamespace App\Xapi\Fake;use Evenement\EventEmitter;/** * A connection to the demo pool's HTTP server (what a TcpConnection to port 80 of a host is): it reads one request and hands it to * $handler (FakeXapi's XvaOps), which answers through reply() / sendBody() as the frames go, at a bandwidth of $rate bytes a frame. * on('open'|'data'|'close'|'error'), write(), close(), poll(): the interface of the engine's streams. */final class FakeHttpStream extends EventEmitter{    public string $requestHead = '';    public string $method = '';    public string $path = '';    /** @var array<string, string> */    public array $headers = [];    /** bytes of the request's body received (an upload) */    public float $received = 0.0;    /** the first bytes of the body (ova.xml of an XVA) */    public string $bodyHead = '';    public bool $headDone = false;    /** bytes of the answer handed to the client so far */    public float $sent = 0.0;    /** what the handler keeps about this request (its task, the VM...) */    public array $context = [];    private string $buffer = '';    private bool $opened = false;    private bool $closed = false;    /** @var list<string> what is to be sent, a bit each frame */    private array $out = [];    private bool $closeAfter = false;    /** @var \Closure(self, string): void called on 'head' (the request line and headers) and 'body' (a part arrived) */    private \Closure $handler;    public int $rate = 393216;    /** @param \Closure(self, string): void $handler */    public function __construct(\Closure $handler)    {        $this->handler = $handler;    }    public function write(string $bytes): bool    {        if ($this->closed) {            return false;        }        $this->buffer .= $bytes;        if (!$this->headDone) {            $end = strpos($this->buffer, "\r\n\r\n");            if ($end === false) {                return true;            }            $this->requestHead = substr($this->buffer, 0, $end);            $this->buffer = (string)substr($this->buffer, $end + 4);            $lines = explode("\r\n", $this->requestHead);            [$this->method, $this->path] = explode(' ', (string)array_shift($lines)) + ['', '', ''];            foreach ($lines as $line) {                [$name, $value] = explode(':', $line, 2) + ['', ''];                $this->headers[strtolower(trim($name))] = trim($value);            }            $this->headDone = true;            ($this->handler)($this, 'head');        }        if ($this->buffer !== '') {            $this->received += strlen($this->buffer);            if (strlen($this->bodyHead) < 262144) {                $this->bodyHead .= substr($this->buffer, 0, 262144 - strlen($this->bodyHead));            }            $this->buffer = '';           // the rest of an upload is counted, not kept            ($this->handler)($this, 'body');        }        return true;    }    /** An answer: the status line and headers now, the body by parts ($body: a string, or a list of strings sent one after the other). */    public function reply(int $status, string $reason, array $headers, string|array $body = '', bool $close = true): void    {        $head = "HTTP/1.1 $status $reason\r\n";        foreach ($headers as $name => $value) {            $head .= "$name: $value\r\n";        }        $this->out[] = $head . "\r\n";        foreach (is_array($body) ? $body : [$body] as $part) {            if ($part !== '') {                $this->out[] = $part;            }        }        $this->closeAfter = $close;    }    public function close(): void    {        if ($this->closed) {            return;        }        $this->closed = true;        $this->emit('close', [null]);        $this->removeAllListeners();    }    public function isClosed(): bool    {        return $this->closed;    }    public function poll(): void    {        if ($this->closed) {            return;        }        if (!$this->opened) {            $this->opened = true;            $this->emit('open');            return;        }        $budget = $this->rate;        while ($this->out !== [] && $budget > 0 && !$this->isClosed()) {            $part = $this->out[0];            $piece = substr($part, 0, $budget);            $rest = (string)substr($part, strlen($piece));            $rest === '' ? array_shift($this->out) : $this->out[0] = $rest;            $budget -= strlen($piece);            $this->sent += strlen($piece);            $this->emit('data', [$piece]);        }        if ($this->out === [] && $this->closeAfter && !$this->isClosed()) {            $this->close();        }    }}