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;
+    }
+}