FrankenPHP 现代化的 PHP 应用服务器
FrankenPHP 是一个现代化的 PHP 应用服务器,它基于 Caddy Web 服务器构建。
Github
https://github.com/dunglas/frankenphp
性能对比
Performance benchmark of PHP runtimes
https://dev.to/dimdev/performance-benchmark-of-php-runtimes-2lmc
概述
以下是 FrankenPHP 的一些核心特性:
• Early Hints(早期提示):FrankenPHP 原生支持 103 Early Hints 状态码,这可以显著减少网页的加载时间,因为它允许服务器在最终响应生成之前发送初步的 HTTP 响应,从而加快网页加载速度。
• Worker 模式:FrankenPHP 允许应用程序在启动后驻留在内存中,这样可以在几毫秒内处理传入的请求,提高响应速度。
• 实时功能:FrankenPHP 内置了 Mercure Hub,允许将事件实时推送到所有连接的设备,无需额外的 JS 库或 SDK。
• 自动 HTTPS、HTTP/2 和 HTTP/3 支持:FrankenPHP 提供了自动 HTTPS 证书生成、续订和撤销,以及对 HTTP/2 和 HTTP/3 的原生支持。
• 兼容性:FrankenPHP 可以与任何 PHP 应用程序一起使用,并且由于其与 worker 模式的集成,使得 Symfony 和 Laravel 项目能够实现更快的性能。
• 作为 Go 库使用:FrankenPHP 也可以作为一个独立的 Go 库,允许在任何使用net/http
的应用程序中嵌入 PHP。
• 简化部署:FrankenPHP 设计简单,只需一个服务和一个二进制文件,不需要 PHP-FPM,它使用为 Go Web 服务器特别定制的 SAPI。
• 性能提升:根据提供的数据,FrankenPHP 能够显著提高 PHP 应用程序的性能,例如,与 PHP-FPM 相比,它可以将某些应用程序的响应时间减少到原来的 1/3.5。
总之:
FrankenPHP 提供了 Docker 镜像和独立的二进制文件,支持主流的操作系统,并且可以轻松地与现有的 PHP 应用程序集成。
它是一个为现代 Web 开发需求设计的服务器解决方案,旨在简化配置和提高性能。
部署
# 安装 FrankenPHP
curl https://frankenphp.dev/install.sh | sh
mv frankenphp /usr/local/bin/
# 服务 public/ 目录
frankenphp php-server -r public/
# 运行命令行脚本
frankenphp php-cli script.php
# 服务 public/ 目录
docker run -v $PWD:/app/public \
-p 443:443/tcp -p 443:443/udp \
dunglas/frankenphp
# 运行命令行脚
docker run -v $PWD:/app \
dunglas/frankenphp php script.php
第三方镜像
https://github.com/shinsenter/php
docker run -p 80:80 -v $PWD:/var/www/html shinsenter/frankenphp:latest
Worker 模式
使用 FrankenPHP Workers
启动应用程序一次并将其保存在内存中。
FrankenPHP 将在几毫秒内处理传入的请求。
启动 Worker 脚本
Docker
将 FRANKENPHP_CONFIG
环境变量的值设置为 worker /path/to/your/worker/script.php
:
docker run \
-e FRANKENPHP_CONFIG="worker /app/path/to/your/worker/script.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
独立二进制
使用 php-server
命令的 --worker
选项, 执行命令使当前目录的内容使用 worker:
frankenphp php-server --worker /path/to/your/worker/script.php
Symfony Runtime
FrankenPHP 的 worker 模式由 Symfony Runtime 组件 支持。
要在 worker 中启动任何 Symfony 应用程序,请安装 PHP Runtime 的 FrankenPHP 软件包:
composer require runtime/frankenphp-symfony
通过定义 APP_RUNTIME
环境变量来启动你的应用服务器,以使用 FrankenPHP Symfony Runtime:
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
自定义应用程序
以下示例演示如何在不依赖第三方库的情况下创建自己的 worker 脚本:
<?php
// public/index.php
// 防止在客户端连接中断时 worker 线程脚本终止
ignore_user_abort(true);
// 启动应用
require __DIR__.'/vendor/autoload.php';
$myApp = new \App\Kernel();
$myApp->boot();
// 循环外的处理程序以获得更好的性能(减少工作量)
$handler = static function () use ($myApp) {
// 收到请求时调用
// 超全局变量 php://input
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
};
for ($nbRequests = 0, $running = true; isset($_SERVER['MAX_REQUESTS']) && ($nbRequests < ((int)$_SERVER['MAX_REQUESTS'])) && $running; ++$nbRequests) {
$running = \frankenphp_handle_request($handler);
// 发送 HTTP 响应后执行某些操作
$myApp->terminate();
// 调用垃圾回收器以减少在页面生成过程中触发垃圾回收器的几率
gc_collect_cycles();
}
// 结束清理
$myApp->shutdown();
然后,启动应用并使用 FRANKENPHP_CONFIG
环境变量来配置你的 worker:
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
默认情况下,每个 CPU 启动一个 worker。
您还可以配置要启动的 worker 数:
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php 42" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
在一定数量的请求后重新启动 Worker
由于 PHP 最初不是为长时间运行的进程而设计的,因此仍然有许多库和遗留代码会发生内存泄露。
在 worker 模式下使用此类代码的解决方法是在处理一定数量的请求后重新启动 worker 程序脚本:
前面的 worker 代码段允许通过设置名为 MAX_REQUESTS
的环境变量来配置要处理的最大请求数。
配置
只需五行配置:现在,您所需要的就是启动一个生产级别的 PHP 服务器(自动 HTTPS、HTTP/3、Brotli 压缩…),由 Caddy 提供支持。
{
# 启用 FrankenPHP
frankenphp
}
localhost {
# 启用压缩(可选)
encode zstd br gzip
# 执行当前目录中的PHP文件并提供资产
php_server
}
FrankenPHP,Caddy 以及 Mercure 和 Vulcain 模块可以使用 Caddy 支持的格式 进行配置。
早期提示
借助“早期提示”利用服务器思考时间来加快页面加载速度
https://developer.chrome.com/docs/web-platform/early-hints?hl=zh-cn
什么是早期提示?
随着时间的推移,网站变得更加复杂。因此,服务器需要执行一些繁琐的工作(例如访问数据库或 CDN 访问源服务器)来为请求的网页生成 HTML,这并不奇怪。遗憾的是,这种“服务器思考时间”会导致浏览器在开始呈现网页之前出现额外的延迟。事实上,在服务器准备响应期间,连接实际上处于空闲状态
Early Hints 是一个 HTTP 状态代码 (103 Early Hints
),用于在最终响应之前发送初步 HTTP 响应。这样一来,当服务器忙于生成主资源时,就可以向浏览器发送有关关键子资源(例如网页的样式表、关键 JavaScript)或网页可能会使用的来源的提示。
浏览器可以在等待主资源时使用这些提示来预热连接并请求子资源。换句话说,提前提示可帮助浏览器通过提前执行一些工作来利用此类“服务器思考时间”,从而加快网页加载速度。
实践
<?php
header('Link: </style.css>; rel=preload; as=style');
headers_send(103);
// 慢速算法和 SQL 查询
echo <<<'HTML'
<!DOCTYPE html>
<title>Hello FrankenPHP</title>
<link rel="stylesheet" href="style.css">
HTML;
早期提示普通模式和 worker 模式都支持
与 Laravel 集成
Docker
使用 FrankenPHP 为 Laravel Web 应用程序提供服务就像将项目挂载到官方 Docker 镜像的 /app
目录中一样简单。
从 Laravel 应用程序的主目录运行以下命令:
docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
尽情享受吧!
本地安装
或者,你可以从本地机器上使用 FrankenPHP 运行 Laravel 项目:
- 下载与您的系统相对应的二进制文件
- 将以下配置添加到 Laravel 项目根目录中名为
Caddyfile
的文件中:
{
frankenphp
}
# 服务器的域名
localhost {
# 将 webroot 设置为 public/ 目录
root * public/
# 启用压缩(可选)
encode zstd br gzip
# 执行当前目录中的 PHP 文件并提供资产
php_server
}
- 从 Laravel 项目的根目录启动 FrankenPHP:
frankenphp run
Laravel Octane
Octane 可以通过 Composer 包管理器安装:
composer require laravel/octane
安装 Octane 后,您可以执行 octane:install
Artisan 命令,该命令会将 Octane 的配置文件安装到您的应用程序中:
php artisan octane:install --server=frankenphp
Octane 服务可以通过 octane:frankenphp
Artisan 命令启动。
php artisan octane:frankenphp
octane:frankenphp
命令可以采用以下选项:
--host
: 服务器应绑定到的 IP 地址(默认值:127.0.0.1
)--port
: 服务器应可用的端口(默认值:8000
)--admin-port
: 管理服务器应可用的端口(默认值:2019
)--workers
: 应可用于处理请求的 worker 数(默认值:auto
)--max-requests
: 在 worker 重启之前要处理的请求数(默认值:500
)--caddyfile
: FrankenPHPCaddyfile
文件的路径--https
: 开启 HTTPS、HTTP/2 和 HTTP/3,自动生成和延长证书--http-redirect
: 启用 HTTP 到 HTTPS 重定向(仅在使用--https
时启用)--watch
: 修改应用程序时自动重新加载服务器--poll
: 在监视时使用文件系统轮询,以便通过网络监视文件--log-level
: 在指定日志级别或高于指定日志级别的日志消息
你可以了解更多关于 Laravel Octane 官方文档。
实时
FrankenPHP 带有一个内置的 Mercure Hub! Mercure 允许将事件实时推送到所有连接的设备:它们将立即收到 JavaScript 事件。
无需 JS 库或 SDK!
要启用 Mercure Hub,请按照 Mercure 网站 中的说明更新 Caddyfile
。
要从您的代码中推送 Mercure 更新,我们推荐 Symfony Mercure Component(不需要 Symfony 框架来使用)。
PHP 应用程序作为独立二进制文件
FrankenPHP 能够将 PHP 应用程序的源代码和资源文件嵌入到静态的、独立的二进制文件中。
由于这个特性,PHP 应用程序可以作为独立的二进制文件分发,包括应用程序本身、PHP 解释器和生产级 Web 服务器 Caddy。
了解有关此功能的更多信息 Kévin 在 SymfonyCon 上的演讲。
准备你的应用
在创建独立二进制文件之前,请确保应用已准备好进行打包。
例如,您可能希望:
- 给应用安装生产环境的依赖
- 导出 autoloader
- 如果可能,为应用启用生产模式
- 丢弃不需要的文件,例如
.git
或测试文件,以减小最终二进制文件的大小
例如,对于 Symfony 应用程序,您可以使用以下命令:
# 导出项目以避免 .git/ 等目录
mkdir $TMPDIR/my-prepared-app
git archive HEAD | tar -x -C $TMPDIR/my-prepared-app
cd $TMPDIR/my-prepared-app
# 设置适当的环境变量
echo APP_ENV=prod > .env.local
echo APP_DEBUG=0 >> .env.local
# 删除测试文件
rm -Rf tests/
# 安装依赖项
composer install --ignore-platform-reqs --no-dev -a
# 优化 .env
composer dump-env prod
创建 Linux 二进制文件
创建 Linux 二进制文件的最简单方法是使用我们提供的基于 Docker 的构建器。
在准备好的应用的存储库中创建一个名为
static-build.Dockerfile
的文件。FROM --platform=linux/amd64 dunglas/frankenphp:static-builder # 复制应用代码 WORKDIR /go/src/app/dist/app COPY . . # 构建静态二进制文件,只选择你需要的 PHP 扩展 WORKDIR /go/src/app/ RUN EMBED=dist/app/ \ PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \ ./build-static.sh
[!CAUTION]
某些 .dockerignore 文件(例如默认的 symfony-docker .dockerignore)会忽略 vendor
文件夹和环境文件。在构建之前,请务必调整或删除 .dockerignore 文件。构建:
docker build -t static-app -f static-build.Dockerfile .
提取二进制文件
docker cp $(docker create --name static-app-tmp static-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-app-tmp
生成的二进制文件是当前目录中名为 my-app
的文件。
为其他操作系统创建二进制文件
如果您不想使用 Docker,或者想要构建 macOS 二进制文件,你可以使用我们提供的 shell 脚本:
git clone https://github.com/dunglas/frankenphp
cd frankenphp
EMBED=/path/to/your/app \
PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \
./build-static.sh
在 dist/
目录中生成的二进制文件名称为 frankenphp-<os>-<arch>
。
使用二进制文件
就是这样!my-app
文件(或其他操作系统上的 dist/frankenphp-<os>-<arch>
)包含您的独立应用程序!
若要启动 Web 应用,请执行:
./my-app php-server
如果您的应用包含 worker 脚本,请使用如下命令启动 worker:
./my-app php-server --worker public/index.php
要启用 HTTPS(自动创建 Let's Encrypt 证书)、HTTP/2 和 HTTP/3,请指定要使用的域名:
./my-app php-server --domain localhost
您还可以运行二进制文件中嵌入的 PHP CLI 脚本:
./my-app php-cli bin/console
自定义构建
https://frankenphp.dev/docs/static/
php的构建依赖
static-php-cli是一个用于静态编译、构建 PHP 解释器的工具,支持众多流行扩展。
目前 static-php-cli 支持 cli、fpm、embed 和 micro SAPI。
static-php-cli也支持将 PHP 代码和 PHP 运行时打包为一个文件并运行。
https://github.com/crazywhalecc/static-php-cli/blob/main/README-zh.md
基本构建流程
docker buildx bake --load static-builder
docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder
demo1
docker buildx bake --load --set static-builder.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder
docker buildx bake \
--load \
--set static-builder.args.PHP_EXTENSIONS=gd \
--set static-builder.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \
static-builder
The following environment variables can be passed to docker build
and to the build-static.sh
script to customize the static build:
FRANKENPHP_VERSION
: the version of FrankenPHP to usePHP_VERSION
: the version of PHP to usePHP_EXTENSIONS
: the PHP extensions to build (list of supported extensions)PHP_EXTENSION_LIBS
: extra libraries to build that add features to the extensionsEMBED
: path of the PHP application to embed in the binaryCLEAN
: when set, libphp and all its dependencies are built from scratch (no cache)NO_COMPRESS
: don’t compress the resulting binary using UPXDEBUG_SYMBOLS
: when set, debug-symbols will not be stripped and will be added within the binaryMIMALLOC
: (experimental, Linux-only) replace musl’s mallocng by mimalloc for improved performanceRELEASE
: (maintainers only) when set, the resulting binary will be uploaded on GitHub
分发二进制文件
创建的二进制文件不会被压缩。
若要在发送文件之前减小文件的大小,可以对其进行压缩。
我们推荐使用 xz
。