From fceaf618b0da14c84011b881cc3889e3d98cd1c8 Mon Sep 17 00:00:00 2001 From: Demin Yin <deminy@deminy.net> Date: Thu, 2 Apr 2020 07:36:13 -0700 Subject: [PATCH] updates for Swoole 4.4.17 Signed-off-by: Demin Yin <deminy@deminy.net> --- output/swoole/constants.php | 6 +- output/swoole_library/src/alias.php | 1 + output/swoole_library/src/core/Constant.php | 10 + .../src/core/Coroutine/FastCGI/Client.php | 178 ++++++++ .../Coroutine/FastCGI/Client/Exception.php | 16 + .../src/core/Coroutine/FastCGI/Proxy.php | 205 +++++++++ .../src/core/Coroutine/WaitGroup.php | 7 +- .../src/core/Coroutine/functions.php | 29 ++ .../swoole_library/src/core/Curl/Handler.php | 11 +- .../src/core/Database/MysqliProxy.php | 5 + .../core/Database/MysqliStatementProxy.php | 5 + .../src/core/Database/PDOProxy.php | 7 +- .../src/core/Database/PDOStatementProxy.php | 4 + output/swoole_library/src/core/FastCGI.php | 94 ++++ .../src/core/FastCGI/FrameParser.php | 90 ++++ .../src/core/FastCGI/HttpRequest.php | 424 ++++++++++++++++++ .../src/core/FastCGI/HttpResponse.php | 116 +++++ .../src/core/FastCGI/Message.php | 80 ++++ .../src/core/FastCGI/Record.php | 232 ++++++++++ .../src/core/FastCGI/Record/AbortRequest.php | 27 ++ .../src/core/FastCGI/Record/BeginRequest.php | 113 +++++ .../src/core/FastCGI/Record/Data.php | 29 ++ .../src/core/FastCGI/Record/EndRequest.php | 117 +++++ .../src/core/FastCGI/Record/GetValues.php | 48 ++ .../core/FastCGI/Record/GetValuesResult.php | 46 ++ .../src/core/FastCGI/Record/Params.php | 120 +++++ .../src/core/FastCGI/Record/Stderr.php | 29 ++ .../src/core/FastCGI/Record/Stdin.php | 29 ++ .../src/core/FastCGI/Record/Stdout.php | 29 ++ .../src/core/FastCGI/Record/UnknownType.php | 75 ++++ .../src/core/FastCGI/Request.php | 58 +++ .../src/core/FastCGI/Response.php | 45 ++ 32 files changed, 2278 insertions(+), 7 deletions(-) create mode 100644 output/swoole_library/src/core/Coroutine/FastCGI/Client.php create mode 100644 output/swoole_library/src/core/Coroutine/FastCGI/Client/Exception.php create mode 100644 output/swoole_library/src/core/Coroutine/FastCGI/Proxy.php create mode 100644 output/swoole_library/src/core/Coroutine/functions.php create mode 100644 output/swoole_library/src/core/FastCGI.php create mode 100644 output/swoole_library/src/core/FastCGI/FrameParser.php create mode 100644 output/swoole_library/src/core/FastCGI/HttpRequest.php create mode 100644 output/swoole_library/src/core/FastCGI/HttpResponse.php create mode 100644 output/swoole_library/src/core/FastCGI/Message.php create mode 100644 output/swoole_library/src/core/FastCGI/Record.php create mode 100644 output/swoole_library/src/core/FastCGI/Record/AbortRequest.php create mode 100644 output/swoole_library/src/core/FastCGI/Record/BeginRequest.php create mode 100644 output/swoole_library/src/core/FastCGI/Record/Data.php create mode 100644 output/swoole_library/src/core/FastCGI/Record/EndRequest.php create mode 100644 output/swoole_library/src/core/FastCGI/Record/GetValues.php create mode 100644 output/swoole_library/src/core/FastCGI/Record/GetValuesResult.php create mode 100644 output/swoole_library/src/core/FastCGI/Record/Params.php create mode 100644 output/swoole_library/src/core/FastCGI/Record/Stderr.php create mode 100644 output/swoole_library/src/core/FastCGI/Record/Stdin.php create mode 100644 output/swoole_library/src/core/FastCGI/Record/Stdout.php create mode 100644 output/swoole_library/src/core/FastCGI/Record/UnknownType.php create mode 100644 output/swoole_library/src/core/FastCGI/Request.php create mode 100644 output/swoole_library/src/core/FastCGI/Response.php diff --git a/output/swoole/constants.php b/output/swoole/constants.php index c2976133..ce404fbf 100644 --- a/output/swoole/constants.php +++ b/output/swoole/constants.php @@ -1,10 +1,10 @@ <?php -define('SWOOLE_VERSION', '4.4.16'); -define('SWOOLE_VERSION_ID', 40416); +define('SWOOLE_VERSION', '4.4.17'); +define('SWOOLE_VERSION_ID', 40417); define('SWOOLE_MAJOR_VERSION', 4); define('SWOOLE_MINOR_VERSION', 4); -define('SWOOLE_RELEASE_VERSION', 16); +define('SWOOLE_RELEASE_VERSION', 17); define('SWOOLE_EXTRA_VERSION', ''); define('SWOOLE_DEBUG', ''); define('SWOOLE_HAVE_COMPRESSION', '1'); diff --git a/output/swoole_library/src/alias.php b/output/swoole_library/src/alias.php index 0d504a97..b4f0fdd2 100644 --- a/output/swoole_library/src/alias.php +++ b/output/swoole_library/src/alias.php @@ -12,4 +12,5 @@ if (SWOOLE_USE_SHORTNAME) { class_alias(Swoole\Coroutine\WaitGroup::class, Co\WaitGroup::class, true); class_alias(Swoole\Coroutine\Server::class, Co\Server::class, true); + class_alias(Swoole\Coroutine\FastCGI\Client::class, Co\FastCGI\Client::class, true); } diff --git a/output/swoole_library/src/core/Constant.php b/output/swoole_library/src/core/Constant.php index b17ebab4..71132ad2 100644 --- a/output/swoole_library/src/core/Constant.php +++ b/output/swoole_library/src/core/Constant.php @@ -174,6 +174,8 @@ class Constant public const OPTION_STACK_SIZE = 'stack_size'; + public const OPTION_SOCKET_DNS_TIMEOUT = 'socket_dns_timeout'; + public const OPTION_SOCKET_CONNECT_TIMEOUT = 'socket_connect_timeout'; public const OPTION_SOCKET_TIMEOUT = 'socket_timeout'; @@ -246,6 +248,8 @@ class Constant public const OPTION_MAX_WAIT_TIME = 'max_wait_time'; + public const OPTION_MAX_QUEUED_BYTES = 'max_queued_bytes'; + public const OPTION_MAX_CORO_NUM = 'max_coro_num'; public const OPTION_SEND_TIMEOUT = 'send_timeout'; @@ -316,8 +320,12 @@ class Constant public const OPTION_STATIC_HANDLER_LOCATIONS = 'static_handler_locations'; + public const OPTION_INPUT_BUFFER_SIZE = 'input_buffer_size'; + public const OPTION_BUFFER_INPUT_SIZE = 'buffer_input_size'; + public const OPTION_OUTPUT_BUFFER_SIZE = 'output_buffer_size'; + public const OPTION_BUFFER_OUTPUT_SIZE = 'buffer_output_size'; public const OPTION_MESSAGE_QUEUE_KEY = 'message_queue_key'; @@ -366,5 +374,7 @@ class Constant public const OPTION_OPEN_SSL = 'open_ssl'; + public const OPTION_OPEN_FASTCGI_PROTOCOL = 'open_fastcgi_protocol'; + /* }}} OPTION */ } diff --git a/output/swoole_library/src/core/Coroutine/FastCGI/Client.php b/output/swoole_library/src/core/Coroutine/FastCGI/Client.php new file mode 100644 index 00000000..f019c1ec --- /dev/null +++ b/output/swoole_library/src/core/Coroutine/FastCGI/Client.php @@ -0,0 +1,178 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\Coroutine\FastCGI; + +use InvalidArgumentException; +use Swoole\Coroutine\FastCGI\Client\Exception; +use Swoole\Coroutine\Socket; +use Swoole\FastCGI\FrameParser; +use Swoole\FastCGI\HttpRequest; +use Swoole\FastCGI\HttpResponse; +use Swoole\FastCGI\Record\EndRequest; +use Swoole\FastCGI\Request; +use Swoole\FastCGI\Response; + +class Client +{ + /** @var int */ + protected $af; + + /** @var string */ + protected $host; + + /** @var int */ + protected $port; + + /** @var bool */ + protected $ssl; + + /** @var Socket */ + protected $socket; + + public function __construct(string $host, int $port = 0, bool $ssl = false) + { + if (stripos($host, 'unix:/') === 0) { + $this->af = AF_UNIX; + $host = '/' . ltrim(substr($host, strlen('unix:/')), '/'); + $port = 0; + } elseif (strpos($host, ':') !== false) { + $this->af = AF_INET6; + } else { + $this->af = AF_INET; + } + $this->host = $host; + $this->port = $port; + $this->ssl = $ssl; + } + + /** + * @throws Exception + * @return HttpResponse|Response + */ + public function execute(Request $request, float $timeout = -1): Response + { + if (!$this->socket) { + $socket = new Socket($this->af, SOCK_STREAM, IPPROTO_IP); + $socket->setProtocol([ + 'open_ssl' => $this->ssl, + 'open_fastcgi_protocol' => true, + ]); + if (!$socket->connect($this->host, $this->port, $timeout)) { + $this->ioException(); + } + $this->socket = $socket; + } else { + $socket = $this->socket; + } + $sendData = (string) $request; + if ($socket->sendAll($sendData) !== strlen($sendData)) { + $this->ioException(); + } + $records = []; + while (true) { + if (SWOOLE_VERSION_ID < 40500) { + $recvData = ''; + while (true) { + $tmp = $socket->recv(8192, $timeout); + if (!$tmp) { + if ($tmp === '') { + $this->ioException(SOCKET_ECONNRESET); + } + $this->ioException(); + } + $recvData .= $tmp; + if (FrameParser::hasFrame($recvData)) { + break; + } + } + } else { + $recvData = $socket->recvPacket($timeout); + if (!$recvData) { + if ($recvData === '') { + $this->ioException(SOCKET_ECONNRESET); + } + $this->ioException(); + } + if (!FrameParser::hasFrame($recvData)) { + $this->ioException(SOCKET_EPROTO); + } + } + do { + $records[] = $record = FrameParser::parseFrame($recvData); + } while (strlen($recvData) !== 0); + if ($record instanceof EndRequest) { + if (!$request->getKeepConn()) { + $this->socket->close(); + $this->socket = null; + } + switch (true) { + case $request instanceof HttpRequest: + return new HttpResponse($records); + default: + return new Response($records); + } + } + } + /* never here */ + exit(1); + } + + public static function parseUrl(string $url): array + { + $url = parse_url($url); + $host = $url['host'] ?? ''; + $port = $url['port'] ?? 0; + if (empty($host)) { + $host = $url['path'] ?? ''; + if (empty($host)) { + throw new InvalidArgumentException('Invalid url'); + } + $host = "unix:/{$host}"; + } + return [$host, $port]; + } + + public static function call(string $url, string $path, $data = '', float $timeout = -1): string + { + $client = new Client(...static::parseUrl($url)); + $pathInfo = parse_url($path); + $path = $pathInfo['path'] ?? ''; + $root = dirname($path); + $scriptName = '/' . basename($path); + $documentUri = $scriptName; + $query = $pathInfo['query'] ?? ''; + $requestUri = $query ? "{$documentUri}?{$query}" : $documentUri; + $request = new HttpRequest(); + $request->withDocumentRoot($root) + ->withScriptFilename($path) + ->withScriptName($documentUri) + ->withDocumentUri($documentUri) + ->withRequestUri($requestUri) + ->withQueryString($query) + ->withBody($data) + ->withMethod($request->getContentLength() === 0 ? 'GET' : 'POST'); + $response = $client->execute($request, $timeout); + return $response->getBody(); + } + + protected function ioException(?int $errno = null): void + { + $socket = $this->socket; + if ($errno !== null) { + $socket->errCode = $errno; + $socket->errMsg = swoole_strerror($errno); + } + $socket->close(); + $this->socket = null; + throw new Exception($socket->errMsg, $socket->errCode); + } +} diff --git a/output/swoole_library/src/core/Coroutine/FastCGI/Client/Exception.php b/output/swoole_library/src/core/Coroutine/FastCGI/Client/Exception.php new file mode 100644 index 00000000..5ab5f413 --- /dev/null +++ b/output/swoole_library/src/core/Coroutine/FastCGI/Client/Exception.php @@ -0,0 +1,16 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\Coroutine\FastCGI\Client; + +class Exception extends \Swoole\Exception +{ +} diff --git a/output/swoole_library/src/core/Coroutine/FastCGI/Proxy.php b/output/swoole_library/src/core/Coroutine/FastCGI/Proxy.php new file mode 100644 index 00000000..42c4b289 --- /dev/null +++ b/output/swoole_library/src/core/Coroutine/FastCGI/Proxy.php @@ -0,0 +1,205 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\Coroutine\FastCGI; + +use InvalidArgumentException; +use Swoole\FastCGI\HttpRequest; +use Swoole\FastCGI\HttpResponse; +use Swoole\Http; + +class Proxy +{ + /* @var string */ + protected $host; + + /* @var int */ + protected $port; + + /* @var float */ + protected $timeout = -1; + + /* @var string */ + protected $documentRoot; + + /* @var bool */ + protected $https = false; + + /* @var string */ + protected $index = 'index.php'; + + /* @var array */ + protected $params = []; + + /* @var null|callable */ + protected $staticFileFilter; + + public function __construct(string $url, string $documentRoot = '/') + { + [$this->host, $this->port] = Client::parseUrl($url); + $this->documentRoot = $documentRoot; + $this->staticFileFilter = [$this, 'staticFileFiltrate']; + } + + public function withTimeout(float $timeout): self + { + $this->timeout = $timeout; + return $this; + } + + public function withHttps(bool $https): self + { + $this->https = $https; + return $this; + } + + public function withIndex(string $index): self + { + $this->index = $index; + return $this; + } + + public function getParam(string $name): ?string + { + return $this->params[$name] ?? null; + } + + public function withParam(string $name, string $value): self + { + $this->params[$name] = $value; + return $this; + } + + public function withoutParam(string $name): self + { + unset($this->params[$name]); + return $this; + } + + public function getParams(): array + { + return $this->params; + } + + public function withParams(array $params): self + { + $this->params = $params; + return $this; + } + + public function withAddedParams(array $params): self + { + $this->params = $params + $this->params; + return $this; + } + + public function withStaticFileFilter(?callable $filter): self + { + $this->staticFileFilter = $filter; + return $this; + } + + public function translateRequest($userRequest): HttpRequest + { + $request = new HttpRequest(); + if ($userRequest instanceof \Swoole\Http\Request) { + $server = $userRequest->server; + $headers = $userRequest->header; + $pathInfo = $userRequest->server['path_info']; + $pathInfo = '/' . (ltrim($pathInfo, '/')); + if (strlen($this->index) !== 0) { + $extension = pathinfo($pathInfo, PATHINFO_EXTENSION); + if (empty($extension)) { + $pathInfo = rtrim($pathInfo, '/') . '/' . $this->index; + } + } + $requestUri = $scriptName = $documentUri = $server['request_uri']; + $queryString = $server['query_string'] ?? ''; + if (strlen($queryString) !== 0) { + $requestUri .= "?{$server['query_string']}"; + } + $request + ->withDocumentRoot($this->documentRoot) + ->withScriptFilename($this->documentRoot . $pathInfo) + ->withScriptName($scriptName) + ->withDocumentUri($documentUri) + ->withServerProtocol($server['server_protocol']) + ->withServerAddr('127.0.0.1') + ->withServerPort($server['server_port']) + ->withRemoteAddr($server['remote_addr']) + ->withRemotePort($server['remote_port']) + ->withMethod($server['request_method']) + ->withRequestUri($requestUri) + ->withQueryString($queryString) + ->withContentType($headers['content-type'] ?? '') + ->withContentLength((int) ($headers['content-length'] ?? 0)) + ->withHeaders($headers) + ->withBody($userRequest->rawContent()) + ->withAddedParams($this->params); + if ($this->https) { + $request->withParam('HTTPS', '1'); + } + } else { + throw new InvalidArgumentException('Not supported on ' . get_class($userRequest)); + } + return $request; + } + + public function translateResponse(HttpResponse $response, $userResponse): void + { + if ($userResponse instanceof \Swoole\Http\Response) { + $userResponse->status($response->getStatusCode(), $response->getReasonPhrase()); + $userResponse->header = $response->getHeaders(); + $userResponse->cookie = $response->getSetCookieHeaderLines(); + $userResponse->end($response->getBody()); + } else { + throw new InvalidArgumentException('Not supported on ' . get_class($userResponse)); + } + } + + public function pass($userRequest, $userResponse): void + { + if (!($userRequest instanceof HttpRequest)) { + $request = $this->translateRequest($userRequest); + } else { + $request = $userRequest; + } + unset($userRequest); + if ($this->staticFileFilter) { + $filter = $this->staticFileFilter; + if ($filter($request, $userResponse)) { + return; + } + } + $client = new Client($this->host, $this->port); + $response = $client->execute($request, $this->timeout); + $this->translateResponse($response, $userResponse); + } + + /* @return bool ['hit' => true, 'miss' => false] */ + public function staticFileFiltrate(HttpRequest $request, $userResponse): bool + { + if ($userResponse instanceof \Swoole\Http\Response) { + $extension = pathinfo($request->getScriptFilename(), PATHINFO_EXTENSION); + if ($extension !== 'php') { + $realPath = realpath($request->getScriptFilename()); + if (!$realPath || strpos($realPath, $this->documentRoot) !== 0 || !is_file($realPath)) { + $userResponse->status(Http\Status::NOT_FOUND); + } else { + $userResponse->sendfile($realPath); + } + return true; + } + return false; + } + throw new InvalidArgumentException('Not supported on ' . get_class($userResponse)); + } +} diff --git a/output/swoole_library/src/core/Coroutine/WaitGroup.php b/output/swoole_library/src/core/Coroutine/WaitGroup.php index ff9be1a2..dc2bfe10 100644 --- a/output/swoole_library/src/core/Coroutine/WaitGroup.php +++ b/output/swoole_library/src/core/Coroutine/WaitGroup.php @@ -34,7 +34,7 @@ public function add(int $delta = 1): void } $count = $this->count + $delta; if ($count < 0) { - throw new InvalidArgumentException('negative WaitGroup counter'); + throw new InvalidArgumentException('WaitGroup misuse: negative counter'); } $this->count = $count; } @@ -43,7 +43,7 @@ public function done(): void { $count = $this->count - 1; if ($count < 0) { - throw new BadMethodCallException('negative WaitGroup counter'); + throw new BadMethodCallException('WaitGroup misuse: negative counter'); } $this->count = $count; if ($count === 0 && $this->waiting) { @@ -53,6 +53,9 @@ public function done(): void public function wait(float $timeout = -1): bool { + if ($this->waiting) { + throw new BadMethodCallException('WaitGroup misuse: reused before previous wait has returned'); + } if ($this->count > 0) { $this->waiting = true; $done = $this->chan->pop($timeout); diff --git a/output/swoole_library/src/core/Coroutine/functions.php b/output/swoole_library/src/core/Coroutine/functions.php new file mode 100644 index 00000000..40dcae38 --- /dev/null +++ b/output/swoole_library/src/core/Coroutine/functions.php @@ -0,0 +1,29 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\Coroutine; + +use Swoole\Coroutine; + +function batch(array $tasks, float $timeout = -1): array +{ + $wg = new WaitGroup(); + $wg->add(count($tasks)); + foreach ($tasks as $id => $task) { + Coroutine::create(function () use ($wg, &$tasks, $id, $task) { + $tasks[$id] = null; + $tasks[$id] = $task(); + $wg->done(); + }); + } + $wg->wait($timeout); + return $tasks; +} diff --git a/output/swoole_library/src/core/Curl/Handler.php b/output/swoole_library/src/core/Curl/Handler.php index 27103a65..ef7ee54f 100644 --- a/output/swoole_library/src/core/Curl/Handler.php +++ b/output/swoole_library/src/core/Curl/Handler.php @@ -444,6 +444,9 @@ private function setOption(int $opt, $value): bool $header = explode(':', $header, 2); $headerName = $header[0]; $headerValue = trim($header[1] ?? ''); + if (strlen($headerValue) === 0) { + continue; + } $this->headers[$headerName] = $headerValue; } break; @@ -509,6 +512,12 @@ private function setOption(int $opt, $value): bool case CURLOPT_PROGRESSFUNCTION: $this->progressFunction = $value; break; + case CURLOPT_HTTPAUTH: + if (!($value & CURLAUTH_BASIC)) { + trigger_error("swoole_curl_setopt(): CURLOPT_HTTPAUTH[{$value}] is not supported", E_USER_WARNING); + return false; + } + break; case CURLOPT_USERPWD: $this->headers['Authorization'] = 'Basic ' . base64_encode($value); break; @@ -647,7 +656,7 @@ private function execute() /* * Http Headers */ - $this->headers['Host'] = $this->urlInfo['host'] . (isset($this->urlInfo['port']) ? (':' . $this->urlInfo['port']) : ''); /* TODO: remove it (built-in support) */ + $this->headers['Host'] = $this->urlInfo['host']; // remove empty headers (keep same with raw cURL) foreach ($this->headers as $headerName => $headerValue) { if ($headerValue === '') { diff --git a/output/swoole_library/src/core/Database/MysqliProxy.php b/output/swoole_library/src/core/Database/MysqliProxy.php index a04bc156..78fbecc7 100644 --- a/output/swoole_library/src/core/Database/MysqliProxy.php +++ b/output/swoole_library/src/core/Database/MysqliProxy.php @@ -49,8 +49,13 @@ public function __construct(callable $constructor) public function __call(string $name, array $arguments) { for ($n = 3; $n--;) { + $this->__object->errno = 0; $ret = @$this->__object->{$name}(...$arguments); if ($ret === false) { + /* no error */ + if ($this->__object->errno === 0) { + break; + } /* no more chances or non-IO failures */ if ( !in_array($this->__object->errno, static::IO_ERRORS, true) || diff --git a/output/swoole_library/src/core/Database/MysqliStatementProxy.php b/output/swoole_library/src/core/Database/MysqliStatementProxy.php index 4a07d8cb..44425067 100644 --- a/output/swoole_library/src/core/Database/MysqliStatementProxy.php +++ b/output/swoole_library/src/core/Database/MysqliStatementProxy.php @@ -49,8 +49,13 @@ public function __construct(mysqli_stmt $object, ?string $queryString, MysqliPro public function __call(string $name, array $arguments) { for ($n = 3; $n--;) { + $this->__object->errno = 0; $ret = @$this->__object->{$name}(...$arguments); if ($ret === false) { + /* no error */ + if ($this->__object->errno === 0) { + break; + } /* no more chances or non-IO failures or in transaction */ if ( !in_array($this->__object->errno, $this->parent::IO_ERRORS, true) || diff --git a/output/swoole_library/src/core/Database/PDOProxy.php b/output/swoole_library/src/core/Database/PDOProxy.php index 215ce531..d25a79df 100644 --- a/output/swoole_library/src/core/Database/PDOProxy.php +++ b/output/swoole_library/src/core/Database/PDOProxy.php @@ -17,6 +17,7 @@ class PDOProxy extends ObjectProxy { + public const IO_METHOD_REGEX = '/^query|prepare|exec|beginTransaction|commit|rollback$/i'; public const IO_ERRORS = [ 2002, // MYSQLND_CR_CONNECTION_ERROR 2006, // MYSQLND_CR_SERVER_GONE_ERROR @@ -47,8 +48,12 @@ public function __call(string $name, array $arguments) for ($n = 3; $n--;) { $ret = @$this->__object->{$name}(...$arguments); if ($ret === false) { - /* no more chances or non-IO failures */ + /* non-IO method */ + if (!preg_match(static::IO_METHOD_REGEX, $name)) { + break; + } $errorInfo = $this->__object->errorInfo(); + /* no more chances or non-IO failures */ if ( !in_array($errorInfo[1], static::IO_ERRORS, true) || $n === 0 || diff --git a/output/swoole_library/src/core/Database/PDOStatementProxy.php b/output/swoole_library/src/core/Database/PDOStatementProxy.php index 25f8a252..60e12723 100644 --- a/output/swoole_library/src/core/Database/PDOStatementProxy.php +++ b/output/swoole_library/src/core/Database/PDOStatementProxy.php @@ -54,6 +54,10 @@ public function __call(string $name, array $arguments) for ($n = 3; $n--;) { $ret = @$this->__object->{$name}(...$arguments); if ($ret === false) { + /* no IO */ + if (strtolower($name) !== 'execute') { + break; + } /* no more chances or non-IO failures or in transaction */ if ( !in_array($this->__object->errorInfo()[1], $this->parent::IO_ERRORS, true) || diff --git a/output/swoole_library/src/core/FastCGI.php b/output/swoole_library/src/core/FastCGI.php new file mode 100644 index 00000000..7ba73a0c --- /dev/null +++ b/output/swoole_library/src/core/FastCGI.php @@ -0,0 +1,94 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole; + +/** + * FastCGI constants. + */ +class FastCGI +{ + /** + * Number of bytes in a FCGI_Header. Future versions of the protocol + * will not reduce this number. + */ + public const HEADER_LEN = 8; + + /** + * Format of FCGI_HEADER for unpacking in PHP + */ + public const HEADER_FORMAT = 'Cversion/Ctype/nrequestId/ncontentLength/CpaddingLength/Creserved'; + + /** + * Max content length of a record + */ + public const MAX_CONTENT_LENGTH = 65535; + + /** + * Value for version component of FCGI_Header + */ + public const VERSION_1 = 1; + + /** + * Values for type component of FCGI_Header + */ + public const BEGIN_REQUEST = 1; + + public const ABORT_REQUEST = 2; + + public const END_REQUEST = 3; + + public const PARAMS = 4; + + public const STDIN = 5; + + public const STDOUT = 6; + + public const STDERR = 7; + + public const DATA = 8; + + public const GET_VALUES = 9; + + public const GET_VALUES_RESULT = 10; + + public const UNKNOWN_TYPE = 11; + + /** + * Value for requestId component of FCGI_Header + */ + public const DEFAULT_REQUEST_ID = 1; + + /** + * Mask for flags component of FCGI_BeginRequestBody + */ + public const KEEP_CONN = 1; + + /** + * Values for role component of FCGI_BeginRequestBody + */ + public const RESPONDER = 1; + + public const AUTHORIZER = 2; + + public const FILTER = 3; + + /** + * Values for protocolStatus component of FCGI_EndRequestBody + */ + public const REQUEST_COMPLETE = 0; + + public const CANT_MPX_CONN = 1; + + public const OVERLOADED = 2; + + public const UNKNOWN_ROLE = 3; +} diff --git a/output/swoole_library/src/core/FastCGI/FrameParser.php b/output/swoole_library/src/core/FastCGI/FrameParser.php new file mode 100644 index 00000000..8b1ae95a --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/FrameParser.php @@ -0,0 +1,90 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI; + +use DomainException; +use RuntimeException; +use Swoole\FastCGI; + +/** + * Utility class to simplify parsing of FastCGI protocol data. + */ +class FrameParser +{ + /** + * Mapping of constants to the classes + * + * @var array + */ + protected static $classMapping = [ + FastCGI::BEGIN_REQUEST => FastCGI\Record\BeginRequest::class, + FastCGI::ABORT_REQUEST => FastCGI\Record\AbortRequest::class, + FastCGI::END_REQUEST => FastCGI\Record\EndRequest::class, + FastCGI::PARAMS => FastCGI\Record\Params::class, + FastCGI::STDIN => FastCGI\Record\Stdin::class, + FastCGI::STDOUT => FastCGI\Record\Stdout::class, + FastCGI::STDERR => FastCGI\Record\Stderr::class, + FastCGI::DATA => FastCGI\Record\Data::class, + FastCGI::GET_VALUES => FastCGI\Record\GetValues::class, + FastCGI::GET_VALUES_RESULT => FastCGI\Record\GetValuesResult::class, + FastCGI::UNKNOWN_TYPE => FastCGI\Record\UnknownType::class, + ]; + + /** + * Checks if the buffer contains a valid frame to parse + * + * @param string $buffer Binary buffer + */ + public static function hasFrame(string $buffer): bool + { + $bufferLength = strlen($buffer); + if ($bufferLength < FastCGI::HEADER_LEN) { + return false; + } + + $fastInfo = unpack(FastCGI::HEADER_FORMAT, $buffer); + if ($bufferLength < FastCGI::HEADER_LEN + $fastInfo['contentLength'] + $fastInfo['paddingLength']) { + return false; + } + + return true; + } + + /** + * Parses a frame from the binary buffer + * + * @param string $buffer Binary buffer + * + * @return Record One of the corresponding FastCGI record + */ + public static function parseFrame(string &$buffer): Record + { + $bufferLength = strlen($buffer); + if ($bufferLength < FastCGI::HEADER_LEN) { + throw new RuntimeException('Not enough data in the buffer to parse'); + } + $recordHeader = unpack(FastCGI::HEADER_FORMAT, $buffer); + $recordType = $recordHeader['type']; + if (!isset(self::$classMapping[$recordType])) { + throw new DomainException("Invalid FastCGI record type {$recordType} received"); + } + + /** @var Record $className */ + $className = self::$classMapping[$recordType]; + $record = $className::unpack($buffer); + + $offset = FastCGI::HEADER_LEN + $record->getContentLength() + $record->getPaddingLength(); + $buffer = substr($buffer, $offset); + + return $record; + } +} diff --git a/output/swoole_library/src/core/FastCGI/HttpRequest.php b/output/swoole_library/src/core/FastCGI/HttpRequest.php new file mode 100644 index 00000000..899ad0d1 --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/HttpRequest.php @@ -0,0 +1,424 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI; + +use InvalidArgumentException; + +class HttpRequest extends Request +{ + protected $params = [ + 'REQUEST_SCHEME' => 'http', + 'REQUEST_METHOD' => 'GET', + 'DOCUMENT_ROOT' => '', + 'SCRIPT_FILENAME' => '', + 'SCRIPT_NAME' => '', + 'DOCUMENT_URI' => '/', + 'REQUEST_URI' => '/', + 'QUERY_STRING' => '', + 'CONTENT_TYPE' => 'text/plain', + 'CONTENT_LENGTH' => '0', + 'GATEWAY_INTERFACE' => 'CGI/1.1', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'SERVER_SOFTWARE' => 'swoole/' . SWOOLE_VERSION, + 'REMOTE_ADDR' => 'unknown', + 'REMOTE_PORT' => '0', + 'SERVER_ADDR' => 'unknown', + 'SERVER_PORT' => '0', + 'SERVER_NAME' => 'Swoole', + 'REDIRECT_STATUS' => '200', + ]; + + public function getScheme(): ?string + { + return $this->params['REQUEST_SCHEME'] ?? null; + } + + public function withScheme(string $scheme): self + { + $this->params['REQUEST_SCHEME'] = $scheme; + return $this; + } + + public function withoutScheme(): void + { + unset($this->params['REQUEST_SCHEME']); + } + + public function getMethod(): ?string + { + return $this->params['REQUEST_METHOD'] ?? null; + } + + public function withMethod(string $method): self + { + $this->params['REQUEST_METHOD'] = $method; + return $this; + } + + public function withoutMethod(): void + { + unset($this->params['REQUEST_METHOD']); + } + + public function getDocumentRoot(): ?string + { + return $this->params['DOCUMENT_ROOT'] ?? null; + } + + public function withDocumentRoot(string $documentRoot): self + { + $this->params['DOCUMENT_ROOT'] = $documentRoot; + return $this; + } + + public function withoutDocumentRoot(): void + { + unset($this->params['DOCUMENT_ROOT']); + } + + public function getScriptFilename(): ?string + { + return $this->params['SCRIPT_FILENAME'] ?? null; + } + + public function withScriptFilename(string $scriptFilename): self + { + $this->params['SCRIPT_FILENAME'] = $scriptFilename; + return $this; + } + + public function withoutScriptFilename(): void + { + unset($this->params['SCRIPT_FILENAME']); + } + + public function getScriptName(): ?string + { + return $this->params['SCRIPT_NAME'] ?? null; + } + + public function withScriptName(string $scriptName): self + { + $this->params['SCRIPT_NAME'] = $scriptName; + return $this; + } + + public function withoutScriptName(): void + { + unset($this->params['SCRIPT_NAME']); + } + + public function withUri(string $uri): self + { + $info = parse_url($uri); + return $this->withRequestUri($uri) + ->withDocumentUri($info['path'] ?? '') + ->withQueryString($info['query'] ?? ''); + } + + public function getDocumentUri(): ?string + { + return $this->params['DOCUMENT_URI'] ?? null; + } + + public function withDocumentUri(string $documentUri): self + { + $this->params['DOCUMENT_URI'] = $documentUri; + return $this; + } + + public function withoutDocumentUri(): void + { + unset($this->params['DOCUMENT_URI']); + } + + public function getRequestUri(): ?string + { + return $this->params['REQUEST_URI'] ?? null; + } + + public function withRequestUri(string $requestUri): self + { + $this->params['REQUEST_URI'] = $requestUri; + return $this; + } + + public function withoutRequestUri(): void + { + unset($this->params['REQUEST_URI']); + } + + public function withQuery($query): self + { + if (is_array($query)) { + $query = http_build_query($query); + } + return $this->withQueryString($query); + } + + public function getQueryString(): ?string + { + return $this->params['QUERY_STRING'] ?? null; + } + + public function withQueryString(string $queryString): self + { + $this->params['QUERY_STRING'] = $queryString; + return $this; + } + + public function withoutQueryString(): void + { + unset($this->params['QUERY_STRING']); + } + + public function getContentType(): ?string + { + return $this->params['CONTENT_TYPE'] ?? null; + } + + public function withContentType(string $contentType): self + { + $this->params['CONTENT_TYPE'] = $contentType; + return $this; + } + + public function withoutContentType(): void + { + unset($this->params['CONTENT_TYPE']); + } + + public function getContentLength(): ?int + { + return isset($this->params['CONTENT_LENGTH']) ? (int) $this->params['CONTENT_LENGTH'] : null; + } + + public function withContentLength(int $contentLength): self + { + $this->params['CONTENT_LENGTH'] = (string) $contentLength; + return $this; + } + + public function withoutContentLength(): void + { + unset($this->params['CONTENT_LENGTH']); + } + + public function getGatewayInterface(): ?string + { + return $this->params['GATEWAY_INTERFACE'] ?? null; + } + + public function withGatewayInterface(string $gatewayInterface): self + { + $this->params['GATEWAY_INTERFACE'] = $gatewayInterface; + return $this; + } + + public function withoutGatewayInterface(): void + { + unset($this->params['GATEWAY_INTERFACE']); + } + + public function getServerProtocol(): ?string + { + return $this->params['SERVER_PROTOCOL'] ?? null; + } + + public function withServerProtocol(string $serverProtocol): self + { + $this->params['SERVER_PROTOCOL'] = $serverProtocol; + return $this; + } + + public function withoutServerProtocol(): void + { + unset($this->params['SERVER_PROTOCOL']); + } + + public function withProtocolVersion(string $protocolVersion): self + { + if (!is_numeric($protocolVersion)) { + throw new InvalidArgumentException('Protocol version must be numeric'); + } + $this->params['SERVER_PROTOCOL'] = "HTTP/{$protocolVersion}"; + return $this; + } + + public function getServerSoftware(): ?string + { + return $this->params['SERVER_SOFTWARE'] ?? null; + } + + public function withServerSoftware(string $serverSoftware): self + { + $this->params['SERVER_SOFTWARE'] = $serverSoftware; + return $this; + } + + public function withoutServerSoftware(): void + { + unset($this->params['SERVER_SOFTWARE']); + } + + public function getRemoteAddr(): ?string + { + return $this->params['REMOTE_ADDR'] ?? null; + } + + public function withRemoteAddr(string $remoteAddr): self + { + $this->params['REMOTE_ADDR'] = $remoteAddr; + return $this; + } + + public function withoutRemoteAddr(): void + { + unset($this->params['REMOTE_ADDR']); + } + + public function getRemotePort(): ?int + { + return isset($this->params['REMOTE_PORT']) ? (int) $this->params['REMOTE_PORT'] : null; + } + + public function withRemotePort(int $remotePort): self + { + $this->params['REMOTE_PORT'] = (string) $remotePort; + return $this; + } + + public function withoutRemotePort(): void + { + unset($this->params['REMOTE_PORT']); + } + + public function getServerAddr(): ?string + { + return $this->params['SERVER_ADDR'] ?? null; + } + + public function withServerAddr(string $serverAddr): self + { + $this->params['SERVER_ADDR'] = $serverAddr; + return $this; + } + + public function withoutServerAddr(): void + { + unset($this->params['SERVER_ADDR']); + } + + public function getServerPort(): ?int + { + return isset($this->params['SERVER_PORT']) ? (int) $this->params['SERVER_PORT'] : null; + } + + public function withServerPort(int $serverPort): self + { + $this->params['SERVER_PORT'] = (string) $serverPort; + return $this; + } + + public function withoutServerPort(): void + { + unset($this->params['SERVER_PORT']); + } + + public function getServerName(): ?string + { + return $this->params['SERVER_NAME'] ?? null; + } + + public function withServerName(string $serverName): self + { + $this->params['SERVER_NAME'] = $serverName; + return $this; + } + + public function withoutServerName(): void + { + unset($this->params['SERVER_NAME']); + } + + public function getRedirectStatus(): ?string + { + return $this->params['REDIRECT_STATUS'] ?? null; + } + + public function withRedirectStatus(string $redirectStatus): self + { + $this->params['REDIRECT_STATUS'] = $redirectStatus; + return $this; + } + + public function withoutRedirectStatus(): void + { + unset($this->params['REDIRECT_STATUS']); + } + + public function getHeader(string $name): ?string + { + return $this->params[static::convertHeaderNameToParamName($name)] ?? null; + } + + public function withHeader(string $name, string $value): self + { + $this->params[static::convertHeaderNameToParamName($name)] = $value; + return $this; + } + + public function withoutHeader(string $name): void + { + unset($this->params[static::convertHeaderNameToParamName($name)]); + } + + public function getHeaders(): array + { + $headers = []; + foreach ($this->params as $name => $value) { + if (strpos($name, 'HTTP_') === 0) { + $headers[static::convertParamNameToHeaderName($name)] = $value; + } + } + return $headers; + } + + public function withHeaders(array $headers): self + { + foreach ($headers as $name => $value) { + $this->withHeader($name, $value); + } + return $this; + } + + /** @return $this */ + public function withBody($body): Message + { + if (is_array($body)) { + $body = http_build_query($body); + $this->withContentType('application/x-www-form-urlencoded'); + } + parent::withBody($body); + return $this->withContentLength(strlen($body)); + } + + protected static function convertHeaderNameToParamName(string $name) + { + return 'HTTP_' . str_replace('-', '_', strtoupper($name)); + } + + protected static function convertParamNameToHeaderName(string $name) + { + return ucwords(str_replace('_', '-', substr($name, strlen('HTTP_'))), '-'); + } +} diff --git a/output/swoole_library/src/core/FastCGI/HttpResponse.php b/output/swoole_library/src/core/FastCGI/HttpResponse.php new file mode 100644 index 00000000..c3776bbe --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/HttpResponse.php @@ -0,0 +1,116 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI; + +use Swoole\Http\Status; + +class HttpResponse extends Response +{ + /** @var int */ + protected $statusCode; + + /** @var string */ + protected $reasonPhrase; + + /** @var array */ + protected $headers = []; + + /** @var array */ + protected $headersMap = []; + + /** @var array */ + protected $setCookieHeaderLines = []; + + public function __construct(array $records = []) + { + parent::__construct($records); + $body = (string) $this->getBody(); + if (strlen($body) === 0) { + return; + } + [$headers, $body] = @explode("\r\n\r\n", $body, 2); + $headers = explode("\r\n", $headers); + foreach ($headers as $header) { + [$name, $value] = @explode(': ', $header, 2); + if (strcasecmp($name, 'Status') === 0) { + [$statusCode, $reasonPhrase] = @explode(' ', $value, 2); + } elseif (strcasecmp($name, 'Set-Cookie') === 0) { + $this->withSetCookieHeaderLine($value); + } else { + $this->withHeader($name, $value); + } + } + $statusCode = (int) ($statusCode ?? Status::OK); + $reasonPhrase = (string) ($reasonPhrase ?? Status::getReasonPhrase($statusCode)); + $this->withStatusCode($statusCode)->withReasonPhrase($reasonPhrase); + $this->withBody($body); + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function withStatusCode(int $statusCode): self + { + $this->statusCode = $statusCode; + return $this; + } + + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } + + public function withReasonPhrase(string $reasonPhrase): self + { + $this->reasonPhrase = $reasonPhrase; + return $this; + } + + public function getHeader(string $name): ?string + { + $name = $this->headersMap[strtolower($name)] ?? null; + return $name ? $this->headers[$name] : null; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function withHeader(string $name, string $value): self + { + $this->headers[$name] = $value; + $this->headersMap[strtolower($name)] = $name; + return $this; + } + + public function withHeaders(array $headers): self + { + foreach ($headers as $name => $value) { + $this->withHeader($name, $value); + } + return $this; + } + + public function getSetCookieHeaderLines(): array + { + return $this->setCookieHeaderLines; + } + + public function withSetCookieHeaderLine(string $value): self + { + $this->setCookieHeaderLines[] = $value; + return $this; + } +} diff --git a/output/swoole_library/src/core/FastCGI/Message.php b/output/swoole_library/src/core/FastCGI/Message.php new file mode 100644 index 00000000..c9604c6b --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Message.php @@ -0,0 +1,80 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI; + +class Message +{ + /** @var array */ + protected $params = []; + + /** @var string */ + protected $body = ''; + + /** @var string */ + protected $error = ''; + + public function getParam(string $name): ?string + { + return $this->params[$name] ?? null; + } + + public function withParam(string $name, string $value): self + { + $this->params[$name] = $value; + return $this; + } + + public function withoutParam(string $name): self + { + unset($this->params[$name]); + return $this; + } + + public function getParams(): array + { + return $this->params; + } + + public function withParams(array $params): self + { + $this->params = $params; + return $this; + } + + public function withAddedParams(array $params): self + { + $this->params = $params + $this->params; + return $this; + } + + public function getBody(): string + { + return $this->body; + } + + public function withBody($body): self + { + $this->body = (string) $body; + return $this; + } + + public function getError(): string + { + return $this->error; + } + + public function withError(string $error): self + { + $this->error = $error; + return $this; + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record.php b/output/swoole_library/src/core/FastCGI/Record.php new file mode 100644 index 00000000..050f3a36 --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record.php @@ -0,0 +1,232 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI; + +use Swoole\FastCGI; + +/** + * FastCGI record. + */ +class Record +{ + /** + * Identifies the FastCGI protocol version. + * + * @var int + */ + protected $version = FastCGI::VERSION_1; + + /** + * Identifies the FastCGI record type, i.e. the general function that the record performs. + * + * @var int + */ + protected $type = FastCGI::UNKNOWN_TYPE; + + /** + * Identifies the FastCGI request to which the record belongs. + * + * @var int + */ + protected $requestId = FastCGI::DEFAULT_REQUEST_ID; + + /** + * Reserved byte for future proposes + * + * @var int + */ + protected $reserved = 0; + + /** + * The number of bytes in the contentData component of the record. + * + * @var int + */ + private $contentLength = 0; + + /** + * The number of bytes in the paddingData component of the record. + * + * @var int + */ + private $paddingLength = 0; + + /** + * Binary data, between 0 and 65535 bytes of data, interpreted according to the record type. + * + * @var string + */ + private $contentData = ''; + + /** + * Padding data, between 0 and 255 bytes of data, which are ignored. + * + * @var string + */ + private $paddingData = ''; + + /** + * Returns the binary message representation of record + */ + final public function __toString(): string + { + $headerPacket = pack( + 'CCnnCC', + $this->version, + $this->type, + $this->requestId, + $this->contentLength, + $this->paddingLength, + $this->reserved + ); + + $payloadPacket = $this->packPayload(); + $paddingPacket = pack("a{$this->paddingLength}", $this->paddingData); + + return $headerPacket . $payloadPacket . $paddingPacket; + } + + /** + * Unpacks the message from the binary data buffer + * + * @param string $data Binary buffer with raw data + * + * @return static + */ + final public static function unpack(string $data): self + { + $self = new static(); + [ + $self->version, + $self->type, + $self->requestId, + $self->contentLength, + $self->paddingLength, + $self->reserved + ] = array_values(unpack(FastCGI::HEADER_FORMAT, $data)); + + $payload = substr($data, FastCGI::HEADER_LEN); + self::unpackPayload($self, $payload); + if (get_called_class() !== __CLASS__ && $self->contentLength > 0) { + static::unpackPayload($self, $payload); + } + + return $self; + } + + /** + * Sets the content data and adjusts the length fields + * + * @return static + */ + public function setContentData(string $data): self + { + $this->contentLength = strlen($data); + if ($this->contentLength > FastCGI::MAX_CONTENT_LENGTH) { + $this->contentLength = FastCGI::MAX_CONTENT_LENGTH; + $this->contentData = substr($data, 0, FastCGI::MAX_CONTENT_LENGTH); + } else { + $this->contentData = $data; + } + $extraLength = $this->contentLength % 8; + $this->paddingLength = $extraLength ? (8 - $extraLength) : 0; + return $this; + } + + /** + * Returns the context data from the record + */ + public function getContentData(): string + { + return $this->contentData; + } + + /** + * Returns the version of record + */ + public function getVersion(): int + { + return $this->version; + } + + /** + * Returns record type + */ + public function getType(): int + { + return $this->type; + } + + /** + * Returns request ID + */ + public function getRequestId(): int + { + return $this->requestId; + } + + /** + * Sets request ID + * + * There should be only one unique ID for all active requests, + * use random number or preferably resetting auto-increment. + * + * @return static + */ + public function setRequestId(int $requestId): self + { + $this->requestId = $requestId; + return $this; + } + + /** + * Returns the size of content length + */ + final public function getContentLength(): int + { + return $this->contentLength; + } + + /** + * Returns the size of padding length + */ + final public function getPaddingLength(): int + { + return $this->paddingLength; + } + + /** + * Method to unpack the payload for the record. + * + * NB: Default implementation will be always called + * + * @param static $self Instance of current frame + * @param string $data Binary data + */ + protected static function unpackPayload($self, string $data): void + { + [ + $self->contentData, + $self->paddingData + ] = array_values( + unpack("a{$self->contentLength}contentData/a{$self->paddingLength}paddingData", $data) + ); + } + + /** + * Implementation of packing the payload + */ + protected function packPayload(): string + { + return pack("a{$this->contentLength}", $this->contentData); + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record/AbortRequest.php b/output/swoole_library/src/core/FastCGI/Record/AbortRequest.php new file mode 100644 index 00000000..7a04dbd6 --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record/AbortRequest.php @@ -0,0 +1,27 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI\Record; + +use Swoole\FastCGI; +use Swoole\FastCGI\Record; + +/** + * The Web server sends a FCGI_ABORT_REQUEST record to abort a request + */ +class AbortRequest extends Record +{ + public function __construct(int $requestId = 0) + { + $this->type = FastCGI::ABORT_REQUEST; + $this->setRequestId($requestId); + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record/BeginRequest.php b/output/swoole_library/src/core/FastCGI/Record/BeginRequest.php new file mode 100644 index 00000000..15e2d133 --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record/BeginRequest.php @@ -0,0 +1,113 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI\Record; + +use Swoole\FastCGI; +use Swoole\FastCGI\Record; + +/** + * The Web server sends a FCGI_BEGIN_REQUEST record to start a request. + */ +class BeginRequest extends Record +{ + /** + * The role component sets the role the Web server expects the application to play. + * The currently-defined roles are: + * FCGI_RESPONDER + * FCGI_AUTHORIZER + * FCGI_FILTER + * + * @var int + */ + protected $role = FastCGI::UNKNOWN_ROLE; + + /** + * The flags component contains a bit that controls connection shutdown. + * + * flags & FCGI_KEEP_CONN: + * If zero, the application closes the connection after responding to this request. + * If not zero, the application does not close the connection after responding to this request; + * the Web server retains responsibility for the connection. + * + * @var int + */ + protected $flags; + + /** + * Reserved data, 5 bytes maximum + * + * @var string + */ + protected $reserved1; + + public function __construct(int $role = FastCGI::UNKNOWN_ROLE, int $flags = 0, string $reserved = '') + { + $this->type = FastCGI::BEGIN_REQUEST; + $this->role = $role; + $this->flags = $flags; + $this->reserved1 = $reserved; + $this->setContentData($this->packPayload()); + } + + /** + * Returns the role + * + * The role component sets the role the Web server expects the application to play. + * The currently-defined roles are: + * FCGI_RESPONDER + * FCGI_AUTHORIZER + * FCGI_FILTER + */ + public function getRole(): int + { + return $this->role; + } + + /** + * Returns the flags + * + * The flags component contains a bit that controls connection shutdown. + * + * flags & FCGI_KEEP_CONN: + * If zero, the application closes the connection after responding to this request. + * If not zero, the application does not close the connection after responding to this request; + * the Web server retains responsibility for the connection. + */ + public function getFlags(): int + { + return $this->flags; + } + + /** + * {@inheritdoc} + * @param static $self + */ + protected static function unpackPayload($self, string $data): void + { + [ + $self->role, + $self->flags, + $self->reserved1 + ] = array_values(unpack('nrole/Cflags/a5reserved', $data)); + } + + /** {@inheritdoc} */ + protected function packPayload(): string + { + return pack( + 'nCa5', + $this->role, + $this->flags, + $this->reserved1 + ); + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record/Data.php b/output/swoole_library/src/core/FastCGI/Record/Data.php new file mode 100644 index 00000000..d7b7df3a --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record/Data.php @@ -0,0 +1,29 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI\Record; + +use Swoole\FastCGI; +use Swoole\FastCGI\Record; + +/** + * Data binary stream + * + * FCGI_DATA is a second stream record type used to send additional data to the application. + */ +class Data extends Record +{ + public function __construct(string $contentData = '') + { + $this->type = FastCGI::DATA; + $this->setContentData($contentData); + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record/EndRequest.php b/output/swoole_library/src/core/FastCGI/Record/EndRequest.php new file mode 100644 index 00000000..a8330240 --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record/EndRequest.php @@ -0,0 +1,117 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI\Record; + +use Swoole\FastCGI; +use Swoole\FastCGI\Record; + +/** + * The application sends a FCGI_END_REQUEST record to terminate a request, either because the application + * has processed the request or because the application has rejected the request. + */ +class EndRequest extends Record +{ + /** + * The appStatus component is an application-level status code. Each role documents its usage of appStatus. + * + * @var int + */ + protected $appStatus = 0; + + /** + * The protocolStatus component is a protocol-level status code. + * + * The possible protocolStatus values are: + * FCGI_REQUEST_COMPLETE: normal end of request. + * FCGI_CANT_MPX_CONN: rejecting a new request. + * This happens when a Web server sends concurrent requests over one connection to an application that is + * designed to process one request at a time per connection. + * FCGI_OVERLOADED: rejecting a new request. + * This happens when the application runs out of some resource, e.g. database connections. + * FCGI_UNKNOWN_ROLE: rejecting a new request. + * This happens when the Web server has specified a role that is unknown to the application. + * + * @var int + */ + protected $protocolStatus = FastCGI::REQUEST_COMPLETE; + + /** + * Reserved data, 3 bytes maximum + * + * @var string + */ + protected $reserved1; + + public function __construct( + int $protocolStatus = FastCGI::REQUEST_COMPLETE, + int $appStatus = 0, + string $reserved = '' + ) { + $this->type = FastCGI::END_REQUEST; + $this->protocolStatus = $protocolStatus; + $this->appStatus = $appStatus; + $this->reserved1 = $reserved; + $this->setContentData($this->packPayload()); + } + + /** + * Returns app status + * + * The appStatus component is an application-level status code. Each role documents its usage of appStatus. + */ + public function getAppStatus(): int + { + return $this->appStatus; + } + + /** + * Returns the protocol status + * + * The possible protocolStatus values are: + * FCGI_REQUEST_COMPLETE: normal end of request. + * FCGI_CANT_MPX_CONN: rejecting a new request. + * This happens when a Web server sends concurrent requests over one connection to an application that is + * designed to process one request at a time per connection. + * FCGI_OVERLOADED: rejecting a new request. + * This happens when the application runs out of some resource, e.g. database connections. + * FCGI_UNKNOWN_ROLE: rejecting a new request. + * This happens when the Web server has specified a role that is unknown to the application. + */ + public function getProtocolStatus(): int + { + return $this->protocolStatus; + } + + /** + * {@inheritdoc} + * @param static $self + */ + protected static function unpackPayload($self, string $data): void + { + [ + $self->appStatus, + $self->protocolStatus, + $self->reserved1 + ] = array_values(unpack('NappStatus/CprotocolStatus/a3reserved', $data)); + } + + /** {@inheritdoc} */ + protected function packPayload(): string + { + return pack( + 'NCa3', + $this->appStatus, + $this->protocolStatus, + $this->reserved1 + ); + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record/GetValues.php b/output/swoole_library/src/core/FastCGI/Record/GetValues.php new file mode 100644 index 00000000..e7c5965e --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record/GetValues.php @@ -0,0 +1,48 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI\Record; + +use Swoole\FastCGI; + +/** + * GetValues API + * + * The Web server can query specific variables within the application. + * The server will typically perform a query on application startup in order to to automate certain aspects of + * system configuration. + * + * The application responds by sending a record {FCGI_GET_VALUES_RESULT, 0, ...} with the values supplied. + * If the application doesn't understand a variable name that was included in the query, it omits that name from + * the response. + * + * FCGI_GET_VALUES is designed to allow an open-ended set of variables. + * + * The initial set provides information to help the server perform application and connection management: + * FCGI_MAX_CONNS: The maximum number of concurrent transport connections this application will accept, + * e.g. "1" or "10". + * FCGI_MAX_REQS: The maximum number of concurrent requests this application will accept, e.g. "1" or "50". + * FCGI_MPXS_CONNS: "0" if this application does not multiplex connections (i.e. handle concurrent requests + * over each connection), "1" otherwise. + */ +class GetValues extends Params +{ + /** + * Constructs a request + * + * @param array $keys List of keys to receive + */ + public function __construct(array $keys = []) + { + parent::__construct(array_fill_keys($keys, '')); + $this->type = FastCGI::GET_VALUES; + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record/GetValuesResult.php b/output/swoole_library/src/core/FastCGI/Record/GetValuesResult.php new file mode 100644 index 00000000..225d8c16 --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record/GetValuesResult.php @@ -0,0 +1,46 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI\Record; + +use Swoole\FastCGI; + +/** + * GetValues API + * + * The Web server can query specific variables within the application. + * The server will typically perform a query on application startup in order to to automate certain aspects of + * system configuration. + * + * The application responds by sending a record {FCGI_GET_VALUES_RESULT, 0, ...} with the values supplied. + * If the application doesn't understand a variable name that was included in the query, it omits that name from + * the response. + * + * FCGI_GET_VALUES is designed to allow an open-ended set of variables. + * + * The initial set provides information to help the server perform application and connection management: + * FCGI_MAX_CONNS: The maximum number of concurrent transport connections this application will accept, + * e.g. "1" or "10". + * FCGI_MAX_REQS: The maximum number of concurrent requests this application will accept, e.g. "1" or "50". + * FCGI_MPXS_CONNS: "0" if this application does not multiplex connections (i.e. handle concurrent requests + * over each connection), "1" otherwise. + */ +class GetValuesResult extends Params +{ + /** + * Constructs a param request + */ + public function __construct(array $values = []) + { + parent::__construct($values); + $this->type = FastCGI::GET_VALUES_RESULT; + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record/Params.php b/output/swoole_library/src/core/FastCGI/Record/Params.php new file mode 100644 index 00000000..f0c2b5cb --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record/Params.php @@ -0,0 +1,120 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI\Record; + +use Swoole\FastCGI; +use Swoole\FastCGI\Record; + +/** + * Params request record + */ +class Params extends Record +{ + /** + * List of params + * + * @var array + */ + protected $values = []; + + /** + * Constructs a param request + */ + public function __construct(array $values = []) + { + $this->type = FastCGI::PARAMS; + $this->values = $values; + $this->setContentData($this->packPayload()); + } + + /** + * Returns an associative list of parameters + */ + public function getValues(): array + { + return $this->values; + } + + /** + * {@inheritdoc} + * @param static $self + */ + protected static function unpackPayload($self, string $data): void + { + $currentOffset = 0; + do { + [$nameLengthHigh] = array_values(unpack('CnameLengthHigh', $data)); + $isLongName = ($nameLengthHigh >> 7 == 1); + $valueOffset = $isLongName ? 4 : 1; + + [$valueLengthHigh] = array_values(unpack('CvalueLengthHigh', substr($data, $valueOffset))); + $isLongValue = ($valueLengthHigh >> 7 == 1); + $dataOffset = $valueOffset + ($isLongValue ? 4 : 1); + + $formatParts = [ + $isLongName ? 'NnameLength' : 'CnameLength', + $isLongValue ? 'NvalueLength' : 'CvalueLength', + ]; + $format = join('/', $formatParts); + [$nameLength, $valueLength] = array_values(unpack($format, $data)); + + // Clear top bit for long record + $nameLength &= ($isLongName ? 0x7fffffff : 0x7f); + $valueLength &= ($isLongValue ? 0x7fffffff : 0x7f); + + [$nameData, $valueData] = array_values( + unpack( + "a{$nameLength}nameData/a{$valueLength}valueData", + substr($data, $dataOffset) + ) + ); + + $self->values[$nameData] = $valueData; + + $keyValueLength = $dataOffset + $nameLength + $valueLength; + $data = substr($data, $keyValueLength); + $currentOffset += $keyValueLength; + } while ($currentOffset < $self->getContentLength()); + } + + /** {@inheritdoc} */ + protected function packPayload(): string + { + $payload = ''; + foreach ($this->values as $nameData => $valueData) { + if ($valueData === null) { + continue; + } + $nameLength = strlen($nameData); + $valueLength = strlen((string) $valueData); + $isLongName = $nameLength > 127; + $isLongValue = $valueLength > 127; + $formatParts = [ + $isLongName ? 'N' : 'C', + $isLongValue ? 'N' : 'C', + "a{$nameLength}", + "a{$valueLength}", + ]; + $format = join('', $formatParts); + + $payload .= pack( + $format, + $isLongName ? ($nameLength | 0x80000000) : $nameLength, + $isLongValue ? ($valueLength | 0x80000000) : $valueLength, + $nameData, + $valueData + ); + } + + return $payload; + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record/Stderr.php b/output/swoole_library/src/core/FastCGI/Record/Stderr.php new file mode 100644 index 00000000..72ef40ed --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record/Stderr.php @@ -0,0 +1,29 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI\Record; + +use Swoole\FastCGI; +use Swoole\FastCGI\Record; + +/** + * Stderr binary stream + * + * FCGI_STDERR is a stream record for sending arbitrary data from the application to the Web server + */ +class Stderr extends Record +{ + public function __construct(string $contentData = '') + { + $this->type = FastCGI::STDERR; + $this->setContentData($contentData); + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record/Stdin.php b/output/swoole_library/src/core/FastCGI/Record/Stdin.php new file mode 100644 index 00000000..01b883ba --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record/Stdin.php @@ -0,0 +1,29 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI\Record; + +use Swoole\FastCGI; +use Swoole\FastCGI\Record; + +/** + * Stdin binary stream + * + * FCGI_STDIN is a stream record type used in sending arbitrary data from the Web server to the application + */ +class Stdin extends Record +{ + public function __construct(string $contentData = '') + { + $this->type = FastCGI::STDIN; + $this->setContentData($contentData); + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record/Stdout.php b/output/swoole_library/src/core/FastCGI/Record/Stdout.php new file mode 100644 index 00000000..41b2a7bc --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record/Stdout.php @@ -0,0 +1,29 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI\Record; + +use Swoole\FastCGI; +use Swoole\FastCGI\Record; + +/** + * Stdout binary stream + * + * FCGI_STDOUT is a stream record for sending arbitrary data from the application to the Web server + */ +class Stdout extends Record +{ + public function __construct(string $contentData = '') + { + $this->type = FastCGI::STDOUT; + $this->setContentData($contentData); + } +} diff --git a/output/swoole_library/src/core/FastCGI/Record/UnknownType.php b/output/swoole_library/src/core/FastCGI/Record/UnknownType.php new file mode 100644 index 00000000..ab502d62 --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Record/UnknownType.php @@ -0,0 +1,75 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI\Record; + +use Swoole\FastCGI; +use Swoole\FastCGI\Record; + +/** + * Record for unknown queries + * + * The set of management record types is likely to grow in future versions of this protocol. + * To provide for this evolution, the protocol includes the FCGI_UNKNOWN_TYPE management record. + * When an application receives a management record whose type T it does not understand, the application responds + * with {FCGI_UNKNOWN_TYPE, 0, {T}}. + */ +class UnknownType extends Record +{ + /** + * Type of the unrecognized management record. + * + * @var int + */ + protected $type1; + + /** + * Reserved data, 7 bytes maximum + * + * @var string + */ + protected $reserved1; + + public function __construct(int $type = 0, string $reserved = '') + { + $this->type = FastCGI::UNKNOWN_TYPE; + $this->type1 = $type; + $this->reserved1 = $reserved; + $this->setContentData($this->packPayload()); + } + + /** + * Returns the unrecognized type + */ + public function getUnrecognizedType(): int + { + return $this->type1; + } + + /** + * {@inheritdoc} + * @param static $self + */ + public static function unpackPayload($self, string $data): void + { + [$self->type1, $self->reserved1] = array_values(unpack('Ctype/a7reserved', $data)); + } + + /** {@inheritdoc} */ + protected function packPayload(): string + { + return pack( + 'Ca7', + $this->type1, + $this->reserved1 + ); + } +} diff --git a/output/swoole_library/src/core/FastCGI/Request.php b/output/swoole_library/src/core/FastCGI/Request.php new file mode 100644 index 00000000..ca50f118 --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Request.php @@ -0,0 +1,58 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI; + +use Swoole\FastCGI; +use Swoole\FastCGI\Record\BeginRequest; +use Swoole\FastCGI\Record\Params; +use Swoole\FastCGI\Record\Stdin; + +class Request extends Message +{ + protected $keepConn = false; + + public function __toString(): string + { + $body = $this->getBody(); + $beginRequestFrame = new BeginRequest(FastCGI::RESPONDER, ($this->keepConn ? FastCGI::KEEP_CONN : 0)); + $paramsFrame = new Params($this->getParams()); + $paramsEofFrame = new Params(); + if (empty($body)) { + $message = "{$beginRequestFrame}{$paramsFrame}{$paramsEofFrame}}"; + } else { + $stdinList = []; + while (true) { + $stdinList[] = $stdin = new Stdin($body); + $stdinLength = $stdin->getContentLength(); + if ($stdinLength === strlen($body)) { + break; + } + $body = substr($body, $stdinLength); + } + $stdinList[] = new Stdin(); + $stdin = implode($stdinList); + $message = "{$beginRequestFrame}{$paramsFrame}{$paramsEofFrame}{$stdin}}"; + } + return $message; + } + + public function getKeepConn(): bool + { + return $this->keepConn; + } + + public function withKeepConn(bool $keepConn): self + { + $this->keepConn = $keepConn; + return $this; + } +} diff --git a/output/swoole_library/src/core/FastCGI/Response.php b/output/swoole_library/src/core/FastCGI/Response.php new file mode 100644 index 00000000..349a5e1c --- /dev/null +++ b/output/swoole_library/src/core/FastCGI/Response.php @@ -0,0 +1,45 @@ +<?php +/** + * This file is part of Swoole. + * + * @link https://www.swoole.com + * @contact team@swoole.com + * @license https://github.com/swoole/library/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace Swoole\FastCGI; + +use Swoole\FastCGI\Record\EndRequest; +use Swoole\FastCGI\Record\Stderr; +use Swoole\FastCGI\Record\Stdout; + +class Response extends Message +{ + public function __construct(array $records = []) + { + if (!static::verify($records)) { + throw new InvalidArgumentException('Bad records'); + } + $body = ''; + $error = ''; + foreach ($records as $record) { + if ($record instanceof Stdout) { + if ($record->getContentLength() > 0) { + $body .= $record->getContentData(); + } + } elseif ($record instanceof Stderr) { + if ($record->getContentLength() > 0) { + $error .= $record->getContentData(); + } + } + } + $this->withBody($body)->withError($error); + } + + public static function verify(array $records): bool + { + return !empty($records) && $records[count($records) - 1] instanceof EndRequest; + } +}