From d52ce94341f85bcdb47861d519014ea43bd0e425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 13 Oct 2025 16:22:11 +0200 Subject: [PATCH] docs: improve Mercure documentation and various other parts --- .gitleaksignore | 1 + caddy/extinit.go | 2 +- docs/config.md | 107 ++++++++++++++++++++++++++------------ docs/docker.md | 9 +++- docs/extensions.md | 8 +-- docs/known-issues.md | 12 +++-- docs/laravel.md | 30 +++++++++++ docs/mercure.md | 121 +++++++++++++++++++++++++++++++++++++++++-- 8 files changed, 243 insertions(+), 47 deletions(-) create mode 100644 .gitleaksignore diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 00000000..8556981e --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1 @@ +/github/workspace/docs/mercure.md:jwt:65 diff --git a/caddy/extinit.go b/caddy/extinit.go index f0327f57..c990b99f 100644 --- a/caddy/extinit.go +++ b/caddy/extinit.go @@ -17,7 +17,7 @@ func init() { caddycmd.RegisterCommand(caddycmd.Command{ Name: "extension-init", Usage: "go_extension.go [--verbose]", - Short: "(Experimental) Initializes a PHP extension from a Go file", + Short: "Initializes a PHP extension from a Go file (EXPERIMENTAL)", Long: ` Initializes a PHP extension from a Go file. This command generates the necessary C files for the extension, including the header and source files, as well as the arginfo file.`, CobraFunc: func(cmd *cobra.Command) { diff --git a/docs/config.md b/docs/config.md index 64654a18..306d7e3f 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,17 +1,38 @@ # Configuration -FrankenPHP, Caddy as well as the Mercure and Vulcain modules can be configured using [the formats supported by Caddy](https://caddyserver.com/docs/getting-started#your-first-config). +FrankenPHP, Caddy as well as the [Mercure](mercure.md) and [Vulcain](https://vulcain.rocks) modules can be configured using [the formats supported by Caddy](https://caddyserver.com/docs/getting-started#your-first-config). -In [the Docker images](docker.md), the `Caddyfile` is located at `/etc/frankenphp/Caddyfile`. -The static binary will also look for the `Caddyfile` in the directory where the `frankenphp run` command is executed. +The most common format is the `Caddyfile`, which is a simple, human-readable text format. +By default, FrankenPHP will look for a `Caddyfile` in the current directory. You can specify a custom path with the `-c` or `--config` option. +A minimal `Caddyfile` to serve a PHP application is shown below: + +```caddyfile +# The hostname to respond to +localhost + +# Optionaly, the directory to serve files from, otherwise defaults to the current directory +#root public/ +php_server +``` + +A more advanced `Caddyfile` enabling more features and providing convenient environment variables is provided [in the FrankenPHP repository](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile), +and with Docker images. + PHP itself can be configured [using a `php.ini` file](https://www.php.net/manual/en/configuration.file.php). -Depending on your installation method, the PHP interpreter will look for configuration files in locations described below. +Depending on your installation method, FrankenPHP and the PHP interpreter will look for configuration files in locations described below. ## Docker +FrankenPHP: + +- `/etc/frankenphp/Caddyfile`: the main configuration file +- `/etc/frankenphp/caddy.d/*.caddy`: additional configuration files that are loaded automatically + +PHP: + - `php.ini`: `/usr/local/etc/php/php.ini` (no `php.ini` is provided by default) - additional configuration files: `/usr/local/etc/php/conf.d/*.ini` - PHP extensions: `/usr/local/lib/php/extensions/no-debug-zts-/` @@ -29,12 +50,25 @@ RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini ## RPM and Debian packages +FrankenPHP: + +- `/etc/frankenphp/Caddyfile`: the main configuration file +- `/etc/frankenphp/caddy.d/*.caddy`: additional configuration files that are loaded automatically + +PHP: + - `php.ini`: `/etc/frankenphp/php.ini` (a `php.ini` file with production presets is provided by default) - additional configuration files: `/etc/frankenphp/php.d/*.ini` - PHP extensions: `/usr/lib/frankenphp/modules/` ## Static binary +FrankenPHP: + +- In the current working directory: `Caddyfile` + +PHP: + - `php.ini`: The directory in which `frankenphp run` or `frankenphp php-server` is executed, then `/etc/frankenphp/php.ini` - additional configuration files: `/etc/frankenphp/php.d/*.ini` - PHP extensions: cannot be loaded, bundle them in the binary itself @@ -229,34 +263,6 @@ and otherwise forward the request to the worker matching the path pattern. } ``` -### Full Duplex (HTTP/1) - -When using HTTP/1.x, it may be desirable to enable full-duplex mode to allow writing a response before the entire body -has been read. (for example: WebSocket, Server-Sent Events, etc.) - -This is an opt-in configuration that needs to be added to the global options in the `Caddyfile`: - -```caddyfile -{ - servers { - enable_full_duplex - } -} -``` - -> [!CAUTION] -> -> Enabling this option may cause old HTTP/1.x clients that don't support full-duplex to deadlock. -> This can also be configured using the `CADDY_GLOBAL_OPTIONS` environment config: - -```sh -CADDY_GLOBAL_OPTIONS="servers { - enable_full_duplex -}" -``` - -You can find more information about this setting in the [Caddy documentation](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex). - ## Environment Variables The following environment variables can be used to inject Caddy directives in the `Caddyfile` without modifying it: @@ -293,6 +299,43 @@ You can also change the PHP configuration using the `php_ini` directive in the ` } ``` +### Disabling HTTPS + +By default, FrankenPHP will automatically enable HTTPS using for all the hostnames, including `localhost`. +If you want to disable HTTPS (for example in a development environment), you can set the `SERVER_NAME` environment variable to `http://` or `:80`: + +Alternatively, you can use all other methods described in the [Caddy documentation](https://caddyserver.com/docs/automatic-https#activation). + +If you want to use HTTPS with the `127.0.0.1` IP address instead of the `localhost` hostname, please read the [known issues](known-issues.md#using-https127001-with-docker) section. + +### Full Duplex (HTTP/1) + +When using HTTP/1.x, it may be desirable to enable full-duplex mode to allow writing a response before the entire body +has been read. (for example: [Mercure](mercure.md), WebSocket, Server-Sent Events, etc.) + +This is an opt-in configuration that needs to be added to the global options in the `Caddyfile`: + +```caddyfile +{ + servers { + enable_full_duplex + } +} +``` + +> [!CAUTION] +> +> Enabling this option may cause old HTTP/1.x clients that don't support full-duplex to deadlock. +> This can also be configured using the `CADDY_GLOBAL_OPTIONS` environment config: + +```sh +CADDY_GLOBAL_OPTIONS="servers { + enable_full_duplex +}" +``` + +You can find more information about this setting in the [Caddy documentation](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex). + ## Enable the Debug Mode When using the Docker image, set the `CADDY_GLOBAL_OPTIONS` environment variable to `debug` to enable the debug mode: diff --git a/docs/docker.md b/docs/docker.md index df52b1e8..9c4fbdab 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -1,6 +1,8 @@ # Building Custom Docker Image -[FrankenPHP Docker images](https://hub.docker.com/r/dunglas/frankenphp) are based on [official PHP images](https://hub.docker.com/_/php/). Debian and Alpine Linux variants are provided for popular architectures. Debian variants are recommended. +[FrankenPHP Docker images](https://hub.docker.com/r/dunglas/frankenphp) are based on [official PHP images](https://hub.docker.com/_/php/). +Debian and Alpine Linux variants are provided for popular architectures. +Debian variants are recommended. Variants for PHP 8.2, 8.3 and 8.4 are provided. @@ -28,6 +30,11 @@ docker build -t my-php-app . docker run -it --rm --name my-running-app my-php-app ``` +## How to Tweak the Configuration + +For convenience, [a default `Caddyfile`](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) containing +useful environment variables is provided in the image. + ## How to Install More PHP Extensions The [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) script is provided in the base image. diff --git a/docs/extensions.md b/docs/extensions.md index 27865d4c..2c2253b9 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -33,7 +33,7 @@ As covered in the manual implementation section below as well, you need to [get The first step to writing a PHP extension in Go is to create a new Go module. You can use the following command for this: ```console -go mod init example.com/example +go mod init example.com/example ``` The second step is to [get the PHP sources](https://www.php.net/downloads.php) for the next steps. Once you have them, decompress them into the directory of your choice, not inside your Go module: @@ -49,7 +49,7 @@ Everything is now setup to write your native function in Go. Create a new file n ```go package example -// #include +// #include import "C" import ( "strings" @@ -126,7 +126,7 @@ package example import "C" import ( "unsafe" - + "github.com/dunglas/frankenphp" ) @@ -790,7 +790,7 @@ import "C" import ( "unsafe" "strings" - + "github.com/dunglas/frankenphp" ) diff --git a/docs/known-issues.md b/docs/known-issues.md index 947089cc..da226903 100644 --- a/docs/known-issues.md +++ b/docs/known-issues.md @@ -13,9 +13,9 @@ The following extensions are known not to be compatible with FrankenPHP: The following extensions have known bugs and unexpected behaviors when used with FrankenPHP: -| Name | Problem | -| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | When using a static build of FrankenPHP (built with the musl libc), the OpenSSL extension may crash under heavy loads. A workaround is to use a dynamically linked build (like the one used in Docker images). This bug is [being tracked by PHP](https://github.com/php/php-src/issues/13648). | +| Name | Problem | +| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | When using musl libc, the OpenSSL extension may crash under heavy loads. The problem doesn't occur when using the more popular GNU libc. This bug is [being tracked by PHP](https://github.com/php/php-src/issues/13648). | ## get_browser @@ -23,7 +23,11 @@ The [get_browser()](https://www.php.net/manual/en/function.get-browser.php) func ## Standalone Binary and Alpine-based Docker Images -The standalone binary and Alpine-based Docker images (`dunglas/frankenphp:*-alpine`) use [musl libc](https://musl.libc.org/) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), to keep a smaller binary size. This may lead to some compatibility issues. In particular, the glob flag `GLOB_BRACE` is [not available](https://www.php.net/manual/en/function.glob.php) +The fully binary and Alpine-based Docker images (`dunglas/frankenphp:*-alpine`) use [musl libc](https://musl.libc.org/) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), to keep a smaller binary size. +This may lead to some compatibility issues. +In particular, the glob flag `GLOB_BRACE` is [not available](https://www.php.net/manual/en/function.glob.php) + +Prefer using the GNU variant of the static binary and Debian-based Docker images if you encounter issues. ## Using `https://127.0.0.1` with Docker diff --git a/docs/laravel.md b/docs/laravel.md index 54203367..094622c4 100644 --- a/docs/laravel.md +++ b/docs/laravel.md @@ -76,6 +76,8 @@ The `octane:frankenphp` command can take the following options: > [!TIP] > To get structured JSON logs (useful when using log analytics solutions), explicitly the pass `--log-level` option. +See also [how to use Mercure with Octane](#mercure-support). + Learn more about [Laravel Octane in its official documentation](https://laravel.com/docs/octane). ## Laravel Apps As Standalone Binaries @@ -166,6 +168,34 @@ This is not suitable for embedded applications, as each new version will be extr Set the `LARAVEL_STORAGE_PATH` environment variable (for example, in your `.env` file) or call the `Illuminate\Foundation\Application::useStoragePath()` method to use a directory outside the temporary directory. +### Mercure Support + +[Mercure](https://mercure.rocks) is a great way to add real-time capabilities to your Laravel apps. +FrankenPHP includes [Mercure support out of the box](mercure.md). + +If you are not using [Octane](#laravel-octane), see [the Mercure documentation entry](mercure.md). + +If you are using Octane, you can use enable Mercure support by adding the following lines to your `config/octane.php` file: + +```php +// ... + +return [ + // ... + + 'mercure' => [ + 'anonymous' => true, + 'publisher_jwt' => '!ChangeThisMercureHubJWTSecretKey!', + 'subscriber_jwt' => '!ChangeThisMercureHubJWTSecretKey!', + ], +]; +``` + +You can use [all directives supported by Mercure](https://mercure.rocks/docs/hub/config#directives) in this array. + +To publish and subscribe to updates, we recommend using the [Laravel Mercure Broadcaster](https://github.com/mvanduijker/laravel-mercure-broadcaster) library. +Alternatively, see [the Mercure documentation](mercure.md) to do it in pure PHP and JavaScript. + ### Running Octane With Standalone Binaries It's even possible to package Laravel Octane apps as standalone binaries! diff --git a/docs/mercure.md b/docs/mercure.md index 34ce3b39..ceebad20 100644 --- a/docs/mercure.md +++ b/docs/mercure.md @@ -3,13 +3,124 @@ FrankenPHP comes with a built-in [Mercure](https://mercure.rocks) hub! Mercure allows you to push real-time events to all the connected devices: they will receive a JavaScript event instantly. -No JS library or SDK is required! +It's a convenient alternative to WebSockets that is simple to use and is natively supported by all modern web browsers! ![Mercure](mercure-hub.png) -To enable the Mercure hub, update the `Caddyfile` as described [on Mercure's site](https://mercure.rocks/docs/hub/config). +## Enabling Mercure -The path of the Mercure hub is `/.well-known/mercure`. -When running FrankenPHP inside Docker, the full send URL would look like `http://php/.well-known/mercure` (with `php` being the container's name running FrankenPHP). +Mercure support is disabled by default. +Here is a minimal example of a `Caddyfile` enabling both FrankenPHP and the Mercure hub: -To push Mercure updates from your code, we recommend the [Symfony Mercure Component](https://symfony.com/components/Mercure) (you don't need the Symfony full-stack framework to use it). +```caddyfile +# The hostname to respond to +localhost + +mercure { + # The secret key used to sign the JWT tokens for publishers + publisher_jwt !ChangeThisMercureHubJWTSecretKey! + # Allows anonymous subscribers (without JWT) + anonymous +} + +root public/ +php_server +``` + +> [!TIP] +> +> The [sample `Caddyfile`](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) +> provided by [the Docker images](docker.md) already includes a commented Mercure configuration +> with convenient environment variables to configure it. +> +> Uncomment the Mercure section in `/etc/frankenphp/Caddyfile` to enable it. + +## Subscribing to Updates + +By default, the Mercure hub is available on the `/.well-known/mercure` path of your FrankenPHP server. +To subscribe to updates, use the native [`EventSource`](https://developer.mozilla.org/docs/Web/API/EventSource) JavaScript class: + +```html + + +Mercure Example + +``` + +## Publishing Updates + +### Using `file_put_contents()` + +To dispatch an update to connected subscribers, send an authenticated POST request to the Mercure hub with the `topic` and `data` parameters: + +```php + [ + 'method' => 'POST', + 'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer " . JWT, + 'content' => http_build_query([ + 'topic' => 'my-topic', + 'data' => json_encode(['key' => 'value']), + ]), +]])); + +// Write to FrankenPHP's logs +error_log("update $updateID published", 4); +``` + +The key passed as parameter of the `mercure.publisher_jwt` option in the `Caddyfile` must used to sign the JWT token used in the `Authorization` header. + +The JWT must include a `mercure` claim with a `publish` permission for the topics you want to publish to. +See [the Mercure documentation](https://mercure.rocks/spec#publishers) about authorization. + +To generate your own tokens, you can use [this jwt.io link](https://www.jwt.io/#token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.PXwpfIGng6KObfZlcOXvcnWCJOWTFLtswGI5DZuWSK4), +but for production apps, it's recommended to use short-lived tokens generated aerodynamically using with a trusted [JWT library](https://www.jwt.io/libraries?programming_language=php). + +### Using Symfony Mercure + +Alternatively, you can use the [Symfony Mercure Component](https://symfony.com/components/Mercure), a standalone PHP library. + +This library handled the JWT generation, update publishing as well as cookie-based authorization for subscribers. + +First, install the library using Composer: + +```console +composer require symfony/mercure lcobucci/jwt +``` + +Then, you can use it like this: + +```php +publish(new \Symfony\Component\Mercure\Update('my-topic', json_encode(['key' => 'value']))); + +// Write to FrankenPHP's logs +error_log("update $updateID published", 4); +``` + +Mercure is also natively supported by: + +- [Laravel](laravel.md#mercure-support) +- [Symfony](https://symfony.com/doc/current/mercure.html) +- [API Platform](https://api-platform.com/docs/core/mercure/)