Skip to content

Commit a468648

Browse files
dunglaswilliamdesfrancislavoieKennedyTedescoshalvah
authored
[2.x] Add support for FrankenPHP (#764)
* Add support for FrankenPHP * make linters happy * Update src/Commands/StatusCommand.php Co-authored-by: William Desportes <[email protected]> * use a static closure in the worker script Co-authored-by: Francis Lavoie <[email protected]> * tabs Co-authored-by: Francis Lavoie <[email protected]> * tabs Co-authored-by: Francis Lavoie <[email protected]> * tabs Co-authored-by: Francis Lavoie <[email protected]> * tabs Co-authored-by: Francis Lavoie <[email protected]> * use match * fix vuln and simplify config * cs * @francislavoie review * Update StartFrankenPhpCommand.php Co-authored-by: Kennedy Tedesco <[email protected]> * Update frankenphp-worker.php Co-authored-by: Kennedy Tedesco <[email protected]> * Update InstallCommand.php Co-authored-by: Kennedy Tedesco <[email protected]> * docs: update README and composer.json * cs * phpstan * simplify * cs * better Caddyfile * Update octane.php Co-authored-by: Shalvah <[email protected]> * fix error handling * disable debug mode as it's slow * Fix swoole and octane write server running * fix: ignore user abort * feat: download the stable version * follow symlinks * improve perf * fix non-tty mode * formatting * formatting * formatting * formatting * formatting * formatting * performance * fix log level support * more robust log level handling in Mercure block * fix logs * better logging handling * cs * cs * missing changes in Caddyfile * Updates changelog * Prettify output * cs * Displays regular error messages * Ensures binary is up-to-date * Ensures binary is up-to-date * Removes mercure * Removes unused warning * Publishes only a simple require * Style * Cleans up code * Uses `--caddyfile` * Fixes catch * formatting * Fixes log output * Fix bug * Removes non-needed comments * Prefixes caddy environment variables * Reports worker exceptions * Displays anything different from 200 * Shutdowns on server exception boot * Fixes 500 responses from worker * Don't publish caddyfile * Fixes double realpath call * Fixes method name * Dont ignore caddy * Fix logs in non local * warn * dont report to master * Fixes method "put" not getting caught * Uses mercure `demo` when requested * Don't display mercure stuff * Adds link to readme: * Update README.md * Update InstallsFrankenPhpDependencies.php * Update InstallCommand.php * Update StartCommand.php * Update StartFrankenPhpCommand.php --------- Co-authored-by: William Desportes <[email protected]> Co-authored-by: Francis Lavoie <[email protected]> Co-authored-by: Kennedy Tedesco <[email protected]> Co-authored-by: Shalvah <[email protected]> Co-authored-by: Nuno Maduro <[email protected]> Co-authored-by: Taylor Otwell <[email protected]>
1 parent 1014bc6 commit a468648

29 files changed

+1001
-34
lines changed

.editorconfig

+3
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ trim_trailing_whitespace = false
1313

1414
[*.{yml,yaml}]
1515
indent_size = 2
16+
17+
[Caddyfile]
18+
indent_style = tab

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
- Fix passing invalid connection session id to `Swoole\Http\Response::create()` by [@smortexa](https://github.com/smortexa) in https://github.com/laravel/octane/pull/737
2121
- Fix missing mode config by [@sy-records](https://github.com/sy-records) in https://github.com/laravel/octane/pull/740
22-
- Add raw type in handleStream method for custom json in stdout by [@mphamid](https://github.com/mphamid) in https://github.com/laravel/octane/pull/742
22+
- Add `raw` type in handleStream method for custom json in stdout by [@mphamid](https://github.com/mphamid) in https://github.com/laravel/octane/pull/742
2323

2424
## [v2.0.5](https://github.com/laravel/octane/compare/v2.0.4...v2.0.5) - 2023-08-08
2525

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
## Introduction
1111

12-
Laravel Octane supercharges your application's performance by serving your application using high-powered application servers, including [Open Swoole](https://openswoole.com), [Swoole](https://github.com/swoole/swoole-src), and [RoadRunner](https://roadrunner.dev). Octane boots your application once, keeps it in memory, and then feeds it requests at supersonic speeds.
12+
Laravel Octane supercharges your application's performance by serving your application using high-powered application servers, including [FrankenPHP](https://frankenphp.dev), [Open Swoole](https://openswoole.com), [Swoole](https://github.com/swoole/swoole-src), and [RoadRunner](https://roadrunner.dev). Octane boots your application once, keeps it in memory, and then feeds it requests at supersonic speeds.
1313

1414
## Official Documentation
1515

bin/bootstrap.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Laravel\Octane\Exceptions\DdException;
4+
use Laravel\Octane\Octane;
45

56
ini_set('display_errors', 'stderr');
67

@@ -20,7 +21,7 @@
2021
$basePath = $_SERVER['APP_BASE_PATH'] ?? $_ENV['APP_BASE_PATH'] ?? $serverState['octaneConfig']['base_path'] ?? null;
2122

2223
if (! is_string($basePath)) {
23-
fwrite(STDERR, 'Cannot find application base path.'.PHP_EOL);
24+
Octane::writeError('Cannot find application base path.');
2425

2526
exit(11);
2627
}
@@ -45,7 +46,7 @@ function dd(...$vars)
4546
*/
4647

4748
if (! is_file($autoload_file = $basePath.'/vendor/autoload.php')) {
48-
fwrite(STDERR, "Composer autoload file was not found. Did you install the project's dependencies?".PHP_EOL);
49+
Octane::writeError("Composer autoload file was not found. Did you install the project's dependencies?");
4950

5051
exit(10);
5152
}

bin/frankenphp-worker.php

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
use Laravel\Octane\ApplicationFactory;
4+
use Laravel\Octane\FrankenPhp\FrankenPhpClient;
5+
use Laravel\Octane\RequestContext;
6+
use Laravel\Octane\Stream;
7+
use Laravel\Octane\Worker;
8+
use Symfony\Component\HttpFoundation\Response;
9+
use Throwable;
10+
11+
if ((! ($_SERVER['FRANKENPHP_WORKER'] ?? false)) || ! function_exists('frankenphp_handle_request')) {
12+
echo 'FrankenPHP must be in worker mode to use this script.';
13+
14+
exit(1);
15+
}
16+
17+
ignore_user_abort(true);
18+
19+
$basePath = require __DIR__.'/bootstrap.php';
20+
21+
/*
22+
|--------------------------------------------------------------------------
23+
| Start The Octane Worker
24+
|--------------------------------------------------------------------------
25+
|
26+
| Next we will start the Octane worker, which is a long running process to
27+
| handle incoming requests to the application. This worker will be used
28+
| by FrankenPHP to serve an entire Laravel application at high speed.
29+
|
30+
*/
31+
32+
$frankenPhpClient = new FrankenPhpClient();
33+
34+
$worker = null;
35+
$requestCount = 0;
36+
$maxRequests = $_ENV['MAX_REQUESTS'] ?? $_SERVER['MAX_REQUESTS'];
37+
38+
try {
39+
$handleRequest = static function () use (&$worker, $basePath, $frankenPhpClient) {
40+
try {
41+
$worker ??= tap(
42+
new Worker(
43+
new ApplicationFactory($basePath), $frankenPhpClient
44+
)
45+
)->boot();
46+
47+
[$request, $context] = $frankenPhpClient->marshalRequest(new RequestContext());
48+
49+
$worker->handle($request, $context);
50+
} catch (Throwable $e) {
51+
if ($worker) {
52+
report($e);
53+
}
54+
55+
$response = new Response(
56+
'Internal Server Error',
57+
500,
58+
[
59+
'Status' => '500 Internal Server Error',
60+
'Content-Type' => 'text/plain',
61+
],
62+
);
63+
64+
$response->send();
65+
66+
Stream::shutdown($e);
67+
}
68+
};
69+
70+
while ($requestCount < $maxRequests && frankenphp_handle_request($handleRequest)) {
71+
$requestCount++;
72+
}
73+
} finally {
74+
$worker?->terminate();
75+
}

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "laravel/octane",
33
"description": "Supercharge your Laravel application's performance.",
4-
"keywords": ["laravel", "octane", "roadrunner", "swoole"],
4+
"keywords": ["laravel", "octane", "roadrunner", "swoole", "frankenphp"],
55
"license": "MIT",
66
"support": {
77
"issues": "https://github.com/laravel/octane/issues",

config/octane.php

+17-17
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
| when starting, restarting, or stopping your server via the CLI. You
3333
| are free to change this to the supported server of your choosing.
3434
|
35-
| Supported: "roadrunner", "swoole"
35+
| Supported: "roadrunner", "swoole", "frankenphp"
3636
|
3737
*/
3838

@@ -135,22 +135,6 @@
135135
//
136136
],
137137

138-
/*
139-
|--------------------------------------------------------------------------
140-
| Octane Cache Table
141-
|--------------------------------------------------------------------------
142-
|
143-
| While using Swoole, you may leverage the Octane cache, which is powered
144-
| by a Swoole table. You may set the maximum number of rows as well as
145-
| the number of bytes per row using the configuration options below.
146-
|
147-
*/
148-
149-
'cache' => [
150-
'rows' => 1000,
151-
'bytes' => 10000,
152-
],
153-
154138
/*
155139
|--------------------------------------------------------------------------
156140
| Octane Swoole Tables
@@ -169,6 +153,22 @@
169153
],
170154
],
171155

156+
/*
157+
|--------------------------------------------------------------------------
158+
| Octane Swoole Cache Table
159+
|--------------------------------------------------------------------------
160+
|
161+
| While using Swoole, you may leverage the Octane cache, which is powered
162+
| by a Swoole table. You may set the maximum number of rows as well as
163+
| the number of bytes per row using the configuration options below.
164+
|
165+
*/
166+
167+
'cache' => [
168+
'rows' => 1000,
169+
'bytes' => 10000,
170+
],
171+
172172
/*
173173
|--------------------------------------------------------------------------
174174
| File Watching

src/ApplicationGateway.php

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public function __construct(protected Application $app, protected Application $s
2525
*/
2626
public function handle(Request $request): Response
2727
{
28+
$request->enableHttpMethodParameterOverride();
29+
2830
$this->dispatchEvent($this->sandbox, new RequestReceived($this->app, $this->sandbox, $request));
2931

3032
if (Octane::hasRouteFor($request->getMethod(), '/'.$request->path())) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
namespace Laravel\Octane\Commands\Concerns;
4+
5+
use GuzzleHttp\Client;
6+
use Illuminate\Support\Facades\Http;
7+
use Laravel\Octane\FrankenPhp\Concerns\FindsFrankenPhpBinary;
8+
use Symfony\Component\Process\Process;
9+
use Throwable;
10+
11+
trait InstallsFrankenPhpDependencies
12+
{
13+
use FindsFrankenPhpBinary;
14+
15+
/**
16+
* The minimum required version of the FrankenPHP binary.
17+
*
18+
* @var string
19+
*/
20+
protected $requiredFrankenPhpVersion = '1.0.0';
21+
22+
/**
23+
* Ensure the FrankenPHP's Caddyfile and worker script are installed.
24+
*
25+
* @return void
26+
*/
27+
public function ensureFrankenPhpWorkerIsInstalled()
28+
{
29+
if (! file_exists(public_path('frankenphp-worker.php'))) {
30+
copy(__DIR__.'/../stubs/frankenphp-worker.php', public_path('frankenphp-worker.php'));
31+
}
32+
}
33+
34+
/**
35+
* Ensure the FrankenPHP binary is installed into the project.
36+
*
37+
* @return string
38+
*/
39+
protected function ensureFrankenPhpBinaryIsInstalled()
40+
{
41+
if (! is_null($frankenphpBinary = $this->findFrankenPhpBinary())) {
42+
return $frankenphpBinary;
43+
}
44+
45+
if ($this->confirm('Unable to locate FrankenPHP binary. Should Octane download the binary for your operating system?', true)) {
46+
$this->downloadFrankenPhpBinary();
47+
}
48+
49+
return base_path('frankenphp');
50+
}
51+
52+
/**
53+
* Download the latest version of the FrankenPHP binary.
54+
*
55+
* @return bool
56+
*/
57+
protected function downloadFrankenPhpBinary()
58+
{
59+
$arch = php_uname('m');
60+
61+
$assetName = match (true) {
62+
PHP_OS_FAMILY === 'Linux' && $arch === 'x86_64' => 'frankenphp-linux-x86_64',
63+
PHP_OS_FAMILY === 'Darwin' => "frankenphp-mac-$arch",
64+
default => null,
65+
};
66+
67+
if ($assetName === null) {
68+
$this->error('FrankenPHP binaries are currently only available for Linux (x86_64) and macOS. Other systems should use the Docker images or compile FrankenPHP manually.');
69+
70+
return false;
71+
}
72+
73+
$assets = Http::accept('application/vnd.github+json')
74+
->withHeaders(['X-GitHub-Api-Version' => '2022-11-28'])
75+
->get('https://api.github.com/repos/dunglas/frankenphp/releases/latest')['assets'];
76+
77+
foreach ($assets as $asset) {
78+
if ($asset['name'] !== $assetName) {
79+
continue;
80+
}
81+
82+
$path = base_path('frankenphp');
83+
84+
$progressBar = null;
85+
86+
(new Client)->get(
87+
$asset['browser_download_url'],
88+
[
89+
'sink' => $path,
90+
'progress' => function ($downloadTotal, $downloadedBytes) use (&$progressBar) {
91+
if ($downloadTotal === 0) {
92+
return;
93+
}
94+
95+
if ($progressBar === null) {
96+
$progressBar = $this->output->createProgressBar($downloadTotal);
97+
$progressBar->start($downloadTotal, $downloadedBytes);
98+
99+
return;
100+
}
101+
102+
$progressBar->setProgress($downloadedBytes);
103+
},
104+
]
105+
);
106+
107+
chmod($path, 0755);
108+
109+
$progressBar->finish();
110+
111+
$this->newLine();
112+
113+
return $path;
114+
}
115+
116+
$this->error('FrankenPHP asset not found.');
117+
118+
return $path;
119+
}
120+
121+
/**
122+
* Ensure the installed FrankenPHP binary meets Octane's requirements.
123+
*
124+
* @param string $frakenPhpBinary
125+
* @return void
126+
*/
127+
protected function ensureFrankenPhpBinaryMeetsRequirements($frakenPhpBinary)
128+
{
129+
$version = tap(new Process([$frakenPhpBinary, '--version'], base_path()))
130+
->run()
131+
->getOutput();
132+
133+
$version = explode(' ', $version)[1] ?? null;
134+
135+
if ($version === null) {
136+
return $this->warn(
137+
'Unable to determine the current FrankenPHP binary version. Please report this issue: https://github.com/laravel/octane/issues/new.',
138+
);
139+
}
140+
141+
if (version_compare($version, $this->requiredFrankenPhpVersion, '>=')) {
142+
return;
143+
}
144+
145+
$this->warn("Your FrankenPHP binary version (<fg=red>$version</>) may be incompatible with Octane.");
146+
147+
if ($this->confirm('Should Octane download the latest FrankenPHP binary version for your operating system?', true)) {
148+
rename($frakenPhpBinary, "$frakenPhpBinary.backup");
149+
150+
try {
151+
$this->downloadFrankenPhpBinary();
152+
} catch (Throwable $e) {
153+
report($e);
154+
155+
rename("$frakenPhpBinary.backup", $frakenPhpBinary);
156+
157+
return $this->warn('Unable to download FrankenPHP binary. The HTTP request exception has been logged.');
158+
}
159+
160+
unlink("$frakenPhpBinary.backup");
161+
}
162+
}
163+
}

src/Commands/Concerns/InstallsRoadRunnerDependencies.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ trait InstallsRoadRunnerDependencies
2121
*
2222
* @var string
2323
*/
24-
protected $requiredVersion = '2023.3.0';
24+
protected $requiredRoadRunnerVersion = '2023.3.0';
2525

2626
/**
2727
* Determine if RoadRunner is installed.
@@ -131,7 +131,7 @@ protected function ensureRoadRunnerBinaryMeetsRequirements($roadRunnerBinary)
131131

132132
$version = explode(' ', $version)[2];
133133

134-
if (version_compare($version, $this->requiredVersion, '>=')) {
134+
if (version_compare($version, $this->requiredRoadRunnerVersion, '>=')) {
135135
return;
136136
}
137137

0 commit comments

Comments
 (0)