mirror of
https://github.com/php/frankenphp.git
synced 2026-03-24 00:52:11 +01:00
Merge branch 'main' into chore/remove-useless-cgo-imports
Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
This commit is contained in:
@@ -4,10 +4,10 @@ root = true
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.sh]
|
||||
[*.{sh,Dockerfile}]
|
||||
indent_style = tab
|
||||
tab_width = 4
|
||||
|
||||
[*.Dockerfile]
|
||||
indent_style = tab
|
||||
tab_width = 4
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
tab_width = 2
|
||||
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
17
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
17
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -7,16 +7,18 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
Before submitting a bug, please double-check that your problem [is not
|
||||
a known issue](https://frankenphp.dev/docs/known-issues/)
|
||||
(especially if you use XDebug or Tideways), and that is has not
|
||||
[already been reported](https://github.com/php/frankenphp/issues).
|
||||
|
||||
Before submitting, please ensure that your issue:
|
||||
|
||||
* Is not [a known issue](https://frankenphp.dev/docs/known-issues/).
|
||||
* Has not [already been reported](https://github.com/php/frankenphp/issues).
|
||||
* Is not caused by a dependency (like Caddy or PHP itself). If the issue is with a dependency, please report it to the upstream project directly.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: |
|
||||
Tell us what you do, what you get and what you expected.
|
||||
Tell us what you do, what you get, and what you expected.
|
||||
Provide us with some step-by-step instructions to reproduce the issue.
|
||||
validations:
|
||||
required: true
|
||||
@@ -29,8 +31,9 @@ body:
|
||||
- Docker (Debian Trixie)
|
||||
- Docker (Debian Bookworm)
|
||||
- Docker (Alpine)
|
||||
- apk packages
|
||||
- deb packages
|
||||
- rpm packages
|
||||
- RPM packages
|
||||
- Static binary
|
||||
- Custom (tell us more in the description)
|
||||
default: 0
|
||||
@@ -55,6 +58,8 @@ body:
|
||||
options:
|
||||
- GNU/Linux
|
||||
- macOS
|
||||
- Windows
|
||||
- FreeBSD
|
||||
- Other (tell us more in the description)
|
||||
default: 0
|
||||
validations:
|
||||
|
||||
2
.github/workflows/docker.yaml
vendored
2
.github/workflows/docker.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: Build Docker images
|
||||
name: Build Docker Images
|
||||
concurrency:
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
24
.github/workflows/docs.yaml
vendored
Normal file
24
.github/workflows/docs.yaml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: Deploy Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "docs/**"
|
||||
- "README.md"
|
||||
- "CONTRIBUTING.md"
|
||||
- "install.ps1"
|
||||
- "install.sh"
|
||||
permissions: {}
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: Trigger website deployment
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.WEBSITE_DEPLOY_TOKEN }}
|
||||
run: gh api repos/dunglas/frankenphp-website/actions/workflows/hugo.yaml/dispatches -f ref=main
|
||||
1
.github/workflows/lint.yaml
vendored
1
.github/workflows/lint.yaml
vendored
@@ -30,6 +30,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
LINTER_RULES_PATH: /
|
||||
MARKDOWN_CONFIG_FILE: .markdown-lint.yaml
|
||||
FILTER_REGEX_EXCLUDE: docs/(cn|es|fr|ja|pt-br|ru|tr)/
|
||||
VALIDATE_CPP: false
|
||||
VALIDATE_JSCPD: false
|
||||
VALIDATE_GO: false
|
||||
|
||||
5
.github/workflows/sanitizers.yaml
vendored
5
.github/workflows/sanitizers.yaml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
- name: Determine PHP version
|
||||
id: determine-php-version
|
||||
run: |
|
||||
curl -fsSL 'https://www.php.net/releases/index.php?json&max=1&version=8.5' -o version.json
|
||||
curl -fsSL 'https://www.php.net/releases/index.php?json&max=1&version=8.5' -o version.json 2>/dev/null || curl -fsSL 'https://phpmirror.static-php.dev/releases/index.php?json&max=1&version=8.5' -o version.json
|
||||
echo version="$(jq -r 'keys[0]' version.json)" >> "$GITHUB_OUTPUT"
|
||||
echo archive="$(jq -r '.[] .source[] | select(.filename |endswith(".xz")) | "https://www.php.net/distributions/" + .filename' version.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: Cache PHP
|
||||
@@ -65,7 +65,8 @@ jobs:
|
||||
name: Compile PHP
|
||||
run: |
|
||||
mkdir php/
|
||||
curl -fsSL "${URL}" | tar -Jx -C php --strip-components=1
|
||||
MIRROR_URL=${URL/https:\/\/www.php.net/https:\/\/phpmirror.static-php.dev}
|
||||
(curl -fsSL "${URL}" || curl -fsSL "${MIRROR_URL}") | tar -Jx -C php --strip-components=1
|
||||
cd php/
|
||||
./configure \
|
||||
CFLAGS="$CFLAGS" \
|
||||
|
||||
4
.github/workflows/static.yaml
vendored
4
.github/workflows/static.yaml
vendored
@@ -453,12 +453,12 @@ jobs:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
with: # zizmor: ignore[cache-poisoning]
|
||||
go-version: "1.26"
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
cache: false
|
||||
cache: ${{ github.event_name != 'release' }}
|
||||
- name: Set FRANKENPHP_VERSION
|
||||
run: |
|
||||
if [ "${GITHUB_REF_TYPE}" == "tag" ]; then
|
||||
|
||||
10
.github/workflows/tests.yaml
vendored
10
.github/workflows/tests.yaml
vendored
@@ -35,6 +35,7 @@ jobs:
|
||||
env:
|
||||
GOMAXPROCS: 10
|
||||
LIBRARY_PATH: ${{ github.workspace }}/watcher/target/lib
|
||||
GOFLAGS: "-tags=nobadger,nomysql,nopgx"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
@@ -69,7 +70,7 @@ jobs:
|
||||
run: ./frankenphp.test -test.v
|
||||
- name: Run Caddy module tests
|
||||
working-directory: caddy/
|
||||
run: go test -tags nobadger,nomysql,nopgx -race -v ./...
|
||||
run: go test -race -v ./...
|
||||
- name: Run Fuzzing Tests
|
||||
working-directory: caddy/
|
||||
run: go test -fuzz FuzzRequest -fuzztime 20s
|
||||
@@ -100,6 +101,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ["8.3", "8.4", "8.5"]
|
||||
env:
|
||||
XCADDY_GO_BUILD_FLAGS: "-tags=nobadger,nomysql,nopgx"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
@@ -126,7 +129,7 @@ jobs:
|
||||
- name: Download PHP sources
|
||||
run: |
|
||||
PHP_VERSION=$(php -r "echo PHP_VERSION;")
|
||||
wget -q "https://www.php.net/distributions/php-${PHP_VERSION}.tar.gz"
|
||||
wget -q "https://www.php.net/distributions/php-${PHP_VERSION}.tar.gz" || wget -q "https://phpmirror.static-php.dev/distributions/php-${PHP_VERSION}.tar.gz"
|
||||
tar xzf "php-${PHP_VERSION}.tar.gz"
|
||||
echo "GEN_STUB_SCRIPT=${PWD}/php-${PHP_VERSION}/build/gen_stub.php" >> "${GITHUB_ENV}"
|
||||
- name: Set CGO flags
|
||||
@@ -141,6 +144,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
GOFLAGS: "-tags=nowatcher,nobadger,nomysql,nopgx"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
@@ -172,4 +176,4 @@ jobs:
|
||||
run: go test -tags nowatcher -race -v ./...
|
||||
- name: Run Caddy module tests
|
||||
working-directory: caddy/
|
||||
run: go test -tags nowatcher,nobadger,nomysql,nopgx -race -v ./...
|
||||
run: go test -race -v ./...
|
||||
|
||||
40
.github/workflows/translate.yaml
vendored
40
.github/workflows/translate.yaml
vendored
@@ -20,7 +20,9 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
# zizmor: ignore[artipacked]
|
||||
# persist-credentials is intentionally left enabled (unlike other workflows)
|
||||
# because this workflow needs to push a branch via git push
|
||||
- id: md_files
|
||||
run: |
|
||||
FILES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- 'docs/*.md')
|
||||
@@ -47,23 +49,27 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
LINTER_RULES_PATH: /
|
||||
MARKDOWN_CONFIG_FILE: .markdown-lint.yaml
|
||||
FIX_NATURAL_LANGUAGE: true
|
||||
VALIDATE_NATURAL_LANGUAGE: false
|
||||
FIX_MARKDOWN: true
|
||||
- name: Create Pull Request
|
||||
if: steps.md_files.outputs.found == 'true'
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
title: "docs: update translations"
|
||||
commit-message: "docs: update translations"
|
||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
branch: translations/${{ github.run_id }}
|
||||
delete-branch: true
|
||||
body: |
|
||||
Translation updates for: ${{ steps.md_files.outputs.files }}.
|
||||
labels: |
|
||||
translations
|
||||
bot
|
||||
draft: false
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ACTOR: ${{ github.actor }}
|
||||
ACTOR_ID: ${{ github.actor_id }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
MD_FILES_LIST: ${{ steps.md_files.outputs.files }}
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
BRANCH="translations/$RUN_ID"
|
||||
git checkout -b "$BRANCH"
|
||||
git add docs/
|
||||
git diff --cached --quiet && exit 0
|
||||
git commit -m "docs: update translations" --author="$ACTOR <$ACTOR_ID+$ACTOR@users.noreply.github.com>"
|
||||
git push origin "$BRANCH"
|
||||
gh pr create \
|
||||
--title "docs: update translations" \
|
||||
--body "Translation updates for: $MD_FILES_LIST." \
|
||||
--label "translations" \
|
||||
--label "bot"
|
||||
|
||||
234
.github/workflows/windows.yaml
vendored
Normal file
234
.github/workflows/windows.yaml
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
---
|
||||
name: Build Windows release
|
||||
|
||||
concurrency:
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*.*.*
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
#checkov:skip=CKV_GHA_7
|
||||
version:
|
||||
description: "FrankenPHP version"
|
||||
required: false
|
||||
type: string
|
||||
schedule:
|
||||
- cron: "0 8 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
GOTOOLCHAIN: local
|
||||
GOFLAGS: "-ldflags=-extldflags=-fuse-ld=lld -tags=nobadger,nomysql,nopgx"
|
||||
PHP_DOWNLOAD_BASE: "https://downloads.php.net/~windows/releases/"
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Determine ref
|
||||
run: |
|
||||
$ref = $env:REF
|
||||
if (-not $ref -and $env:GITHUB_EVENT_NAME -eq "schedule") {
|
||||
$ref = (gh release view --repo php/frankenphp --json tagName --jq '.tagName')
|
||||
}
|
||||
|
||||
"REF=$ref" >> $env:GITHUB_ENV
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REF: ${{ (github.ref_type == 'tag' && github.ref_name) || (github.event_name == 'workflow_dispatch' && inputs.version) || '' }}
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ env.REF || '' }}
|
||||
path: frankenphp
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set FRANKENPHP_VERSION
|
||||
run: |
|
||||
$ref = $env:REF
|
||||
|
||||
if ($env:GITHUB_REF_TYPE -eq "tag") {
|
||||
$frankenphpVersion = $env:GITHUB_REF_NAME.Substring(1)
|
||||
} elseif ($ref) {
|
||||
if ($ref.StartsWith("v")) {
|
||||
$frankenphpVersion = $ref.Substring(1)
|
||||
} else {
|
||||
$frankenphpVersion = $ref
|
||||
}
|
||||
} else {
|
||||
$frankenphpVersion = $env:GITHUB_SHA
|
||||
}
|
||||
|
||||
"FRANKENPHP_VERSION=$frankenphpVersion" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with: # zizmor: ignore[cache-poisoning]
|
||||
go-version: "1.26"
|
||||
cache-dependency-path: |
|
||||
frankenphp/go.sum
|
||||
frankenphp/caddy/go.sum
|
||||
cache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
check-latest: true
|
||||
|
||||
- name: Install Vcpkg Libraries
|
||||
working-directory: frankenphp
|
||||
run: "vcpkg install"
|
||||
|
||||
- name: Download Watcher
|
||||
run: |
|
||||
$latestTag = gh release list --repo e-dant/watcher --limit 1 --exclude-drafts --exclude-pre-releases --json tagName --jq '.[0].tagName'
|
||||
Write-Host "Latest Watcher version: $latestTag"
|
||||
|
||||
gh release download $latestTag --repo e-dant/watcher --pattern "*x86_64-pc-windows-msvc.tar" -O watcher.tar
|
||||
|
||||
tar -xf "watcher.tar" -C "$env:GITHUB_WORKSPACE"
|
||||
Rename-Item -Path "$env:GITHUB_WORKSPACE\x86_64-pc-windows-msvc" -NewName "watcher"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Download PHP
|
||||
run: |
|
||||
$webContent = Invoke-WebRequest -Uri $env:PHP_DOWNLOAD_BASE
|
||||
$links = $webContent.Links.Href | Where-Object { $_ -match "php-\d+\.\d+\.\d+-Win32-vs17-x64\.zip$" }
|
||||
|
||||
if (-not $links) { throw "Could not find PHP zip files at $env:PHP_DOWNLOAD_BASE" }
|
||||
|
||||
$latestFile = $links | Sort-Object { if ($_ -match '(\d+\.\d+\.\d+)') { [version]$matches[1] } } | Select-Object -Last 1
|
||||
|
||||
$version = if ($latestFile -match '(\d+\.\d+\.\d+)') { $matches[1] }
|
||||
Write-Host "Detected latest PHP version: $version"
|
||||
|
||||
"PHP_VERSION=$version" >> $env:GITHUB_ENV
|
||||
|
||||
$phpZip = "php-$version-Win32-vs17-x64.zip"
|
||||
$develZip = "php-devel-pack-$version-Win32-vs17-x64.zip"
|
||||
|
||||
$dirName = "frankenphp-windows-x86_64"
|
||||
|
||||
"DIR_NAME=$dirName" >> $env:GITHUB_ENV
|
||||
|
||||
Invoke-WebRequest -Uri "$env:PHP_DOWNLOAD_BASE/$phpZip" -OutFile "$env:TEMP\php.zip"
|
||||
Expand-Archive -Path "$env:TEMP\php.zip" -DestinationPath "$env:GITHUB_WORKSPACE\$dirName"
|
||||
|
||||
Invoke-WebRequest -Uri "$env:PHP_DOWNLOAD_BASE/$develZip" -OutFile "$env:TEMP\php-devel.zip"
|
||||
Expand-Archive -Path "$env:TEMP\php-devel.zip" -DestinationPath "$env:GITHUB_WORKSPACE\php-devel"
|
||||
|
||||
- name: Prepare env
|
||||
run: |
|
||||
$vcpkgRoot = "$env:GITHUB_WORKSPACE\frankenphp\vcpkg_installed\x64-windows"
|
||||
$watcherRoot = "$env:GITHUB_WORKSPACE\watcher"
|
||||
$phpBin = "$env:GITHUB_WORKSPACE\$env:DIR_NAME"
|
||||
$phpDevel = "$env:GITHUB_WORKSPACE\php-devel\php-$env:PHP_VERSION-devel-vs17-x64"
|
||||
|
||||
"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm\bin" >> $env:GITHUB_PATH
|
||||
"$vcpkgRoot\bin" >> $env:GITHUB_PATH
|
||||
"$watcherRoot" >> $env:GITHUB_PATH
|
||||
"$phpBin" >> $env:GITHUB_PATH
|
||||
|
||||
"CGO_CFLAGS=-DFRANKENPHP_VERSION=$env:FRANKENPHP_VERSION -I$vcpkgRoot\include -I$watcherRoot -I$phpDevel\include -I$phpDevel\include\main -I$phpDevel\include\TSRM -I$phpDevel\include\Zend -I$phpDevel\include\ext" >> $env:GITHUB_ENV
|
||||
"CGO_LDFLAGS=-L$vcpkgRoot\lib -lbrotlienc -L$watcherRoot -llibwatcher-c -L$phpBin -L$phpDevel\lib -lphp8ts -lphp8embed" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Embed Windows icon and metadata
|
||||
working-directory: frankenphp\caddy\frankenphp
|
||||
run: |
|
||||
go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest
|
||||
|
||||
$major = 0; $minor = 0; $patch = 0; $build = 0
|
||||
if ($env:FRANKENPHP_VERSION -match '^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)$') {
|
||||
$major = [int]$Matches['major']
|
||||
$minor = [int]$Matches['minor']
|
||||
$patch = [int]$Matches['patch']
|
||||
}
|
||||
|
||||
$json = @{
|
||||
FixedFileInfo = @{
|
||||
FileVersion = @{ Major = $major; Minor = $minor; Patch = $patch; Build = $build }
|
||||
ProductVersion = @{ Major = $major; Minor = $minor; Patch = $patch; Build = $build }
|
||||
}
|
||||
StringFileInfo = @{
|
||||
CompanyName = "FrankenPHP"
|
||||
FileDescription = "The modern PHP app server"
|
||||
FileVersion = $env:FRANKENPHP_VERSION
|
||||
InternalName = "frankenphp"
|
||||
OriginalFilename = "frankenphp.exe"
|
||||
LegalCopyright = "(c) 2022 Kévin Dunglas, MIT License"
|
||||
ProductName = "FrankenPHP"
|
||||
ProductVersion = $env:FRANKENPHP_VERSION
|
||||
Comments = "https://frankenphp.dev/"
|
||||
}
|
||||
VarFileInfo = @{
|
||||
Translation = @{ LangID = 9; CharsetID = 1200 }
|
||||
}
|
||||
} | ConvertTo-Json -Depth 10
|
||||
$json | Set-Content "versioninfo.json"
|
||||
|
||||
goversioninfo -64 -icon ..\..\frankenphp.ico versioninfo.json -o resource.syso
|
||||
|
||||
- name: Build FrankenPHP
|
||||
run: |
|
||||
$customVersion = "FrankenPHP $env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy"
|
||||
go build -ldflags="-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion' -X 'github.com/caddyserver/caddy/v2/modules/caddyhttp.ServerHeader=FrankenPHP Caddy'"
|
||||
working-directory: frankenphp\caddy\frankenphp
|
||||
|
||||
- name: Create Directory
|
||||
run: |
|
||||
Copy-Item frankenphp\caddy\frankenphp\frankenphp.exe $env:DIR_NAME
|
||||
Copy-Item watcher\libwatcher-c.dll $env:DIR_NAME
|
||||
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\brotlienc.dll $env:DIR_NAME
|
||||
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\brotlidec.dll $env:DIR_NAME
|
||||
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\brotlicommon.dll $env:DIR_NAME
|
||||
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\pthreadVC3.dll $env:DIR_NAME
|
||||
|
||||
- name: Upload Artifact
|
||||
if: ${{ !env.REF }}
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ env.DIR_NAME }}
|
||||
path: ${{ env.DIR_NAME }}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Zip Release Artifact
|
||||
if: ${{ env.REF }}
|
||||
run: Compress-Archive -Path "$env:DIR_NAME\*" -DestinationPath "$env:DIR_NAME.zip"
|
||||
|
||||
- name: Upload Release Asset
|
||||
if: ${{ env.REF }}
|
||||
run: gh release upload "$env:REF" "$env:DIR_NAME.zip" --repo php/frankenphp --clobber
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run Tests
|
||||
run: |
|
||||
"opcache.enable=0`r`nopcache.enable_cli=0" | Out-File php.ini
|
||||
$env:PHPRC = Get-Location
|
||||
|
||||
go test -race ./...
|
||||
cd caddy
|
||||
go test -race ./...
|
||||
working-directory: ${{ github.workspace }}\frankenphp
|
||||
42
.github/workflows/wrap-issue-details.yaml
vendored
Normal file
42
.github/workflows/wrap-issue-details.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Wrap Issue Content
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
wrap_content:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const body = context.payload.issue.body;
|
||||
|
||||
const wrapSection = (inputBody, marker, summary) => {
|
||||
const regex = new RegExp(`(${marker})\\s*([\\s\\S]*?)(?=\\n### |$)`);
|
||||
|
||||
return inputBody.replace(regex, (match, header, content) => {
|
||||
const trimmed = content.trim();
|
||||
if (!trimmed || trimmed.includes("<details>")) return match;
|
||||
|
||||
return `${header}\n\n<details>\n<summary>${summary}</summary>\n\n${trimmed}\n\n</details>\n`;
|
||||
});
|
||||
};
|
||||
|
||||
let newBody = body;
|
||||
newBody = wrapSection(newBody, "### PHP configuration", "phpinfo() output");
|
||||
newBody = wrapSection(newBody, "### Relevant log output", "Relevant log output");
|
||||
|
||||
if (newBody !== body) {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: newBody
|
||||
});
|
||||
}
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,14 +1,15 @@
|
||||
/caddy/frankenphp/Build
|
||||
/caddy/frankenphp/frankenphp
|
||||
/internal/testserver/testserver
|
||||
/internal/testcli/testcli
|
||||
/dist
|
||||
/github_conf
|
||||
/internal/testserver/testserver
|
||||
/internal/testcli/testcli
|
||||
/package/etc/php.ini
|
||||
/super-linter-output
|
||||
/vcpkg_installed/
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
__debug_bin
|
||||
frankenphp.test
|
||||
caddy/frankenphp/Build
|
||||
package/etc/php.ini
|
||||
*.log
|
||||
|
||||
@@ -17,7 +17,7 @@ The image contains the usual development tools (Go, GDB, Valgrind, Neovim...) an
|
||||
- additional configuration files: `/etc/frankenphp/php.d/*.ini`
|
||||
- php extensions: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
If your Docker version is lower than 23.0, the build will fail due to dockerignore [pattern issue](https://github.com/moby/moby/pull/42676). Add directories to `.dockerignore`.
|
||||
If your Docker version is lower than 23.0, the build will fail due to dockerignore [pattern issue](https://github.com/moby/moby/pull/42676). Add directories to `.dockerignore`:
|
||||
|
||||
```patch
|
||||
!testdata/*.php
|
||||
@@ -30,14 +30,14 @@ If your Docker version is lower than 23.0, the build will fail due to dockerigno
|
||||
|
||||
[Follow the instructions to compile from sources](https://frankenphp.dev/docs/compile/) and pass the `--debug` configuration flag.
|
||||
|
||||
## Running the test suite
|
||||
## Running the Test Suite
|
||||
|
||||
```console
|
||||
export CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)"
|
||||
export CGO_CFLAGS=-O0 -g $(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)"
|
||||
go test -race -v ./...
|
||||
```
|
||||
|
||||
## Caddy module
|
||||
## Caddy Module
|
||||
|
||||
Build Caddy with the FrankenPHP Caddy module:
|
||||
|
||||
@@ -57,13 +57,13 @@ cd testdata/
|
||||
The server is listening on `127.0.0.1:80`:
|
||||
|
||||
> [!NOTE]
|
||||
> if you are using Docker, you will have to either bind container port 80 or execute from inside the container
|
||||
> If you are using Docker, you will have to either bind container port 80 or execute from inside the container
|
||||
|
||||
```console
|
||||
curl -vk http://127.0.0.1/phpinfo.php
|
||||
```
|
||||
|
||||
## Minimal test server
|
||||
## Minimal Test Server
|
||||
|
||||
Build the minimal test server:
|
||||
|
||||
@@ -86,9 +86,76 @@ The server is listening on `127.0.0.1:8080`:
|
||||
curl -v http://127.0.0.1:8080/phpinfo.php
|
||||
```
|
||||
|
||||
## Windows Development
|
||||
|
||||
1. Configure Git to always use `lf` line endings
|
||||
|
||||
```powershell
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
```
|
||||
|
||||
2. Install Visual Studio, Git, and Go:
|
||||
|
||||
```powershell
|
||||
winget install -e --id Microsoft.VisualStudio.2022.Community --override "--passive --wait --add Microsoft.VisualStudio.Workload.NativeDesktop --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --includeRecommended"
|
||||
winget install -e --id GoLang.Go
|
||||
winget install -e --id Git.Git
|
||||
```
|
||||
|
||||
3. Install vcpkg:
|
||||
|
||||
```powershell
|
||||
cd C:\
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
.\vcpkg\bootstrap-vcpkg.bat
|
||||
```
|
||||
|
||||
4. [Download the latest version of the watcher library for Windows](https://github.com/e-dant/watcher/releases) and extract it to a directory named `C:\watcher`
|
||||
5. [Download the latest **Thread Safe** version of PHP and of the PHP SDK for Windows](https://windows.php.net/download/), extract them in directories named `C:\php` and `C:\php-devel`
|
||||
6. Clone the FrankenPHP Git repository:
|
||||
|
||||
```powershell
|
||||
git clone https://github.com/php/frankenphp C:\frankenphp
|
||||
cd C:\frankenphp
|
||||
```
|
||||
|
||||
7. Install the dependencies:
|
||||
|
||||
```powershell
|
||||
C:\vcpkg\vcpkg.exe install
|
||||
```
|
||||
|
||||
8. Configure the needed environment variables (PowerShell):
|
||||
|
||||
```powershell
|
||||
$env:PATH += ';C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\bin'
|
||||
$env:CC = 'clang'
|
||||
$env:CXX = 'clang++'
|
||||
$env:CGO_CFLAGS = "-O0 -g -IC:\frankenphp\vcpkg_installed\x64-windows\include -IC:\watcher -IC:\php-devel\include -IC:\php-devel\include\main -IC:\php-devel\include\TSRM -IC:\php-devel\include\Zend -IC:\php-devel\include\ext"
|
||||
$env:CGO_LDFLAGS = '-LC:\frankenphp\vcpkg_installed\x64-windows\lib -lbrotlienc -LC:\watcher -llibwatcher-c -LC:\php -LC:\php-devel\lib -lphp8ts -lphp8embed'
|
||||
```
|
||||
|
||||
9. Run the tests:
|
||||
|
||||
```powershell
|
||||
go test -race -ldflags '-extldflags="-fuse-ld=lld"' ./...
|
||||
cd caddy
|
||||
go test -race -ldflags '-extldflags="-fuse-ld=lld"' -tags nobadger,nomysql,nopgx ./...
|
||||
cd ..
|
||||
```
|
||||
|
||||
10. Build the binary:
|
||||
|
||||
```powershell
|
||||
cd caddy/frankenphp
|
||||
go build -ldflags '-extldflags="-fuse-ld=lld"' -tags nobadger,nomysql,nopgx
|
||||
cd ../..
|
||||
```
|
||||
|
||||
## Building Docker Images Locally
|
||||
|
||||
Print bake plan:
|
||||
Print Bake plan:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --print
|
||||
@@ -125,7 +192,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
|
||||
```
|
||||
|
||||
2. Replace your current version of `frankenphp` by the debug FrankenPHP executable
|
||||
2. Replace your current version of `frankenphp` with the debug FrankenPHP executable
|
||||
3. Start FrankenPHP as usual (alternatively, you can directly start FrankenPHP with GDB: `gdb --args frankenphp run`)
|
||||
4. Attach to the process with GDB:
|
||||
|
||||
@@ -155,7 +222,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
|
||||
```patch
|
||||
- name: Set CGO flags
|
||||
run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
|
||||
run: echo "CGO_CFLAGS=-O0 -g $(php-config --includes)" >> "$GITHUB_ENV"
|
||||
+ - run: |
|
||||
+ sudo apt install gdb
|
||||
+ mkdir -p /home/runner/.config/gdb/
|
||||
@@ -207,7 +274,7 @@ strace -e 'trace=!futex,epoll_ctl,epoll_pwait,tgkill,rt_sigreturn' -p 1
|
||||
|
||||
## Translating the Documentation
|
||||
|
||||
To translate the documentation and the site in a new language,
|
||||
To translate the documentation and the site into a new language,
|
||||
follow these steps:
|
||||
|
||||
1. Create a new directory named with the language's 2-character ISO code in this repository's `docs/` directory
|
||||
@@ -215,6 +282,6 @@ follow these steps:
|
||||
3. Copy the `README.md` and `CONTRIBUTING.md` files from the root directory to the new directory
|
||||
4. Translate the content of the files, but don't change the filenames, also don't translate strings starting with `> [!` (it's special markup for GitHub)
|
||||
5. Create a Pull Request with the translations
|
||||
6. In the [site repository](https://github.com/dunglas/frankenphp-website/tree/main), copy and translate the translation files in the `content/`, `data/` and `i18n/` directories
|
||||
6. In the [site repository](https://github.com/dunglas/frankenphp-website/tree/main), copy and translate the translation files in the `content/`, `data/`, and `i18n/` directories
|
||||
7. Translate the values in the created YAML file
|
||||
8. Open a Pull Request on the site repository
|
||||
|
||||
@@ -93,7 +93,8 @@ RUN --mount=type=secret,id=github-token \
|
||||
sed 's/"//g' | \
|
||||
xargs curl -L | \
|
||||
tar xz --strip-components 1 && \
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && \
|
||||
# -Wno-error=use-after-free: GCC 12 on Bookworm i386 emits a spurious warning in libstdc++ basic_string.h
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-Wno-error=use-after-free" && \
|
||||
cmake --build build && \
|
||||
cmake --install build && \
|
||||
ldconfig
|
||||
@@ -117,7 +118,7 @@ ENV CGO_LDFLAGS="-L/usr/local/lib -lssl -lcrypto -lreadline -largon2 -lcurl -lon
|
||||
|
||||
WORKDIR /go/src/app/caddy/frankenphp
|
||||
RUN GOBIN=/usr/local/bin \
|
||||
../../go.sh install -ldflags "-w -s -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" -buildvcs=true && \
|
||||
../../go.sh install -ldflags "-w -s -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy' -X 'github.com/caddyserver/caddy/v2/modules/caddyhttp.ServerHeader=FrankenPHP Caddy'" -buildvcs=true && \
|
||||
setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
cp Caddyfile /etc/frankenphp/Caddyfile && \
|
||||
frankenphp version && \
|
||||
|
||||
24
README.md
24
README.md
@@ -4,7 +4,7 @@
|
||||
|
||||
FrankenPHP is a modern application server for PHP built on top of the [Caddy](https://caddyserver.com/) web server.
|
||||
|
||||
FrankenPHP gives superpowers to your PHP apps thanks to its stunning features: [_Early Hints_](https://frankenphp.dev/docs/early-hints/), [worker mode](https://frankenphp.dev/docs/worker/), [real-time capabilities](https://frankenphp.dev/docs/mercure/), automatic HTTPS, HTTP/2, and HTTP/3 support...
|
||||
FrankenPHP gives superpowers to your PHP apps thanks to its stunning features: [_Early Hints_](https://frankenphp.dev/docs/early-hints/), [worker mode](https://frankenphp.dev/docs/worker/), [real-time capabilities](https://frankenphp.dev/docs/mercure/), [hot reloading](https://frankenphp.dev/docs/hot-reload/), automatic HTTPS, HTTP/2, and HTTP/3 support...
|
||||
|
||||
FrankenPHP works with any PHP app and makes your Laravel and Symfony projects faster than ever thanks to their official integrations with the worker mode.
|
||||
|
||||
@@ -16,33 +16,39 @@ FrankenPHP can also be used as a standalone Go library to embed PHP in any app u
|
||||
|
||||
## Getting Started
|
||||
|
||||
On Windows, use [WSL](https://learn.microsoft.com/windows/wsl/) to run FrankenPHP.
|
||||
|
||||
### Install Script
|
||||
|
||||
You can copy this line into your terminal to automatically
|
||||
On Linux and macOS, copy this line into your terminal to automatically
|
||||
install an appropriate version for your platform:
|
||||
|
||||
```console
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
```
|
||||
|
||||
On Windows, run this in PowerShell:
|
||||
|
||||
```powershell
|
||||
irm https://frankenphp.dev/install.ps1 | iex
|
||||
```
|
||||
|
||||
### Standalone Binary
|
||||
|
||||
We provide static FrankenPHP binaries for development purposes on Linux and macOS
|
||||
containing [PHP 8.4](https://www.php.net/releases/8.4/en.php) and most popular PHP extensions.
|
||||
We provide FrankenPHP binaries for Linux, macOS and Windows
|
||||
containing [PHP 8.5](https://www.php.net/releases/8.5/).
|
||||
|
||||
Linux binaries are statically linked, so they can be used on any Linux distribution without installing any dependency. macOS binaries are also self-contained.
|
||||
They contain most popular PHP extensions.
|
||||
Windows archives contain the official PHP binary for Windows.
|
||||
|
||||
[Download FrankenPHP](https://github.com/php/frankenphp/releases)
|
||||
|
||||
**Installing extensions:** Most common extensions are bundled. It's not possible to install more extensions.
|
||||
|
||||
### rpm Packages
|
||||
|
||||
Our maintainers offer rpm packages for all systems using `dnf`. To install, run:
|
||||
|
||||
```console
|
||||
sudo dnf install https://rpm.henderkes.com/static-php-1-0.noarch.rpm
|
||||
sudo dnf module enable php-zts:static-8.4 # 8.2-8.5 available
|
||||
sudo dnf module enable php-zts:static-8.5 # 8.2-8.5 available
|
||||
sudo dnf install frankenphp
|
||||
```
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ ENV CGO_LDFLAGS="-lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLA
|
||||
|
||||
WORKDIR /go/src/app/caddy/frankenphp
|
||||
RUN GOBIN=/usr/local/bin \
|
||||
../../go.sh install -ldflags "-w -s -extldflags '-Wl,-z,stack-size=0x80000' -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" -buildvcs=true && \
|
||||
../../go.sh install -ldflags "-w -s -extldflags '-Wl,-z,stack-size=0x80000' -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy' -X 'github.com/caddyserver/caddy/v2/modules/caddyhttp.ServerHeader=FrankenPHP Caddy'" -buildvcs=true && \
|
||||
setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
([ -z "${NO_COMPRESS}" ] && upx --best /usr/local/bin/frankenphp || true) && \
|
||||
frankenphp version && \
|
||||
|
||||
@@ -62,7 +62,7 @@ fi
|
||||
if [ -z "${PHP_VERSION}" ]; then
|
||||
get_latest_php_version() {
|
||||
input="$1"
|
||||
json=$(curl -s "https://www.php.net/releases/index.php?json&version=$input")
|
||||
json=$(curl -fsSL "https://www.php.net/releases/index.php?json&version=$input" 2>/dev/null || curl -fsSL "https://phpmirror.static-php.dev/releases/index.php?json&version=$input")
|
||||
latest=$(echo "$json" | jq -r '.version')
|
||||
|
||||
if [[ "$latest" == "$input"* ]]; then
|
||||
|
||||
25
caddy/app.go
25
caddy/app.go
@@ -55,6 +55,8 @@ type FrankenPHPApp struct {
|
||||
PhpIni map[string]string `json:"php_ini,omitempty"`
|
||||
// The maximum amount of time a request may be stalled waiting for a thread
|
||||
MaxWaitTime time.Duration `json:"max_wait_time,omitempty"`
|
||||
// The maximum amount of time an autoscaled thread may be idle before being deactivated
|
||||
MaxIdleTime time.Duration `json:"max_idle_time,omitempty"`
|
||||
|
||||
opts []frankenphp.Option
|
||||
metrics frankenphp.Metrics
|
||||
@@ -150,6 +152,7 @@ func (f *FrankenPHPApp) Start() error {
|
||||
frankenphp.WithMetrics(f.metrics),
|
||||
frankenphp.WithPhpIni(f.PhpIni),
|
||||
frankenphp.WithMaxWaitTime(f.MaxWaitTime),
|
||||
frankenphp.WithMaxIdleTime(f.MaxIdleTime),
|
||||
)
|
||||
|
||||
for _, w := range f.Workers {
|
||||
@@ -173,23 +176,22 @@ func (f *FrankenPHPApp) Start() error {
|
||||
}
|
||||
|
||||
func (f *FrankenPHPApp) Stop() error {
|
||||
ctx := caddy.ActiveContext()
|
||||
|
||||
if f.logger.Enabled(caddy.ActiveContext(), slog.LevelInfo) {
|
||||
f.logger.LogAttrs(ctx, slog.LevelInfo, "FrankenPHP stopped 🐘")
|
||||
if f.logger.Enabled(f.ctx, slog.LevelInfo) {
|
||||
f.logger.LogAttrs(f.ctx, slog.LevelInfo, "FrankenPHP stopped 🐘")
|
||||
}
|
||||
|
||||
// attempt a graceful shutdown if caddy is exiting
|
||||
// note: Exiting() is currently marked as 'experimental'
|
||||
// https://github.com/caddyserver/caddy/blob/e76405d55058b0a3e5ba222b44b5ef00516116aa/caddy.go#L810
|
||||
if caddy.Exiting() {
|
||||
frankenphp.DrainWorkers()
|
||||
frankenphp.Shutdown()
|
||||
}
|
||||
|
||||
// reset the configuration so it doesn't bleed into later tests
|
||||
f.Workers = nil
|
||||
f.NumThreads = 0
|
||||
f.MaxWaitTime = 0
|
||||
f.MaxIdleTime = 0
|
||||
|
||||
optionsMU.Lock()
|
||||
options = nil
|
||||
@@ -242,6 +244,17 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
}
|
||||
|
||||
f.MaxWaitTime = v
|
||||
case "max_idle_time":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
v, err := time.ParseDuration(d.Val())
|
||||
if err != nil {
|
||||
return d.Err("max_idle_time must be a valid duration (example: 30s)")
|
||||
}
|
||||
|
||||
f.MaxIdleTime = v
|
||||
case "php_ini":
|
||||
parseIniLine := func(d *caddyfile.Dispenser) error {
|
||||
key := d.Val()
|
||||
@@ -298,7 +311,7 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
|
||||
f.Workers = append(f.Workers, wc)
|
||||
default:
|
||||
return wrongSubDirectiveError("frankenphp", "num_threads, max_threads, php_ini, worker, max_wait_time", d.Val())
|
||||
return wrongSubDirectiveError("frankenphp", "num_threads, max_threads, php_ini, worker, max_wait_time, max_idle_time", d.Val())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,32 +20,62 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// waitForServerReady polls the server with retries until it responds to HTTP requests.
|
||||
// This handles a race condition during Caddy config reload on macOS where SO_REUSEPORT
|
||||
// can briefly route connections to the old listener being shut down,
|
||||
// initServer initializes a Caddy test server and waits for it to be ready.
|
||||
// After InitServer, it polls the server to handle a race condition on macOS where
|
||||
// SO_REUSEPORT can briefly route connections to the old listener being shut down,
|
||||
// resulting in "connection reset by peer".
|
||||
func waitForServerReady(t *testing.T, url string) {
|
||||
func initServer(t *testing.T, tester *caddytest.Tester, config string, format string) {
|
||||
t.Helper()
|
||||
tester.InitServer(config, format)
|
||||
|
||||
client := &http.Client{Timeout: 1 * time.Second}
|
||||
for range 10 {
|
||||
resp, err := client.Get(url)
|
||||
if err == nil {
|
||||
require.NoError(t, resp.Body.Close())
|
||||
|
||||
return
|
||||
require.Eventually(t, func() bool {
|
||||
resp, err := client.Get("http://localhost:" + testPort)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
require.NoError(t, resp.Body.Close())
|
||||
|
||||
return true
|
||||
}, 5*time.Second, 100*time.Millisecond, "server failed to become ready")
|
||||
}
|
||||
|
||||
var testPort = "9080"
|
||||
|
||||
// skipIfSymlinkNotValid skips the test if the given path is not a valid symlink
|
||||
func skipIfSymlinkNotValid(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
t.Skipf("symlink test skipped: cannot stat %s: %v", path, err)
|
||||
}
|
||||
|
||||
if info.Mode()&os.ModeSymlink == 0 {
|
||||
t.Skipf("symlink test skipped: %s is not a symlink (git may not support symlinks on this platform)", path)
|
||||
}
|
||||
}
|
||||
|
||||
// escapeMetricLabel escapes backslashes in label values for Prometheus text format
|
||||
func escapeMetricLabel(s string) string {
|
||||
return strings.ReplaceAll(s, "\\", "\\\\")
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// setup custom environment vars for TestOsEnv
|
||||
if os.Setenv("ENV1", "value1") != nil || os.Setenv("ENV2", "value2") != nil {
|
||||
fmt.Println("Failed to set environment variables for tests")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestPHP(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -75,7 +105,7 @@ func TestPHP(t *testing.T) {
|
||||
|
||||
func TestLargeRequest(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -104,7 +134,7 @@ func TestLargeRequest(t *testing.T) {
|
||||
func TestWorker(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -141,7 +171,7 @@ func TestGlobalAndModuleWorker(t *testing.T) {
|
||||
testPortNum, _ := strconv.Atoi(testPort)
|
||||
testPortTwo := strconv.Itoa(testPortNum + 1)
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -191,7 +221,7 @@ func TestGlobalAndModuleWorker(t *testing.T) {
|
||||
|
||||
func TestModuleWorkerInheritsEnv(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -216,7 +246,7 @@ func TestNamedModuleWorkers(t *testing.T) {
|
||||
testPortNum, _ := strconv.Atoi(testPort)
|
||||
testPortTwo := strconv.Itoa(testPortNum + 1)
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -265,7 +295,7 @@ func TestNamedModuleWorkers(t *testing.T) {
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -296,7 +326,7 @@ func TestEnv(t *testing.T) {
|
||||
|
||||
func TestJsonEnv(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
@@ -379,7 +409,7 @@ func TestJsonEnv(t *testing.T) {
|
||||
|
||||
func TestCustomCaddyVariablesInEnv(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -413,7 +443,7 @@ func TestCustomCaddyVariablesInEnv(t *testing.T) {
|
||||
|
||||
func TestPHPServerDirective(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -427,7 +457,6 @@ func TestPHPServerDirective(t *testing.T) {
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
waitForServerReady(t, "http://localhost:"+testPort)
|
||||
tester.AssertGetResponse("http://localhost:"+testPort, http.StatusOK, "I am by birth a Genevese (i not set)")
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/hello.txt", http.StatusOK, "Hello\n")
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/not-found.txt", http.StatusOK, "I am by birth a Genevese (i not set)")
|
||||
@@ -435,7 +464,7 @@ func TestPHPServerDirective(t *testing.T) {
|
||||
|
||||
func TestPHPServerDirectiveDisableFileServer(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -453,7 +482,6 @@ func TestPHPServerDirectiveDisableFileServer(t *testing.T) {
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
waitForServerReady(t, "http://localhost:"+testPort)
|
||||
tester.AssertGetResponse("http://localhost:"+testPort, http.StatusOK, "I am by birth a Genevese (i not set)")
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/not-found.txt", http.StatusOK, "I am by birth a Genevese (i not set)")
|
||||
}
|
||||
@@ -461,7 +489,7 @@ func TestPHPServerDirectiveDisableFileServer(t *testing.T) {
|
||||
func TestMetrics(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -542,7 +570,7 @@ func TestMetrics(t *testing.T) {
|
||||
func TestWorkerMetrics(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -573,6 +601,7 @@ func TestWorkerMetrics(t *testing.T) {
|
||||
`, "caddyfile")
|
||||
|
||||
workerName, _ := fastabs.FastAbs("../testdata/index.php")
|
||||
workerName = escapeMetricLabel(workerName)
|
||||
|
||||
// Make some requests
|
||||
for i := range 10 {
|
||||
@@ -642,7 +671,7 @@ func TestWorkerMetrics(t *testing.T) {
|
||||
func TestNamedWorkerMetrics(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -737,7 +766,7 @@ func TestNamedWorkerMetrics(t *testing.T) {
|
||||
func TestAutoWorkerConfig(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -760,6 +789,7 @@ func TestAutoWorkerConfig(t *testing.T) {
|
||||
`, "caddyfile")
|
||||
|
||||
workerName, _ := fastabs.FastAbs("../testdata/index.php")
|
||||
workerName = escapeMetricLabel(workerName)
|
||||
|
||||
// Make some requests
|
||||
for i := range 10 {
|
||||
@@ -835,8 +865,9 @@ func TestAllDefinedServerVars(t *testing.T) {
|
||||
expectedBody = strings.ReplaceAll(expectedBody, "{documentRoot}", documentRoot)
|
||||
expectedBody = strings.ReplaceAll(expectedBody, "\r\n", "\n")
|
||||
expectedBody = strings.ReplaceAll(expectedBody, "{testPort}", testPort)
|
||||
expectedBody = strings.ReplaceAll(expectedBody, documentRoot+"/", documentRoot+string(filepath.Separator))
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -868,7 +899,7 @@ func TestAllDefinedServerVars(t *testing.T) {
|
||||
|
||||
func TestPHPIniConfiguration(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -896,7 +927,7 @@ func TestPHPIniConfiguration(t *testing.T) {
|
||||
|
||||
func TestPHPIniBlockConfiguration(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -935,11 +966,8 @@ func testSingleIniConfiguration(tester *caddytest.Tester, key string, value stri
|
||||
}
|
||||
|
||||
func TestOsEnv(t *testing.T) {
|
||||
require.NoError(t, os.Setenv("ENV1", "value1"))
|
||||
require.NoError(t, os.Setenv("ENV2", "value2"))
|
||||
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -969,7 +997,7 @@ func TestOsEnv(t *testing.T) {
|
||||
|
||||
func TestMaxWaitTime(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1010,7 +1038,7 @@ func TestMaxWaitTime(t *testing.T) {
|
||||
|
||||
func TestMaxWaitTimeWorker(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1093,7 +1121,7 @@ func getStatusCode(url string, t *testing.T) int {
|
||||
func TestMultiWorkersMetrics(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1202,7 +1230,7 @@ func TestMultiWorkersMetrics(t *testing.T) {
|
||||
func TestDisabledMetrics(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1282,7 +1310,7 @@ func TestDisabledMetrics(t *testing.T) {
|
||||
func TestWorkerRestart(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1384,7 +1412,7 @@ func TestWorkerRestart(t *testing.T) {
|
||||
|
||||
func TestWorkerMatchDirective(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1417,7 +1445,7 @@ func TestWorkerMatchDirective(t *testing.T) {
|
||||
|
||||
func TestWorkerMatchDirectiveWithMultipleWorkers(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1458,7 +1486,7 @@ func TestWorkerMatchDirectiveWithMultipleWorkers(t *testing.T) {
|
||||
|
||||
func TestWorkerMatchDirectiveWithoutFileServer(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1491,7 +1519,7 @@ func TestWorkerMatchDirectiveWithoutFileServer(t *testing.T) {
|
||||
|
||||
func TestDd(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1515,7 +1543,7 @@ func TestDd(t *testing.T) {
|
||||
|
||||
func TestLog(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1545,6 +1573,7 @@ func TestLog(t *testing.T) {
|
||||
func TestSymlinkWorkerPaths(t *testing.T) {
|
||||
cwd, _ := os.Getwd()
|
||||
publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public")
|
||||
skipIfSymlinkNotValid(t, publicDir)
|
||||
|
||||
t.Run("NeighboringWorkerScript", func(t *testing.T) {
|
||||
// Scenario: neighboring worker script
|
||||
@@ -1552,7 +1581,7 @@ func TestSymlinkWorkerPaths(t *testing.T) {
|
||||
// When I execute `frankenphp php-server --listen localhost:8080 -w index.php` from `public`
|
||||
// Then I expect to see the worker script executed successfully
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1582,7 +1611,7 @@ func TestSymlinkWorkerPaths(t *testing.T) {
|
||||
// When I execute `frankenphp --listen localhost:8080 -w nested/index.php` from `public`
|
||||
// Then I expect to see the worker script executed successfully
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1612,7 +1641,7 @@ func TestSymlinkWorkerPaths(t *testing.T) {
|
||||
// When I execute `frankenphp --listen localhost:8080 -w public/index.php` from the root folder
|
||||
// Then I expect to see the worker script executed successfully
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1646,7 +1675,7 @@ func TestSymlinkWorkerPaths(t *testing.T) {
|
||||
// When I execute `frankenphp --listen localhost:8080 -w public/index.php -r public` from the root folder
|
||||
// Then I expect to see the worker script executed successfully
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1680,11 +1709,12 @@ func TestSymlinkResolveRoot(t *testing.T) {
|
||||
cwd, _ := os.Getwd()
|
||||
testDir := filepath.Join(cwd, "..", "testdata", "symlinks", "test")
|
||||
publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public")
|
||||
skipIfSymlinkNotValid(t, publicDir)
|
||||
|
||||
t.Run("ResolveRootSymlink", func(t *testing.T) {
|
||||
// Tests that resolve_root_symlink directive works correctly
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1712,7 +1742,7 @@ func TestSymlinkResolveRoot(t *testing.T) {
|
||||
t.Run("NoResolveRootSymlink", func(t *testing.T) {
|
||||
// Tests that symlinks are preserved when resolve_root_symlink is false (non-worker mode)
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1738,11 +1768,12 @@ func TestSymlinkResolveRoot(t *testing.T) {
|
||||
func TestSymlinkWorkerBehavior(t *testing.T) {
|
||||
cwd, _ := os.Getwd()
|
||||
publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public")
|
||||
skipIfSymlinkNotValid(t, publicDir)
|
||||
|
||||
t.Run("WorkerScriptFailsWithoutWorkerMode", func(t *testing.T) {
|
||||
// Tests that accessing a worker-only script without configuring it as a worker actually results in an error
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
@@ -1766,7 +1797,7 @@ func TestSymlinkWorkerBehavior(t *testing.T) {
|
||||
t.Run("MultipleRequests", func(t *testing.T) {
|
||||
// Tests that symlinked workers handle multiple requests correctly
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
initServer(t, tester, `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
|
||||
5
caddy/frankenphp/cbrotli.go
Normal file
5
caddy/frankenphp/cbrotli.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build !nobrotli
|
||||
|
||||
package main
|
||||
|
||||
import _ "github.com/dunglas/caddy-cbrotli"
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
// plug in Caddy modules here.
|
||||
_ "github.com/caddyserver/caddy/v2/modules/standard"
|
||||
_ "github.com/dunglas/caddy-cbrotli"
|
||||
_ "github.com/dunglas/frankenphp/caddy"
|
||||
_ "github.com/dunglas/mercure/caddy"
|
||||
_ "github.com/dunglas/vulcain/caddy"
|
||||
|
||||
100
caddy/go.mod
100
caddy/go.mod
@@ -7,13 +7,13 @@ replace github.com/dunglas/frankenphp => ../
|
||||
retract v1.0.0-rc.1 // Human error
|
||||
|
||||
require (
|
||||
github.com/caddyserver/caddy/v2 v2.11.1
|
||||
github.com/caddyserver/caddy/v2 v2.11.2
|
||||
github.com/caddyserver/certmagic v0.25.2
|
||||
github.com/dunglas/caddy-cbrotli v1.0.1
|
||||
github.com/dunglas/frankenphp v1.11.2
|
||||
github.com/dunglas/mercure v0.21.8
|
||||
github.com/dunglas/mercure/caddy v0.21.8
|
||||
github.com/dunglas/vulcain/caddy v1.2.1
|
||||
github.com/dunglas/frankenphp v1.12.1
|
||||
github.com/dunglas/mercure v0.21.11
|
||||
github.com/dunglas/mercure/caddy v0.21.11
|
||||
github.com/dunglas/vulcain/caddy v1.4.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
@@ -39,7 +39,7 @@ require (
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 // indirect
|
||||
github.com/MicahParks/jwkset v0.11.0 // indirect
|
||||
github.com/MicahParks/keyfunc/v3 v3.8.0 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.15.0 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.23.1 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
|
||||
@@ -62,9 +62,9 @@ require (
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dunglas/httpsfv v1.1.0 // indirect
|
||||
github.com/dunglas/skipfilter v1.0.0 // indirect
|
||||
github.com/dunglas/vulcain v1.2.1 // indirect
|
||||
github.com/dunglas/vulcain v1.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a // indirect
|
||||
github.com/e-dant/watcher v0.0.0-20260223030516-06f84a1314be // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
@@ -74,8 +74,8 @@ require (
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.5 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
@@ -85,12 +85,12 @@ require (
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/brotli/go/cbrotli v1.1.0 // indirect
|
||||
github.com/google/cel-go v0.27.0 // indirect
|
||||
github.com/google/certificate-transparency-go v1.3.2 // indirect
|
||||
github.com/google/certificate-transparency-go v1.3.3 // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
github.com/google/go-tspi v0.3.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
@@ -131,7 +131,7 @@ require (
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/otlptranslator v1.0.0 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/rs/cors v1.11.1 // indirect
|
||||
@@ -142,7 +142,7 @@ require (
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/slackhq/nebula v1.10.3 // indirect
|
||||
github.com/smallstep/certificates v0.30.0-rc2.0.20260211214201-20608299c29c // indirect
|
||||
github.com/smallstep/certificates v0.30.0-rc3 // indirect
|
||||
github.com/smallstep/cli-utils v0.12.2 // indirect
|
||||
github.com/smallstep/linkedca v0.25.0 // indirect
|
||||
github.com/smallstep/nosql v0.7.0 // indirect
|
||||
@@ -170,56 +170,56 @@ require (
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 // indirect
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.65.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.40.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.40.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.40.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.62.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.16.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.16.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.67.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.42.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.42.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.42.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.step.sm/crypto v0.76.2 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20260213171211-a408498e5541 // indirect
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/oauth2 v0.35.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/term v0.40.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
google.golang.org/api v0.267.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
||||
google.golang.org/grpc v1.79.1 // indirect
|
||||
google.golang.org/api v0.270.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/grpc v1.79.2 // indirect
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
204
caddy/go.sum
204
caddy/go.sum
@@ -46,8 +46,8 @@ github.com/MicahParks/keyfunc/v3 v3.8.0 h1:Hx2dgIjAXGk9slakM6rV9BOeaWDPEXXZ4Us8g
|
||||
github.com/MicahParks/keyfunc/v3 v3.8.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4 h1:4aKySrrg9G/5oRtJ3TrZLObVqxgQ9f1znCRBwEwjuVw=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.15.0 h1:gCbixa3UiG7g6WUZNVOfEEg2HTc1vR4OVdMkX8t1ZFc=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.15.0/go.mod h1:eq4wdNXxtJIS/oikeCzdX1rBzek7ANzbth041hrU8Q4=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
@@ -95,8 +95,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
||||
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/caddyserver/caddy/v2 v2.11.1 h1:C7sQpsFOC5CH+31KqJc7EoOf8mXrOEkFyYd6GpIqm/s=
|
||||
github.com/caddyserver/caddy/v2 v2.11.1/go.mod h1:EOKnXuSSGlq2SuItwQuEVIsY5bRRi7tPJNHDm99XQXo=
|
||||
github.com/caddyserver/caddy/v2 v2.11.2 h1:iOlpsSiSKqEW+SIXrcZsZ/NO74SzB/ycqqvAIEfIm64=
|
||||
github.com/caddyserver/caddy/v2 v2.11.2/go.mod h1:ASNYYmKhIVWWMGPfNxclI5DqKEgU3FhmL+6NZWzQEag=
|
||||
github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc=
|
||||
github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg=
|
||||
github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
|
||||
@@ -152,21 +152,21 @@ github.com/dunglas/caddy-cbrotli v1.0.1 h1:mkg7EB1GmoyfBt3kY3mq4o/0bfnBeq7ZLQjmV
|
||||
github.com/dunglas/caddy-cbrotli v1.0.1/go.mod h1:uXABy3tjy1FABF+3JWKVh1ajFvIO/kfpwHaeZGSBaAY=
|
||||
github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54=
|
||||
github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
|
||||
github.com/dunglas/mercure v0.21.8 h1:D+SxSq0VqdB29lfMXrsvDkFvq/cTL94aKCC0R4heKV0=
|
||||
github.com/dunglas/mercure v0.21.8/go.mod h1:kt4RJpixJOcPN+x9Z53VBhpJYSdyEEzuu9/99vJIocQ=
|
||||
github.com/dunglas/mercure/caddy v0.21.8 h1:jfWSRUoialL3iH1AlmrVAIoU8EbGrLLGd4r+nhbBalg=
|
||||
github.com/dunglas/mercure/caddy v0.21.8/go.mod h1:rU3iqkU44FASio9Fqqmwn50I0l7w67XDsXuKsqzSDrE=
|
||||
github.com/dunglas/mercure v0.21.11 h1:4Sd/Q77j8uh9SI5D9ZMg5sePlWs336+9CKxDQC1FV34=
|
||||
github.com/dunglas/mercure v0.21.11/go.mod h1:WPMgfqonUiO1qB+W8Tya63Ngag9ZwplGMXSOy8P/uMg=
|
||||
github.com/dunglas/mercure/caddy v0.21.11 h1:WnasC7EiqBPAB0CpBEPrm7vLiuL7o3BOVmfGDghnyVM=
|
||||
github.com/dunglas/mercure/caddy v0.21.11/go.mod h1:MlGm4jbpBV+9nizn03PDejTEM916z3WDP9zO/Yw8OYQ=
|
||||
github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4=
|
||||
github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w=
|
||||
github.com/dunglas/vulcain v1.2.1 h1:pkPwvIfoa/xmWSVUyhntbIKT+XO2VFMyhLKv1gA61O8=
|
||||
github.com/dunglas/vulcain v1.2.1/go.mod h1:Qv1hxHP8rMDp6ZrpQQPSOh1OibuAYScNNCL/46sCRXU=
|
||||
github.com/dunglas/vulcain/caddy v1.2.1 h1:Bh5awZ9WoNqsBv7OZfs1SktJqRgJaF5avI5oDmxs6lI=
|
||||
github.com/dunglas/vulcain/caddy v1.2.1/go.mod h1:8QrmLTfURmW2VgjTR6Gb9a53FrZjspFQfX5FTy/f6dw=
|
||||
github.com/dunglas/vulcain v1.4.0 h1:uGMTLKmw53yJNKBwCtD3GOmnmGw4SfsIqYfb3NEKvbA=
|
||||
github.com/dunglas/vulcain v1.4.0/go.mod h1:WJjUJ/anaMlV4JWUCLNTNkviPFQL817yaVW5ErfiaMY=
|
||||
github.com/dunglas/vulcain/caddy v1.4.0 h1:u377qYQwDKRA2/CcZ7yIbRehuY+AjQM5xpkIyynsn1c=
|
||||
github.com/dunglas/vulcain/caddy v1.4.0/go.mod h1:qu6VV33pP42D8tIZDURE0ngt2MBR9OE34lCeMBo8caM=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a h1:e/m9m8cJgjzw2Ol7tKTu4B/lM5F3Ym7ryKI+oyw0T8Y=
|
||||
github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM=
|
||||
github.com/e-dant/watcher v0.0.0-20260223030516-06f84a1314be h1:vqHrvilasyJcnru/0Z4FoojsQJUIfXGVplte7JtupfY=
|
||||
github.com/e-dant/watcher v0.0.0-20260223030516-06f84a1314be/go.mod h1:PmV4IVmBJVqT2NcfTGN4+sZ+qGe3PA0qkphAtOHeFG0=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
@@ -191,12 +191,12 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
|
||||
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
|
||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
|
||||
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
|
||||
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
|
||||
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
|
||||
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
@@ -222,8 +222,8 @@ github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76
|
||||
github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo=
|
||||
github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw=
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A=
|
||||
github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs=
|
||||
github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo=
|
||||
github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -238,8 +238,8 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||
@@ -350,8 +350,8 @@ github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTU
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
|
||||
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
@@ -379,8 +379,8 @@ github.com/slackhq/nebula v1.10.3 h1:EstYj8ODEcv6T0R9X5BVq1zgWZnyU5gtPzk99QF1PMU
|
||||
github.com/slackhq/nebula v1.10.3/go.mod h1:IL5TUQm4x9IFx2kCKPYm1gP47pwd5b8QGnnBH2RHnvs=
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
|
||||
github.com/smallstep/certificates v0.30.0-rc2.0.20260211214201-20608299c29c h1:XQpX0IPYUAoJ661YlgfOJmY48ZOhIbglw4E2gw9mcyc=
|
||||
github.com/smallstep/certificates v0.30.0-rc2.0.20260211214201-20608299c29c/go.mod h1:75NRLmYJq6ZcCb8ApJc+W1eL4oMYwjeufMJDHpv4rx4=
|
||||
github.com/smallstep/certificates v0.30.0-rc3 h1:Lx/NNJ4n+L3Pyx5NtVRGXeqviPPXTFFGLRiC1fCwU50=
|
||||
github.com/smallstep/certificates v0.30.0-rc3/go.mod h1:e5/ylYYpvnjCVZz6RpyOkpTe73EGPYoL+8TZZ5EtLjI=
|
||||
github.com/smallstep/cli-utils v0.12.2 h1:lGzM9PJrH/qawbzMC/s2SvgLdJPKDWKwKzx9doCVO+k=
|
||||
github.com/smallstep/cli-utils v0.12.2/go.mod h1:uCPqefO29goHLGqFnwk0i8W7XJu18X3WHQFRtOm/00Y=
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca h1:VX8L0r8vybH0bPeaIxh4NQzafKQiqvlOn8pmOXbFLO4=
|
||||
@@ -476,62 +476,62 @@ go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 h1:I/7S/yWobR3QHFLqHsJ8QOndoiFsj1VgHpQiq43KlUI=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.65.0/go.mod h1:jPF6gn3y1E+nozCAEQj3c6NZ8KY+tvAgSVfvoOJUFac=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 h1:2gApdml7SznX9szEKFjKjM4qGcGSvAybYLBY319XG3g=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.65.0/go.mod h1:0QqAGlbHXhmPYACG3n5hNzO5DnEqqtg4VcK5pr22RI0=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.65.0 h1:kTaCycF9Xkm8VBBvH0rJ4wFeRjtIV55Erk3uuVsIs5s=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.65.0/go.mod h1:rooPzAbXfxMX9fsPJjmOBg2SN4RhFEV8D7cfGK+N3tE=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.40.0 h1:4VIrh75jW4RTimUNx1DSk+6H9/nDr1FvmKoOVDh3K04=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.40.0/go.mod h1:B0dCov9KNQGlut3T8wZZjDnLXEXdBroM7bFsHh/gRos=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.40.0 h1:xariChe8OOVF3rNlfzGFgQc61npQmXhzZj/i82mxMfg=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.40.0/go.mod h1:72WvbdxbOfXaELEQfonFfOL6osvcVjI7uJEE8C2nkrs=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.40.0 h1:aXl9uobjJs5vquMLt9ZkI/3zIuz8XQ3TqOKSWx0/xdU=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.40.0/go.mod h1:ioMePqe6k6c/ovXSkmkMr1mbN5qRBGJxNTVop7/2XO0=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.40.0 h1:Lon8J5SPmWaL1Ko2TIlCNHJ42/J1b5XbJlgJaE/9m7I=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.40.0/go.mod h1:dKWtJTlp1Yj+8Cneye5idO46eRPIbi23qVuJYKjNnvY=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.62.0 h1:krvC4JMfIOVdEuNPTtQ0ZjCiXrybhv+uOHMfHRmnvVo=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.62.0/go.mod h1:fgOE6FM/swEnsVQCqCnbOfRV4tOnWPg7bVeo4izBuhQ=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 h1:ivlbaajBWJqhcCPniDqDJmRwj4lc6sRT+dCAVKNmxlQ=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0/go.mod h1:u/G56dEKDDwXNCVLsbSrllB2o8pbtFLUC4HpR66r2dc=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8=
|
||||
go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4=
|
||||
go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||
go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI=
|
||||
go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.67.0 h1:XhcQRf4MeqwQw96FcnatDAj6gwE19SUrWZ1VwNg77iE=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.67.0/go.mod h1:7OK06SuNIBIlc5Uq3JGQEsKHuXw29t9OJemvDYyP1dk=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.42.0 h1:Kbr3xDxs6kcxp5ThXTKWK2OtwLhNoXBVtqguNYcsZL0=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.42.0/go.mod h1:Jzw9hZHtxdpCN7x8S17UH59X/EiFivp6VXLs9bdM1OQ=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.42.0 h1:B2Pew5ufEtgkjLF+tSkXjgYZXQr9m7aCm1wLKB0URbU=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.42.0/go.mod h1:iPgUcSEF5DORW6+yNbdw/YevUy+QqJ508ncjhrRSCjc=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 h1:jP8unWI6q5kcb3gpGLjKDGaUa+JW+nHKWvpS/q+YuWA=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.42.0/go.mod h1:xd89e/pUyPatUP1C4z1UknD9jHptESO99tWyvd4mWD4=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.42.0 h1:uQjD1NNqX1+DfcAoWParPt1egNg9vC9gH4xarJ9Khxo=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.42.0/go.mod h1:yw/c2TCmQLIv109HBOCn6NlJ8Dp7MNfjMcqQZRnAMmg=
|
||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
|
||||
go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg=
|
||||
go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI=
|
||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
|
||||
go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw=
|
||||
go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
|
||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.step.sm/crypto v0.76.2 h1:JJ/yMcs/rmcCAwlo+afrHjq74XBFRTJw5B2y4Q4Z4c4=
|
||||
@@ -548,8 +548,8 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -580,10 +580,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -591,8 +591,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -609,8 +609,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -633,8 +633,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
@@ -646,16 +646,16 @@ golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE=
|
||||
google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
|
||||
google.golang.org/api v0.270.0 h1:4rJZbIuWSTohczG9mG2ukSDdt9qKx4sSSHIydTN26L4=
|
||||
google.golang.org/api v0.270.0/go.mod h1:5+H3/8DlXpQWrSz4RjGGwz5HfJAQSEI8Bc6JqQNH77U=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d h1:EocjzKLywydp5uZ5tJ79iP6Q0UjDnyiHkGRWxuPBP8s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
|
||||
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 h1:/WILD1UcXj/ujCxgoL/DvRgt2CP3txG8+FwkUbb9110=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1/go.mod h1:YNKnb2OAApgYn2oYY47Rn7alMr1zWjb2U8Q0aoGWiNc=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
|
||||
package caddy
|
||||
|
||||
type mercureContext struct {
|
||||
}
|
||||
import (
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
func (f *FrankenPHPModule) configureHotReload(_ *FrankenPHPApp) error {
|
||||
return nil
|
||||
type mercureContext struct {
|
||||
}
|
||||
|
||||
func (f *FrankenPHPModule) assignMercureHub(_ caddy.Context) {
|
||||
}
|
||||
|
||||
func createMercureRoute() (caddyhttp.Route, error) {
|
||||
return caddyhttp.Route{}, nil
|
||||
}
|
||||
|
||||
@@ -3,10 +3,15 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"github.com/dunglas/mercure"
|
||||
mercureCaddy "github.com/dunglas/mercure/caddy"
|
||||
"os"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -31,3 +36,36 @@ func (f *FrankenPHPModule) assignMercureHub(ctx caddy.Context) {
|
||||
f.Workers[i] = wc
|
||||
}
|
||||
}
|
||||
|
||||
func createMercureRoute() (caddyhttp.Route, error) {
|
||||
mercurePublisherJwtKey := os.Getenv("MERCURE_PUBLISHER_JWT_KEY")
|
||||
if mercurePublisherJwtKey == "" {
|
||||
return caddyhttp.Route{}, errors.New(`The "MERCURE_PUBLISHER_JWT_KEY" environment variable must be set to use the Mercure.rocks hub`)
|
||||
}
|
||||
|
||||
mercureSubscriberJwtKey := os.Getenv("MERCURE_SUBSCRIBER_JWT_KEY")
|
||||
if mercureSubscriberJwtKey == "" {
|
||||
return caddyhttp.Route{}, errors.New(`The "MERCURE_SUBSCRIBER_JWT_KEY" environment variable must be set to use the Mercure.rocks hub`)
|
||||
}
|
||||
|
||||
mercureRoute := caddyhttp.Route{
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
|
||||
mercureCaddy.Mercure{
|
||||
PublisherJWT: mercureCaddy.JWTConfig{
|
||||
Alg: os.Getenv("MERCURE_PUBLISHER_JWT_ALG"),
|
||||
Key: mercurePublisherJwtKey,
|
||||
},
|
||||
SubscriberJWT: mercureCaddy.JWTConfig{
|
||||
Alg: os.Getenv("MERCURE_SUBSCRIBER_JWT_ALG"),
|
||||
Key: mercureSubscriberJwtKey,
|
||||
},
|
||||
},
|
||||
"handler",
|
||||
"mercure",
|
||||
nil,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
return mercureRoute, nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -22,8 +23,6 @@ import (
|
||||
"github.com/dunglas/frankenphp/internal/fastabs"
|
||||
)
|
||||
|
||||
var serverHeader = []string{"FrankenPHP Caddy"}
|
||||
|
||||
// FrankenPHPModule represents the "php_server" and "php" directives in the Caddyfile
|
||||
// they are responsible for forwarding requests to FrankenPHP via "ServeHTTP"
|
||||
//
|
||||
@@ -249,8 +248,6 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c
|
||||
return caddyhttp.Error(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
// TODO: set caddyhttp.ServerHeader when https://github.com/caddyserver/caddy/pull/7338 will be released
|
||||
w.Header()["Server"] = serverHeader
|
||||
if err = frankenphp.ServeHTTP(w, fr); err != nil && !errors.As(err, &frankenphp.ErrRejected{}) {
|
||||
return caddyhttp.Error(http.StatusInternalServerError, err)
|
||||
}
|
||||
@@ -497,7 +494,13 @@ func parsePhpServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
|
||||
if indexFile != "off" {
|
||||
dirRedir := false
|
||||
dirIndex := "{http.request.uri.path}/" + indexFile
|
||||
// On Windows, first_exist_fallback doesn't work correctly because
|
||||
// glob is skipped and patterns are returned as-is without checking existence.
|
||||
// Use first_exist instead to ensure all files are checked.
|
||||
tryPolicy := "first_exist_fallback"
|
||||
if runtime.GOOS == "windows" {
|
||||
tryPolicy = "first_exist"
|
||||
}
|
||||
|
||||
// if tryFiles wasn't overridden, use a reasonable default
|
||||
if len(tryFiles) == 0 {
|
||||
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
mercureModule "github.com/dunglas/mercure/caddy"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
@@ -253,33 +251,9 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) {
|
||||
}
|
||||
|
||||
if mercure {
|
||||
mercurePublisherJwtKey := os.Getenv("MERCURE_PUBLISHER_JWT_KEY")
|
||||
if mercurePublisherJwtKey == "" {
|
||||
panic(`The "MERCURE_PUBLISHER_JWT_KEY" environment variable must be set to use the Mercure.rocks hub`)
|
||||
}
|
||||
|
||||
mercureSubscriberJwtKey := os.Getenv("MERCURE_SUBSCRIBER_JWT_KEY")
|
||||
if mercureSubscriberJwtKey == "" {
|
||||
panic(`The "MERCURE_SUBSCRIBER_JWT_KEY" environment variable must be set to use the Mercure.rocks hub`)
|
||||
}
|
||||
|
||||
mercureRoute := caddyhttp.Route{
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
|
||||
mercureModule.Mercure{
|
||||
PublisherJWT: mercureModule.JWTConfig{
|
||||
Alg: os.Getenv("MERCURE_PUBLISHER_JWT_ALG"),
|
||||
Key: mercurePublisherJwtKey,
|
||||
},
|
||||
SubscriberJWT: mercureModule.JWTConfig{
|
||||
Alg: os.Getenv("MERCURE_SUBSCRIBER_JWT_ALG"),
|
||||
Key: mercureSubscriberJwtKey,
|
||||
},
|
||||
},
|
||||
"handler",
|
||||
"mercure",
|
||||
nil,
|
||||
),
|
||||
},
|
||||
mercureRoute, err := createMercureRoute()
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
subroute.Routes = append(caddyhttp.RouteList{mercureRoute}, subroute.Routes...)
|
||||
|
||||
205
cgi.go
205
cgi.go
@@ -1,15 +1,15 @@
|
||||
package frankenphp
|
||||
|
||||
// #cgo nocallback frankenphp_register_bulk
|
||||
// #cgo nocallback frankenphp_register_variables_from_request_info
|
||||
// #cgo nocallback frankenphp_register_server_vars
|
||||
// #cgo nocallback frankenphp_register_variable_safe
|
||||
// #cgo nocallback frankenphp_register_single
|
||||
// #cgo noescape frankenphp_register_bulk
|
||||
// #cgo noescape frankenphp_register_variables_from_request_info
|
||||
// #cgo nocallback frankenphp_register_known_variable
|
||||
// #cgo nocallback frankenphp_init_persistent_string
|
||||
// #cgo noescape frankenphp_register_server_vars
|
||||
// #cgo noescape frankenphp_register_variable_safe
|
||||
// #cgo noescape frankenphp_register_single
|
||||
// #include <php_variables.h>
|
||||
// #cgo noescape frankenphp_register_known_variable
|
||||
// #cgo noescape frankenphp_init_persistent_string
|
||||
// #include "frankenphp.h"
|
||||
// #include <php_variables.h>
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
@@ -26,47 +26,6 @@ import (
|
||||
"golang.org/x/text/search"
|
||||
)
|
||||
|
||||
// Protocol versions, in Apache mod_ssl format: https://httpd.apache.org/docs/current/mod/mod_ssl.html
|
||||
// Note that these are slightly different from SupportedProtocols in caddytls/config.go
|
||||
var tlsProtocolStrings = map[uint16]string{
|
||||
tls.VersionTLS10: "TLSv1",
|
||||
tls.VersionTLS11: "TLSv1.1",
|
||||
tls.VersionTLS12: "TLSv1.2",
|
||||
tls.VersionTLS13: "TLSv1.3",
|
||||
}
|
||||
|
||||
// Known $_SERVER keys
|
||||
var knownServerKeys = []string{
|
||||
"CONTENT_LENGTH",
|
||||
"DOCUMENT_ROOT",
|
||||
"DOCUMENT_URI",
|
||||
"GATEWAY_INTERFACE",
|
||||
"HTTP_HOST",
|
||||
"HTTPS",
|
||||
"PATH_INFO",
|
||||
"PHP_SELF",
|
||||
"REMOTE_ADDR",
|
||||
"REMOTE_HOST",
|
||||
"REMOTE_PORT",
|
||||
"REQUEST_SCHEME",
|
||||
"SCRIPT_FILENAME",
|
||||
"SCRIPT_NAME",
|
||||
"SERVER_NAME",
|
||||
"SERVER_PORT",
|
||||
"SERVER_PROTOCOL",
|
||||
"SERVER_SOFTWARE",
|
||||
"SSL_PROTOCOL",
|
||||
"SSL_CIPHER",
|
||||
"AUTH_TYPE",
|
||||
"REMOTE_IDENT",
|
||||
"CONTENT_TYPE",
|
||||
"PATH_TRANSLATED",
|
||||
"QUERY_STRING",
|
||||
"REMOTE_USER",
|
||||
"REQUEST_METHOD",
|
||||
"REQUEST_URI",
|
||||
}
|
||||
|
||||
// cStringHTTPMethods caches C string versions of common HTTP methods
|
||||
// to avoid allocations in pinCString on every request.
|
||||
var cStringHTTPMethods = map[string]*C.char{
|
||||
@@ -87,7 +46,6 @@ var cStringHTTPMethods = map[string]*C.char{
|
||||
// Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
|
||||
func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
request := fc.request
|
||||
keys := mainThread.knownServerKeys
|
||||
// Separate remote IP and port; more lenient than net.SplitHostPort
|
||||
var ip, port string
|
||||
if idx := strings.LastIndex(request.RemoteAddr, ":"); idx > -1 {
|
||||
@@ -102,24 +60,21 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
ip = ip[1 : len(ip)-1]
|
||||
}
|
||||
|
||||
var https, sslProtocol, sslCipher, rs string
|
||||
var rs, https, sslProtocol *C.zend_string
|
||||
var sslCipher string
|
||||
|
||||
if request.TLS == nil {
|
||||
rs = "http"
|
||||
https = ""
|
||||
sslProtocol = ""
|
||||
rs = C.frankenphp_strings.httpLowercase
|
||||
https = C.frankenphp_strings.empty
|
||||
sslProtocol = C.frankenphp_strings.empty
|
||||
sslCipher = ""
|
||||
} else {
|
||||
rs = "https"
|
||||
https = "on"
|
||||
rs = C.frankenphp_strings.httpsLowercase
|
||||
https = C.frankenphp_strings.on
|
||||
|
||||
// and pass the protocol details in a manner compatible with Apache's mod_ssl
|
||||
// (which is why these have an SSL_ prefix and not TLS_).
|
||||
if v, ok := tlsProtocolStrings[request.TLS.Version]; ok {
|
||||
sslProtocol = v
|
||||
} else {
|
||||
sslProtocol = ""
|
||||
}
|
||||
sslProtocol = tlsProtocol(request.TLS.Version)
|
||||
|
||||
if request.TLS.CipherSuite != 0 {
|
||||
sslCipher = tls.CipherSuiteName(request.TLS.CipherSuite)
|
||||
@@ -139,9 +94,9 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
// even if the port is the default port for the scheme and could otherwise be omitted from a URI.
|
||||
// https://tools.ietf.org/html/rfc3875#section-4.1.15
|
||||
switch rs {
|
||||
case "https":
|
||||
case C.frankenphp_strings.httpsLowercase:
|
||||
reqPort = "443"
|
||||
case "http":
|
||||
case C.frankenphp_strings.httpLowercase:
|
||||
reqPort = "80"
|
||||
}
|
||||
}
|
||||
@@ -156,59 +111,59 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
requestURI = fc.requestURI
|
||||
}
|
||||
|
||||
C.frankenphp_register_bulk(
|
||||
trackVarsArray,
|
||||
packCgiVariable(keys["REMOTE_ADDR"], ip),
|
||||
packCgiVariable(keys["REMOTE_HOST"], ip),
|
||||
packCgiVariable(keys["REMOTE_PORT"], port),
|
||||
packCgiVariable(keys["DOCUMENT_ROOT"], fc.documentRoot),
|
||||
packCgiVariable(keys["PATH_INFO"], fc.pathInfo),
|
||||
packCgiVariable(keys["PHP_SELF"], ensureLeadingSlash(request.URL.Path)),
|
||||
packCgiVariable(keys["DOCUMENT_URI"], fc.docURI),
|
||||
packCgiVariable(keys["SCRIPT_FILENAME"], fc.scriptFilename),
|
||||
packCgiVariable(keys["SCRIPT_NAME"], fc.scriptName),
|
||||
packCgiVariable(keys["HTTPS"], https),
|
||||
packCgiVariable(keys["SSL_PROTOCOL"], sslProtocol),
|
||||
packCgiVariable(keys["REQUEST_SCHEME"], rs),
|
||||
packCgiVariable(keys["SERVER_NAME"], reqHost),
|
||||
packCgiVariable(keys["SERVER_PORT"], serverPort),
|
||||
// Variables defined in CGI 1.1 spec
|
||||
// Some variables are unused but cleared explicitly to prevent
|
||||
// the parent environment from interfering.
|
||||
// These values can not be overridden
|
||||
packCgiVariable(keys["CONTENT_LENGTH"], contentLength),
|
||||
packCgiVariable(keys["GATEWAY_INTERFACE"], "CGI/1.1"),
|
||||
packCgiVariable(keys["SERVER_PROTOCOL"], request.Proto),
|
||||
packCgiVariable(keys["SERVER_SOFTWARE"], "FrankenPHP"),
|
||||
packCgiVariable(keys["HTTP_HOST"], request.Host),
|
||||
// These values are always empty but must be defined:
|
||||
packCgiVariable(keys["AUTH_TYPE"], ""),
|
||||
packCgiVariable(keys["REMOTE_IDENT"], ""),
|
||||
// Request uri of the original request
|
||||
packCgiVariable(keys["REQUEST_URI"], requestURI),
|
||||
packCgiVariable(keys["SSL_CIPHER"], sslCipher),
|
||||
)
|
||||
requestPath := ensureLeadingSlash(request.URL.Path)
|
||||
|
||||
// These values are already present in the SG(request_info), so we'll register them from there
|
||||
C.frankenphp_register_variables_from_request_info(
|
||||
trackVarsArray,
|
||||
keys["CONTENT_TYPE"],
|
||||
keys["PATH_TRANSLATED"],
|
||||
keys["QUERY_STRING"],
|
||||
keys["REMOTE_USER"],
|
||||
keys["REQUEST_METHOD"],
|
||||
)
|
||||
}
|
||||
C.frankenphp_register_server_vars(trackVarsArray, C.frankenphp_server_vars{
|
||||
// approximate total length to avoid array re-hashing:
|
||||
// 28 CGI vars + headers + environment
|
||||
total_num_vars: C.size_t(28 + len(request.Header) + len(fc.env) + lengthOfEnv),
|
||||
|
||||
func packCgiVariable(key *C.zend_string, value string) C.ht_key_value_pair {
|
||||
return C.ht_key_value_pair{key, toUnsafeChar(value), C.size_t(len(value))}
|
||||
// CGI vars with variable values
|
||||
remote_addr: toUnsafeChar(ip),
|
||||
remote_addr_len: C.size_t(len(ip)),
|
||||
remote_host: toUnsafeChar(ip),
|
||||
remote_host_len: C.size_t(len(ip)),
|
||||
remote_port: toUnsafeChar(port),
|
||||
remote_port_len: C.size_t(len(port)),
|
||||
document_root: toUnsafeChar(fc.documentRoot),
|
||||
document_root_len: C.size_t(len(fc.documentRoot)),
|
||||
path_info: toUnsafeChar(fc.pathInfo),
|
||||
path_info_len: C.size_t(len(fc.pathInfo)),
|
||||
php_self: toUnsafeChar(requestPath),
|
||||
php_self_len: C.size_t(len(requestPath)),
|
||||
document_uri: toUnsafeChar(fc.docURI),
|
||||
document_uri_len: C.size_t(len(fc.docURI)),
|
||||
script_filename: toUnsafeChar(fc.scriptFilename),
|
||||
script_filename_len: C.size_t(len(fc.scriptFilename)),
|
||||
script_name: toUnsafeChar(fc.scriptName),
|
||||
script_name_len: C.size_t(len(fc.scriptName)),
|
||||
server_name: toUnsafeChar(reqHost),
|
||||
server_name_len: C.size_t(len(reqHost)),
|
||||
server_port: toUnsafeChar(serverPort),
|
||||
server_port_len: C.size_t(len(serverPort)),
|
||||
content_length: toUnsafeChar(contentLength),
|
||||
content_length_len: C.size_t(len(contentLength)),
|
||||
server_protocol: toUnsafeChar(request.Proto),
|
||||
server_protocol_len: C.size_t(len(request.Proto)),
|
||||
http_host: toUnsafeChar(request.Host),
|
||||
http_host_len: C.size_t(len(request.Host)),
|
||||
request_uri: toUnsafeChar(requestURI),
|
||||
request_uri_len: C.size_t(len(requestURI)),
|
||||
ssl_cipher: toUnsafeChar(sslCipher),
|
||||
ssl_cipher_len: C.size_t(len(sslCipher)),
|
||||
|
||||
// CGI vars with known values
|
||||
request_scheme: rs, // "http" or "https"
|
||||
ssl_protocol: sslProtocol, // values from tlsProtocol
|
||||
https: https, // "on" or empty
|
||||
})
|
||||
}
|
||||
|
||||
func addHeadersToServer(ctx context.Context, request *http.Request, trackVarsArray *C.zval) {
|
||||
for field, val := range request.Header {
|
||||
if k := mainThread.commonHeaders[field]; k != nil {
|
||||
if k := commonHeaders[field]; k != nil {
|
||||
v := strings.Join(val, ", ")
|
||||
C.frankenphp_register_single(k, toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
|
||||
C.frankenphp_register_known_variable(k, toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -227,8 +182,8 @@ func addPreparedEnvToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
fc.env = nil
|
||||
}
|
||||
|
||||
//export go_register_variables
|
||||
func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
|
||||
//export go_register_server_variables
|
||||
func go_register_server_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
|
||||
thread := phpThreads[threadIndex]
|
||||
fc := thread.frankenPHPContext()
|
||||
|
||||
@@ -410,8 +365,32 @@ func ensureLeadingSlash(path string) string {
|
||||
return "/" + path
|
||||
}
|
||||
|
||||
// toUnsafeChar returns a *C.char pointing at the backing bytes the Go string.
|
||||
// If C does not store the string, it may be passed directly in a Cgo call (most efficient).
|
||||
// If C stores the string, it must be pinned explicitly instead (inefficient).
|
||||
// C may never modify the string.
|
||||
func toUnsafeChar(s string) *C.char {
|
||||
sData := unsafe.StringData(s)
|
||||
|
||||
return (*C.char)(unsafe.Pointer(sData))
|
||||
return (*C.char)(unsafe.Pointer(unsafe.StringData(s)))
|
||||
}
|
||||
|
||||
// initialize a global zend_string that must never be freed and is ignored by GC
|
||||
func newPersistentZendString(str string) *C.zend_string {
|
||||
return C.frankenphp_init_persistent_string(toUnsafeChar(str), C.size_t(len(str)))
|
||||
}
|
||||
|
||||
// Protocol versions, in Apache mod_ssl format: https://httpd.apache.org/docs/current/mod/mod_ssl.html
|
||||
// Note that these are slightly different from SupportedProtocols in caddytls/config.go
|
||||
func tlsProtocol(proto uint16) *C.zend_string {
|
||||
switch proto {
|
||||
case tls.VersionTLS10:
|
||||
return C.frankenphp_strings.tls1
|
||||
case tls.VersionTLS11:
|
||||
return C.frankenphp_strings.tls11
|
||||
case tls.VersionTLS12:
|
||||
return C.frankenphp_strings.tls12
|
||||
case tls.VersionTLS13:
|
||||
return C.frankenphp_strings.tls13
|
||||
default:
|
||||
return C.frankenphp_strings.empty
|
||||
}
|
||||
}
|
||||
|
||||
6
cgo.go
6
cgo.go
@@ -1,9 +1,11 @@
|
||||
package frankenphp
|
||||
|
||||
// #cgo darwin pkg-config: libxml-2.0
|
||||
// #cgo CFLAGS: -Wall -Werror
|
||||
// #cgo unix CFLAGS: -Wall -Werror
|
||||
// #cgo linux CFLAGS: -D_GNU_SOURCE
|
||||
// #cgo LDFLAGS: -lphp -lm -lutil
|
||||
// #cgo unix LDFLAGS: -lphp -lm -lutil
|
||||
// #cgo linux LDFLAGS: -ldl -lresolv
|
||||
// #cgo darwin LDFLAGS: -Wl,-rpath,/usr/local/lib -liconv -ldl
|
||||
// #cgo windows CFLAGS: -D_WINDOWS -DWINDOWS=1 -DZEND_WIN32=1 -DPHP_WIN32=1 -DWIN32 -D_MBCS -D_USE_MATH_DEFINES -DNDebug -DNDEBUG -DZEND_DEBUG=0 -DZTS=1 -DFD_SETSIZE=256
|
||||
// #cgo windows LDFLAGS: -lpthreadVC3
|
||||
import "C"
|
||||
|
||||
29
cli.go
Normal file
29
cli.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package frankenphp
|
||||
|
||||
// #include "frankenphp.h"
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// ExecuteScriptCLI executes the PHP script passed as parameter.
|
||||
// It returns the exit status code of the script.
|
||||
func ExecuteScriptCLI(script string, args []string) int {
|
||||
// Ensure extensions are registered before CLI execution
|
||||
registerExtensions()
|
||||
|
||||
cScript := C.CString(script)
|
||||
defer C.free(unsafe.Pointer(cScript))
|
||||
|
||||
argc, argv := convertArgs(args)
|
||||
defer freeArgs(argv)
|
||||
|
||||
return int(C.frankenphp_execute_script_cli(cScript, argc, (**C.char)(unsafe.Pointer(&argv[0])), false))
|
||||
}
|
||||
|
||||
func ExecutePHPCode(phpCode string) int {
|
||||
// Ensure extensions are registered before CLI execution
|
||||
registerExtensions()
|
||||
|
||||
cCode := C.CString(phpCode)
|
||||
defer C.free(unsafe.Pointer(cCode))
|
||||
return int(C.frankenphp_execute_script_cli(cCode, 0, nil, true))
|
||||
}
|
||||
55
cli_test.go
Normal file
55
cli_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package frankenphp_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExecuteScriptCLI(t *testing.T) {
|
||||
if _, err := os.Stat("internal/testcli/testcli"); err != nil {
|
||||
t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`")
|
||||
}
|
||||
|
||||
cmd := exec.Command("internal/testcli/testcli", "testdata/command.php", "foo", "bar")
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
assert.Error(t, err)
|
||||
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(err, &exitError) {
|
||||
assert.Equal(t, 3, exitError.ExitCode())
|
||||
}
|
||||
|
||||
stdoutStderrStr := string(stdoutStderr)
|
||||
|
||||
assert.Contains(t, stdoutStderrStr, `"foo"`)
|
||||
assert.Contains(t, stdoutStderrStr, `"bar"`)
|
||||
assert.Contains(t, stdoutStderrStr, "From the CLI")
|
||||
}
|
||||
|
||||
func TestExecuteCLICode(t *testing.T) {
|
||||
if _, err := os.Stat("internal/testcli/testcli"); err != nil {
|
||||
t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`")
|
||||
}
|
||||
|
||||
cmd := exec.Command("internal/testcli/testcli", "-r", "echo 'Hello World';")
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
assert.NoError(t, err)
|
||||
|
||||
stdoutStderrStr := string(stdoutStderr)
|
||||
assert.Equal(t, stdoutStderrStr, `Hello World`)
|
||||
}
|
||||
|
||||
func ExampleExecuteScriptCLI() {
|
||||
if len(os.Args) <= 1 {
|
||||
log.Println("Usage: my-program script.php")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(frankenphp.ExecuteScriptCLI(os.Args[1], os.Args))
|
||||
}
|
||||
@@ -1,17 +1,35 @@
|
||||
# 配置
|
||||
|
||||
FrankenPHP、Caddy 以及 Mercure 和 Vulcain 模块可以使用 [Caddy 支持的格式](https://caddyserver.com/docs/getting-started#your-first-config) 进行配置。
|
||||
FrankenPHP、Caddy 以及 [Mercure](mercure.md) 和 [Vulcain](https://vulcain.rocks) 模块可以使用 [Caddy 支持的格式](https://caddyserver.com/docs/getting-started#your-first-config) 进行配置。
|
||||
|
||||
在 [Docker 镜像](docker.md) 中,`Caddyfile` 位于 `/etc/frankenphp/Caddyfile`。
|
||||
静态二进制文件也会在执行 `frankenphp run` 命令的目录中查找 `Caddyfile`。
|
||||
你可以使用 `-c` 或 `--config` 选项指定自定义路径。
|
||||
最常见的格式是 `Caddyfile`,它是一种简单、易读的文本格式。默认情况下,FrankenPHP 会在当前目录中查找 `Caddyfile`。你可以使用 `-c` 或 `--config` 选项指定自定义路径。
|
||||
|
||||
PHP 本身可以[使用 `php.ini` 文件](https://www.php.net/manual/zh/configuration.file.php)进行配置。
|
||||
以下是用于服务 PHP 应用程序的最小 `Caddyfile` 示例:
|
||||
|
||||
根据你的安装方法,PHP 解释器将在上述位置查找配置文件。
|
||||
```caddyfile
|
||||
# 响应的主机名
|
||||
localhost
|
||||
|
||||
# 可选:提供文件的目录,否则默认为当前目录
|
||||
#root public/
|
||||
php_server
|
||||
```
|
||||
|
||||
一个更高级的 `Caddyfile`,支持更多功能并提供方便的环境变量,可以在 [FrankenPHP 仓库中](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile)找到,并随 Docker 镜像提供。
|
||||
|
||||
PHP 本身可以[使用 `php.ini` 文件](https://www.php.net/manual/en/configuration.file.php)进行配置。
|
||||
|
||||
根据你的安装方法,FrankenPHP 和 PHP 解释器将在以下位置查找配置文件。
|
||||
|
||||
## Docker
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: 主配置文件
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: 自动加载的附加配置文件
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/usr/local/etc/php/php.ini`(默认情况下不提供 `php.ini`)
|
||||
- 附加配置文件: `/usr/local/etc/php/conf.d/*.ini`
|
||||
- PHP 扩展: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
@@ -29,12 +47,24 @@ RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
|
||||
## RPM 和 Debian 包
|
||||
|
||||
- `php.ini`: `/etc/frankenphp/php.ini`(默认情况下提供带有生产预设的 `php.ini` 文件)
|
||||
- 附加配置文件: `/etc/frankenphp/php.d/*.ini`
|
||||
- PHP 扩展: `/usr/lib/frankenphp/modules/`
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: 主配置文件
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: 自动加载的附加配置文件
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/etc/php-zts/php.ini`(默认情况下提供带有生产预设的 `php.ini` 文件)
|
||||
- 附加配置文件: `/etc/php-zts/conf.d/*.ini`
|
||||
|
||||
## 静态二进制文件
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- 在当前工作目录: `Caddyfile`
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: 执行 `frankenphp run` 或 `frankenphp php-server` 的目录,然后是 `/etc/frankenphp/php.ini`
|
||||
- 附加配置文件: `/etc/frankenphp/php.d/*.ini`
|
||||
- PHP 扩展: 无法加载,将它们打包在二进制文件本身中
|
||||
@@ -55,15 +85,15 @@ localhost {
|
||||
}
|
||||
```
|
||||
|
||||
你还可以使用全局选项显式配置 FrankenPHP:
|
||||
`frankenphp` [全局选项](https://caddyserver.com/docs/caddyfile/concepts#global-options) 可用于配置 FrankenPHP。
|
||||
你还可以使用 `frankenphp` [全局选项](https://caddyserver.com/docs/caddyfile/concepts#global-options) 显式配置 FrankenPHP:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
num_threads <num_threads> # 设置要启动的 PHP 线程数量。默认:可用 CPU 数量的 2 倍。
|
||||
max_threads <max_threads> # 限制可以在运行时启动的额外 PHP 线程的数量。默认值:num_threads。可以设置为 'auto'。
|
||||
max_threads <num_threads> # 限制可以在运行时启动的额外 PHP 线程的数量。默认值:num_threads。可以设置为 'auto'。
|
||||
max_wait_time <duration> # 设置请求在超时之前可以等待的最大时间,直到找到一个空闲的 PHP 线程。 默认:禁用。
|
||||
max_idle_time <duration> # 设置一个自动扩展的线程在被停用之前可以空闲的最长时间。默认:5s。
|
||||
php_ini <key> <value> # 设置一个 php.ini 指令。可以多次使用以设置多个指令。
|
||||
worker {
|
||||
file <path> # 设置工作脚本的路径。
|
||||
@@ -79,7 +109,7 @@ localhost {
|
||||
# ...
|
||||
```
|
||||
|
||||
或者,您可以使用 `worker` 选项的一行简短形式。
|
||||
或者,您可以使用 `worker` 选项的一行简短形式:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
@@ -91,7 +121,7 @@ localhost {
|
||||
# ...
|
||||
```
|
||||
|
||||
如果您在同一服务器上服务多个应用程序,您还可以定义多个工作线程:
|
||||
如果您在同一服务器上服务多个应用程序,您还可以定义多个工作线程:
|
||||
|
||||
```caddyfile
|
||||
app.example.com {
|
||||
@@ -113,12 +143,12 @@ other.example.com {
|
||||
# ...
|
||||
```
|
||||
|
||||
使用 `php_server` 指令通常是您需要的。
|
||||
使用 `php_server` 指令通常是您需要的,
|
||||
但是如果你需要完全控制,你可以使用更低级的 `php` 指令。
|
||||
`php` 指令将所有输入传递给 PHP,而不是先检查是否
|
||||
是一个PHP文件。在[性能页面](performance.md#try_files)中了解更多关于它的信息。
|
||||
|
||||
使用 `php_server` 指令等同于以下配置:
|
||||
使用 `php_server` 指令等同于以下配置:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
@@ -152,7 +182,7 @@ php_server [<matcher>] {
|
||||
file_server off # 禁用内置的 file_server 指令。
|
||||
worker { # 为此服务器创建特定的worker。可以多次指定以创建多个workers。
|
||||
file <path> # 设置工作脚本的路径,可以相对于 php_server 根目录
|
||||
num <num> # 设置要启动的 PHP 线程数,默认为可用数量的 2 倍
|
||||
num <num> # 设置要启动的 PHP 线程数,默认为可用 CPU 数量的 2 倍
|
||||
name <name> # 为worker设置名称,用于日志和指标。默认值:worker文件的绝对路径。定义在 php_server 块中时,始终以 m# 开头。
|
||||
watch <path> # 设置要监视文件更改的路径。可以为多个路径多次指定。
|
||||
env <key> <value> # 设置一个额外的环境变量为给定值。可以多次指定以设置多个环境变量。此工作进程的环境变量也从 php_server 父进程继承,但可以在此处覆盖。
|
||||
@@ -167,7 +197,7 @@ php_server [<matcher>] {
|
||||
由于 workers 只会启动您的应用程序一次并将其保留在内存中,
|
||||
因此对您的 PHP 文件的任何更改不会立即反映出来。
|
||||
|
||||
Wworkers 可以通过 `watch` 指令在文件更改时重新启动。
|
||||
Workers 可以通过 `watch` 指令在文件更改时重新启动。
|
||||
这对开发环境很有用。
|
||||
|
||||
```caddyfile
|
||||
@@ -181,8 +211,10 @@ Wworkers 可以通过 `watch` 指令在文件更改时重新启动。
|
||||
}
|
||||
```
|
||||
|
||||
如果没有指定 `watch` 目录,它将回退到 `./**/*.{php,yaml,yml,twig,env}`,
|
||||
这将监视启动 FrankenPHP 进程的目录及其子目录中的所有 `.php`、`.yaml`、`.yml`、`.twig` 和 `.env` 文件。
|
||||
此功能通常与[热重载](hot-reload.md)结合使用。
|
||||
|
||||
如果没有指定 `watch` 目录,它将回退到 `./**/*.{env,php,twig,yaml,yml}`,
|
||||
这将监视启动 FrankenPHP 进程的目录及其子目录中的所有 `.env`、`.php`、`.twig`、`.yaml` 和 `.yml` 文件。
|
||||
你也可以通过 [shell 文件名模式](https://pkg.go.dev/path/filepath#Match) 指定一个或多个目录:
|
||||
|
||||
```caddyfile
|
||||
@@ -213,8 +245,7 @@ Wworkers 可以通过 `watch` 指令在文件更改时重新启动。
|
||||
如果您想将工作脚本放在公共目录外,可以通过 `match` 指令来实现。
|
||||
|
||||
`match` 指令是 `try_files` 的一种优化替代方案,仅在 `php_server` 和 `php` 内部可用。
|
||||
以下示例将在公共目录中提供文件(如果存在)
|
||||
并将请求转发给与路径模式匹配的 worker。
|
||||
以下示例将始终在公共目录中提供文件(如果存在),否则会将请求转发给与路径模式匹配的 worker。
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
@@ -229,34 +260,6 @@ Wworkers 可以通过 `watch` 指令在文件更改时重新启动。
|
||||
}
|
||||
```
|
||||
|
||||
### 全双工 (HTTP/1)
|
||||
|
||||
在使用HTTP/1.x时,可能希望启用全双工模式,以便在完整主体之前写入响应。
|
||||
已被阅读。(例如:WebSocket、服务器发送事件等。)
|
||||
|
||||
这是一个可选配置,需要添加到 `Caddyfile` 中的全局选项中:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> 启用此选项可能导致不支持全双工的旧HTTP/1.x客户端死锁。
|
||||
> 这也可以通过配置 `CADDY_GLOBAL_OPTIONS` 环境配置来实现:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
您可以在[Caddy文档](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex)中找到有关此设置的更多信息。
|
||||
|
||||
## 环境变量
|
||||
|
||||
可以使用以下环境变量在不修改 `Caddyfile` 的情况下注入 Caddy 指令:
|
||||
@@ -268,15 +271,13 @@ CADDY_GLOBAL_OPTIONS="servers {
|
||||
|
||||
至于 FPM 和 CLI SAPIs,环境变量默认在 `$_SERVER` 超全局中暴露。
|
||||
|
||||
[the `variables_order` PHP 指令](https://www.php.net/manual/en/ini.core.php#ini.variables-order) 的 `S` 值始终等于 `ES`,无论 `E` 在该指令中的其他位置如何。
|
||||
`variables_order` PHP 指令中 `S` 的值始终等于 `ES`,无论 `E` 在该指令中的其他位置如何。
|
||||
|
||||
## PHP 配置
|
||||
|
||||
加载[附加的 PHP 配置文件](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan),
|
||||
`PHP_INI_SCAN_DIR`环境变量可以被使用。
|
||||
设置后,PHP 将加载给定目录中所有带有 `.ini` 扩展名的文件。
|
||||
为了加载[附加的 PHP 配置文件](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan),可以使用 `PHP_INI_SCAN_DIR` 环境变量。设置后,PHP 将加载给定目录中所有带有 `.ini` 扩展名的文件。
|
||||
|
||||
您还可以通过在 `Caddyfile` 中使用 `php_ini` 指令来更改 PHP 配置:
|
||||
您还可以通过在 `Caddyfile` 中使用 `php_ini` 指令来更改 PHP 配置:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
@@ -293,6 +294,42 @@ CADDY_GLOBAL_OPTIONS="servers {
|
||||
}
|
||||
```
|
||||
|
||||
### 禁用 HTTPS
|
||||
|
||||
默认情况下,FrankenPHP 会自动为所有主机名(包括 `localhost`)启用 HTTPS。
|
||||
如果你想禁用 HTTPS(例如在开发环境中),你可以将 `SERVER_NAME` 环境变量设置为 `http://` 或 `:80`:
|
||||
|
||||
或者,你可以使用 [Caddy 文档](https://caddyserver.com/docs/automatic-https#activation) 中描述的所有其他方法。
|
||||
|
||||
如果你想将 HTTPS 与 `127.0.0.1` IP 地址而不是 `localhost` 主机名一起使用,请阅读[已知问题](known-issues.md#using-https127001-with-docker)部分。
|
||||
|
||||
### 全双工 (HTTP/1)
|
||||
|
||||
在使用 HTTP/1.x 时,可能希望启用全双工模式,以便在整个请求体被读取之前允许写入响应。(例如:[Mercure](mercure.md)、WebSocket、Server-Sent Events 等)
|
||||
|
||||
这是一个可选配置,需要添加到 `Caddyfile` 中的全局选项中:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> 启用此选项可能导致不支持全双工的旧 HTTP/1.x 客户端死锁。
|
||||
> 这也可以通过 `CADDY_GLOBAL_OPTIONS` 环境变量配置来实现:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
您可以在[Caddy文档](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex)中找到有关此设置的更多信息。
|
||||
|
||||
## 启用调试模式
|
||||
|
||||
使用Docker镜像时,将`CADDY_GLOBAL_OPTIONS`环境变量设置为`debug`以启用调试模式:
|
||||
@@ -302,4 +339,3 @@ docker run -v $PWD:/app/public \
|
||||
-e CADDY_GLOBAL_OPTIONS=debug \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# 构建自定义 Docker 镜像
|
||||
|
||||
[FrankenPHP Docker 镜像](https://hub.docker.com/r/dunglas/frankenphp) 基于 [官方 PHP 镜像](https://hub.docker.com/_/php/)。提供适用于流行架构的 Debian 和 Alpine Linux 变体。推荐使用 Debian 变体。
|
||||
[FrankenPHP Docker 镜像](https://hub.docker.com/r/dunglas/frankenphp) 基于 [官方 PHP 镜像](https://hub.docker.com/_/php/)。
|
||||
提供适用于流行架构的 Debian 和 Alpine Linux 变体。
|
||||
推荐使用 Debian 变体。
|
||||
|
||||
提供 PHP 8.2、8.3、8.4 和 8.5 的变体。
|
||||
|
||||
标签遵循此模式:`dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
|
||||
- `<frankenphp-version>` 和 `<php-version>` 分别是 FrankenPHP 和 PHP 的版本号,范围从主版本(例如 `1`)、次版本(例如 `1.2`)到补丁版本(例如 `1.2.3`)。
|
||||
- `<os>` 要么是 `bookworm`(用于 Debian Bookworm)要么是 `alpine`(用于 Alpine 的最新稳定版本)。
|
||||
- `<os>` 要么是 `trixie`(用于 Debian Trixie),`bookworm`(用于 Debian Bookworm),要么是 `alpine`(用于 Alpine 的最新稳定版本)。
|
||||
|
||||
[浏览标签](https://hub.docker.com/r/dunglas/frankenphp/tags)。
|
||||
|
||||
@@ -28,6 +30,10 @@ docker build -t my-php-app .
|
||||
docker run -it --rm --name my-running-app my-php-app
|
||||
```
|
||||
|
||||
## 如何调整配置
|
||||
|
||||
为了方便,镜像中提供了一个包含有用环境变量的[默认 `Caddyfile`](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile)。
|
||||
|
||||
## 如何安装更多 PHP 扩展
|
||||
|
||||
[`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) 脚本在基础镜像中提供。
|
||||
@@ -79,13 +85,11 @@ FROM dunglas/frankenphp AS runner
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
```
|
||||
|
||||
FrankenPHP 提供的 `builder` 镜像包含 `libphp` 的编译版本。
|
||||
[用于构建的镜像](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) 适用于所有版本的 FrankenPHP 和 PHP,包括 Alpine 和 Debian。
|
||||
FrankenPHP 提供的[构建器镜像](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder)适用于所有版本的 FrankenPHP 和 PHP,同时支持 Debian 和 Alpine。
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> 如果你的系统基于 musl libc(Alpine Linux 上默认使用)并搭配 Symfony 使用,
|
||||
> 你可能需要 [增加默认堆栈大小](compile.md#using-xcaddy)。
|
||||
> 如果你正在使用 Alpine Linux 和 Symfony,你可能需要[增加默认堆栈大小](compile.md#using-xcaddy)。
|
||||
|
||||
## 默认启用 worker 模式
|
||||
|
||||
@@ -99,9 +103,9 @@ FROM dunglas/frankenphp
|
||||
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
|
||||
```
|
||||
|
||||
## 开发挂载宿主机目录
|
||||
## 在开发中使用卷
|
||||
|
||||
要使用 FrankenPHP 轻松开发,请从包含应用程序源代码的主机挂载目录作为 Docker 容器中的 volume:
|
||||
要使用 FrankenPHP 轻松开发,请从包含应用程序源代码的主机挂载目录作为 Docker 容器中的卷:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app
|
||||
@@ -109,7 +113,7 @@ docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-a
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> `--tty` 选项允许使用清晰可读的日志,而不是 JSON 日志。
|
||||
> `--tty` 选项允许使用易读的日志,而不是 JSON 日志。
|
||||
|
||||
使用 Docker Compose:
|
||||
|
||||
@@ -131,10 +135,10 @@ services:
|
||||
- ./:/app/public
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
# 在生产环境中注释以下行,它允许在 dev 中使用清晰可读日志
|
||||
# 在生产环境中注释以下行,它允许在开发环境中使用易读日志
|
||||
tty: true
|
||||
|
||||
# Caddy 证书和配置所需的挂载目录
|
||||
# Caddy 证书和配置所需的数据卷
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
@@ -152,23 +156,23 @@ FROM dunglas/frankenphp
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# 在基于 alpine 的发行版使用 "adduser -D ${USER}"
|
||||
# 在基于 Alpine 的发行版使用 "adduser -D ${USER}"
|
||||
useradd ${USER}; \
|
||||
# 需要开放80和443端口的权限
|
||||
# 添加绑定到 80 和 443 端口的额外能力
|
||||
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
|
||||
# 需要 /config/caddy 和 /data/caddy 目录的写入权限
|
||||
# 赋予 /config/caddy 和 /data/caddy 目录的写入权限
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
### 无权限运行
|
||||
### 在不使用能力的情况下运行
|
||||
|
||||
即使在无根运行时,FrankenPHP 也需要 `CAP_NET_BIND_SERVICE` 权限来将
|
||||
即使在无根运行时,FrankenPHP 也需要 `CAP_NET_BIND_SERVICE` 能力来将
|
||||
Web 服务器绑定到特权端口(80 和 443)。
|
||||
|
||||
如果你在非特权端口(1024 及以上)上公开 FrankenPHP,则可以以非 root 用户身份运行
|
||||
Web 服务器,并且不需要任何权限:
|
||||
Web 服务器,并且不需要任何能力:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
@@ -176,11 +180,11 @@ FROM dunglas/frankenphp
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# 在基于 alpine 的发行版使用 "adduser -D ${USER}"
|
||||
# 在基于 Alpine 的发行版使用 "adduser -D ${USER}"
|
||||
useradd ${USER}; \
|
||||
# 移除默认权限
|
||||
# 移除默认能力
|
||||
setcap -r /usr/local/bin/frankenphp; \
|
||||
# 给予 /config/caddy 和 /data/caddy 写入权限
|
||||
# 赋予 /config/caddy 和 /data/caddy 目录的写入权限
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
@@ -191,14 +195,85 @@ USER ${USER}
|
||||
|
||||
## 更新
|
||||
|
||||
Docker 镜像会按照以下条件更新:
|
||||
Docker 镜像会在以下情况下构建:
|
||||
|
||||
- 发布新的版本后
|
||||
- 每日 4:00(UTC 时间)检查新的 PHP 镜像
|
||||
- 每日 UTC 时间上午 4 点,如果新的官方 PHP 镜像可用
|
||||
|
||||
## 强化镜像
|
||||
|
||||
为了进一步减少 FrankenPHP Docker 镜像的攻击面和大小,还可以基于 [Google distroless](https://github.com/GoogleContainerTools/distroless) 或 [Docker hardened](https://www.docker.com/products/hardened-images) 镜像构建它们。
|
||||
|
||||
> [!WARNING]
|
||||
> 这些最小化的基础镜像不包含 shell 或包管理器,这使得调试更加困难。因此,仅在安全性优先级很高的情况下,才推荐将其用于生产环境。
|
||||
|
||||
当添加额外的 PHP 扩展时,你需要一个中间构建阶段:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp AS builder
|
||||
|
||||
# 在此处添加额外的 PHP 扩展
|
||||
RUN install-php-extensions pdo_mysql pdo_pgsql #...
|
||||
|
||||
# 将 frankenphp 和所有已安装扩展的共享库复制到临时位置
|
||||
# 你也可以通过分析 frankenphp 二进制文件和每个扩展 .so 文件的 ldd 输出手动执行此步骤
|
||||
RUN apt-get update && apt-get install -y libtree && \
|
||||
EXT_DIR="$(php -r 'echo ini_get("extension_dir");')" && \
|
||||
FRANKENPHP_BIN="$(which frankenphp)"; \
|
||||
LIBS_TMP_DIR="/tmp/libs"; \
|
||||
mkdir -p "$LIBS_TMP_DIR"; \
|
||||
for target in "$FRANKENPHP_BIN" $(find "$EXT_DIR" -maxdepth 2 -type f -name "*.so"); do \
|
||||
libtree -pv "$target" | sed 's/.*── \(.*\) \[.*/\1/' | grep -v "^$target" | while IFS= read -r lib; do \
|
||||
[ -z "$lib" ] && continue; \
|
||||
base=$(basename "$lib"); \
|
||||
destfile="$LIBS_TMP_DIR/$base"; \
|
||||
if [ ! -f "$destfile" ]; then \
|
||||
cp "$lib" "$destfile"; \
|
||||
fi; \
|
||||
done; \
|
||||
done
|
||||
|
||||
|
||||
# Distroless debian 基础镜像,确保它与基础镜像使用相同的 debian 版本
|
||||
FROM gcr.io/distroless/base-debian13
|
||||
# Docker hardened 镜像替代方案
|
||||
# FROM dhi.io/debian:13
|
||||
|
||||
# 你的应用程序和 Caddyfile 要复制到容器中的位置
|
||||
ARG PATH_TO_APP="."
|
||||
ARG PATH_TO_CADDYFILE="./Caddyfile"
|
||||
|
||||
# 将你的应用程序复制到 /app
|
||||
# 为了进一步强化,请确保只有可写路径由非 root 用户拥有
|
||||
COPY --chown=nonroot:nonroot "$PATH_TO_APP" /app
|
||||
COPY "$PATH_TO_CADDYFILE" /etc/caddy/Caddyfile
|
||||
|
||||
# 复制 frankenphp 和必要的库
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
|
||||
COPY --from=builder /tmp/libs /usr/lib
|
||||
|
||||
# 复制 php.ini 配置文件
|
||||
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
|
||||
COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
|
||||
|
||||
# Caddy 数据目录——即使在只读根文件系统上,也必须对非 root 用户可写
|
||||
ENV XDG_CONFIG_HOME=/config \
|
||||
XDG_DATA_HOME=/data
|
||||
COPY --from=builder --chown=nonroot:nonroot /data/caddy /data/caddy
|
||||
COPY --from=builder --chown=nonroot:nonroot /config/caddy /config/caddy
|
||||
|
||||
USER nonroot
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 运行 frankenphp 并使用提供的 Caddyfile 的入口点
|
||||
ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "-c", "/etc/caddy/Caddyfile"]
|
||||
```
|
||||
|
||||
## 开发版本
|
||||
|
||||
可在此 [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev) 仓库获取开发版本。
|
||||
每次在 GitHub 仓库的主分支有新的 commit 都会触发一次新的 build。
|
||||
开发版本可在 [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev) Docker 仓库中获取。
|
||||
每次将新的提交推送到 GitHub 仓库的主分支时,都会触发一次新的构建。
|
||||
|
||||
`latest*` tag 指向最新的 `main` 分支,且同样支持 `sha-<git-commit-hash>` 的 tag。
|
||||
`latest*` 标签指向 `main` 分支的 HEAD。形式为 `sha-<git-commit-hash>` 的标签也可用。
|
||||
|
||||
172
docs/cn/extension-workers.md
Normal file
172
docs/cn/extension-workers.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# 扩展 Worker
|
||||
|
||||
扩展 Worker 使您的 [FrankenPHP 扩展](https://frankenphp.dev/docs/extensions/) 能够管理专用的 PHP 线程池,用于执行后台任务、处理异步事件或实现自定义协议。适用于队列系统、事件监听器、调度器等。
|
||||
|
||||
## 注册 Worker
|
||||
|
||||
### 静态注册
|
||||
|
||||
如果您的 worker 不需要用户配置(固定的脚本路径、固定的线程数),您可以直接在 `init()` 函数中注册 worker。
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"github.com/dunglas/frankenphp"
|
||||
"github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
// 与 worker 池通信的全局句柄
|
||||
var worker frankenphp.Workers
|
||||
|
||||
func init() {
|
||||
// 模块加载时注册 worker。
|
||||
worker = caddy.RegisterWorkers(
|
||||
"my-internal-worker", // 唯一名称
|
||||
"worker.php", // 脚本路径(相对于执行目录或绝对路径)
|
||||
2, // 固定线程数
|
||||
// 可选的生命周期钩子
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
// 全局设置逻辑...
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 在 Caddy 模块中(用户可配置)
|
||||
|
||||
如果您计划共享您的扩展(例如通用的队列或事件监听器),您应该将其封装在一个 Caddy 模块中。这允许用户通过 `Caddyfile` 配置脚本路径和线程数。这需要实现 `caddy.Provisioner` 接口并解析 Caddyfile ([查看示例](https://github.com/dunglas/frankenphp-queue/blob/989120d394d66dd6c8e2101cac73dd622fade334/caddy.go))。
|
||||
|
||||
### 在纯 Go 应用程序中(嵌入式)
|
||||
|
||||
如果您 [在没有 Caddy 的标准 Go 应用程序中嵌入 FrankenPHP](https://pkg.go.dev/github.com/dunglas/frankenphp#example-ServeHTTP),您可以在初始化选项时使用 `frankenphp.WithExtensionWorkers` 注册扩展 worker。
|
||||
|
||||
## 与 Worker 交互
|
||||
|
||||
一旦 worker 池激活,您就可以向其分派任务。这可以在 [导出到 PHP 的原生函数](https://frankenphp.dev/docs/extensions/#writing-the-extension) 中完成,也可以从任何 Go 逻辑中完成,例如 cron 调度器、事件监听器 (MQTT、Kafka) 或任何其他 goroutine。
|
||||
|
||||
### 无头模式:`SendMessage`
|
||||
|
||||
使用 `SendMessage` 将原始数据直接传递给您的 worker 脚本。这非常适合队列或简单命令。
|
||||
|
||||
#### 示例:一个异步队列扩展
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_queue_push(mixed $data): bool
|
||||
func my_queue_push(data *C.zval) bool {
|
||||
// 1. 确保 worker 已准备就绪
|
||||
if worker == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. 分派给后台 worker
|
||||
_, err := worker.SendMessage(
|
||||
context.Background(), // 标准 Go 上下文
|
||||
unsafe.Pointer(data), // 要传递给 worker 的数据
|
||||
nil, // 可选的 http.ResponseWriter
|
||||
)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP 模拟:`SendRequest`
|
||||
|
||||
如果您的扩展需要调用一个期望标准 Web 环境(填充 `$_SERVER`、`$_GET` 等)的 PHP 脚本,请使用 `SendRequest`。
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_worker_http_request(string $path): string
|
||||
func my_worker_http_request(path *C.zend_string) unsafe.Pointer {
|
||||
// 1. 准备请求和记录器
|
||||
url := frankenphp.GoString(unsafe.Pointer(path))
|
||||
req, _ := http.NewRequest("GET", url, http.NoBody)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// 2. 分派给 worker
|
||||
if err := worker.SendRequest(rr, req); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3. 返回捕获的响应
|
||||
return frankenphp.PHPString(rr.Body.String(), false)
|
||||
}
|
||||
```
|
||||
|
||||
## Worker 脚本
|
||||
|
||||
PHP worker 脚本在一个循环中运行,可以处理原始消息和 HTTP 请求。
|
||||
|
||||
```php
|
||||
<?php
|
||||
// 在同一个循环中处理原始消息和 HTTP 请求
|
||||
$handler = function ($payload = null) {
|
||||
// 情况 1:消息模式
|
||||
if ($payload !== null) {
|
||||
return "Received payload: " . $payload;
|
||||
}
|
||||
|
||||
// 情况 2:HTTP 模式(标准 PHP 超全局变量会被填充)
|
||||
echo "Hello from page: " . $_SERVER['REQUEST_URI'];
|
||||
};
|
||||
|
||||
while (frankenphp_handle_request($handler)) {
|
||||
gc_collect_cycles();
|
||||
}
|
||||
```
|
||||
|
||||
## 生命周期钩子
|
||||
|
||||
FrankenPHP 提供了钩子,用于在生命周期的特定点执行 Go 代码。
|
||||
|
||||
| 钩子类型 | 选项名称 | 签名 | 上下文与用例 |
|
||||
| :------- | :--------------------------- | :----------------------- | :--------------------------------------------------- |
|
||||
| **服务器** | `WithWorkerOnServerStartup` | `func()` | 全局设置。**只运行一次**。示例:连接到 NATS/Redis。 |
|
||||
| **服务器** | `WithWorkerOnServerShutdown` | `func()` | 全局清理。**只运行一次**。示例:关闭共享连接。 |
|
||||
| **线程** | `WithWorkerOnReady` | `func(threadID int)` | 每线程设置。在线程启动时调用。接收线程 ID。 |
|
||||
| **线程** | `WithWorkerOnShutdown` | `func(threadID int)` | 每线程清理。接收线程 ID。 |
|
||||
|
||||
### 示例
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dunglas/frankenphp"
|
||||
frankenphpCaddy "github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
workerHandle = frankenphpCaddy.RegisterWorkers(
|
||||
"my-worker", "worker.php", 2,
|
||||
|
||||
// 服务器启动(全局)
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
fmt.Println("扩展:服务器正在启动...")
|
||||
}),
|
||||
|
||||
// 线程就绪(每线程)
|
||||
// 注意:此函数接受一个表示线程 ID 的整数
|
||||
frankenphp.WithWorkerOnReady(func(id int) {
|
||||
fmt.Printf("扩展:Worker 线程 #%d 已就绪。\n", id)
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
## 线程和 Worker 数量
|
||||
|
||||
默认情况下,FrankenPHP 启动的线程和 worker(在 worker 模式下)数量是可用 CPU 数量的 2 倍。
|
||||
默认情况下,FrankenPHP 启动的线程和 worker(在 worker 模式下)数量是可用 CPU 核心数的 2 倍。
|
||||
|
||||
适当的值很大程度上取决于你的应用程序是如何编写的、它做什么以及你的硬件。
|
||||
我们强烈建议更改这些值。为了获得最佳的系统稳定性,建议 `num_threads` x `memory_limit` < `available_memory`。
|
||||
@@ -41,11 +41,11 @@
|
||||
|
||||
另外,[一些错误只在使用 musl 时发生](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl)。
|
||||
|
||||
在生产环境中,我们建议使用链接到 glibc 的 FrankenPHP。
|
||||
在生产环境中,我们建议使用链接到 glibc 的 FrankenPHP,并使用适当的优化级别进行编译。
|
||||
|
||||
这可以通过使用 Debian Docker 镜像(默认)、从我们的 [Releases](https://github.com/php/frankenphp/releases) 下载 -gnu 后缀二进制文件,或通过[从源代码编译 FrankenPHP](compile.md) 来实现。
|
||||
这可以通过使用 Debian Docker 镜像、使用[我们的维护者提供的 .deb、.rpm 或 .apk 包](https://pkgs.henderkes.com),或通过[从源代码编译 FrankenPHP](compile.md) 来实现。
|
||||
|
||||
或者,我们提供使用 [mimalloc 分配器](https://github.com/microsoft/mimalloc) 编译的静态 musl 二进制文件,这缓解了线程场景中的问题。
|
||||
对于更精简或更安全的容器,你可能需要考虑使用[强化的 Debian 镜像](docker.md#hardening-images)而不是 Alpine。
|
||||
|
||||
## Go 运行时配置
|
||||
|
||||
@@ -89,6 +89,18 @@ php_server {
|
||||
```
|
||||
|
||||
这可以显著减少不必要的文件操作数量。
|
||||
上述配置的 worker 等效项为:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
php_server { # 如果完全不需要文件服务器,请使用 "php" 而不是 "php_server"
|
||||
root /root/to/your/app
|
||||
worker /path/to/worker.php {
|
||||
match * # 将所有请求直接发送到 worker
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
另一种具有 0 个不必要文件系统操作的方法是改用 `php` 指令并按路径将
|
||||
文件与 PHP 分开。如果你的整个应用程序由一个入口文件提供服务,这种方法效果很好。
|
||||
@@ -155,3 +167,28 @@ FrankenPHP 使用官方 PHP 解释器。
|
||||
|
||||
有关更多详细信息,请阅读[专门的 Symfony 文档条目](https://symfony.com/doc/current/performance.html)
|
||||
(即使你不使用 Symfony,大多数提示也很有用)。
|
||||
|
||||
## 拆分线程池
|
||||
|
||||
应用程序与慢速外部服务交互是很常见的,例如在高负载下往往不可靠或持续需要 10 秒以上才能响应的 API。
|
||||
在这种情况下,将线程池拆分以拥有专用的“慢速”池可能会很有益。这可以防止慢速端点消耗所有服务器资源/线程,并限制指向慢速端点的请求并发性,类似于连接池。
|
||||
|
||||
```caddyfile
|
||||
example.com {
|
||||
php_server {
|
||||
root /app/public # 你的应用程序根目录
|
||||
worker index.php {
|
||||
match /slow-endpoint/* # 所有路径为 /slow-endpoint/* 的请求都由这个线程池处理
|
||||
num 1 # 匹配 /slow-endpoint/* 的请求至少有 1 个线程
|
||||
max_threads 20 # 如果需要,允许最多 20 个线程处理匹配 /slow-endpoint/* 的请求
|
||||
}
|
||||
worker index.php {
|
||||
match * # 所有其他请求单独处理
|
||||
num 1 # 其他请求至少有 1 个线程,即使慢速端点开始挂起
|
||||
max_threads 20 # 如果需要,允许最多 20 个线程处理其他请求
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通常,也建议通过使用消息队列等相关机制,异步处理非常慢的端点。
|
||||
|
||||
@@ -35,8 +35,13 @@ frankenphp php-server --worker /path/to/your/worker/script.php
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
|
||||
```
|
||||
|
||||
此功能通常与[热重载](hot-reload.md)结合使用。
|
||||
|
||||
## Symfony Runtime
|
||||
|
||||
> [!TIP]
|
||||
> 以下部分仅在 Symfony 7.4 之前是必需的,因为 Symfony 7.4 引入了对 FrankenPHP worker 模式的原生支持。
|
||||
|
||||
FrankenPHP 的 worker 模式由 [Symfony Runtime Component](https://symfony.com/doc/current/components/runtime.html) 支持。
|
||||
要在 worker 中启动任何 Symfony 应用程序,请安装 [PHP Runtime](https://github.com/php-runtime/runtime) 的 FrankenPHP 包:
|
||||
|
||||
@@ -67,9 +72,6 @@ docker run \
|
||||
<?php
|
||||
// public/index.php
|
||||
|
||||
// 防止客户端连接中断时 worker 脚本终止
|
||||
ignore_user_abort(true);
|
||||
|
||||
// 启动你的应用程序
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
@@ -78,9 +80,15 @@ $myApp->boot();
|
||||
|
||||
// 在循环外的处理器以获得更好的性能(减少工作量)
|
||||
$handler = static function () use ($myApp) {
|
||||
// 当收到请求时调用,
|
||||
// 超全局变量、php://input 等都会被重置
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
try {
|
||||
// 当收到请求时调用,
|
||||
// 超全局变量、php://input 等都会被重置
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
} catch (\Throwable $exception) {
|
||||
// `set_exception_handler` 仅在 worker 脚本结束时调用,
|
||||
// 这可能不是您所期望的,因此在此处捕获并处理异常
|
||||
(new \MyCustomExceptionHandler)->handleException($exception);
|
||||
}
|
||||
};
|
||||
|
||||
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
|
||||
@@ -144,7 +152,7 @@ curl -X POST http://localhost:2019/frankenphp/workers/restart
|
||||
但是,如果 worker 脚本在短时间内继续以非零退出代码失败
|
||||
(例如,脚本中有拼写错误),FrankenPHP 将崩溃并出现错误:`too many consecutive failures`。
|
||||
|
||||
可以在你的 [Caddyfile](config.md#caddyfile-配置) 中使用 `max_consecutive_failures` 选项配置连续失败的次数:
|
||||
可以在你的 [Caddyfile](config.md#caddyfile-config) 中使用 `max_consecutive_failures` 选项配置连续失败的次数:
|
||||
|
||||
```caddyfile
|
||||
frankenphp {
|
||||
@@ -176,4 +184,3 @@ $handler = static function () use ($workerServer) {
|
||||
};
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
@@ -96,6 +96,7 @@ You can also explicitly configure FrankenPHP using the [global option](https://c
|
||||
num_threads <num_threads> # Sets the number of PHP threads to start. Default: 2x the number of available CPUs.
|
||||
max_threads <num_threads> # Limits the number of additional PHP threads that can be started at runtime. Default: num_threads. Can be set to 'auto'.
|
||||
max_wait_time <duration> # Sets the maximum time a request may wait for a free PHP thread before timing out. Default: disabled.
|
||||
max_idle_time <duration> # Sets the maximum time an autoscaled thread may be idle before being deactivated. Default: 5s.
|
||||
php_ini <key> <value> # Set a php.ini directive. Can be used several times to set multiple directives.
|
||||
worker {
|
||||
file <path> # Sets the path to the worker script.
|
||||
|
||||
@@ -256,15 +256,16 @@ COPY "$PATH_TO_CADDYFILE" /etc/caddy/Caddyfile
|
||||
|
||||
# Copy frankenphp and necessary libs
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
COPY --from=builder --chown=nonroot:nonroot /usr/local/lib/php/extensions /usr/local/lib/php/extensions
|
||||
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
|
||||
COPY --from=builder /tmp/libs /usr/lib
|
||||
|
||||
# Copy php.ini configuration files
|
||||
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
|
||||
COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
|
||||
|
||||
# Create necessary caddy dirs
|
||||
# These dirs also need to be writable in case of a read-only root filesystem
|
||||
# Caddy data dirs — must be writable for nonroot, even on a read-only root filesystem
|
||||
ENV XDG_CONFIG_HOME=/config \
|
||||
XDG_DATA_HOME=/data
|
||||
COPY --from=builder --chown=nonroot:nonroot /data/caddy /data/caddy
|
||||
COPY --from=builder --chown=nonroot:nonroot /config/caddy /config/caddy
|
||||
|
||||
|
||||
220
docs/es/CONTRIBUTING.md
Normal file
220
docs/es/CONTRIBUTING.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Contribuir
|
||||
|
||||
## Compilar PHP
|
||||
|
||||
### Con Docker (Linux)
|
||||
|
||||
Construya la imagen Docker de desarrollo:
|
||||
|
||||
```console
|
||||
docker build -t frankenphp-dev -f dev.Dockerfile .
|
||||
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 -p 443:443 -p 443:443/udp -v $PWD:/go/src/app -it frankenphp-dev
|
||||
```
|
||||
|
||||
La imagen contiene las herramientas de desarrollo habituales (Go, GDB, Valgrind, Neovim...) y utiliza las siguientes ubicaciones de configuración de PHP:
|
||||
|
||||
- php.ini: `/etc/frankenphp/php.ini` Se proporciona un archivo php.ini con ajustes preestablecidos de desarrollo por defecto.
|
||||
- archivos de configuración adicionales: `/etc/frankenphp/php.d/*.ini`
|
||||
- extensiones php: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
Si su versión de Docker es inferior a 23.0, la construcción fallará debido a un [problema de patrón](https://github.com/moby/moby/pull/42676) en `.dockerignore`. Agregue los directorios a `.dockerignore`:
|
||||
|
||||
```patch
|
||||
!testdata/*.php
|
||||
!testdata/*.txt
|
||||
+!caddy
|
||||
+!internal
|
||||
```
|
||||
|
||||
### Sin Docker (Linux y macOS)
|
||||
|
||||
[Siga las instrucciones para compilar desde las fuentes](compile.md) y pase la bandera de configuración `--debug`.
|
||||
|
||||
## Ejecutar la suite de pruebas
|
||||
|
||||
```console
|
||||
go test -tags watcher -race -v ./...
|
||||
```
|
||||
|
||||
## Módulo Caddy
|
||||
|
||||
Construir Caddy con el módulo FrankenPHP:
|
||||
|
||||
```console
|
||||
cd caddy/frankenphp/
|
||||
go build -tags watcher,brotli,nobadger,nomysql,nopgx
|
||||
cd ../../
|
||||
```
|
||||
|
||||
Ejecutar Caddy con el módulo FrankenPHP:
|
||||
|
||||
```console
|
||||
cd testdata/
|
||||
../caddy/frankenphp/frankenphp run
|
||||
```
|
||||
|
||||
El servidor está configurado para escuchar en la dirección `127.0.0.1:80`:
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Si está usando Docker, deberá enlazar el puerto 80 del contenedor o ejecutar desde dentro del contenedor.
|
||||
|
||||
```console
|
||||
curl -vk http://127.0.0.1/phpinfo.php
|
||||
```
|
||||
|
||||
## Servidor de prueba mínimo
|
||||
|
||||
Construir el servidor de prueba mínimo:
|
||||
|
||||
```console
|
||||
cd internal/testserver/
|
||||
go build
|
||||
cd ../../
|
||||
```
|
||||
|
||||
Iniciar el servidor de prueba:
|
||||
|
||||
```console
|
||||
cd testdata/
|
||||
../internal/testserver/testserver
|
||||
```
|
||||
|
||||
El servidor está configurado para escuchar en la dirección `127.0.0.1:8080`:
|
||||
|
||||
```console
|
||||
curl -v http://127.0.0.1:8080/phpinfo.php
|
||||
```
|
||||
|
||||
## Construir localmente las imágenes Docker
|
||||
|
||||
Mostrar el plan de compilación:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --print
|
||||
```
|
||||
|
||||
Construir localmente las imágenes FrankenPHP para amd64:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/amd64"
|
||||
```
|
||||
|
||||
Construir localmente las imágenes FrankenPHP para arm64:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/arm64"
|
||||
```
|
||||
|
||||
Construir desde cero las imágenes FrankenPHP para arm64 y amd64 y subirlas a Docker Hub:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
```
|
||||
|
||||
## Depurar errores de segmentación con las compilaciones estáticas
|
||||
|
||||
1. Descargue la versión de depuración del binario FrankenPHP desde GitHub o cree su propia compilación estática incluyendo símbolos de depuración:
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.args.DEBUG_SYMBOLS=1 \
|
||||
--set "static-builder.platform=linux/amd64" \
|
||||
static-builder
|
||||
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
|
||||
```
|
||||
|
||||
2. Reemplace su versión actual de `frankenphp` por el ejecutable de depuración de FrankenPHP.
|
||||
3. Inicie FrankenPHP como de costumbre (alternativamente, puede iniciar FrankenPHP directamente con GDB: `gdb --args frankenphp run`).
|
||||
4. Adjunte el proceso con GDB:
|
||||
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
|
||||
5. Si es necesario, escriba `continue` en el shell de GDB.
|
||||
6. Haga que FrankenPHP falle.
|
||||
7. Escriba `bt` en el shell de GDB.
|
||||
8. Copie la salida.
|
||||
|
||||
## Depurar errores de segmentación en GitHub Actions
|
||||
|
||||
1. Abrir `.github/workflows/tests.yml`
|
||||
2. Activar los símbolos de depuración de la biblioteca PHP:
|
||||
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
|
||||
3. Activar `tmate` para conectarse al contenedor:
|
||||
|
||||
```patch
|
||||
- name: Set CGO flags
|
||||
run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
|
||||
+ - run: |
|
||||
+ sudo apt install gdb
|
||||
+ mkdir -p /home/runner/.config/gdb/
|
||||
+ printf "set auto-load safe-path /\nhandle SIG34 nostop noprint pass" > /home/runner/.config/gdb/gdbinit
|
||||
+ - uses: mxschmitt/action-tmate@v3
|
||||
```
|
||||
|
||||
4. Conectarse al contenedor.
|
||||
5. Abrir `frankenphp.go`.
|
||||
6. Activar `cgosymbolizer`:
|
||||
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
|
||||
7. Descargar el módulo: `go get`.
|
||||
8. Dentro del contenedor, puede usar GDB y similares:
|
||||
|
||||
```console
|
||||
go test -tags watcher -c -ldflags=-w
|
||||
gdb --args frankenphp.test -test.run ^MyTest$
|
||||
```
|
||||
|
||||
9. Cuando el error esté corregido, revierta todos los cambios.
|
||||
|
||||
## Recursos diversos para el desarrollo
|
||||
|
||||
- [Integración de PHP en uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
- [Integración de PHP en NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
- [Integración de PHP en Go (go-php)](https://github.com/deuill/go-php)
|
||||
- [Integración de PHP en Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
- [Integración de PHP en C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
- [Extending and Embedding PHP por Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
- [¿Qué es TSRMLS_CC, exactamente?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
- [Integración de PHP en Mac](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4)
|
||||
- [Bindings SDL](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
|
||||
## Recursos relacionados con Docker
|
||||
|
||||
- [Definición del archivo Bake](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
- [`docker buildx build`](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
|
||||
## Comando útil
|
||||
|
||||
```console
|
||||
apk add strace util-linux gdb
|
||||
strace -e 'trace=!futex,epoll_ctl,epoll_pwait,tgkill,rt_sigreturn' -p 1
|
||||
```
|
||||
|
||||
## Traducir la documentación
|
||||
|
||||
Para traducir la documentación y el sitio a un nuevo idioma, siga estos pasos:
|
||||
|
||||
1. Cree un nuevo directorio con el código ISO de 2 caracteres del idioma en el directorio `docs/` de este repositorio.
|
||||
2. Copie todos los archivos `.md` de la raíz del directorio `docs/` al nuevo directorio (siempre use la versión en inglés como fuente de traducción, ya que siempre está actualizada).
|
||||
3. Copie los archivos `README.md` y `CONTRIBUTING.md` del directorio raíz al nuevo directorio.
|
||||
4. Traduzca el contenido de los archivos, pero no cambie los nombres de los archivos, tampoco traduzca las cadenas que comiencen por `> [!` (es un marcado especial para GitHub).
|
||||
5. Cree una Pull Request con las traducciones.
|
||||
6. En el [repositorio del sitio](https://github.com/dunglas/frankenphp-website/tree/main), copie y traduzca los archivos de traducción en los directorios `content/`, `data/` y `i18n/`.
|
||||
7. Traduzca los valores en el archivo YAML creado.
|
||||
8. Abra una Pull Request en el repositorio del sitio.
|
||||
162
docs/es/README.md
Normal file
162
docs/es/README.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# FrankenPHP: el servidor de aplicaciones PHP moderno, escrito en Go
|
||||
|
||||
<h1 align="center"><a href="https://frankenphp.dev"><img src="../../frankenphp.png" alt="FrankenPHP" width="600"></a></h1>
|
||||
|
||||
FrankenPHP es un servidor de aplicaciones moderno para PHP construido sobre el servidor web [Caddy](https://caddyserver.com/).
|
||||
|
||||
FrankenPHP otorga superpoderes a tus aplicaciones PHP gracias a sus características de vanguardia: [_Early Hints_](early-hints.md), [modo worker](worker.md), [funcionalidades en tiempo real](mercure.md), HTTPS automático, soporte para HTTP/2 y HTTP/3...
|
||||
|
||||
FrankenPHP funciona con cualquier aplicación PHP y hace que tus proyectos Laravel y Symfony sean más rápidos que nunca gracias a sus integraciones oficiales con el modo worker.
|
||||
|
||||
FrankenPHP también puede usarse como una biblioteca Go autónoma que permite integrar PHP en cualquier aplicación usando `net/http`.
|
||||
|
||||
Descubre más detalles sobre este servidor de aplicaciones en la grabación de esta conferencia dada en el Forum PHP 2022:
|
||||
|
||||
<a href="https://dunglas.dev/2022/10/frankenphp-the-modern-php-app-server-written-in-go/"><img src="https://dunglas.dev/wp-content/uploads/2022/10/frankenphp.png" alt="Diapositivas" width="600"></a>
|
||||
|
||||
## Para Comenzar
|
||||
|
||||
En Windows, usa [WSL](https://learn.microsoft.com/es-es/windows/wsl/) para ejecutar FrankenPHP.
|
||||
|
||||
### Script de instalación
|
||||
|
||||
Puedes copiar esta línea en tu terminal para instalar automáticamente
|
||||
una versión adaptada a tu plataforma:
|
||||
|
||||
```console
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
```
|
||||
|
||||
### Binario autónomo
|
||||
|
||||
Proporcionamos binarios estáticos de FrankenPHP para desarrollo, para Linux y macOS,
|
||||
conteniendo [PHP 8.4](https://www.php.net/releases/8.4/es.php) y la mayoría de las extensiones PHP populares.
|
||||
|
||||
[Descargar FrankenPHP](https://github.com/php/frankenphp/releases)
|
||||
|
||||
**Instalación de extensiones:** Las extensiones más comunes están incluidas. No es posible instalar más.
|
||||
|
||||
### Paquetes rpm
|
||||
|
||||
Nuestros mantenedores proponen paquetes rpm para todos los sistemas que usan `dnf`. Para instalar, ejecuta:
|
||||
|
||||
```console
|
||||
sudo dnf install https://rpm.henderkes.com/static-php-1-0.noarch.rpm
|
||||
sudo dnf module enable php-zts:static-8.4 # 8.2-8.5 disponibles
|
||||
sudo dnf install frankenphp
|
||||
```
|
||||
|
||||
**Instalación de extensiones:** `sudo dnf install php-zts-<extension>`
|
||||
|
||||
Para extensiones no disponibles por defecto, usa [PIE](https://github.com/php/pie):
|
||||
|
||||
```console
|
||||
sudo dnf install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### Paquetes deb
|
||||
|
||||
Nuestros mantenedores proponen paquetes deb para todos los sistemas que usan `apt`. Para instalar, ejecuta:
|
||||
|
||||
```console
|
||||
sudo curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg && \
|
||||
echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | sudo tee /etc/apt/sources.list.d/static-php.list && \
|
||||
sudo apt update
|
||||
sudo apt install frankenphp
|
||||
```
|
||||
|
||||
**Instalación de extensiones:** `sudo apt install php-zts-<extension>`
|
||||
|
||||
Para extensiones no disponibles por defecto, usa [PIE](https://github.com/php/pie):
|
||||
|
||||
```console
|
||||
sudo apt install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Las [imágenes Docker](https://frankenphp.dev/docs/es/docker/) también están disponibles:
|
||||
|
||||
```console
|
||||
docker run -v .:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
Ve a `https://localhost`, ¡listo!
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> No intentes usar `https://127.0.0.1`. Usa `https://localhost` y acepta el certificado auto-firmado.
|
||||
> Usa [la variable de entorno `SERVER_NAME`](config.md#variables-de-entorno) para cambiar el dominio a usar.
|
||||
|
||||
### Homebrew
|
||||
|
||||
FrankenPHP también está disponible como paquete [Homebrew](https://brew.sh) para macOS y Linux.
|
||||
|
||||
Para instalarlo:
|
||||
|
||||
```console
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
**Instalación de extensiones:** Usa [PIE](https://github.com/php/pie).
|
||||
|
||||
### Uso
|
||||
|
||||
Para servir el contenido del directorio actual, ejecuta:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
También puedes ejecutar scripts en línea de comandos con:
|
||||
|
||||
```console
|
||||
frankenphp php-cli /ruta/a/tu/script.php
|
||||
```
|
||||
|
||||
Para los paquetes deb y rpm, también puedes iniciar el servicio systemd:
|
||||
|
||||
```console
|
||||
sudo systemctl start frankenphp
|
||||
```
|
||||
|
||||
## Documentación
|
||||
|
||||
- [El modo clásico](classic.md)
|
||||
- [El modo worker](worker.md)
|
||||
- [Soporte para Early Hints (código de estado HTTP 103)](early-hints.md)
|
||||
- [Tiempo real](mercure.md)
|
||||
- [Hot reloading](https://frankenphp.dev/docs/hot-reload/)
|
||||
- [Registro de actividad](https://frankenphp.dev/docs/logging/)
|
||||
- [Servir eficientemente archivos estáticos grandes](x-sendfile.md)
|
||||
- [Configuración](config.md)
|
||||
- [Escribir extensiones PHP en Go](extensions.md)
|
||||
- [Imágenes Docker](docker.md)
|
||||
- [Despliegue en producción](production.md)
|
||||
- [Optimización del rendimiento](performance.md)
|
||||
- [Crear aplicaciones PHP **autónomas**, auto-ejecutables](embed.md)
|
||||
- [Crear una compilación estática](static.md)
|
||||
- [Compilar desde las fuentes](compile.md)
|
||||
- [Monitoreo de FrankenPHP](metrics.md)
|
||||
- [Integración con WordPress](https://frankenphp.dev/docs/wordpress/)
|
||||
- [Integración con Laravel](laravel.md)
|
||||
- [Problemas conocidos](known-issues.md)
|
||||
- [Aplicación de demostración (Symfony) y benchmarks](https://github.com/dunglas/frankenphp-demo)
|
||||
- [Documentación de la biblioteca Go](https://pkg.go.dev/github.com/dunglas/frankenphp)
|
||||
- [Contribuir y depurar](CONTRIBUTING.md)
|
||||
|
||||
## Ejemplos y esqueletos
|
||||
|
||||
- [Symfony](https://github.com/dunglas/symfony-docker)
|
||||
- [API Platform](https://api-platform.com/docs/distribution/)
|
||||
- [Laravel](laravel.md)
|
||||
- [Sulu](https://sulu.io/blog/running-sulu-with-frankenphp)
|
||||
- [WordPress](https://github.com/StephenMiracle/frankenwp)
|
||||
- [Drupal](https://github.com/dunglas/frankenphp-drupal)
|
||||
- [Joomla](https://github.com/alexandreelise/frankenphp-joomla)
|
||||
- [TYPO3](https://github.com/ochorocho/franken-typo3)
|
||||
- [Magento2](https://github.com/ekino/frankenphp-magento2)
|
||||
11
docs/es/classic.md
Normal file
11
docs/es/classic.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Usando el Modo Clásico
|
||||
|
||||
Sin ninguna configuración adicional, FrankenPHP opera en modo clásico. En este modo, FrankenPHP funciona como un servidor PHP tradicional, sirviendo directamente archivos PHP. Esto lo convierte en un reemplazo directo para PHP-FPM o Apache con mod_php.
|
||||
|
||||
Al igual que Caddy, FrankenPHP acepta un número ilimitado de conexiones y utiliza un [número fijo de hilos](config.md#caddyfile-config) para atenderlas. La cantidad de conexiones aceptadas y en cola está limitada únicamente por los recursos disponibles del sistema.
|
||||
El *pool* de hilos de PHP opera con un número fijo de hilos inicializados al inicio, comparable al modo estático de PHP-FPM. También es posible permitir que los hilos [escale automáticamente en tiempo de ejecución](performance.md#max_threads), similar al modo dinámico de PHP-FPM.
|
||||
|
||||
Las conexiones en cola esperarán indefinidamente hasta que un hilo de PHP esté disponible para atenderlas. Para evitar esto, puedes usar la configuración `max_wait_time` en la [configuración global de FrankenPHP](config.md#caddyfile-config) para limitar la duración que una petición puede esperar por un hilo de PHP libre antes de ser rechazada.
|
||||
Adicionalmente, puedes establecer un [tiempo límite de escritura razonable en Caddy](https://caddyserver.com/docs/caddyfile/options#timeouts).
|
||||
|
||||
Cada instancia de Caddy iniciará solo un *pool* de hilos de FrankenPHP, el cual será compartido entre todos los bloques `php_server`.
|
||||
133
docs/es/compile.md
Normal file
133
docs/es/compile.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Compilar desde fuentes
|
||||
|
||||
Este documento explica cómo crear un binario de FrankenPHP que cargará PHP como una biblioteca dinámica.
|
||||
Esta es la forma recomendada.
|
||||
|
||||
Alternativamente, también se pueden crear [compilaciones estáticas y mayormente estáticas](static.md).
|
||||
|
||||
## Instalar PHP
|
||||
|
||||
FrankenPHP es compatible con PHP 8.2 y versiones superiores.
|
||||
|
||||
### Con Homebrew (Linux y Mac)
|
||||
|
||||
La forma más sencilla de instalar una versión de libphp compatible con FrankenPHP es usar los paquetes ZTS proporcionados por [Homebrew PHP](https://github.com/shivammathur/homebrew-php).
|
||||
|
||||
Primero, si no lo ha hecho ya, instale [Homebrew](https://brew.sh).
|
||||
|
||||
Luego, instale la variante ZTS de PHP, Brotli (opcional, para soporte de compresión) y watcher (opcional, para detección de cambios en archivos):
|
||||
|
||||
```console
|
||||
brew install shivammathur/php/php-zts brotli watcher
|
||||
brew link --overwrite --force shivammathur/php/php-zts
|
||||
```
|
||||
|
||||
### Compilando PHP
|
||||
|
||||
Alternativamente, puede compilar PHP desde las fuentes con las opciones necesarias para FrankenPHP siguiendo estos pasos.
|
||||
|
||||
Primero, [obtenga las fuentes de PHP](https://www.php.net/downloads.php) y extráigalas:
|
||||
|
||||
```console
|
||||
tar xf php-*
|
||||
cd php-*/
|
||||
```
|
||||
|
||||
Luego, ejecute el script `configure` con las opciones necesarias para su plataforma.
|
||||
Las siguientes banderas de `./configure` son obligatorias, pero puede agregar otras, por ejemplo, para compilar extensiones o características adicionales.
|
||||
|
||||
#### Linux
|
||||
|
||||
```console
|
||||
./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--enable-zend-max-execution-timers
|
||||
```
|
||||
|
||||
#### Mac
|
||||
|
||||
Use el gestor de paquetes [Homebrew](https://brew.sh/) para instalar las dependencias requeridas y opcionales:
|
||||
|
||||
```console
|
||||
brew install libiconv bison brotli re2c pkg-config watcher
|
||||
echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
|
||||
```
|
||||
|
||||
Luego ejecute el script de configuración:
|
||||
|
||||
```console
|
||||
./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--with-iconv=/opt/homebrew/opt/libiconv/
|
||||
```
|
||||
|
||||
#### Compilar PHP
|
||||
|
||||
Finalmente, compile e instale PHP:
|
||||
|
||||
```console
|
||||
make -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## Instalar dependencias opcionales
|
||||
|
||||
Algunas características de FrankenPHP dependen de dependencias opcionales del sistema que deben instalarse.
|
||||
Alternativamente, estas características pueden deshabilitarse pasando etiquetas de compilación al compilador Go.
|
||||
|
||||
| Característica | Dependencia | Etiqueta de compilación para deshabilitarla |
|
||||
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
||||
| Compresión Brotli | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| Reiniciar workers al cambiar archivos | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
| [Mercure](mercure.md) | [Biblioteca Mercure Go](https://pkg.go.dev/github.com/dunglas/mercure) (instalada automáticamente, licencia AGPL) | nomercure |
|
||||
|
||||
## Compilar la aplicación Go
|
||||
|
||||
Ahora puede construir el binario final.
|
||||
|
||||
### Usando xcaddy
|
||||
|
||||
La forma recomendada es usar [xcaddy](https://github.com/caddyserver/xcaddy) para compilar FrankenPHP.
|
||||
`xcaddy` también permite agregar fácilmente [módulos personalizados de Caddy](https://caddyserver.com/docs/modules/) y extensiones de FrankenPHP:
|
||||
|
||||
```console
|
||||
CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
|
||||
CGO_CFLAGS=$(php-config --includes) \
|
||||
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
|
||||
xcaddy build \
|
||||
--output frankenphp \
|
||||
--with github.com/dunglas/frankenphp/caddy \
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy \
|
||||
--with github.com/dunglas/caddy-cbrotli
|
||||
# Agregue módulos adicionales de Caddy y extensiones de FrankenPHP aquí
|
||||
# opcionalmente, si desea compilar desde sus fuentes de frankenphp:
|
||||
# --with github.com/dunglas/frankenphp=$(pwd) \
|
||||
# --with github.com/dunglas/frankenphp/caddy=$(pwd)/caddy
|
||||
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Si está usando musl libc (predeterminado en Alpine Linux) y Symfony,
|
||||
> es posible que deba aumentar el tamaño de pila predeterminado.
|
||||
> De lo contrario, podría obtener errores como `PHP Fatal error: Maximum call stack size of 83360 bytes reached during compilation. Try splitting expression`
|
||||
>
|
||||
> Para hacerlo, cambie la variable de entorno `XCADDY_GO_BUILD_FLAGS` a algo como:
|
||||
> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'`
|
||||
> (cambie el valor del tamaño de pila según las necesidades de su aplicación).
|
||||
|
||||
### Sin xcaddy
|
||||
|
||||
Alternativamente, es posible compilar FrankenPHP sin `xcaddy` usando directamente el comando `go`:
|
||||
|
||||
```console
|
||||
curl -L https://github.com/php/frankenphp/archive/refs/heads/main.tar.gz | tar xz
|
||||
cd frankenphp-main/caddy/frankenphp
|
||||
CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build -tags=nobadger,nomysql,nopgx
|
||||
```
|
||||
349
docs/es/config.md
Normal file
349
docs/es/config.md
Normal file
@@ -0,0 +1,349 @@
|
||||
# Configuración
|
||||
|
||||
FrankenPHP, Caddy así como los módulos [Mercure](mercure.md) y [Vulcain](https://vulcain.rocks) pueden configurarse usando [los formatos soportados por Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
|
||||
El formato más común es el `Caddyfile`, que es un formato de texto simple y legible.
|
||||
Por defecto, FrankenPHP buscará un `Caddyfile` en el directorio actual.
|
||||
Puede especificar una ruta personalizada con la opción `-c` o `--config`.
|
||||
|
||||
Un `Caddyfile` mínimo para servir una aplicación PHP se muestra a continuación:
|
||||
|
||||
```caddyfile
|
||||
# El nombre de host al que responder
|
||||
localhost
|
||||
|
||||
# Opcionalmente, el directorio desde el que servir archivos, por defecto es el directorio actual
|
||||
#root public/
|
||||
php_server
|
||||
```
|
||||
|
||||
Un `Caddyfile` más avanzado que habilita más características y proporciona variables de entorno convenientes está disponible [en el repositorio de FrankenPHP](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile),
|
||||
y con las imágenes de Docker.
|
||||
|
||||
PHP en sí puede configurarse [usando un archivo `php.ini`](https://www.php.net/manual/es/configuration.file.php).
|
||||
|
||||
Dependiendo de su método de instalación, FrankenPHP y el intérprete de PHP buscarán archivos de configuración en las ubicaciones descritas a continuación.
|
||||
|
||||
## Docker
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: el archivo de configuración principal
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: archivos de configuración adicionales que se cargan automáticamente
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/usr/local/etc/php/php.ini` (no se proporciona ningún `php.ini` por defecto)
|
||||
- archivos de configuración adicionales: `/usr/local/etc/php/conf.d/*.ini`
|
||||
- extensiones de PHP: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
- Debe copiar una plantilla oficial proporcionada por el proyecto PHP:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# Producción:
|
||||
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
|
||||
|
||||
# O desarrollo:
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
```
|
||||
|
||||
## Paquetes RPM y Debian
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: el archivo de configuración principal
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: archivos de configuración adicionales que se cargan automáticamente
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/etc/php-zts/php.ini` (se proporciona un archivo `php.ini` con ajustes de producción por defecto)
|
||||
- archivos de configuración adicionales: `/etc/php-zts/conf.d/*.ini`
|
||||
|
||||
## Binario estático
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- En el directorio de trabajo actual: `Caddyfile`
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: El directorio en el que se ejecuta `frankenphp run` o `frankenphp php-server`, luego `/etc/frankenphp/php.ini`
|
||||
- archivos de configuración adicionales: `/etc/frankenphp/php.d/*.ini`
|
||||
- extensiones de PHP: no pueden cargarse, debe incluirlas en el binario mismo
|
||||
- copie uno de `php.ini-production` o `php.ini-development` proporcionados [en las fuentes de PHP](https://github.com/php/php-src/).
|
||||
|
||||
## Configuración de Caddyfile
|
||||
|
||||
Las directivas `php_server` o `php` [de HTTP](https://caddyserver.com/docs/caddyfile/concepts#directives) pueden usarse dentro de los bloques de sitio para servir su aplicación PHP.
|
||||
|
||||
Ejemplo mínimo:
|
||||
|
||||
```caddyfile
|
||||
localhost {
|
||||
# Habilitar compresión (opcional)
|
||||
encode zstd br gzip
|
||||
# Ejecutar archivos PHP en el directorio actual y servir activos
|
||||
php_server
|
||||
}
|
||||
```
|
||||
|
||||
También puede configurar explícitamente FrankenPHP usando la [opción global](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Establece el número de hilos de PHP para iniciar. Por defecto: 2x el número de CPUs disponibles.
|
||||
max_threads <num_threads> # Limita el número de hilos de PHP adicionales que pueden iniciarse en tiempo de ejecución. Por defecto: num_threads. Puede establecerse como 'auto'.
|
||||
max_wait_time <duration> # Establece el tiempo máximo que una solicitud puede esperar por un hilo de PHP libre antes de agotar el tiempo de espera. Por defecto: deshabilitado.
|
||||
php_ini <key> <value> # Establece una directiva php.ini. Puede usarse varias veces para establecer múltiples directivas.
|
||||
worker {
|
||||
file <path> # Establece la ruta al script del worker.
|
||||
num <num> # Establece el número de hilos de PHP para iniciar, por defecto es 2x el número de CPUs disponibles.
|
||||
env <key> <value> # Establece una variable de entorno adicional con el valor dado. Puede especificarse más de una vez para múltiples variables de entorno.
|
||||
watch <path> # Establece la ruta para observar cambios en archivos. Puede especificarse más de una vez para múltiples rutas.
|
||||
name <name> # Establece el nombre del worker, usado en logs y métricas. Por defecto: ruta absoluta del archivo del worker
|
||||
max_consecutive_failures <num> # Establece el número máximo de fallos consecutivos antes de que el worker se considere no saludable, -1 significa que el worker siempre se reiniciará. Por defecto: 6.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Alternativamente, puede usar la forma corta de una línea de la opción `worker`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker <file> <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
También puede definir múltiples workers si sirve múltiples aplicaciones en el mismo servidor:
|
||||
|
||||
```caddyfile
|
||||
app.example.com {
|
||||
root /path/to/app/public
|
||||
php_server {
|
||||
root /path/to/app/public # permite un mejor almacenamiento en caché
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
other.example.com {
|
||||
root /path/to/other/public
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Usar la directiva `php_server` es generalmente lo que necesita,
|
||||
pero si necesita un control total, puede usar la directiva de bajo nivel `php`.
|
||||
La directiva `php` pasa toda la entrada a PHP, en lugar de verificar primero si
|
||||
es un archivo PHP o no. Lea más sobre esto en la [página de rendimiento](performance.md#try_files).
|
||||
|
||||
Usar la directiva `php_server` es equivalente a esta configuración:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
# Agrega barra final para solicitudes de directorio
|
||||
@canonicalPath {
|
||||
file {path}/index.php
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
# Si el archivo solicitado no existe, intenta archivos índice
|
||||
@indexFiles file {
|
||||
try_files {path} {path}/index.php index.php
|
||||
split_path .php
|
||||
}
|
||||
rewrite @indexFiles {http.matchers.file.relative}
|
||||
# ¡FrankenPHP!
|
||||
@phpFiles path *.php
|
||||
php @phpFiles
|
||||
file_server
|
||||
}
|
||||
```
|
||||
|
||||
Las directivas `php_server` y `php` tienen las siguientes opciones:
|
||||
|
||||
```caddyfile
|
||||
php_server [<matcher>] {
|
||||
root <directory> # Establece la carpeta raíz del sitio. Por defecto: directiva `root`.
|
||||
split_path <delim...> # Establece las subcadenas para dividir la URI en dos partes. La primera subcadena coincidente se usará para dividir la "información de ruta" del path. La primera parte se sufija con la subcadena coincidente y se asumirá como el nombre del recurso real (script CGI). La segunda parte se establecerá como PATH_INFO para que el script la use. Por defecto: `.php`
|
||||
resolve_root_symlink false # Desactiva la resolución del directorio `root` a su valor real evaluando un enlace simbólico, si existe (habilitado por defecto).
|
||||
env <key> <value> # Establece una variable de entorno adicional con el valor dado. Puede especificarse más de una vez para múltiples variables de entorno.
|
||||
file_server off # Desactiva la directiva incorporada file_server.
|
||||
worker { # Crea un worker específico para este servidor. Puede especificarse más de una vez para múltiples workers.
|
||||
file <path> # Establece la ruta al script del worker, puede ser relativa a la raíz de php_server
|
||||
num <num> # Establece el número de hilos de PHP para iniciar, por defecto es 2x el número de CPUs disponibles.
|
||||
name <name> # Establece el nombre para el worker, usado en logs y métricas. Por defecto: ruta absoluta del archivo del worker. Siempre comienza con m# cuando se define en un bloque php_server.
|
||||
watch <path> # Establece la ruta para observar cambios en archivos. Puede especificarse más de una vez para múltiples rutas.
|
||||
env <key> <value> # Establece una variable de entorno adicional con el valor dado. Puede especificarse más de una vez para múltiples variables de entorno. Las variables de entorno para este worker también se heredan del php_server padre, pero pueden sobrescribirse aquí.
|
||||
match <path> # hace coincidir el worker con un patrón de ruta. Anula try_files y solo puede usarse en la directiva php_server.
|
||||
}
|
||||
worker <other_file> <num> # También puede usar la forma corta como en el bloque global frankenphp.
|
||||
}
|
||||
```
|
||||
|
||||
### Observando cambios en archivos
|
||||
|
||||
Dado que los workers solo inician su aplicación una vez y la mantienen en memoria, cualquier cambio
|
||||
en sus archivos PHP no se reflejará inmediatamente.
|
||||
|
||||
Los workers pueden reiniciarse al cambiar archivos mediante la directiva `watch`.
|
||||
Esto es útil para entornos de desarrollo.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Esta función se utiliza frecuentemente en combinación con [hot reload](hot-reload.md).
|
||||
|
||||
Si el directorio `watch` no está especificado, retrocederá a `./**/*.{env,php,twig,yaml,yml}`,
|
||||
lo cual vigila todos los archivos `.env`, `.php`, `.twig`, `.yaml` y `.yml` en el directorio y subdirectorios
|
||||
donde se inició el proceso de FrankenPHP. También puede especificar uno o más directorios mediante un
|
||||
[patrón de nombres de ficheros de shell](https://pkg.go.dev/path/filepath#Match):
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch /path/to/app # observa todos los archivos en todos los subdirectorios de /path/to/app
|
||||
watch /path/to/app/*.php # observa archivos que terminan en .php en /path/to/app
|
||||
watch /path/to/app/**/*.php # observa archivos PHP en /path/to/app y subdirectorios
|
||||
watch /path/to/app/**/*.{php,twig} # observa archivos PHP y Twig en /path/to/app y subdirectorios
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- El patrón `**` significa observación recursiva
|
||||
- Los directorios también pueden ser relativos (al lugar donde se inicia el proceso de FrankenPHP)
|
||||
- Si tiene múltiples workers definidos, todos ellos se reiniciarán cuando cambie un archivo
|
||||
- Tenga cuidado al observar archivos que se crean en tiempo de ejecución (como logs) ya que podrían causar reinicios no deseados de workers.
|
||||
|
||||
El observador de archivos se basa en [e-dant/watcher](https://github.com/e-dant/watcher).
|
||||
|
||||
## Coincidencia del worker con una ruta
|
||||
|
||||
En aplicaciones PHP tradicionales, los scripts siempre se colocan en el directorio público.
|
||||
Esto también es cierto para los scripts de workers, que se tratan como cualquier otro script PHP.
|
||||
Si desea colocar el script del worker fuera del directorio público, puede hacerlo mediante la directiva `match`.
|
||||
|
||||
La directiva `match` es una alternativa optimizada a `try_files` solo disponible dentro de `php_server` y `php`.
|
||||
El siguiente ejemplo siempre servirá un archivo en el directorio público si está presente
|
||||
y de lo contrario reenviará la solicitud al worker que coincida con el patrón de ruta.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
php_server {
|
||||
worker {
|
||||
file /path/to/worker.php # el archivo puede estar fuera de la ruta pública
|
||||
match /api/* # todas las solicitudes que comiencen con /api/ serán manejadas por este worker
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Variables de entorno
|
||||
|
||||
Las siguientes variables de entorno pueden usarse para inyectar directivas de Caddy en el `Caddyfile` sin modificarlo:
|
||||
|
||||
- `SERVER_NAME`: cambia [las direcciones en las que escuchar](https://caddyserver.com/docs/caddyfile/concepts#addresses), los nombres de host proporcionados también se usarán para el certificado TLS generado
|
||||
- `SERVER_ROOT`: cambia el directorio raíz del sitio, por defecto es `public/`
|
||||
- `CADDY_GLOBAL_OPTIONS`: inyecta [opciones globales](https://caddyserver.com/docs/caddyfile/options)
|
||||
- `FRANKENPHP_CONFIG`: inyecta configuración bajo la directiva `frankenphp`
|
||||
|
||||
Al igual que en FPM y SAPIs CLI, las variables de entorno se exponen por defecto en la superglobal `$_SERVER`.
|
||||
|
||||
El valor `S` de [la directiva `variables_order` de PHP](https://www.php.net/manual/en/ini.core.php#ini.variables-order) siempre es equivalente a `ES` independientemente de la ubicación de `E` en otro lugar de esta directiva.
|
||||
|
||||
## Configuración de PHP
|
||||
|
||||
Para cargar [archivos de configuración adicionales de PHP](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan),
|
||||
puede usarse la variable de entorno `PHP_INI_SCAN_DIR`.
|
||||
Cuando se establece, PHP cargará todos los archivos con la extensión `.ini` presentes en los directorios dados.
|
||||
|
||||
También puede cambiar la configuración de PHP usando la directiva `php_ini` en el `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
php_ini memory_limit 256M
|
||||
|
||||
# o
|
||||
|
||||
php_ini {
|
||||
memory_limit 256M
|
||||
max_execution_time 15
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Deshabilitar HTTPS
|
||||
|
||||
Por defecto, FrankenPHP habilitará automáticamente HTTPS para todos los nombres de host, incluyendo `localhost`.
|
||||
Si desea deshabilitar HTTPS (por ejemplo en un entorno de desarrollo), puede establecer la variable de entorno `SERVER_NAME` a `http://` o `:80`:
|
||||
|
||||
Alternativamente, puede usar todos los otros métodos descritos en la [documentación de Caddy](https://caddyserver.com/docs/automatic-https#activation).
|
||||
|
||||
Si desea usar HTTPS con la dirección IP `127.0.0.1` en lugar del nombre de host `localhost`, lea la sección de [problemas conocidos](known-issues.md#using-https127001-with-docker).
|
||||
|
||||
### Dúplex completo (HTTP/1)
|
||||
|
||||
Al usar HTTP/1.x, puede ser deseable habilitar el modo dúplex completo para permitir escribir una respuesta antes de que se haya leído todo el cuerpo
|
||||
(por ejemplo: [Mercure](mercure.md), WebSocket, Eventos enviados por el servidor, etc.).
|
||||
|
||||
Esta es una configuración opcional que debe agregarse a las opciones globales en el `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Habilitar esta opción puede causar que clientes HTTP/1.x antiguos que no soportan dúplex completo se bloqueen.
|
||||
> Esto también puede configurarse usando la configuración de entorno `CADDY_GLOBAL_OPTIONS`:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
Puede encontrar más información sobre esta configuración en la [documentación de Caddy](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
|
||||
|
||||
## Habilitar el modo de depuración
|
||||
|
||||
Al usar la imagen de Docker, establezca la variable de entorno `CADDY_GLOBAL_OPTIONS` a `debug` para habilitar el modo de depuración:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public \
|
||||
-e CADDY_GLOBAL_OPTIONS=debug \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
283
docs/es/docker.md
Normal file
283
docs/es/docker.md
Normal file
@@ -0,0 +1,283 @@
|
||||
# Construir una imagen Docker personalizada
|
||||
|
||||
Las [imágenes Docker de FrankenPHP](https://hub.docker.com/r/dunglas/frankenphp) están basadas en [imágenes oficiales de PHP](https://hub.docker.com/_/php/).
|
||||
Se proporcionan variantes para Debian y Alpine Linux en arquitecturas populares.
|
||||
Se recomiendan las variantes de Debian.
|
||||
|
||||
Se proporcionan variantes para PHP 8.2, 8.3, 8.4 y 8.5.
|
||||
|
||||
Las etiquetas siguen este patrón: `dunglas/frankenphp:<versión-frankenphp>-php<versión-php>-<sistema-operativo>`
|
||||
|
||||
- `<versión-frankenphp>` y `<versión-php>` son los números de versión de FrankenPHP y PHP respectivamente, que van desde versiones principales (ej. `1`), menores (ej. `1.2`) hasta versiones de parche (ej. `1.2.3`).
|
||||
- `<sistema-operativo>` es `trixie` (para Debian Trixie), `bookworm` (para Debian Bookworm) o `alpine` (para la última versión estable de Alpine).
|
||||
|
||||
[Explorar etiquetas](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
## Cómo usar las imágenes
|
||||
|
||||
Cree un archivo `Dockerfile` en su proyecto:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
COPY . /app/public
|
||||
```
|
||||
|
||||
Luego, ejecute estos comandos para construir y ejecutar la imagen Docker:
|
||||
|
||||
```console
|
||||
docker build -t my-php-app .
|
||||
docker run -it --rm --name my-running-app my-php-app
|
||||
```
|
||||
|
||||
## Cómo ajustar la configuración
|
||||
|
||||
Para mayor comodidad, se proporciona en la imagen un [archivo `Caddyfile` predeterminado](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) que contiene variables de entorno útiles.
|
||||
|
||||
## Cómo instalar más extensiones de PHP
|
||||
|
||||
El script [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) está disponible en la imagen base.
|
||||
Agregar extensiones adicionales de PHP es sencillo:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# agregue extensiones adicionales aquí:
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
```
|
||||
|
||||
## Cómo instalar más módulos de Caddy
|
||||
|
||||
FrankenPHP está construido sobre Caddy, y todos los [módulos de Caddy](https://caddyserver.com/docs/modules/) pueden usarse con FrankenPHP.
|
||||
|
||||
La forma más fácil de instalar módulos personalizados de Caddy es usar [xcaddy](https://github.com/caddyserver/xcaddy):
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp:builder AS builder
|
||||
|
||||
# Copie xcaddy en la imagen del constructor
|
||||
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
|
||||
|
||||
# CGO debe estar habilitado para construir FrankenPHP
|
||||
RUN CGO_ENABLED=1 \
|
||||
XCADDY_SETCAP=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
|
||||
CGO_CFLAGS=$(php-config --includes) \
|
||||
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
|
||||
xcaddy build \
|
||||
--output /usr/local/bin/frankenphp \
|
||||
--with github.com/dunglas/frankenphp=./ \
|
||||
--with github.com/dunglas/frankenphp/caddy=./caddy/ \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
# Mercure y Vulcain están incluidos en la compilación oficial, pero puede eliminarlos si lo desea
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Agregue módulos adicionales de Caddy aquí
|
||||
|
||||
FROM dunglas/frankenphp AS runner
|
||||
|
||||
# Reemplace el binario oficial por el que contiene sus módulos personalizados
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
```
|
||||
|
||||
La imagen `builder` proporcionada por FrankenPHP contiene una versión compilada de `libphp`.
|
||||
Se proporcionan [imágenes de constructor](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) para todas las versiones de FrankenPHP y PHP, tanto para Debian como para Alpine.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Si está usando Alpine Linux y Symfony,
|
||||
> es posible que deba [aumentar el tamaño de pila predeterminado](compile.md#using-xcaddy).
|
||||
|
||||
## Habilitar el modo Worker por defecto
|
||||
|
||||
Establezca la variable de entorno `FRANKENPHP_CONFIG` para iniciar FrankenPHP con un script de worker:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# ...
|
||||
|
||||
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
|
||||
```
|
||||
|
||||
## Usar un volumen en desarrollo
|
||||
|
||||
Para desarrollar fácilmente con FrankenPHP, monte el directorio de su host que contiene el código fuente de la aplicación como un volumen en el contenedor Docker:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> La opción `--tty` permite tener logs legibles en lugar de logs en formato JSON.
|
||||
|
||||
Con Docker Compose:
|
||||
|
||||
```yaml
|
||||
# compose.yaml
|
||||
|
||||
services:
|
||||
php:
|
||||
image: dunglas/frankenphp
|
||||
# descomente la siguiente línea si desea usar un Dockerfile personalizado
|
||||
#build: .
|
||||
# descomente la siguiente línea si desea ejecutar esto en un entorno de producción
|
||||
# restart: always
|
||||
ports:
|
||||
- "80:80" # HTTP
|
||||
- "443:443" # HTTPS
|
||||
- "443:443/udp" # HTTP/3
|
||||
volumes:
|
||||
- ./:/app/public
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
# comente la siguiente línea en producción, permite tener logs legibles en desarrollo
|
||||
tty: true
|
||||
|
||||
# Volúmenes necesarios para los certificados y configuración de Caddy
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
```
|
||||
|
||||
## Ejecutar como usuario no root
|
||||
|
||||
FrankenPHP puede ejecutarse como usuario no root en Docker.
|
||||
|
||||
Aquí hay un ejemplo de `Dockerfile` que hace esto:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Use "adduser -D ${USER}" para distribuciones basadas en alpine
|
||||
useradd ${USER}; \
|
||||
# Agregar capacidad adicional para enlazar a los puertos 80 y 443
|
||||
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
|
||||
# Dar acceso de escritura a /config/caddy y /data/caddy
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
### Ejecutar sin capacidades
|
||||
|
||||
Incluso cuando se ejecuta sin root, FrankenPHP necesita la capacidad `CAP_NET_BIND_SERVICE` para enlazar el servidor web en puertos privilegiados (80 y 443).
|
||||
|
||||
Si expone FrankenPHP en un puerto no privilegiado (1024 y superior), es posible ejecutar el servidor web como usuario no root, y sin necesidad de ninguna capacidad:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Use "adduser -D ${USER}" para distribuciones basadas en alpine
|
||||
useradd ${USER}; \
|
||||
# Eliminar la capacidad predeterminada
|
||||
setcap -r /usr/local/bin/frankenphp; \
|
||||
# Dar acceso de escritura a /config/caddy y /data/caddy
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
Luego, establezca la variable de entorno `SERVER_NAME` para usar un puerto no privilegiado.
|
||||
Ejemplo: `:8000`
|
||||
|
||||
## Actualizaciones
|
||||
|
||||
Las imágenes Docker se construyen:
|
||||
|
||||
- Cuando se etiqueta una nueva versión
|
||||
- Diariamente a las 4 am UTC, si hay nuevas versiones de las imágenes oficiales de PHP disponibles
|
||||
|
||||
## Endurecimiento de Imágenes
|
||||
|
||||
Para reducir aún más la superficie de ataque y el tamaño de tus imágenes Docker de FrankenPHP, también es posible construirlas sobre una imagen
|
||||
[Google distroless](https://github.com/GoogleContainerTools/distroless) o
|
||||
[Docker hardened](https://www.docker.com/products/hardened-images).
|
||||
|
||||
> [!WARNING]
|
||||
> Estas imágenes base mínimas no incluyen un shell ni gestor de paquetes, lo que hace que la depuración sea más difícil.
|
||||
> Por lo tanto, se recomiendan solo para producción si la seguridad es una alta prioridad.
|
||||
|
||||
Cuando agregues extensiones PHP adicionales, necesitarás una etapa de construcción intermedia:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp AS builder
|
||||
|
||||
# Agregar extensiones PHP adicionales aquí
|
||||
RUN install-php-extensions pdo_mysql pdo_pgsql #...
|
||||
|
||||
# Copiar bibliotecas compartidas de frankenphp y todas las extensiones instaladas a una ubicación temporal
|
||||
# También puedes hacer este paso manualmente analizando la salida de ldd del binario frankenphp y cada archivo .so de extensión
|
||||
RUN apt-get update && apt-get install -y libtree && \
|
||||
EXT_DIR="$(php -r 'echo ini_get("extension_dir");')" && \
|
||||
FRANKENPHP_BIN="$(which frankenphp)"; \
|
||||
LIBS_TMP_DIR="/tmp/libs"; \
|
||||
mkdir -p "$LIBS_TMP_DIR"; \
|
||||
for target in "$FRANKENPHP_BIN" $(find "$EXT_DIR" -maxdepth 2 -type f -name "*.so"); do \
|
||||
libtree -pv "$target" | sed 's/.*── \(.*\) \[.*/\1/' | grep -v "^$target" | while IFS= read -r lib; do \
|
||||
[ -z "$lib" ] && continue; \
|
||||
base=$(basename "$lib"); \
|
||||
destfile="$LIBS_TMP_DIR/$base"; \
|
||||
if [ ! -f "$destfile" ]; then \
|
||||
cp "$lib" "$destfile"; \
|
||||
fi; \
|
||||
done; \
|
||||
done
|
||||
|
||||
|
||||
# Imagen base distroless de Debian, asegúrate de que sea la misma versión de Debian que la imagen base
|
||||
FROM gcr.io/distroless/base-debian13
|
||||
# Alternativa de imagen endurecida de Docker
|
||||
# FROM dhi.io/debian:13
|
||||
|
||||
# Ubicación de tu aplicación y Caddyfile que se copiará al contenedor
|
||||
ARG PATH_TO_APP="."
|
||||
ARG PATH_TO_CADDYFILE="./Caddyfile"
|
||||
|
||||
# Copiar tu aplicación en /app
|
||||
# Para mayor endurecimiento asegúrate de que solo las rutas escribibles sean propiedad del usuario nonroot
|
||||
COPY --chown=nonroot:nonroot "$PATH_TO_APP" /app
|
||||
COPY "$PATH_TO_CADDYFILE" /etc/caddy/Caddyfile
|
||||
|
||||
# Copiar frankenphp y bibliotecas necesarias
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
|
||||
COPY --from=builder /tmp/libs /usr/lib
|
||||
|
||||
# Copiar archivos de configuración php.ini
|
||||
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
|
||||
COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
|
||||
|
||||
# Directorios de datos de Caddy — deben ser escribibles para nonroot, incluso en un sistema de archivos raíz de solo lectura
|
||||
ENV XDG_CONFIG_HOME=/config \
|
||||
XDG_DATA_HOME=/data
|
||||
COPY --from=builder --chown=nonroot:nonroot /data/caddy /data/caddy
|
||||
COPY --from=builder --chown=nonroot:nonroot /config/caddy /config/caddy
|
||||
|
||||
USER nonroot
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# punto de entrada para ejecutar frankenphp con el Caddyfile proporcionado
|
||||
ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "-c", "/etc/caddy/Caddyfile"]
|
||||
```
|
||||
|
||||
## Versiones de desarrollo
|
||||
|
||||
Las versiones de desarrollo están disponibles en el [repositorio Docker `dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev).
|
||||
Se activa una nueva compilación cada vez que se envía un commit a la rama principal del repositorio de GitHub.
|
||||
|
||||
Las etiquetas `latest*` apuntan a la cabeza de la rama `main`.
|
||||
También están disponibles etiquetas de la forma `sha-<hash-del-commit-git>`.
|
||||
21
docs/es/early-hints.md
Normal file
21
docs/es/early-hints.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Early Hints (Pistas Tempranas)
|
||||
|
||||
FrankenPHP soporta nativamente el [código de estado 103 Early Hints](https://developer.chrome.com/blog/early-hints/).
|
||||
El uso de Early Hints puede mejorar el tiempo de carga de sus páginas web hasta en un 30%.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
header('Link: </style.css>; rel=preload; as=style');
|
||||
headers_send(103);
|
||||
|
||||
// sus algoritmos lentos y consultas SQL 🤪
|
||||
|
||||
echo <<<'HTML'
|
||||
<!DOCTYPE html>
|
||||
<title>Hola FrankenPHP</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
HTML;
|
||||
```
|
||||
|
||||
Early Hints están soportados tanto en el modo normal como en el modo [worker](worker.md).
|
||||
144
docs/es/embed.md
Normal file
144
docs/es/embed.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Aplicaciones PHP como Binarios Autónomos
|
||||
|
||||
FrankenPHP tiene la capacidad de incrustar el código fuente y los activos de aplicaciones PHP en un binario estático y autónomo.
|
||||
|
||||
Gracias a esta característica, las aplicaciones PHP pueden distribuirse como binarios autónomos que incluyen la aplicación en sí, el intérprete de PHP y Caddy, un servidor web de nivel de producción.
|
||||
|
||||
Obtenga más información sobre esta característica [en la presentación realizada por Kévin en SymfonyCon 2023](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/).
|
||||
|
||||
Para incrustar aplicaciones Laravel, [lea esta entrada específica de documentación](laravel.md#laravel-apps-as-standalone-binaries).
|
||||
|
||||
## Preparando su Aplicación
|
||||
|
||||
Antes de crear el binario autónomo, asegúrese de que su aplicación esté lista para ser incrustada.
|
||||
|
||||
Por ejemplo, probablemente querrá:
|
||||
|
||||
- Instalar las dependencias de producción de la aplicación
|
||||
- Volcar el autoload
|
||||
- Activar el modo de producción de su aplicación (si lo hay)
|
||||
- Eliminar archivos innecesarios como `.git` o pruebas para reducir el tamaño de su binario final
|
||||
|
||||
Por ejemplo, para una aplicación Symfony, puede usar los siguientes comandos:
|
||||
|
||||
```console
|
||||
# Exportar el proyecto para deshacerse de .git/, etc.
|
||||
mkdir $TMPDIR/my-prepared-app
|
||||
git archive HEAD | tar -x -C $TMPDIR/my-prepared-app
|
||||
cd $TMPDIR/my-prepared-app
|
||||
|
||||
# Establecer las variables de entorno adecuadas
|
||||
echo APP_ENV=prod > .env.local
|
||||
echo APP_DEBUG=0 >> .env.local
|
||||
|
||||
# Eliminar las pruebas y otros archivos innecesarios para ahorrar espacio
|
||||
# Alternativamente, agregue estos archivos con el atributo export-ignore en su archivo .gitattributes
|
||||
rm -Rf tests/
|
||||
|
||||
# Instalar las dependencias
|
||||
composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# Optimizar .env
|
||||
composer dump-env prod
|
||||
```
|
||||
|
||||
### Personalizar la Configuración
|
||||
|
||||
Para personalizar [la configuración](config.md), puede colocar un archivo `Caddyfile` así como un archivo `php.ini`
|
||||
en el directorio principal de la aplicación a incrustar (`$TMPDIR/my-prepared-app` en el ejemplo anterior).
|
||||
|
||||
## Crear un Binario para Linux
|
||||
|
||||
La forma más fácil de crear un binario para Linux es usar el constructor basado en Docker que proporcionamos.
|
||||
|
||||
1. Cree un archivo llamado `static-build.Dockerfile` en el repositorio de su aplicación:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# Si tiene la intención de ejecutar el binario en sistemas musl-libc, use static-builder-musl en su lugar
|
||||
|
||||
# Copie su aplicación
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Construya el binario estático
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Algunos archivos `.dockerignore` (por ejemplo, el [`.dockerignore` predeterminado de Symfony Docker](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))
|
||||
> ignorarán el directorio `vendor/` y los archivos `.env`. Asegúrese de ajustar o eliminar el archivo `.dockerignore` antes de la construcción.
|
||||
|
||||
2. Construya:
|
||||
|
||||
```console
|
||||
docker build -t static-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Extraiga el binario:
|
||||
|
||||
```console
|
||||
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
|
||||
```
|
||||
|
||||
El binario resultante es el archivo llamado `my-app` en el directorio actual.
|
||||
|
||||
## Crear un Binario para Otros Sistemas Operativos
|
||||
|
||||
Si no desea usar Docker o desea construir un binario para macOS, use el script de shell que proporcionamos:
|
||||
|
||||
```console
|
||||
git clone https://github.com/php/frankenphp
|
||||
cd frankenphp
|
||||
EMBED=/path/to/your/app ./build-static.sh
|
||||
```
|
||||
|
||||
El binario resultante es el archivo llamado `frankenphp-<os>-<arch>` en el directorio `dist/`.
|
||||
|
||||
## Usar el Binario
|
||||
|
||||
¡Listo! El archivo `my-app` (o `dist/frankenphp-<os>-<arch>` en otros sistemas operativos) contiene su aplicación autónoma.
|
||||
|
||||
Para iniciar la aplicación web, ejecute:
|
||||
|
||||
```console
|
||||
./my-app php-server
|
||||
```
|
||||
|
||||
Si su aplicación contiene un [script worker](worker.md), inicie el worker con algo como:
|
||||
|
||||
```console
|
||||
./my-app php-server --worker public/index.php
|
||||
```
|
||||
|
||||
Para habilitar HTTPS (se crea automáticamente un certificado de Let's Encrypt), HTTP/2 y HTTP/3, especifique el nombre de dominio a usar:
|
||||
|
||||
```console
|
||||
./my-app php-server --domain localhost
|
||||
```
|
||||
|
||||
También puede ejecutar los scripts CLI de PHP incrustados en su binario:
|
||||
|
||||
```console
|
||||
./my-app php-cli bin/console
|
||||
```
|
||||
|
||||
## Extensiones de PHP
|
||||
|
||||
Por defecto, el script construirá las extensiones requeridas por el archivo `composer.json` de su proyecto, si existe.
|
||||
Si el archivo `composer.json` no existe, se construirán las extensiones predeterminadas, como se documenta en [la entrada de compilaciones estáticas](static.md).
|
||||
|
||||
Para personalizar las extensiones, use la variable de entorno `PHP_EXTENSIONS`.
|
||||
|
||||
## Personalizar la Compilación
|
||||
|
||||
[Lea la documentación de compilación estática](static.md) para ver cómo personalizar el binario (extensiones, versión de PHP, etc.).
|
||||
|
||||
## Distribuir el Binario
|
||||
|
||||
En Linux, el binario creado se comprime usando [UPX](https://upx.github.io).
|
||||
|
||||
En Mac, para reducir el tamaño del archivo antes de enviarlo, puede comprimirlo.
|
||||
Recomendamos `xz`.
|
||||
172
docs/es/extension-workers.md
Normal file
172
docs/es/extension-workers.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Extension Workers
|
||||
|
||||
Los Extension Workers permiten que tu [extensión FrankenPHP](https://frankenphp.dev/docs/extensions/) gestione un pool dedicado de hilos PHP para ejecutar tareas en segundo plano, manejar eventos asíncronos o implementar protocolos personalizados. Útil para sistemas de colas, listeners de eventos, programadores, etc.
|
||||
|
||||
## Registrando el Worker
|
||||
|
||||
### Registro Estático
|
||||
|
||||
Si no necesitas hacer que el worker sea configurable por el usuario (ruta de script fija, número fijo de hilos), simplemente puedes registrar el worker en la función `init()`.
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"github.com/dunglas/frankenphp"
|
||||
"github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
// Manejador global para comunicarse con el pool de workers
|
||||
var worker frankenphp.Workers
|
||||
|
||||
func init() {
|
||||
// Registrar el worker cuando se carga el módulo.
|
||||
worker = caddy.RegisterWorkers(
|
||||
"my-internal-worker", // Nombre único
|
||||
"worker.php", // Ruta del script (relativa a la ejecución o absoluta)
|
||||
2, // Número fijo de hilos
|
||||
// Hooks de ciclo de vida opcionales
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
// Lógica de configuración global...
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### En un Módulo Caddy (Configurable por el usuario)
|
||||
|
||||
Si planeas compartir tu extensión (como una cola genérica o listener de eventos), deberías envolverla en un módulo Caddy. Esto permite a los usuarios configurar la ruta del script y el número de hilos a través de su `Caddyfile`. Esto requiere implementar la interfaz `caddy.Provisioner` y analizar el Caddyfile ([ver un ejemplo](https://github.com/dunglas/frankenphp-queue/blob/989120d394d66dd6c8e2101cac73dd622fade334/caddy.go)).
|
||||
|
||||
### En una Aplicación Go Pura (Embedding)
|
||||
|
||||
Si estás [embebiendo FrankenPHP en una aplicación Go estándar sin caddy](https://pkg.go.dev/github.com/dunglas/frankenphp#example-ServeHTTP), puedes registrar extension workers usando `frankenphp.WithExtensionWorkers` al inicializar las opciones.
|
||||
|
||||
## Interactuando con Workers
|
||||
|
||||
Una vez que el pool de workers está activo, puedes enviar tareas a él. Esto se puede hacer dentro de [funciones nativas exportadas a PHP](https://frankenphp.dev/docs/extensions/#writing-the-extension), o desde cualquier lógica Go como un programador cron, un listener de eventos (MQTT, Kafka), o cualquier otra goroutine.
|
||||
|
||||
### Modo Sin Cabeza: `SendMessage`
|
||||
|
||||
Usa `SendMessage` para pasar datos sin procesar directamente a tu script worker. Esto es ideal para colas o comandos simples.
|
||||
|
||||
#### Ejemplo: Una Extensión de Cola Asíncrona
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_queue_push(mixed $data): bool
|
||||
func my_queue_push(data *C.zval) bool {
|
||||
// 1. Asegurar que el worker esté listo
|
||||
if worker == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. Enviar al worker en segundo plano
|
||||
_, err := worker.SendMessage(
|
||||
context.Background(), // Contexto estándar de Go
|
||||
unsafe.Pointer(data), // Datos para pasar al worker
|
||||
nil, // http.ResponseWriter opcional
|
||||
)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
```
|
||||
|
||||
### Emulación HTTP: `SendRequest`
|
||||
|
||||
Usa `SendRequest` si tu extensión necesita invocar un script PHP que espera un entorno web estándar (poblando `$_SERVER`, `$_GET`, etc.).
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_worker_http_request(string $path): string
|
||||
func my_worker_http_request(path *C.zend_string) unsafe.Pointer {
|
||||
// 1. Preparar la solicitud y el grabador
|
||||
url := frankenphp.GoString(unsafe.Pointer(path))
|
||||
req, _ := http.NewRequest("GET", url, http.NoBody)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// 2. Enviar al worker
|
||||
if err := worker.SendRequest(rr, req); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3. Devolver la respuesta capturada
|
||||
return frankenphp.PHPString(rr.Body.String(), false)
|
||||
}
|
||||
```
|
||||
|
||||
## Script Worker
|
||||
|
||||
El script worker PHP se ejecuta en un bucle y puede manejar tanto mensajes sin procesar como solicitudes HTTP.
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Manejar tanto mensajes sin procesar como solicitudes HTTP en el mismo bucle
|
||||
$handler = function ($payload = null) {
|
||||
// Caso 1: Modo Mensaje
|
||||
if ($payload !== null) {
|
||||
return "Payload recibido: " . $payload;
|
||||
}
|
||||
|
||||
// Caso 2: Modo HTTP (las superglobales estándar de PHP están pobladas)
|
||||
echo "Hola desde la página: " . $_SERVER['REQUEST_URI'];
|
||||
};
|
||||
|
||||
while (frankenphp_handle_request($handler)) {
|
||||
gc_collect_cycles();
|
||||
}
|
||||
```
|
||||
|
||||
## Hooks de Ciclo de Vida
|
||||
|
||||
FrankenPHP proporciona hooks para ejecutar código Go en puntos específicos del ciclo de vida.
|
||||
|
||||
| Tipo de Hook | Nombre de Opción | Firma | Contexto y Caso de Uso |
|
||||
| :----------- | :--------------------------- | :------------------- | :-------------------------------------------------------------------------- |
|
||||
| **Server** | `WithWorkerOnServerStartup` | `func()` | Configuración global. Se ejecuta **Una vez**. Ejemplo: Conectar a NATS/Redis. |
|
||||
| **Server** | `WithWorkerOnServerShutdown` | `func()` | Limpieza global. Se ejecuta **Una vez**. Ejemplo: Cerrar conexiones compartidas. |
|
||||
| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuración por hilo. Llamado cuando un hilo inicia. Recibe el ID del hilo. |
|
||||
| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Limpieza por hilo. Recibe el ID del hilo. |
|
||||
|
||||
### Ejemplo
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dunglas/frankenphp"
|
||||
frankenphpCaddy "github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
workerHandle = frankenphpCaddy.RegisterWorkers(
|
||||
"my-worker", "worker.php", 2,
|
||||
|
||||
// Inicio del Servidor (Global)
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
fmt.Println("Extension: Servidor iniciando...")
|
||||
}),
|
||||
|
||||
// Hilo Listo (Por Hilo)
|
||||
// Nota: La función acepta un entero que representa el ID del hilo
|
||||
frankenphp.WithWorkerOnReady(func(id int) {
|
||||
fmt.Printf("Extension: Hilo worker #%d está listo.\n", id)
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
893
docs/es/extensions.md
Normal file
893
docs/es/extensions.md
Normal file
@@ -0,0 +1,893 @@
|
||||
# Escribir Extensiones PHP en Go
|
||||
|
||||
Con FrankenPHP, puedes **escribir extensiones PHP en Go**, lo que te permite crear **funciones nativas de alto rendimiento** que pueden ser llamadas directamente desde PHP. Tus aplicaciones pueden aprovechar cualquier biblioteca Go existente o nueva, así como el famoso modelo de concurrencia de **goroutines directamente desde tu código PHP**.
|
||||
|
||||
Escribir extensiones PHP típicamente se hace en C, pero también es posible escribirlas en otros lenguajes con un poco de trabajo adicional. Las extensiones PHP te permiten aprovechar el poder de lenguajes de bajo nivel para extender las funcionalidades de PHP, por ejemplo, añadiendo funciones nativas o optimizando operaciones específicas.
|
||||
|
||||
Gracias a los módulos de Caddy, puedes escribir extensiones PHP en Go e integrarlas muy rápidamente en FrankenPHP.
|
||||
|
||||
## Dos Enfoques
|
||||
|
||||
FrankenPHP proporciona dos formas de crear extensiones PHP en Go:
|
||||
|
||||
1. **Usando el Generador de Extensiones** - El enfoque recomendado que genera todo el código repetitivo necesario para la mayoría de los casos de uso, permitiéndote enfocarte en escribir tu código Go.
|
||||
2. **Implementación Manual** - Control total sobre la estructura de la extensión para casos de uso avanzados.
|
||||
|
||||
Comenzaremos con el enfoque del generador ya que es la forma más fácil de empezar, luego mostraremos la implementación manual para aquellos que necesitan un control completo.
|
||||
|
||||
## Usando el Generador de Extensiones
|
||||
|
||||
FrankenPHP incluye una herramienta que te permite **crear una extensión PHP** usando solo Go. **No necesitas escribir código C** ni usar CGO directamente: FrankenPHP también incluye una **API de tipos públicos** para ayudarte a escribir tus extensiones en Go sin tener que preocuparte por **la manipulación de tipos entre PHP/C y Go**.
|
||||
|
||||
> [!TIP]
|
||||
> Si quieres entender cómo se pueden escribir extensiones en Go desde cero, puedes leer la sección de implementación manual a continuación que demuestra cómo escribir una extensión PHP en Go sin usar el generador.
|
||||
|
||||
Ten en cuenta que esta herramienta **no es un generador de extensiones completo**. Está diseñada para ayudarte a escribir extensiones simples en Go, pero no proporciona las características más avanzadas de las extensiones PHP. Si necesitas escribir una extensión más **compleja y optimizada**, es posible que necesites escribir algo de código C o usar CGO directamente.
|
||||
|
||||
### Requisitos Previos
|
||||
|
||||
Como se cubre en la sección de implementación manual a continuación, necesitas [obtener las fuentes de PHP](https://www.php.net/downloads.php) y crear un nuevo módulo Go.
|
||||
|
||||
#### Crear un Nuevo Módulo y Obtener las Fuentes de PHP
|
||||
|
||||
El primer paso para escribir una extensión PHP en Go es crear un nuevo módulo Go. Puedes usar el siguiente comando para esto:
|
||||
|
||||
```console
|
||||
go mod init ejemplo.com/ejemplo
|
||||
```
|
||||
|
||||
El segundo paso es [obtener las fuentes de PHP](https://www.php.net/downloads.php) para los siguientes pasos. Una vez que las tengas, descomprímelas en el directorio de tu elección, no dentro de tu módulo Go:
|
||||
|
||||
```console
|
||||
tar xf php-*
|
||||
```
|
||||
|
||||
### Escribiendo la Extensión
|
||||
|
||||
Todo está listo para escribir tu función nativa en Go. Crea un nuevo archivo llamado `stringext.go`. Nuestra primera función tomará una cadena como argumento, el número de veces para repetirla, un booleano para indicar si invertir la cadena, y devolverá la cadena resultante. Esto debería verse así:
|
||||
|
||||
```go
|
||||
package ejemplo
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function repeat_this(string $str, int $count, bool $reverse): string
|
||||
func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
result := strings.Repeat(str, int(count))
|
||||
if reverse {
|
||||
runes := []rune(result)
|
||||
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||
runes[i], runes[j] = runes[j], runes[i]
|
||||
}
|
||||
result = string(runes)
|
||||
}
|
||||
|
||||
return frankenphp.PHPString(result, false)
|
||||
}
|
||||
```
|
||||
|
||||
Hay dos cosas importantes a tener en cuenta aquí:
|
||||
|
||||
- Un comentario de directiva `//export_php:function` define la firma de la función en PHP. Así es como el generador sabe cómo generar la función PHP con los parámetros y tipo de retorno correctos;
|
||||
- La función debe devolver un `unsafe.Pointer`. FrankenPHP proporciona una API para ayudarte con la manipulación de tipos entre C y Go.
|
||||
|
||||
Mientras que el primer punto se explica por sí mismo, el segundo puede ser más difícil de entender. Profundicemos en la manipulación de tipos en la siguiente sección.
|
||||
|
||||
### Manipulación de Tipos
|
||||
|
||||
Aunque algunos tipos de variables tienen la misma representación en memoria entre C/PHP y Go, algunos tipos requieren más lógica para ser usados directamente. Esta es quizá la parte más difícil cuando se trata de escribir extensiones porque requiere entender los internos del motor Zend y cómo se almacenan las variables internamente en PHP.
|
||||
Esta tabla resume lo que necesitas saber:
|
||||
|
||||
| Tipo PHP | Tipo Go | Conversión directa | Helper de C a Go | Helper de Go a C | Soporte para Métodos de Clase |
|
||||
|---------------------|--------------------------------|---------------------|---------------------------------------|----------------------------------------|-------------------------------|
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ |
|
||||
| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ |
|
||||
| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ |
|
||||
| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `callable` | `*C.zval` | ❌ | - | frankenphp.CallPHPCallable() | ❌ |
|
||||
| `object` | `struct` | ❌ | _Aún no implementado_ | _Aún no implementado_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Esta tabla aún no es exhaustiva y se completará a medida que la API de tipos de FrankenPHP se vuelva más completa.
|
||||
>
|
||||
> Para métodos de clase específicamente, los tipos primitivos y los arrays están actualmente soportados. Los objetos aún no pueden usarse como parámetros de métodos o tipos de retorno.
|
||||
|
||||
Si te refieres al fragmento de código de la sección anterior, puedes ver que se usan helpers para convertir el primer parámetro y el valor de retorno. El segundo y tercer parámetro de nuestra función `repeat_this()` no necesitan ser convertidos ya que la representación en memoria de los tipos subyacentes es la misma para C y Go.
|
||||
|
||||
#### Trabajando con Arrays
|
||||
|
||||
FrankenPHP proporciona soporte nativo para arrays PHP a través de `frankenphp.AssociativeArray` o conversión directa a un mapa o slice.
|
||||
|
||||
`AssociativeArray` representa un [mapa hash](https://es.wikipedia.org/wiki/Tabla_hash) compuesto por un campo `Map: map[string]any` y un campo opcional `Order: []string` (a diferencia de los "arrays asociativos" de PHP, los mapas de Go no están ordenados).
|
||||
|
||||
Si no se necesita orden o asociación, también es posible convertir directamente a un slice `[]any` o un mapa no ordenado `map[string]any`.
|
||||
|
||||
**Creando y manipulando arrays en Go:**
|
||||
|
||||
```go
|
||||
package ejemplo
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
// export_php:function process_data_ordered(array $input): array
|
||||
func process_data_ordered_map(arr *C.zend_array) unsafe.Pointer {
|
||||
// Convertir array asociativo PHP a Go manteniendo el orden
|
||||
associativeArray, err := frankenphp.GoAssociativeArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// manejar error
|
||||
}
|
||||
|
||||
// iterar sobre las entradas en orden
|
||||
for _, key := range associativeArray.Order {
|
||||
value, _ = associativeArray.Map[key]
|
||||
// hacer algo con key y value
|
||||
}
|
||||
|
||||
// devolver un array ordenado
|
||||
// si 'Order' no está vacío, solo se respetarán los pares clave-valor en 'Order'
|
||||
return frankenphp.PHPAssociativeArray[string](frankenphp.AssociativeArray[string]{
|
||||
Map: map[string]string{
|
||||
"clave1": "valor1",
|
||||
"clave2": "valor2",
|
||||
},
|
||||
Order: []string{"clave1", "clave2"},
|
||||
})
|
||||
}
|
||||
|
||||
// export_php:function process_data_unordered(array $input): array
|
||||
func process_data_unordered_map(arr *C.zend_array) unsafe.Pointer {
|
||||
// Convertir array asociativo PHP a un mapa Go sin mantener el orden
|
||||
// ignorar el orden será más eficiente
|
||||
goMap, err := frankenphp.GoMap[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// manejar error
|
||||
}
|
||||
|
||||
// iterar sobre las entradas sin un orden específico
|
||||
for key, value := range goMap {
|
||||
// hacer algo con key y value
|
||||
}
|
||||
|
||||
// devolver un array no ordenado
|
||||
return frankenphp.PHPMap(map[string]string {
|
||||
"clave1": "valor1",
|
||||
"clave2": "valor2",
|
||||
})
|
||||
}
|
||||
|
||||
// export_php:function process_data_packed(array $input): array
|
||||
func process_data_packed(arr *C.zend_array) unsafe.Pointer {
|
||||
// Convertir array empaquetado PHP a Go
|
||||
goSlice, err := frankenphp.GoPackedArray(unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// manejar error
|
||||
}
|
||||
|
||||
// iterar sobre el slice en orden
|
||||
for index, value := range goSlice {
|
||||
// hacer algo con index y value
|
||||
}
|
||||
|
||||
// devolver un array empaquetado
|
||||
return frankenphp.PHPPackedArray([]string{"valor1", "valor2", "valor3"})
|
||||
}
|
||||
```
|
||||
|
||||
**Características clave de la conversión de arrays:**
|
||||
|
||||
- **Pares clave-valor ordenados** - Opción para mantener el orden del array asociativo
|
||||
- **Optimizado para múltiples casos** - Opción para prescindir del orden para un mejor rendimiento o convertir directamente a un slice
|
||||
- **Detección automática de listas** - Al convertir a PHP, detecta automáticamente si el array debe ser una lista empaquetada o un mapa hash
|
||||
- **Arrays Anidados** - Los arrays pueden estar anidados y convertirán automáticamente todos los tipos soportados (`int64`, `float64`, `string`, `bool`, `nil`, `AssociativeArray`, `map[string]any`, `[]any`)
|
||||
- **Objetos no soportados** - Actualmente, solo se pueden usar tipos escalares y arrays como valores. Proporcionar un objeto resultará en un valor `null` en el array PHP.
|
||||
|
||||
##### Métodos Disponibles: Empaquetados y Asociativos
|
||||
|
||||
- `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convertir a un array PHP ordenado con pares clave-valor
|
||||
- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convertir un mapa a un array PHP no ordenado con pares clave-valor
|
||||
- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convertir un slice a un array PHP empaquetado con solo valores indexados
|
||||
- `frankenphp.GoAssociativeArray(arr unsafe.Pointer, ordered bool) frankenphp.AssociativeArray` - Convertir un array PHP a un `AssociativeArray` de Go ordenado (mapa con orden)
|
||||
- `frankenphp.GoMap(arr unsafe.Pointer) map[string]any` - Convertir un array PHP a un mapa Go no ordenado
|
||||
- `frankenphp.GoPackedArray(arr unsafe.Pointer) []any` - Convertir un array PHP a un slice Go
|
||||
- `frankenphp.IsPacked(zval *C.zend_array) bool` - Verificar si un array PHP está empaquetado (solo indexado) o es asociativo (pares clave-valor)
|
||||
|
||||
### Trabajando con Callables
|
||||
|
||||
FrankenPHP proporciona una forma de trabajar con callables de PHP usando el helper `frankenphp.CallPHPCallable`. Esto te permite llamar a funciones o métodos de PHP desde código Go.
|
||||
|
||||
Para mostrar esto, creemos nuestra propia función `array_map()` que toma un callable y un array, aplica el callable a cada elemento del array, y devuelve un nuevo array con los resultados:
|
||||
|
||||
```go
|
||||
// export_php:function my_array_map(array $data, callable $callback): array
|
||||
func my_array_map(arr *C.zend_array, callback *C.zval) unsafe.Pointer {
|
||||
goSlice, err := frankenphp.GoPackedArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
result := make([]any, len(goSlice))
|
||||
|
||||
for index, value := range goSlice {
|
||||
result[index] = frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value})
|
||||
}
|
||||
|
||||
return frankenphp.PHPPackedArray(result)
|
||||
}
|
||||
```
|
||||
|
||||
Observa cómo usamos `frankenphp.CallPHPCallable()` para llamar al callable de PHP pasado como parámetro. Esta función toma un puntero al callable y un array de argumentos, y devuelve el resultado de la ejecución del callable. Puedes usar la sintaxis de callable a la que estás acostumbrado:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$result = my_array_map([1, 2, 3], function($x) { return $x * 2; });
|
||||
// $result será [2, 4, 6]
|
||||
|
||||
$result = my_array_map(['hola', 'mundo'], 'strtoupper');
|
||||
// $result será ['HOLA', 'MUNDO']
|
||||
```
|
||||
|
||||
### Declarando una Clase Nativa de PHP
|
||||
|
||||
El generador soporta la declaración de **clases opacas** como estructuras Go, que pueden usarse para crear objetos PHP. Puedes usar el comentario de directiva `//export_php:class` para definir una clase PHP. Por ejemplo:
|
||||
|
||||
```go
|
||||
package ejemplo
|
||||
|
||||
//export_php:class User
|
||||
type UserStruct struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
```
|
||||
|
||||
#### ¿Qué son las Clases Opaque?
|
||||
|
||||
Las **clases opacas** son clases donde la estructura interna (propiedades) está oculta del código PHP. Esto significa:
|
||||
|
||||
- **Sin acceso directo a propiedades**: No puedes leer o escribir propiedades directamente desde PHP (`$user->name` no funcionará)
|
||||
- **Interfaz solo de métodos** - Todas las interacciones deben pasar a través de los métodos que defines
|
||||
- **Mejor encapsulación** - La estructura de datos interna está completamente controlada por el código Go
|
||||
- **Seguridad de tipos** - Sin riesgo de que el código PHP corrompa el estado interno con tipos incorrectos
|
||||
- **API más limpia** - Obliga a diseñar una interfaz pública adecuada
|
||||
|
||||
Este enfoque proporciona una mejor encapsulación y evita que el código PHP corrompa accidentalmente el estado interno de tus objetos Go. Todas las interacciones con el objeto deben pasar a través de los métodos que defines explícitamente.
|
||||
|
||||
#### Añadiendo Métodos a las Clases
|
||||
|
||||
Dado que las propiedades no son directamente accesibles, **debes definir métodos** para interactuar con tus clases opacas. Usa la directiva `//export_php:method` para definir el comportamiento:
|
||||
|
||||
```go
|
||||
package ejemplo
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:class User
|
||||
type UserStruct struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
//export_php:method User::getName(): string
|
||||
func (us *UserStruct) GetUserName() unsafe.Pointer {
|
||||
return frankenphp.PHPString(us.Name, false)
|
||||
}
|
||||
|
||||
//export_php:method User::setAge(int $age): void
|
||||
func (us *UserStruct) SetUserAge(age int64) {
|
||||
us.Age = int(age)
|
||||
}
|
||||
|
||||
//export_php:method User::getAge(): int
|
||||
func (us *UserStruct) GetUserAge() int64 {
|
||||
return int64(us.Age)
|
||||
}
|
||||
|
||||
//export_php:method User::setNamePrefix(string $prefix = "User"): void
|
||||
func (us *UserStruct) SetNamePrefix(prefix *C.zend_string) {
|
||||
us.Name = frankenphp.GoString(unsafe.Pointer(prefix)) + ": " + us.Name
|
||||
}
|
||||
```
|
||||
|
||||
#### Parámetros Nulos
|
||||
|
||||
El generador soporta parámetros nulos usando el prefijo `?` en las firmas de PHP. Cuando un parámetro es nulo, se convierte en un puntero en tu función Go, permitiéndote verificar si el valor era `null` en PHP:
|
||||
|
||||
```go
|
||||
package ejemplo
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:method User::updateInfo(?string $name, ?int $age, ?bool $active): void
|
||||
func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool) {
|
||||
// Verificar si se proporcionó name (no es null)
|
||||
if name != nil {
|
||||
us.Name = frankenphp.GoString(unsafe.Pointer(name))
|
||||
}
|
||||
|
||||
// Verificar si se proporcionó age (no es null)
|
||||
if age != nil {
|
||||
us.Age = int(*age)
|
||||
}
|
||||
|
||||
// Verificar si se proporcionó active (no es null)
|
||||
if active != nil {
|
||||
us.Active = *active
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Puntos clave sobre parámetros nulos:**
|
||||
|
||||
- **Tipos primitivos nulos** (`?int`, `?float`, `?bool`) se convierten en punteros (`*int64`, `*float64`, `*bool`) en Go
|
||||
- **Strings nulos** (`?string`) permanecen como `*C.zend_string` pero pueden ser `nil`
|
||||
- **Verificar `nil`** antes de desreferenciar valores de puntero
|
||||
- **`null` de PHP se convierte en `nil` de Go** - cuando PHP pasa `null`, tu función Go recibe un puntero `nil`
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Actualmente, los métodos de clase tienen las siguientes limitaciones. **Los objetos no están soportados** como tipos de parámetro o tipos de retorno. **Los arrays están completamente soportados** para ambos parámetros y tipos de retorno. Tipos soportados: `string`, `int`, `float`, `bool`, `array`, y `void` (para tipo de retorno). **Los tipos de parámetros nulos están completamente soportados** para todos los tipos escalares (`?string`, `?int`, `?float`, `?bool`).
|
||||
|
||||
Después de generar la extensión, podrás usar la clase y sus métodos en PHP. Ten en cuenta que **no puedes acceder a las propiedades directamente**:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$user = new User();
|
||||
|
||||
// ✅ Esto funciona - usando métodos
|
||||
$user->setAge(25);
|
||||
echo $user->getName(); // Salida: (vacío, valor por defecto)
|
||||
echo $user->getAge(); // Salida: 25
|
||||
$user->setNamePrefix("Empleado");
|
||||
|
||||
// ✅ Esto también funciona - parámetros nulos
|
||||
$user->updateInfo("John", 30, true); // Todos los parámetros proporcionados
|
||||
$user->updateInfo("Jane", null, false); // Age es null
|
||||
$user->updateInfo(null, 25, null); // Name y active son null
|
||||
|
||||
// ❌ Esto NO funcionará - acceso directo a propiedades
|
||||
// echo $user->name; // Error: No se puede acceder a la propiedad privada
|
||||
// $user->age = 30; // Error: No se puede acceder a la propiedad privada
|
||||
```
|
||||
|
||||
Este diseño asegura que tu código Go tenga control completo sobre cómo se accede y modifica el estado del objeto, proporcionando una mejor encapsulación y seguridad de tipos.
|
||||
|
||||
### Declarando Constantes
|
||||
|
||||
El generador soporta exportar constantes Go a PHP usando dos directivas: `//export_php:const` para constantes globales y `//export_php:classconst` para constantes de clase. Esto te permite compartir valores de configuración, códigos de estado y otras constantes entre código Go y PHP.
|
||||
|
||||
#### Constantes Globales
|
||||
|
||||
Usa la directiva `//export_php:const` para crear constantes globales de PHP:
|
||||
|
||||
```go
|
||||
package ejemplo
|
||||
|
||||
//export_php:const
|
||||
const MAX_CONNECTIONS = 100
|
||||
|
||||
//export_php:const
|
||||
const API_VERSION = "1.2.3"
|
||||
|
||||
//export_php:const
|
||||
const STATUS_OK = iota
|
||||
|
||||
//export_php:const
|
||||
const STATUS_ERROR = iota
|
||||
```
|
||||
|
||||
#### Constantes de Clase
|
||||
|
||||
Usa la directiva `//export_php:classconst ClassName` para crear constantes que pertenecen a una clase PHP específica:
|
||||
|
||||
```go
|
||||
package ejemplo
|
||||
|
||||
//export_php:classconst User
|
||||
const STATUS_ACTIVE = 1
|
||||
|
||||
//export_php:classconst User
|
||||
const STATUS_INACTIVE = 0
|
||||
|
||||
//export_php:classconst User
|
||||
const ROLE_ADMIN = "admin"
|
||||
|
||||
//export_php:classconst Order
|
||||
const STATE_PENDING = iota
|
||||
|
||||
//export_php:classconst Order
|
||||
const STATE_PROCESSING = iota
|
||||
|
||||
//export_php:classconst Order
|
||||
const STATE_COMPLETED = iota
|
||||
```
|
||||
|
||||
Las constantes de clase son accesibles usando el ámbito del nombre de clase en PHP:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// Constantes globales
|
||||
echo MAX_CONNECTIONS; // 100
|
||||
echo API_VERSION; // "1.2.3"
|
||||
|
||||
// Constantes de clase
|
||||
echo User::STATUS_ACTIVE; // 1
|
||||
echo User::ROLE_ADMIN; // "admin"
|
||||
echo Order::STATE_PENDING; // 0
|
||||
```
|
||||
|
||||
La directiva soporta varios tipos de valores incluyendo strings, enteros, booleanos, floats y constantes iota. Cuando se usa `iota`, el generador asigna automáticamente valores secuenciales (0, 1, 2, etc.). Las constantes globales se vuelven disponibles en tu código PHP como constantes globales, mientras que las constantes de clase tienen alcance a sus respectivas clases usando visibilidad pública. Cuando se usan enteros, se soportan diferentes notaciones posibles (binario, hexadecimal, octal) y se vuelcan tal cual en el archivo stub de PHP.
|
||||
|
||||
Puedes usar constantes tal como estás acostumbrado en el código Go. Por ejemplo, tomemos la función `repeat_this()` que declaramos anteriormente y cambiemos el último argumento a un entero:
|
||||
|
||||
```go
|
||||
package ejemplo
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:const
|
||||
const STR_REVERSE = iota
|
||||
|
||||
//export_php:const
|
||||
const STR_NORMAL = iota
|
||||
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_LOWERCASE = 1
|
||||
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_UPPERCASE = 2
|
||||
|
||||
//export_php:function repeat_this(string $str, int $count, int $mode): string
|
||||
func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
// invertir la cadena
|
||||
}
|
||||
|
||||
if mode == STR_NORMAL {
|
||||
// no hacer nada, solo para mostrar la constante
|
||||
}
|
||||
|
||||
return frankenphp.PHPString(result, false)
|
||||
}
|
||||
|
||||
//export_php:class StringProcessor
|
||||
type StringProcessorStruct struct {
|
||||
// campos internos
|
||||
}
|
||||
|
||||
//export_php:method StringProcessor::process(string $input, int $mode): string
|
||||
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
|
||||
return frankenphp.PHPString(str, false)
|
||||
}
|
||||
```
|
||||
|
||||
### Usando Espacios de Nombres
|
||||
|
||||
El generador soporta organizar las funciones, clases y constantes de tu extensión PHP bajo un espacio de nombres usando la directiva `//export_php:namespace`. Esto ayuda a evitar conflictos de nombres y proporciona una mejor organización para la API de tu extensión.
|
||||
|
||||
#### Declarando un Espacio de Nombres
|
||||
|
||||
Usa la directiva `//export_php:namespace` al inicio de tu archivo Go para colocar todos los símbolos exportados bajo un espacio de nombres específico:
|
||||
|
||||
```go
|
||||
//export_php:namespace Mi\Extensión
|
||||
package ejemplo
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function hello(): string
|
||||
func hello() string {
|
||||
return "Hola desde el espacio de nombres Mi\\Extensión!"
|
||||
}
|
||||
|
||||
//export_php:class User
|
||||
type UserStruct struct {
|
||||
// campos internos
|
||||
}
|
||||
|
||||
//export_php:method User::getName(): string
|
||||
func (u *UserStruct) GetName() unsafe.Pointer {
|
||||
return frankenphp.PHPString("John Doe", false)
|
||||
}
|
||||
|
||||
//export_php:const
|
||||
const STATUS_ACTIVE = 1
|
||||
```
|
||||
|
||||
#### Usando la Extensión con Espacio de Nombres en PHP
|
||||
|
||||
Cuando se declara un espacio de nombres, todas las funciones, clases y constantes se colocan bajo ese espacio de nombres en PHP:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
echo Mi\Extensión\hello(); // "Hola desde el espacio de nombres Mi\Extensión!"
|
||||
|
||||
$user = new Mi\Extensión\User();
|
||||
echo $user->getName(); // "John Doe"
|
||||
|
||||
echo Mi\Extensión\STATUS_ACTIVE; // 1
|
||||
```
|
||||
|
||||
#### Notas Importantes
|
||||
|
||||
- Solo se permite **una** directiva de espacio de nombres por archivo. Si se encuentran múltiples directivas de espacio de nombres, el generador devolverá un error.
|
||||
- El espacio de nombres se aplica a **todos** los símbolos exportados en el archivo: funciones, clases, métodos y constantes.
|
||||
- Los nombres de espacios de nombres siguen las convenciones de espacios de nombres de PHP usando barras invertidas (`\`) como separadores.
|
||||
- Si no se declara un espacio de nombres, los símbolos se exportan al espacio de nombres global como de costumbre.
|
||||
|
||||
### Generando la Extensión
|
||||
|
||||
Aquí es donde ocurre la magia, y tu extensión ahora puede ser generada. Puedes ejecutar el generador con el siguiente comando:
|
||||
|
||||
```console
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init mi_extensión.go
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> No olvides establecer la variable de entorno `GEN_STUB_SCRIPT` a la ruta del archivo `gen_stub.php` en las fuentes de PHP que descargaste anteriormente. Este es el mismo script `gen_stub.php` mencionado en la sección de implementación manual.
|
||||
|
||||
Si todo salió bien, se debería haber creado un nuevo directorio llamado `build`. Este directorio contiene los archivos generados para tu extensión, incluyendo el archivo `mi_extensión.go` con los stubs de funciones PHP generadas.
|
||||
|
||||
### Integrando la Extensión Generada en FrankenPHP
|
||||
|
||||
Nuestra extensión ahora está lista para ser compilada e integrada en FrankenPHP. Para hacerlo, consulta la documentación de [compilación de FrankenPHP](compile.md) para aprender cómo compilar FrankenPHP. Agrega el módulo usando la bandera `--with`, apuntando a la ruta de tu módulo:
|
||||
|
||||
```console
|
||||
CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
|
||||
CGO_CFLAGS=$(php-config --includes) \
|
||||
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
|
||||
xcaddy build \
|
||||
--output frankenphp \
|
||||
--with github.com/mi-cuenta/mi-módulo/build
|
||||
```
|
||||
|
||||
Ten en cuenta que apuntas al subdirectorio `/build` que se creó durante el paso de generación. Sin embargo, esto no es obligatorio: también puedes copiar los archivos generados a tu directorio de módulo y apuntar a él directamente.
|
||||
|
||||
### Probando tu Extensión Generada
|
||||
|
||||
Puedes crear un archivo PHP para probar las funciones y clases que has creado. Por ejemplo, crea un archivo `index.php` con el siguiente contenido:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// Usando constantes globales
|
||||
var_dump(repeat_this('Hola Mundo', 5, STR_REVERSE));
|
||||
|
||||
// Usando constantes de clase
|
||||
$processor = new StringProcessor();
|
||||
echo $processor->process('Hola Mundo', StringProcessor::MODE_LOWERCASE); // "hola mundo"
|
||||
echo $processor->process('Hola Mundo', StringProcessor::MODE_UPPERCASE); // "HOLA MUNDO"
|
||||
```
|
||||
|
||||
Una vez que hayas integrado tu extensión en FrankenPHP como se demostró en la sección anterior, puedes ejecutar este archivo de prueba usando `./frankenphp php-server`, y deberías ver tu extensión funcionando.
|
||||
|
||||
## Implementación Manual
|
||||
|
||||
Si quieres entender cómo funcionan las extensiones o necesitas un control total sobre tu extensión, puedes escribirlas manualmente. Este enfoque te da control completo pero requiere más código repetitivo.
|
||||
|
||||
### Función Básica
|
||||
|
||||
Veremos cómo escribir una extensión PHP simple en Go que define una nueva función nativa. Esta función será llamada desde PHP y desencadenará una goroutine que registra un mensaje en los logs de Caddy. Esta función no toma ningún parámetro y no devuelve nada.
|
||||
|
||||
#### Definir la Función Go
|
||||
|
||||
En tu módulo, necesitas definir una nueva función nativa que será llamada desde PHP. Para esto, crea un archivo con el nombre que desees, por ejemplo, `extension.go`, y agrega el siguiente código:
|
||||
|
||||
```go
|
||||
package ejemplo
|
||||
|
||||
// #include "extension.h"
|
||||
import "C"
|
||||
import (
|
||||
"log/slog"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry))
|
||||
}
|
||||
|
||||
//export go_print_something
|
||||
func go_print_something() {
|
||||
go func() {
|
||||
slog.Info("¡Hola desde una goroutine!")
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
La función `frankenphp.RegisterExtension()` simplifica el proceso de registro de la extensión manejando la lógica interna de registro de PHP. La función `go_print_something` usa la directiva `//export` para indicar que será accesible en el código C que escribiremos, gracias a CGO.
|
||||
|
||||
En este ejemplo, nuestra nueva función desencadenará una goroutine que registra un mensaje en los logs de Caddy.
|
||||
|
||||
#### Definir la Función PHP
|
||||
|
||||
Para permitir que PHP llame a nuestra función, necesitamos definir una función PHP correspondiente. Para esto, crearemos un archivo stub, por ejemplo, `extension.stub.php`, que contendrá el siguiente código:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
/** @generate-class-entries */
|
||||
|
||||
function go_print(): void {}
|
||||
```
|
||||
|
||||
Este archivo define la firma de la función `go_print()`, que será llamada desde PHP. La directiva `@generate-class-entries` permite a PHP generar automáticamente entradas de funciones para nuestra extensión.
|
||||
|
||||
Esto no se hace manualmente, sino usando un script proporcionado en las fuentes de PHP (asegúrate de ajustar la ruta al script `gen_stub.php` según dónde se encuentren tus fuentes de PHP):
|
||||
|
||||
```bash
|
||||
php ../php-src/build/gen_stub.php extension.stub.php
|
||||
```
|
||||
|
||||
Este script generará un archivo llamado `extension_arginfo.h` que contiene la información necesaria para que PHP sepa cómo definir y llamar a nuestra función.
|
||||
|
||||
#### Escribir el Puente entre Go y C
|
||||
|
||||
Ahora, necesitamos escribir el puente entre Go y C. Crea un archivo llamado `extension.h` en el directorio de tu módulo con el siguiente contenido:
|
||||
|
||||
```c
|
||||
#ifndef _EXTENSION_H
|
||||
#define _EXTENSION_H
|
||||
|
||||
#include <php.h>
|
||||
|
||||
extern zend_module_entry ext_module_entry;
|
||||
|
||||
#endif
|
||||
```
|
||||
|
||||
A continuación, crea un archivo llamado `extension.c` que realizará los siguientes pasos:
|
||||
|
||||
- Incluir los encabezados de PHP;
|
||||
- Declarar nuestra nueva función nativa de PHP `go_print()`;
|
||||
- Declarar los metadatos de la extensión.
|
||||
|
||||
Comencemos incluyendo los encabezados requeridos:
|
||||
|
||||
```c
|
||||
#include <php.h>
|
||||
#include "extension.h"
|
||||
#include "extension_arginfo.h"
|
||||
|
||||
// Contiene símbolos exportados por Go
|
||||
#include "_cgo_export.h"
|
||||
```
|
||||
|
||||
Luego definimos nuestra función PHP como una función de lenguaje nativo:
|
||||
|
||||
```c
|
||||
PHP_FUNCTION(go_print)
|
||||
{
|
||||
ZEND_PARSE_PARAMETERS_NONE();
|
||||
|
||||
go_print_something();
|
||||
}
|
||||
|
||||
zend_module_entry ext_module_entry = {
|
||||
STANDARD_MODULE_HEADER,
|
||||
"ext_go",
|
||||
ext_functions, /* Funciones */
|
||||
NULL, /* MINIT */
|
||||
NULL, /* MSHUTDOWN */
|
||||
NULL, /* RINIT */
|
||||
NULL, /* RSHUTDOWN */
|
||||
NULL, /* MINFO */
|
||||
"0.1.1",
|
||||
STANDARD_MODULE_PROPERTIES
|
||||
};
|
||||
```
|
||||
|
||||
En este caso, nuestra función no toma parámetros y no devuelve nada. Simplemente llama a la función Go que definimos anteriormente, exportada usando la directiva `//export`.
|
||||
|
||||
Finalmente, definimos los metadatos de la extensión en una estructura `zend_module_entry`, como su nombre, versión y propiedades. Esta información es necesaria para que PHP reconozca y cargue nuestra extensión. Ten en cuenta que `ext_functions` es un array de punteros a las funciones PHP que definimos, y fue generado automáticamente por el script `gen_stub.php` en el archivo `extension_arginfo.h`.
|
||||
|
||||
El registro de la extensión es manejado automáticamente por la función `RegisterExtension()` de FrankenPHP que llamamos en nuestro código Go.
|
||||
|
||||
### Uso Avanzado
|
||||
|
||||
Ahora que sabemos cómo crear una extensión PHP básica en Go, compliquemos nuestro ejemplo. Ahora crearemos una función PHP que tome una cadena como parámetro y devuelva su versión en mayúsculas.
|
||||
|
||||
#### Definir el Stub de la Función PHP
|
||||
|
||||
Para definir la nueva función PHP, modificaremos nuestro archivo `extension.stub.php` para incluir la nueva firma de la función:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
/** @generate-class-entries */
|
||||
|
||||
/**
|
||||
* Convierte una cadena a mayúsculas.
|
||||
*
|
||||
* @param string $string La cadena a convertir.
|
||||
* @return string La versión en mayúsculas de la cadena.
|
||||
*/
|
||||
function go_upper(string $string): string {}
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> ¡No descuides la documentación de tus funciones! Es probable que compartas tus stubs de extensión con otros desarrolladores para documentar cómo usar tu extensión y qué características están disponibles.
|
||||
|
||||
Al regenerar el archivo stub con el script `gen_stub.php`, el archivo `extension_arginfo.h` debería verse así:
|
||||
|
||||
```c
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_go_upper, 0, 1, IS_STRING, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_FUNCTION(go_upper);
|
||||
|
||||
static const zend_function_entry ext_functions[] = {
|
||||
ZEND_FE(go_upper, arginfo_go_upper)
|
||||
ZEND_FE_END
|
||||
};
|
||||
```
|
||||
|
||||
Podemos ver que la función `go_upper` está definida con un parámetro de tipo `string` y un tipo de retorno `string`.
|
||||
|
||||
#### Manipulación de Tipos entre Go y PHP/C
|
||||
|
||||
Tu función Go no puede aceptar directamente una cadena PHP como parámetro. Necesitas convertirla a una cadena Go. Afortunadamente, FrankenPHP proporciona funciones helper para manejar la conversión entre cadenas PHP y cadenas Go, similar a lo que vimos en el enfoque del generador.
|
||||
|
||||
El archivo de encabezado sigue siendo simple:
|
||||
|
||||
```c
|
||||
#ifndef _EXTENSION_H
|
||||
#define _EXTENSION_H
|
||||
|
||||
#include <php.h>
|
||||
|
||||
extern zend_module_entry ext_module_entry;
|
||||
|
||||
#endif
|
||||
```
|
||||
|
||||
Ahora podemos escribir el puente entre Go y C en nuestro archivo `extension.c`. Pasaremos la cadena PHP directamente a nuestra función Go:
|
||||
|
||||
```c
|
||||
PHP_FUNCTION(go_upper)
|
||||
{
|
||||
zend_string *str;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(1, 1)
|
||||
Z_PARAM_STR(str)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
zend_string *result = go_upper(str);
|
||||
RETVAL_STR(result);
|
||||
}
|
||||
```
|
||||
|
||||
Puedes aprender más sobre `ZEND_PARSE_PARAMETERS_START` y el análisis de parámetros en la página dedicada del [Libro de Internals de PHP](https://www.phpinternalsbook.com/php7/extensions_design/php_functions.html#parsing-parameters-zend-parse-parameters). Aquí, le decimos a PHP que nuestra función toma un parámetro obligatorio de tipo `string` como `zend_string`. Luego pasamos esta cadena directamente a nuestra función Go y devolvemos el resultado usando `RETVAL_STR`.
|
||||
|
||||
Solo queda una cosa por hacer: implementar la función `go_upper` en Go.
|
||||
|
||||
#### Implementar la Función Go
|
||||
|
||||
Nuestra función Go tomará un `*C.zend_string` como parámetro, lo convertirá a una cadena Go usando la función helper de FrankenPHP, lo procesará y devolverá el resultado como un nuevo `*C.zend_string`. Las funciones helper manejan toda la complejidad de gestión de memoria y conversión por nosotros.
|
||||
|
||||
```go
|
||||
package ejemplo
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
"strings"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export go_upper
|
||||
func go_upper(s *C.zend_string) *C.zend_string {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
upper := strings.ToUpper(str)
|
||||
|
||||
return (*C.zend_string)(frankenphp.PHPString(upper, false))
|
||||
}
|
||||
```
|
||||
|
||||
Este enfoque es mucho más limpio y seguro que la gestión manual de memoria.
|
||||
Las funciones helper de FrankenPHP manejan la conversión entre el formato `zend_string` de PHP y las cadenas Go automáticamente.
|
||||
El parámetro `false` en `PHPString()` indica que queremos crear una nueva cadena no persistente (liberada al final de la solicitud).
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> En este ejemplo, no realizamos ningún manejo de errores, pero siempre debes verificar que los punteros no sean `nil` y que los datos sean válidos antes de usarlos en tus funciones Go.
|
||||
|
||||
### Integrando la Extensión en FrankenPHP
|
||||
|
||||
Nuestra extensión ahora está lista para ser compilada e integrada en FrankenPHP. Para hacerlo, consulta la documentación de [compilación de FrankenPHP](compile.md) para aprender cómo compilar FrankenPHP. Agrega el módulo usando la bandera `--with`, apuntando a la ruta de tu módulo:
|
||||
|
||||
```console
|
||||
CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
|
||||
CGO_CFLAGS=$(php-config --includes) \
|
||||
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
|
||||
xcaddy build \
|
||||
--output frankenphp \
|
||||
--with github.com/mi-cuenta/mi-módulo
|
||||
```
|
||||
|
||||
¡Eso es todo! Tu extensión ahora está integrada en FrankenPHP y puede ser usada en tu código PHP.
|
||||
|
||||
### Probando tu Extensión
|
||||
|
||||
Después de integrar tu extensión en FrankenPHP, puedes crear un archivo `index.php` con ejemplos para las funciones que has implementado:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// Probar función básica
|
||||
go_print();
|
||||
|
||||
// Probar función avanzada
|
||||
echo go_upper("hola mundo") . "\n";
|
||||
```
|
||||
|
||||
Ahora puedes ejecutar FrankenPHP con este archivo usando `./frankenphp php-server`, y deberías ver tu extensión funcionando.
|
||||
31
docs/es/github-actions.md
Normal file
31
docs/es/github-actions.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Usando GitHub Actions
|
||||
|
||||
Este repositorio construye y despliega la imagen Docker en [Docker Hub](https://hub.docker.com/r/dunglas/frankenphp) en
|
||||
cada pull request aprobado o en tu propio fork una vez configurado.
|
||||
|
||||
## Configurando GitHub Actions
|
||||
|
||||
En la configuración del repositorio, bajo secrets, agrega los siguientes secretos:
|
||||
|
||||
- `REGISTRY_LOGIN_SERVER`: El registro Docker a usar (ej. `docker.io`).
|
||||
- `REGISTRY_USERNAME`: El nombre de usuario para iniciar sesión en el registro (ej. `dunglas`).
|
||||
- `REGISTRY_PASSWORD`: La contraseña para iniciar sesión en el registro (ej. una clave de acceso).
|
||||
- `IMAGE_NAME`: El nombre de la imagen (ej. `dunglas/frankenphp`).
|
||||
|
||||
## Construyendo y Subiendo la Imagen
|
||||
|
||||
1. Crea un Pull Request o haz push a tu fork.
|
||||
2. GitHub Actions construirá la imagen y ejecutará cualquier prueba.
|
||||
3. Si la construcción es exitosa, la imagen será subida al registro usando la etiqueta `pr-x`, donde `x` es el número del PR.
|
||||
|
||||
## Desplegando la Imagen
|
||||
|
||||
1. Una vez que el Pull Request sea fusionado, GitHub Actions ejecutará nuevamente las pruebas y construirá una nueva imagen.
|
||||
2. Si la construcción es exitosa, la etiqueta `main` será actualizada en el registro Docker.
|
||||
|
||||
## Lanzamientos (Releases)
|
||||
|
||||
1. Crea una nueva etiqueta (tag) en el repositorio.
|
||||
2. GitHub Actions construirá la imagen y ejecutará cualquier prueba.
|
||||
3. Si la construcción es exitosa, la imagen será subida al registro usando el nombre de la etiqueta como etiqueta (ej. se crearán `v1.2.3` y `v1.2`).
|
||||
4. La etiqueta `latest` también será actualizada.
|
||||
139
docs/es/hot-reload.md
Normal file
139
docs/es/hot-reload.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Hot reload
|
||||
|
||||
FrankenPHP incluye una función de **hot reload** integrada diseñada para mejorar significativamente la experiencia del desarrollador.
|
||||
|
||||

|
||||
|
||||
Esta función proporciona un flujo de trabajo similar a **Hot Module Replacement (HMR)** encontrado en herramientas modernas de JavaScript (como Vite o webpack).
|
||||
En lugar de actualizar manualmente el navegador después de cada cambio de archivo (código PHP, plantillas, archivos JavaScript y CSS...),
|
||||
FrankenPHP actualiza el contenido en tiempo real.
|
||||
|
||||
La Hot Reload funciona de forma nativa con WordPress, Laravel, Symfony y cualquier otra aplicación o framework PHP.
|
||||
|
||||
Cuando está activada, FrankenPHP vigila el directorio de trabajo actual en busca de cambios en el sistema de archivos.
|
||||
Cuando se modifica un archivo, envía una actualización [Mercure](mercure.md) al navegador.
|
||||
|
||||
Dependiendo de la configuración, el navegador:
|
||||
|
||||
- **Transformará el DOM** (preservando la posición de desplazamiento y el estado de los inputs) si [Idiomorph](https://github.com/bigskysoftware/idiomorph) está cargado.
|
||||
- **Recargará la página** (recarga en vivo estándar) si Idiomorph no está presente.
|
||||
|
||||
## Configuración
|
||||
|
||||
Para habilitar la Hot Reload, active Mercure y luego agregue la subdirectiva `hot_reload` a la directiva `php_server` en su `Caddyfile`.
|
||||
|
||||
> [!WARNING]
|
||||
> Esta función está destinada **únicamente a entornos de desarrollo**.
|
||||
> No active `hot_reload` en producción, ya que vigilar el sistema de archivos implica una sobrecarga de rendimiento y expone endpoints internos.
|
||||
|
||||
```caddyfile
|
||||
localhost
|
||||
|
||||
mercure {
|
||||
anonymous
|
||||
}
|
||||
|
||||
root public/
|
||||
php_server {
|
||||
hot_reload
|
||||
}
|
||||
```
|
||||
|
||||
Por omisión, FrankenPHP vigilará todos los archivos en el directorio de trabajo actual que coincidan con este patrón glob: `./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}`
|
||||
|
||||
Es posible establecer explícitamente los archivos a vigilar usando la sintaxis glob:
|
||||
|
||||
```caddyfile
|
||||
localhost
|
||||
|
||||
mercure {
|
||||
anonymous
|
||||
}
|
||||
|
||||
root public/
|
||||
php_server {
|
||||
hot_reload src/**/*{.php,.js} config/**/*.yaml
|
||||
}
|
||||
```
|
||||
|
||||
Use la forma larga para especificar el tema de Mercure a utilizar, así como qué directorios o archivos vigilar, proporcionando rutas a la opción `hot_reload`:
|
||||
|
||||
```caddyfile
|
||||
localhost
|
||||
|
||||
mercure {
|
||||
anonymous
|
||||
}
|
||||
|
||||
root public/
|
||||
php_server {
|
||||
hot_reload {
|
||||
topic hot-reload-topic
|
||||
watch src/**/*.php
|
||||
watch assets/**/*.{ts,json}
|
||||
watch templates/
|
||||
watch public/css/
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integración Lado-Cliente
|
||||
|
||||
Mientras el servidor detecta los cambios, el navegador necesita suscribirse a estos eventos para actualizar la página.
|
||||
FrankenPHP expone la URL del Mercure Hub a utilizar para suscribirse a los cambios de archivos a través de la variable de entorno `$_SERVER['FRANKENPHP_HOT_RELOAD']`.
|
||||
|
||||
Una biblioteca JavaScript de conveniencia, [frankenphp-hot-reload](https://www.npmjs.com/package/frankenphp-hot-reload), también está disponible para manejar la lógica lado-cliente.
|
||||
Para usarla, agregue lo siguiente a su diseño principal:
|
||||
|
||||
```php
|
||||
<!DOCTYPE html>
|
||||
<title>FrankenPHP Hot Reload</title>
|
||||
<?php if (isset($_SERVER['FRANKENPHP_HOT_RELOAD'])): ?>
|
||||
<meta name="frankenphp-hot-reload:url" content="<?=$_SERVER['FRANKENPHP_HOT_RELOAD']?>">
|
||||
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
|
||||
<?php endif ?>
|
||||
```
|
||||
|
||||
La biblioteca se suscribirá automáticamente al hub de Mercure, obtendrá la URL actual en segundo plano cuando se detecte un cambio en un archivo y transformará el DOM.
|
||||
Está disponible como un paquete [npm](https://www.npmjs.com/package/frankenphp-hot-reload) y en [GitHub](https://github.com/dunglas/frankenphp-hot-reload).
|
||||
|
||||
Alternativamente, puede implementar su propia lógica lado-cliente suscribiéndose directamente al hub de Mercure usando la clase nativa de JavaScript `EventSource`.
|
||||
|
||||
### Modo Worker
|
||||
|
||||
Si está ejecutando su aplicación en [Modo Worker](https://frankenphp.dev/docs/worker/), el script de su aplicación permanece en memoria.
|
||||
Esto significa que los cambios en su código PHP no se reflejarán inmediatamente, incluso si el navegador se recarga.
|
||||
|
||||
Para la mejor experiencia de desarrollador, debe combinar `hot_reload` con [la subdirectiva `watch` en la directiva `worker`](config.md#watching-for-file-changes).
|
||||
|
||||
- `hot_reload`: actualiza el **navegador** cuando los archivos cambian
|
||||
- `worker.watch`: reinicia el worker cuando los archivos cambian
|
||||
|
||||
```caddy
|
||||
localhost
|
||||
|
||||
mercure {
|
||||
anonymous
|
||||
}
|
||||
|
||||
root public/
|
||||
php_server {
|
||||
hot_reload
|
||||
worker {
|
||||
file /path/to/my_worker.php
|
||||
watch
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Funcionamiento
|
||||
|
||||
1. **Vigilancia**: FrankenPHP monitorea el sistema de archivos en busca de modificaciones usando [la biblioteca `e-dant/watcher`](https://github.com/e-dant/watcher) internamente (contribuimos con el binding de Go).
|
||||
2. **Reinicio (Modo Worker)**: si `watch` está habilitado en la configuración del worker, el worker de PHP se reinicia para cargar el nuevo código.
|
||||
3. **Envío**: se envía una carga útil JSON que contiene la lista de archivos modificados al [hub de Mercure](https://mercure.rocks) integrado.
|
||||
4. **Recepción**: El navegador, escuchando a través de la biblioteca JavaScript, recibe el evento de Mercure.
|
||||
5. **Actualización**:
|
||||
|
||||
- Si se detecta **Idiomorph**, obtiene el contenido actualizado y transforma el HTML actual para que coincida con el nuevo estado, aplicando los cambios al instante sin perder el estado.
|
||||
- De lo contrario, se llama a `window.location.reload()` para recargar la página.
|
||||
147
docs/es/known-issues.md
Normal file
147
docs/es/known-issues.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Problemas Conocidos
|
||||
|
||||
## Extensiones PHP no Soportadas
|
||||
|
||||
Las siguientes extensiones se sabe que no son compatibles con FrankenPHP:
|
||||
|
||||
| Nombre | Razón | Alternativas |
|
||||
| ----------------------------------------------------------------------------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/es/imap.installation.php) | No es thread-safe | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
|
||||
| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | No es thread-safe | - |
|
||||
|
||||
## Extensiones PHP con Errores
|
||||
|
||||
Las siguientes extensiones tienen errores conocidos y comportamientos inesperados cuando se usan con FrankenPHP:
|
||||
|
||||
| Nombre | Problema |
|
||||
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ext-openssl](https://www.php.net/manual/es/book.openssl.php) | Cuando se usa musl libc, la extensión OpenSSL puede fallar bajo cargas pesadas. El problema no ocurre cuando se usa la más popular GNU libc. Este error está [siendo rastreado por PHP](https://github.com/php/php-src/issues/13648). |
|
||||
|
||||
## get_browser
|
||||
|
||||
La función [get_browser()](https://www.php.net/manual/es/function.get-browser.php) parece funcionar mal después de un tiempo. Una solución es almacenar en caché (por ejemplo, con [APCu](https://www.php.net/manual/es/book.apcu.php)) los resultados por User Agent, ya que son estáticos.
|
||||
|
||||
## Binario Autónomo e Imágenes Docker Basadas en Alpine
|
||||
|
||||
Los binarios completamente estáticos y las imágenes Docker basadas en Alpine (`dunglas/frankenphp:*-alpine`) usan [musl libc](https://musl.libc.org/) en lugar de [glibc](https://www.etalabs.net/compare_libcs.html), para mantener un tamaño de binario más pequeño.
|
||||
Esto puede llevar a algunos problemas de compatibilidad.
|
||||
En particular, la bandera glob `GLOB_BRACE` [no está disponible](https://www.php.net/manual/es/function.glob.php).
|
||||
|
||||
Se recomienda usar la variante GNU del binario estático y las imágenes Docker basadas en Debian si encuentras problemas.
|
||||
|
||||
## Usar `https://127.0.0.1` con Docker
|
||||
|
||||
Por defecto, FrankenPHP genera un certificado TLS para `localhost`.
|
||||
Es la opción más fácil y recomendada para el desarrollo local.
|
||||
|
||||
Si realmente deseas usar `127.0.0.1` como host en su lugar, es posible configurarlo para generar un certificado para él estableciendo el nombre del servidor en `127.0.0.1`.
|
||||
|
||||
Desafortunadamente, esto no es suficiente al usar Docker debido a [su sistema de red](https://docs.docker.com/network/).
|
||||
Obtendrás un error TLS similar a `curl: (35) LibreSSL/3.3.6: error:1404B438:SSL routines:ST_CONNECT:tlsv1 alert internal error`.
|
||||
|
||||
Si estás usando Linux, una solución es usar [el controlador de red host](https://docs.docker.com/network/network-tutorial-host/):
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e SERVER_NAME="127.0.0.1" \
|
||||
-v $PWD:/app/public \
|
||||
--network host \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
El controlador de red host no está soportado en Mac y Windows. En estas plataformas, tendrás que adivinar la dirección IP del contenedor e incluirla en los nombres del servidor.
|
||||
|
||||
Ejecuta `docker network inspect bridge` y busca la clave `Containers` para identificar la última dirección IP actualmente asignada bajo la clave `IPv4Address`, y incrementa en uno. Si no hay contenedores en ejecución, la primera dirección IP asignada suele ser `172.17.0.2`.
|
||||
|
||||
Luego, incluye esto en la variable de entorno `SERVER_NAME`:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e SERVER_NAME="127.0.0.1, 172.17.0.3" \
|
||||
-v $PWD:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Asegúrate de reemplazar `172.17.0.3` con la IP que se asignará a tu contenedor.
|
||||
|
||||
Ahora deberías poder acceder a `https://127.0.0.1` desde la máquina host.
|
||||
|
||||
Si no es así, inicia FrankenPHP en modo depuración para intentar identificar el problema:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e CADDY_GLOBAL_OPTIONS="debug" \
|
||||
-e SERVER_NAME="127.0.0.1" \
|
||||
-v $PWD:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
## Scripts de Composer que Referencian `@php`
|
||||
|
||||
Los [scripts de Composer](https://getcomposer.org/doc/articles/scripts.md) pueden querer ejecutar un binario PHP para algunas tareas, por ejemplo, en [un proyecto Laravel](laravel.md) para ejecutar `@php artisan package:discover --ansi`. Esto [actualmente falla](https://github.com/php/frankenphp/issues/483#issuecomment-1899890915) por dos razones:
|
||||
|
||||
- Composer no sabe cómo llamar al binario de FrankenPHP;
|
||||
- Composer puede agregar configuraciones de PHP usando la bandera `-d` en el comando, que FrankenPHP aún no soporta.
|
||||
|
||||
Como solución alternativa, podemos crear un script de shell en `/usr/local/bin/php` que elimine los parámetros no soportados y luego llame a FrankenPHP:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
args=("$@")
|
||||
index=0
|
||||
for i in "$@"
|
||||
do
|
||||
if [ "$i" == "-d" ]; then
|
||||
unset 'args[$index]'
|
||||
unset 'args[$index+1]'
|
||||
fi
|
||||
index=$((index+1))
|
||||
done
|
||||
|
||||
/usr/local/bin/frankenphp php-cli ${args[@]}
|
||||
```
|
||||
|
||||
Luego, establece la variable de entorno `PHP_BINARY` a la ruta de nuestro script `php` y ejecuta Composer:
|
||||
|
||||
```console
|
||||
export PHP_BINARY=/usr/local/bin/php
|
||||
composer install
|
||||
```
|
||||
|
||||
## Solución de Problemas de TLS/SSL con Binarios Estáticos
|
||||
|
||||
Al usar los binarios estáticos, puedes encontrar los siguientes errores relacionados con TLS, por ejemplo, al enviar correos electrónicos usando STARTTLS:
|
||||
|
||||
```text
|
||||
No se puede conectar con STARTTLS: stream_socket_enable_crypto(): La operación SSL falló con el código 5. Mensajes de error de OpenSSL:
|
||||
error:80000002:librería del sistema::No existe el archivo o el directorio
|
||||
error:80000002:librería del sistema::No existe el archivo o el directorio
|
||||
error:80000002:librería del sistema::No existe el archivo o el directorio
|
||||
error:0A000086:rutinas de SSL::falló la verificación del certificado
|
||||
```
|
||||
|
||||
Dado que el binario estático no incluye certificados TLS, necesitas indicar a OpenSSL la ubicación de tu instalación local de certificados CA.
|
||||
|
||||
Inspecciona la salida de [`openssl_get_cert_locations()`](https://www.php.net/manual/es/function.openssl-get-cert-locations.php),
|
||||
para encontrar dónde deben instalarse los certificados CA y guárdalos en esa ubicación.
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Los contextos web y CLI pueden tener configuraciones diferentes.
|
||||
> Asegúrate de ejecutar `openssl_get_cert_locations()` en el contexto adecuado.
|
||||
|
||||
[Los certificados CA extraídos de Mozilla pueden descargarse del sitio de cURL](https://curl.se/docs/caextract.html).
|
||||
|
||||
Alternativamente, muchas distribuciones, incluyendo Debian, Ubuntu y Alpine, proporcionan paquetes llamados `ca-certificates` que contienen estos certificados.
|
||||
|
||||
También es posible usar `SSL_CERT_FILE` y `SSL_CERT_DIR` para indicar a OpenSSL dónde buscar los certificados CA:
|
||||
|
||||
```console
|
||||
# Establecer variables de entorno de certificados TLS
|
||||
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
export SSL_CERT_DIR=/etc/ssl/certs
|
||||
```
|
||||
214
docs/es/laravel.md
Normal file
214
docs/es/laravel.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# Laravel
|
||||
|
||||
## Docker
|
||||
|
||||
Servir una aplicación web [Laravel](https://laravel.com) con FrankenPHP es tan fácil como montar el proyecto en el directorio `/app` de la imagen Docker oficial.
|
||||
|
||||
Ejecuta este comando desde el directorio principal de tu aplicación Laravel:
|
||||
|
||||
```console
|
||||
docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
|
||||
```
|
||||
|
||||
¡Y listo!
|
||||
|
||||
## Instalación Local
|
||||
|
||||
Alternativamente, puedes ejecutar tus proyectos Laravel con FrankenPHP desde tu máquina local:
|
||||
|
||||
1. [Descarga el binario correspondiente a tu sistema](../#binario-autónomo)
|
||||
2. Agrega la siguiente configuración a un archivo llamado `Caddyfile` en el directorio raíz de tu proyecto Laravel:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
|
||||
# El nombre de dominio de tu servidor
|
||||
localhost {
|
||||
# Establece el directorio web raíz en public/
|
||||
root public/
|
||||
# Habilita la compresión (opcional)
|
||||
encode zstd br gzip
|
||||
# Ejecuta archivos PHP desde el directorio public/ y sirve los assets
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Inicia FrankenPHP desde el directorio raíz de tu proyecto Laravel: `frankenphp run`
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
Octane se puede instalar a través del gestor de paquetes Composer:
|
||||
|
||||
```console
|
||||
composer require laravel/octane
|
||||
```
|
||||
|
||||
Después de instalar Octane, puedes ejecutar el comando Artisan `octane:install`, que instalará el archivo de configuración de Octane en tu aplicación:
|
||||
|
||||
```console
|
||||
php artisan octane:install --server=frankenphp
|
||||
```
|
||||
|
||||
El servidor Octane se puede iniciar mediante el comando Artisan `octane:frankenphp`.
|
||||
|
||||
```console
|
||||
php artisan octane:frankenphp
|
||||
```
|
||||
|
||||
El comando `octane:frankenphp` puede tomar las siguientes opciones:
|
||||
|
||||
- `--host`: La dirección IP a la que el servidor debe enlazarse (por defecto: `127.0.0.1`)
|
||||
- `--port`: El puerto en el que el servidor debe estar disponible (por defecto: `8000`)
|
||||
- `--admin-port`: El puerto en el que el servidor de administración debe estar disponible (por defecto: `2019`)
|
||||
- `--workers`: El número de workers que deben estar disponibles para manejar solicitudes (por defecto: `auto`)
|
||||
- `--max-requests`: El número de solicitudes a procesar antes de recargar el servidor (por defecto: `500`)
|
||||
- `--caddyfile`: La ruta al archivo `Caddyfile` de FrankenPHP (por defecto: [Caddyfile de plantilla en Laravel Octane](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile))
|
||||
- `--https`: Habilita HTTPS, HTTP/2 y HTTP/3, y genera y renueva certificados automáticamente
|
||||
- `--http-redirect`: Habilita la redirección de HTTP a HTTPS (solo se habilita si se pasa --https)
|
||||
- `--watch`: Recarga automáticamente el servidor cuando se modifica la aplicación
|
||||
- `--poll`: Usa la sondea del sistema de archivos mientras se observa para vigilar archivos a través de una red
|
||||
- `--log-level`: Registra mensajes en o por encima del nivel de registro especificado, usando el registrador nativo de Caddy
|
||||
|
||||
> [!TIP]
|
||||
> Para obtener registros JSON estructurados (útil al usar soluciones de análisis de registros), pasa explícitamente la opción `--log-level`.
|
||||
|
||||
Consulta también [cómo usar Mercure con Octane](#soporte-para-mercure).
|
||||
|
||||
Aprende más sobre [Laravel Octane en su documentación oficial](https://laravel.com/docs/octane).
|
||||
|
||||
## Aplicaciones Laravel como Binarios Autónomos
|
||||
|
||||
Usando [la característica de incrustación de aplicaciones de FrankenPHP](embed.md), es posible distribuir aplicaciones Laravel
|
||||
como binarios autónomos.
|
||||
|
||||
Sigue estos pasos para empaquetar tu aplicación Laravel como un binario autónomo para Linux:
|
||||
|
||||
1. Crea un archivo llamado `static-build.Dockerfile` en el repositorio de tu aplicación:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# Si tienes intención de ejecutar el binario en sistemas musl-libc, usa static-builder-musl en su lugar
|
||||
|
||||
# Copia tu aplicación
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Elimina las pruebas y otros archivos innecesarios para ahorrar espacio
|
||||
# Alternativamente, agrega estos archivos a un archivo .dockerignore
|
||||
RUN rm -Rf tests/
|
||||
|
||||
# Copia el archivo .env
|
||||
RUN cp .env.example .env
|
||||
# Cambia APP_ENV y APP_DEBUG para que estén listos para producción
|
||||
RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env
|
||||
|
||||
# Realiza otros cambios en tu archivo .env si es necesario
|
||||
|
||||
# Instala las dependencias
|
||||
RUN composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# Compila el binario estático
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Algunos archivos `.dockerignore`
|
||||
> ignorarán el directorio `vendor/` y los archivos `.env`. Asegúrate de ajustar o eliminar el archivo `.dockerignore` antes de la compilación.
|
||||
|
||||
2. Compila:
|
||||
|
||||
```console
|
||||
docker build -t static-laravel-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Extrae el binario:
|
||||
|
||||
```console
|
||||
docker cp $(docker create --name static-laravel-app-tmp static-laravel-app):/go/src/app/dist/frankenphp-linux-x86_64 frankenphp ; docker rm static-laravel-app-tmp
|
||||
```
|
||||
|
||||
4. Rellena las cachés:
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan optimize
|
||||
```
|
||||
|
||||
5. Ejecuta las migraciones de la base de datos (si las hay):
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan migrate
|
||||
```
|
||||
|
||||
6. Genera la clave secreta de la aplicación:
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan key:generate
|
||||
```
|
||||
|
||||
7. Inicia el servidor:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
¡Tu aplicación ya está lista!
|
||||
|
||||
Aprende más sobre las opciones disponibles y cómo compilar binarios para otros sistemas operativos en la documentación de [incrustación de aplicaciones](embed.md).
|
||||
|
||||
### Cambiar la Ruta de Almacenamiento
|
||||
|
||||
Por defecto, Laravel almacena los archivos subidos, cachés, registros, etc. en el directorio `storage/` de la aplicación.
|
||||
Esto no es adecuado para aplicaciones incrustadas, ya que cada nueva versión se extraerá en un directorio temporal diferente.
|
||||
|
||||
Establece la variable de entorno `LARAVEL_STORAGE_PATH` (por ejemplo, en tu archivo `.env`) o llama al método `Illuminate\Foundation\Application::useStoragePath()` para usar un directorio fuera del directorio temporal.
|
||||
|
||||
### Soporte para Mercure
|
||||
|
||||
[Mercure](https://mercure.rocks) es una excelente manera de agregar capacidades en tiempo real a tus aplicaciones Laravel.
|
||||
FrankenPHP incluye [soporte para Mercure integrado](mercure.md).
|
||||
|
||||
Si no estás usando [Octane](#laravel-octane), consulta [la entrada de documentación de Mercure](mercure.md).
|
||||
|
||||
Si estás usando Octane, puedes habilitar el soporte para Mercure agregando las siguientes líneas a tu archivo `config/octane.php`:
|
||||
|
||||
```php
|
||||
// ...
|
||||
|
||||
return [
|
||||
// ...
|
||||
|
||||
'mercure' => [
|
||||
'anonymous' => true,
|
||||
'publisher_jwt' => '!CambiaEstaClaveSecretaJWTDelHubMercure!',
|
||||
'subscriber_jwt' => '!CambiaEstaClaveSecretaJWTDelHubMercure!',
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
Puedes usar [todas las directivas soportadas por Mercure](https://mercure.rocks/docs/hub/config#directives) en este array.
|
||||
|
||||
Para publicar y suscribirte a actualizaciones, recomendamos usar la biblioteca [Laravel Mercure Broadcaster](https://github.com/mvanduijker/laravel-mercure-broadcaster).
|
||||
Alternativamente, consulta [la documentación de Mercure](mercure.md) para hacerlo en PHP y JavaScript puros.
|
||||
|
||||
### Ejecutar Octane con Binarios Autónomos
|
||||
|
||||
¡Incluso es posible empaquetar aplicaciones Laravel Octane como binarios autónomos!
|
||||
|
||||
Para hacerlo, [instala Octane correctamente](#laravel-octane) y sigue los pasos descritos en [la sección anterior](#aplicaciones-laravel-como-binarios-autónomos).
|
||||
|
||||
Luego, para iniciar FrankenPHP en modo worker a través de Octane, ejecuta:
|
||||
|
||||
```console
|
||||
PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Para que el comando funcione, el binario autónomo **debe** llamarse `frankenphp`
|
||||
> porque Octane necesita un programa llamado `frankenphp` disponible en la ruta.
|
||||
71
docs/es/logging.md
Normal file
71
docs/es/logging.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Registro de actividad
|
||||
|
||||
FrankenPHP se integra perfectamente con [el sistema de registro de Caddy](https://caddyserver.com/docs/logging).
|
||||
Puede registrar mensajes usando funciones estándar de PHP o aprovechar la función dedicada `frankenphp_log()` para capacidades avanzadas de registro estructurado.
|
||||
|
||||
## `frankenphp_log()`
|
||||
|
||||
La función `frankenphp_log()` le permite emitir registros estructurados directamente desde su aplicación PHP,
|
||||
facilitando la ingesta en plataformas como Datadog, Grafana Loki o Elastic, así como el soporte para OpenTelemetry.
|
||||
|
||||
Internamente, `frankenphp_log()` envuelve [el paquete `log/slog` de Go](https://pkg.go.dev/log/slog) para proporcionar funciones avanzadas de registro.
|
||||
|
||||
Estos registros incluyen el nivel de gravedad y datos de contexto opcionales.
|
||||
|
||||
```php
|
||||
function frankenphp_log(string $message, int $level = FRANKENPHP_LOG_LEVEL_INFO, array $context = []): void
|
||||
```
|
||||
|
||||
### Parámetros
|
||||
|
||||
- **`message`**: El string del mensaje de registro.
|
||||
- **`level`**: El nivel de gravedad del registro. Puede ser cualquier entero arbitrario. Se proporcionan constantes de conveniencia para niveles comunes: `FRANKENPHP_LOG_LEVEL_DEBUG` (`-4`), `FRANKENPHP_LOG_LEVEL_INFO` (`0`), `FRANKENPHP_LOG_LEVEL_WARN` (`4`) y `FRANKENPHP_LOG_LEVEL_ERROR` (`8`)). Por omisión es `FRANKENPHP_LOG_LEVEL_INFO`.
|
||||
- **`context`**: Un array asociativo de datos adicionales para incluir en la entrada del registro.
|
||||
|
||||
### Ejemplo
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// Registrar un mensaje informativo simple
|
||||
frankenphp_log("¡Hola desde FrankenPHP!");
|
||||
|
||||
// Registrar una advertencia con datos de contexto
|
||||
frankenphp_log(
|
||||
"Uso de memoria alto",
|
||||
FRANKENPHP_LOG_LEVEL_WARN,
|
||||
[
|
||||
'uso_actual' => memory_get_usage(),
|
||||
'uso_pico' => memory_get_peak_usage(),
|
||||
],
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
Al ver los registros (por ejemplo, mediante `docker compose logs`), la salida aparecerá como JSON estructurado:
|
||||
|
||||
```json
|
||||
{"level":"info","ts":1704067200,"logger":"frankenphp","msg":"¡Hola desde FrankenPHP!"}
|
||||
{"level":"warn","ts":1704067200,"logger":"frankenphp","msg":"Uso de memoria alto","uso_actual":10485760,"uso_pico":12582912}
|
||||
```
|
||||
|
||||
## `error_log()`
|
||||
|
||||
FrankenPHP también permite el registro mediante la función estándar `error_log()`. Si el parámetro `$message_type` es `4` (SAPI),
|
||||
estos mensajes se redirigen al registrador de Caddy.
|
||||
|
||||
Por omisión, los mensajes enviados a través de `error_log()` se tratan como texto no estructurado.
|
||||
Son útiles para la compatibilidad con aplicaciones o bibliotecas existentes que dependen de la biblioteca estándar de PHP.
|
||||
|
||||
### Uso
|
||||
|
||||
```php
|
||||
error_log("Fallo en la conexión a la base de datos", 4);
|
||||
```
|
||||
|
||||
Esto aparecerá en los registros de Caddy, a menudo con un prefijo que indica que se originó desde PHP.
|
||||
|
||||
> [!TIP]
|
||||
> Para una mejor observabilidad en entornos de producción, prefiera `frankenphp_log()`
|
||||
> ya que permite filtrar registros por nivel (Depuración, Error, etc.)
|
||||
> y consultar campos específicos en su infraestructura de registro.
|
||||
151
docs/es/mercure.md
Normal file
151
docs/es/mercure.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Tiempo Real
|
||||
|
||||
¡FrankenPHP incluye un hub [Mercure](https://mercure.rocks) integrado!
|
||||
Mercure te permite enviar eventos en tiempo real a todos los dispositivos conectados: recibirán un evento JavaScript al instante.
|
||||
|
||||
¡Es una alternativa conveniente a WebSockets que es simple de usar y es soportada nativamente por todos los navegadores web modernos!
|
||||
|
||||

|
||||
|
||||
## Habilitando Mercure
|
||||
|
||||
El soporte para Mercure está deshabilitado por defecto.
|
||||
Aquí tienes un ejemplo mínimo de un `Caddyfile` que habilita tanto FrankenPHP como el hub Mercure:
|
||||
|
||||
```caddyfile
|
||||
# El nombre de host al que responder
|
||||
localhost
|
||||
|
||||
mercure {
|
||||
# La clave secreta usada para firmar los tokens JWT para los publicadores
|
||||
publisher_jwt !CambiaEstaClaveSecretaJWTDelHubMercure!
|
||||
# Cuando se establece publisher_jwt, ¡también debes establecer subscriber_jwt!
|
||||
subscriber_jwt !CambiaEstaClaveSecretaJWTDelHubMercure!
|
||||
# Permite suscriptores anónimos (sin JWT)
|
||||
anonymous
|
||||
}
|
||||
|
||||
root public/
|
||||
php_server
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> El [`Caddyfile` de ejemplo](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile)
|
||||
> proporcionado por [las imágenes Docker](docker.md) ya incluye una configuración comentada de Mercure
|
||||
> con variables de entorno convenientes para configurarlo.
|
||||
>
|
||||
> Descomenta la sección Mercure en `/etc/frankenphp/Caddyfile` para habilitarla.
|
||||
|
||||
## Suscribiéndose a Actualizaciones
|
||||
|
||||
Por defecto, el hub Mercure está disponible en la ruta `/.well-known/mercure` de tu servidor FrankenPHP.
|
||||
Para suscribirte a actualizaciones, usa la clase nativa [`EventSource`](https://developer.mozilla.org/es/docs/Web/API/EventSource) de JavaScript:
|
||||
|
||||
```html
|
||||
<!-- public/index.html -->
|
||||
<!doctype html>
|
||||
<title>Ejemplo Mercure</title>
|
||||
<script>
|
||||
const eventSource = new EventSource("/.well-known/mercure?topic=mi-tema");
|
||||
eventSource.onmessage = function (event) {
|
||||
console.log("Nuevo mensaje:", event.data);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## Publicando Actualizaciones
|
||||
|
||||
### Usando `mercure_publish()`
|
||||
|
||||
FrankenPHP proporciona una función conveniente `mercure_publish()` para publicar actualizaciones en el hub Mercure integrado:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// public/publish.php
|
||||
|
||||
$updateID = mercure_publish('mi-tema', json_encode(['clave' => 'valor']));
|
||||
|
||||
// Escribir en los registros de FrankenPHP
|
||||
error_log("actualización $updateID publicada", 4);
|
||||
```
|
||||
|
||||
La firma completa de la función es:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @param string|string[] $topics
|
||||
*/
|
||||
function mercure_publish(string|array $topics, string $data = '', bool $private = false, ?string $id = null, ?string $type = null, ?int $retry = null): string {}
|
||||
```
|
||||
|
||||
### Usando `file_get_contents()`
|
||||
|
||||
Para enviar una actualización a los suscriptores conectados, envía una solicitud POST autenticada al hub Mercure con los parámetros `topic` y `data`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// public/publish.php
|
||||
|
||||
const JWT_SECRET = '!ChangeThisMercureHubJWTSecretKey!'; // Debe ser la misma que mercure.publisher_jwt en Caddyfile
|
||||
|
||||
$updateID = file_get_contents('https://localhost/.well-known/mercure', context: stream_context_create(['http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer " . JWT,
|
||||
'content' => http_build_query([
|
||||
'topic' => 'mi-tema',
|
||||
'data' => json_encode(['clave' => 'valor']),
|
||||
]),
|
||||
]]));
|
||||
|
||||
// Escribir en los registros de FrankenPHP
|
||||
error_log("actualización $updateID publicada", 4);
|
||||
```
|
||||
|
||||
La clave pasada como parámetro de la opción `mercure.publisher_jwt` en el `Caddyfile` debe usarse para firmar el token JWT usado en el encabezado `Authorization`.
|
||||
|
||||
El JWT debe incluir un reclamo `mercure` con un permiso `publish` para los temas a los que deseas publicar.
|
||||
Consulta [la documentación de Mercure](https://mercure.rocks/spec#publishers) sobre autorización.
|
||||
|
||||
Para generar tus propios tokens, puedes usar [este enlace de jwt.io](https://www.jwt.io/#token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.PXwpfIGng6KObfZlcOXvcnWCJOWTFLtswGI5DZuWSK4),
|
||||
pero para aplicaciones en producción, se recomienda usar tokens de corta duración generados dinámicamente usando una biblioteca [JWT](https://www.jwt.io/libraries?programming_language=php) confiable.
|
||||
|
||||
### Usando Symfony Mercure
|
||||
|
||||
Alternativamente, puedes usar el [Componente Symfony Mercure](https://symfony.com/components/Mercure), una biblioteca PHP independiente.
|
||||
|
||||
Esta biblioteca maneja la generación de JWT, la publicación de actualizaciones así como la autorización basada en cookies para los suscriptores.
|
||||
|
||||
Primero, instala la biblioteca usando Composer:
|
||||
|
||||
```console
|
||||
composer require symfony/mercure lcobucci/jwt
|
||||
```
|
||||
|
||||
Luego, puedes usarla de la siguiente manera:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// public/publish.php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
const JWT_SECRET = '!CambiaEstaClaveSecretaJWTDelHubMercure!'; // Debe ser la misma que mercure.publisher_jwt en Caddyfile
|
||||
|
||||
// Configurar el proveedor de tokens JWT
|
||||
$jwFactory = new \Symfony\Component\Mercure\Jwt\LcobucciFactory(JWT_SECRET);
|
||||
$provider = new \Symfony\Component\Mercure\Jwt\FactoryTokenProvider($jwFactory, publish: ['*']);
|
||||
|
||||
$hub = new \Symfony\Component\Mercure\Hub('https://localhost/.well-known/mercure', $provider);
|
||||
// Serializar la actualización y enviarla al hub, que la transmitirá a los clientes
|
||||
$updateID = $hub->publish(new \Symfony\Component\Mercure\Update('mi-tema', json_encode(['clave' => 'valor'])));
|
||||
|
||||
// Escribir en los registros de FrankenPHP
|
||||
error_log("actualización $updateID publicada", 4);
|
||||
```
|
||||
|
||||
Mercure también es soportado nativamente por:
|
||||
|
||||
- [Laravel](laravel.md#soporte-para-mercure)
|
||||
- [Symfony](https://symfony.com/doc/current/mercure.html)
|
||||
- [API Platform](https://api-platform.com/docs/core/mercure/)
|
||||
17
docs/es/metrics.md
Normal file
17
docs/es/metrics.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Métricas
|
||||
|
||||
Cuando las [métricas de Caddy](https://caddyserver.com/docs/metrics) están habilitadas, FrankenPHP expone las siguientes métricas:
|
||||
|
||||
- `frankenphp_total_threads`: El número total de hilos PHP.
|
||||
- `frankenphp_busy_threads`: El número de hilos PHP procesando actualmente una solicitud (los workers en ejecución siempre consumen un hilo).
|
||||
- `frankenphp_queue_depth`: El número de solicitudes regulares en cola
|
||||
- `frankenphp_total_workers{worker="[nombre_worker]"}`: El número total de workers.
|
||||
- `frankenphp_busy_workers{worker="[nombre_worker]"}`: El número de workers procesando actualmente una solicitud.
|
||||
- `frankenphp_worker_request_time{worker="[nombre_worker]"}`: El tiempo dedicado al procesamiento de solicitudes por todos los workers.
|
||||
- `frankenphp_worker_request_count{worker="[nombre_worker]"}`: El número de solicitudes procesadas por todos los workers.
|
||||
- `frankenphp_ready_workers{worker="[nombre_worker]"}`: El número de workers que han llamado a `frankenphp_handle_request` al menos una vez.
|
||||
- `frankenphp_worker_crashes{worker="[nombre_worker]"}`: El número de veces que un worker ha terminado inesperadamente.
|
||||
- `frankenphp_worker_restarts{worker="[nombre_worker]"}`: El número de veces que un worker ha sido reiniciado deliberadamente.
|
||||
- `frankenphp_worker_queue_depth{worker="[nombre_worker]"}`: El número de solicitudes en cola.
|
||||
|
||||
Para las métricas de los workers, el marcador de posición `[nombre_worker]` es reemplazado por el nombre del worker en el Caddyfile; de lo contrario, se usará la ruta absoluta del archivo del worker.
|
||||
187
docs/es/performance.md
Normal file
187
docs/es/performance.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# Rendimiento
|
||||
|
||||
Por defecto, FrankenPHP intenta ofrecer un buen compromiso entre rendimiento y facilidad de uso.
|
||||
Sin embargo, es posible mejorar sustancialmente el rendimiento usando una configuración adecuada.
|
||||
|
||||
## Número de Hilos y Workers
|
||||
|
||||
Por defecto, FrankenPHP inicia 2 veces más hilos y workers (en modo worker) que el número de CPUs disponibles.
|
||||
|
||||
Los valores apropiados dependen en gran medida de cómo está escrita tu aplicación, qué hace y tu hardware.
|
||||
Recomendamos encarecidamente cambiar estos valores. Para una mejor estabilidad del sistema, se recomienda que `num_threads` x `memory_limit` < `memoria_disponible`.
|
||||
|
||||
Para encontrar los valores correctos, es mejor ejecutar pruebas de carga que simulen tráfico real.
|
||||
[k6](https://k6.io) y [Gatling](https://gatling.io) son buenas herramientas para esto.
|
||||
|
||||
Para configurar el número de hilos, usa la opción `num_threads` de las directivas `php_server` y `php`.
|
||||
Para cambiar el número de workers, usa la opción `num` de la sección `worker` de la directiva `frankenphp`.
|
||||
|
||||
### `max_threads`
|
||||
|
||||
Aunque siempre es mejor saber exactamente cómo será tu tráfico, las aplicaciones reales tienden a ser más impredecibles.
|
||||
La configuración `max_threads` [configuración](config.md#caddyfile-config) permite a FrankenPHP generar automáticamente hilos adicionales en tiempo de ejecución hasta el límite especificado.
|
||||
`max_threads` puede ayudarte a determinar cuántos hilos necesitas para manejar tu tráfico y puede hacer que el servidor sea más resiliente a picos de latencia.
|
||||
Si se establece en `auto`, el límite se estimará en función del `memory_limit` en tu `php.ini`. Si no puede hacerlo,
|
||||
`auto` se establecerá por defecto en 2x `num_threads`. Ten en cuenta que `auto` puede subestimar fuertemente el número de hilos necesarios.
|
||||
`max_threads` es similar a [pm.max_children](https://www.php.net/manual/es/install.fpm.configuration.php#pm.max-children) de PHP FPM. La principal diferencia es que FrankenPHP usa hilos en lugar de procesos y los delega automáticamente entre diferentes scripts de worker y el 'modo clásico' según sea necesario.
|
||||
|
||||
## Modo Worker
|
||||
|
||||
Habilitar [el modo worker](worker.md) mejora drásticamente el rendimiento,
|
||||
pero tu aplicación debe adaptarse para ser compatible con este modo:
|
||||
debes crear un script de worker y asegurarte de que la aplicación no tenga fugas de memoria.
|
||||
|
||||
## No Usar musl
|
||||
|
||||
La variante Alpine Linux de las imágenes Docker oficiales y los binarios predeterminados que proporcionamos usan [la libc musl](https://musl.libc.org).
|
||||
|
||||
Se sabe que PHP es [más lento](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381) cuando usa esta biblioteca C alternativa en lugar de la biblioteca GNU tradicional,
|
||||
especialmente cuando se compila en modo ZTS (thread-safe), que es requerido para FrankenPHP. La diferencia puede ser significativa en un entorno con muchos hilos.
|
||||
|
||||
Además, [algunos errores solo ocurren cuando se usa musl](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl).
|
||||
|
||||
En entornos de producción, recomendamos usar FrankenPHP vinculado a glibc, compilado con un nivel de optimización adecuado.
|
||||
|
||||
Esto se puede lograr usando las imágenes Docker de Debian, usando los paquetes de nuestros mantenedores [.deb](https://debs.henderkes.com) o [.rpm](https://rpms.henderkes.com), o [compilando FrankenPHP desde las fuentes](compile.md).
|
||||
|
||||
## Configuración del Runtime de Go
|
||||
|
||||
FrankenPHP está escrito en Go.
|
||||
|
||||
En general, el runtime de Go no requiere ninguna configuración especial, pero en ciertas circunstancias,
|
||||
una configuración específica mejora el rendimiento.
|
||||
|
||||
Probablemente quieras establecer la variable de entorno `GODEBUG` en `cgocheck=0` (el valor predeterminado en las imágenes Docker de FrankenPHP).
|
||||
|
||||
Si ejecutas FrankenPHP en contenedores (Docker, Kubernetes, LXC...) y limitas la memoria disponible para los contenedores,
|
||||
establece la variable de entorno `GOMEMLIMIT` en la cantidad de memoria disponible.
|
||||
|
||||
Para más detalles, [la página de documentación de Go dedicada a este tema](https://pkg.go.dev/runtime#hdr-Environment_Variables) es una lectura obligada para aprovechar al máximo el runtime.
|
||||
|
||||
## `file_server`
|
||||
|
||||
Por defecto, la directiva `php_server` configura automáticamente un servidor de archivos para
|
||||
servir archivos estáticos (assets) almacenados en el directorio raíz.
|
||||
|
||||
Esta característica es conveniente, pero tiene un costo.
|
||||
Para deshabilitarla, usa la siguiente configuración:
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
file_server off
|
||||
}
|
||||
```
|
||||
|
||||
## `try_files`
|
||||
|
||||
Además de los archivos estáticos y los archivos PHP, `php_server` también intentará servir los archivos de índice de tu aplicación
|
||||
y los índices de directorio (`/ruta/` -> `/ruta/index.php`). Si no necesitas índices de directorio,
|
||||
puedes deshabilitarlos definiendo explícitamente `try_files` de esta manera:
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
root /ruta/a/tu/app # agregar explícitamente la raíz aquí permite un mejor almacenamiento en caché
|
||||
}
|
||||
```
|
||||
|
||||
Esto puede reducir significativamente el número de operaciones de archivo innecesarias.
|
||||
|
||||
Un enfoque alternativo con 0 operaciones innecesarias de sistema de archivos sería usar en su lugar la directiva `php` y separar
|
||||
los archivos de PHP por ruta. Este enfoque funciona bien si toda tu aplicación es servida por un solo archivo de entrada.
|
||||
Un ejemplo de [configuración](config.md#caddyfile-config) que sirve archivos estáticos detrás de una carpeta `/assets` podría verse así:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
@assets {
|
||||
path /assets/*
|
||||
}
|
||||
|
||||
# todo lo que está detrás de /assets es manejado por el servidor de archivos
|
||||
file_server @assets {
|
||||
root /ruta/a/tu/app
|
||||
}
|
||||
|
||||
# todo lo que no está en /assets es manejado por tu archivo index o worker PHP
|
||||
rewrite index.php
|
||||
php {
|
||||
root /ruta/a/tu/app # agregar explícitamente la raíz aquí permite un mejor almacenamiento en caché
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Marcadores de Posición (Placeholders)
|
||||
|
||||
Puedes usar [marcadores de posición](https://caddyserver.com/docs/conventions#placeholders) en las directivas `root` y `env`.
|
||||
Sin embargo, esto evita el almacenamiento en caché de estos valores y conlleva un costo significativo de rendimiento.
|
||||
|
||||
Si es posible, evita los marcadores de posición en estas directivas.
|
||||
|
||||
## `resolve_root_symlink`
|
||||
|
||||
Por defecto, si la raíz del documento es un enlace simbólico, se resuelve automáticamente por FrankenPHP (esto es necesario para que PHP funcione correctamente).
|
||||
Si la raíz del documento no es un enlace simbólico, puedes deshabilitar esta característica.
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
resolve_root_symlink false
|
||||
}
|
||||
```
|
||||
|
||||
Esto mejorará el rendimiento si la directiva `root` contiene [marcadores de posición](https://caddyserver.com/docs/conventions#placeholders).
|
||||
La ganancia será negligible en otros casos.
|
||||
|
||||
## Registros (Logs)
|
||||
|
||||
El registro es obviamente muy útil, pero, por definición,
|
||||
requiere operaciones de E/S y asignaciones de memoria, lo que reduce considerablemente el rendimiento.
|
||||
Asegúrate de [establecer el nivel de registro](https://caddyserver.com/docs/caddyfile/options#log) correctamente,
|
||||
y registra solo lo necesario.
|
||||
|
||||
## Rendimiento de PHP
|
||||
|
||||
FrankenPHP usa el intérprete oficial de PHP.
|
||||
Todas las optimizaciones de rendimiento habituales relacionadas con PHP se aplican con FrankenPHP.
|
||||
|
||||
En particular:
|
||||
|
||||
- verifica que [OPcache](https://www.php.net/manual/es/book.opcache.php) esté instalado, habilitado y correctamente configurado
|
||||
- habilita [optimizaciones del autoload de Composer](https://getcomposer.org/doc/articles/autoloader-optimization.md)
|
||||
- asegúrate de que la caché `realpath` sea lo suficientemente grande para las necesidades de tu aplicación
|
||||
- usa [preloading](https://www.php.net/manual/es/opcache.preloading.php)
|
||||
|
||||
Para más detalles, lee [la entrada de documentación dedicada de Symfony](https://symfony.com/doc/current/performance.html)
|
||||
(la mayoría de los consejos son útiles incluso si no usas Symfony).
|
||||
|
||||
## Dividiendo el Pool de Hilos
|
||||
|
||||
Es común que las aplicaciones interactúen con servicios externos lentos, como una
|
||||
API que tiende a ser poco confiable bajo alta carga o que consistentemente tarda 10+ segundos en responder.
|
||||
En tales casos, puede ser beneficioso dividir el pool de hilos para tener pools "lentos" dedicados.
|
||||
Esto evita que los endpoints lentos consuman todos los recursos/hilos del servidor y
|
||||
limita la concurrencia de solicitudes hacia el endpoint lento, similar a un
|
||||
pool de conexiones.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
max_threads 100 # máximo 100 hilos compartidos por todos los workers
|
||||
}
|
||||
}
|
||||
|
||||
ejemplo.com {
|
||||
php_server {
|
||||
root /app/public # la raíz de tu aplicación
|
||||
worker index.php {
|
||||
match /endpoint-lento/* # todas las solicitudes con la ruta /endpoint-lento/* son manejadas por este pool de hilos
|
||||
num 10 # mínimo 10 hilos para solicitudes que coincidan con /endpoint-lento/*
|
||||
}
|
||||
worker index.php {
|
||||
match * # todas las demás solicitudes son manejadas por separado
|
||||
num 20 # mínimo 20 hilos para otras solicitudes, incluso si los endpoints lentos comienzan a colgarse
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
En general, también es aconsejable manejar endpoints muy lentos de manera asíncrona, utilizando mecanismos relevantes como colas de mensajes.
|
||||
144
docs/es/production.md
Normal file
144
docs/es/production.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Despliegue en Producción
|
||||
|
||||
En este tutorial, aprenderemos cómo desplegar una aplicación PHP en un único servidor usando Docker Compose.
|
||||
|
||||
Si estás usando Symfony, consulta la documentación "[Despliegue en producción](https://github.com/dunglas/symfony-docker/blob/main/docs/production.md)" del proyecto Symfony Docker (que usa FrankenPHP).
|
||||
|
||||
Si estás usando API Platform (que también usa FrankenPHP), consulta [la documentación de despliegue del framework](https://api-platform.com/docs/deployment/).
|
||||
|
||||
## Preparando tu Aplicación
|
||||
|
||||
Primero, crea un archivo `Dockerfile` en el directorio raíz de tu proyecto PHP:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# Asegúrate de reemplazar "tu-dominio.ejemplo.com" por tu nombre de dominio
|
||||
ENV SERVER_NAME=tu-dominio.ejemplo.com
|
||||
# Si quieres deshabilitar HTTPS, usa este valor en su lugar:
|
||||
#ENV SERVER_NAME=:80
|
||||
|
||||
# Si tu proyecto no usa el directorio "public" como raíz web, puedes establecerlo aquí:
|
||||
# ENV SERVER_ROOT=web/
|
||||
|
||||
# Habilitar configuración de producción de PHP
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
|
||||
# Copiar los archivos PHP de tu proyecto en el directorio público
|
||||
COPY . /app/public
|
||||
# Si usas Symfony o Laravel, necesitas copiar todo el proyecto en su lugar:
|
||||
#COPY . /app
|
||||
```
|
||||
|
||||
Consulta "[Construyendo una Imagen Docker Personalizada](docker.md)" para más detalles y opciones,
|
||||
y para aprender cómo personalizar la configuración, instalar extensiones PHP y módulos de Caddy.
|
||||
|
||||
Si tu proyecto usa Composer,
|
||||
asegúrate de incluirlo en la imagen Docker e instalar tus dependencias.
|
||||
|
||||
Luego, agrega un archivo `compose.yaml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
php:
|
||||
image: dunglas/frankenphp
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80" # HTTP
|
||||
- "443:443" # HTTPS
|
||||
- "443:443/udp" # HTTP/3
|
||||
volumes:
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
|
||||
# Volúmenes necesarios para los certificados y configuración de Caddy
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Los ejemplos anteriores están destinados a uso en producción.
|
||||
> En desarrollo, es posible que desees usar un volumen, una configuración PHP diferente y un valor diferente para la variable de entorno `SERVER_NAME`.
|
||||
>
|
||||
> Consulta el proyecto [Symfony Docker](https://github.com/dunglas/symfony-docker)
|
||||
> (que usa FrankenPHP) para un ejemplo más avanzado usando imágenes multi-etapa,
|
||||
> Composer, extensiones PHP adicionales, etc.
|
||||
|
||||
Finalmente, si usas Git, haz commit de estos archivos y haz push.
|
||||
|
||||
## Preparando un Servidor
|
||||
|
||||
Para desplegar tu aplicación en producción, necesitas un servidor.
|
||||
En este tutorial, usaremos una máquina virtual proporcionada por DigitalOcean, pero cualquier servidor Linux puede funcionar.
|
||||
Si ya tienes un servidor Linux con Docker instalado, puedes saltar directamente a [la siguiente sección](#configurando-un-nombre-de-dominio).
|
||||
|
||||
De lo contrario, usa [este enlace de afiliado](https://m.do.co/c/5d8aabe3ab80) para obtener $200 de crédito gratuito, crea una cuenta y luego haz clic en "Crear un Droplet".
|
||||
Luego, haz clic en la pestaña "Marketplace" bajo la sección "Elegir una imagen" y busca la aplicación llamada "Docker".
|
||||
Esto aprovisionará un servidor Ubuntu con las últimas versiones de Docker y Docker Compose ya instaladas.
|
||||
|
||||
Para fines de prueba, los planes más económicos serán suficientes.
|
||||
Para un uso real en producción, probablemente querrás elegir un plan en la sección "uso general" que se adapte a tus necesidades.
|
||||
|
||||

|
||||
|
||||
Puedes mantener los valores predeterminados para otras configuraciones o ajustarlos según tus necesidades.
|
||||
No olvides agregar tu clave SSH o crear una contraseña y luego presionar el botón "Finalizar y crear".
|
||||
|
||||
Luego, espera unos segundos mientras se aprovisiona tu Droplet.
|
||||
Cuando tu Droplet esté listo, usa SSH para conectarte:
|
||||
|
||||
```console
|
||||
ssh root@<ip-del-droplet>
|
||||
```
|
||||
|
||||
## Configurando un Nombre de Dominio
|
||||
|
||||
En la mayoría de los casos, querrás asociar un nombre de dominio a tu sitio.
|
||||
Si aún no tienes un nombre de dominio, deberás comprar uno a través de un registrador.
|
||||
|
||||
Luego, crea un registro DNS de tipo `A` para tu nombre de dominio que apunte a la dirección IP de tu servidor:
|
||||
|
||||
```dns
|
||||
tu-dominio.ejemplo.com. IN A 207.154.233.113
|
||||
```
|
||||
|
||||
Ejemplo con el servicio de Dominios de DigitalOcean ("Redes" > "Dominios"):
|
||||
|
||||

|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Let's Encrypt, el servicio utilizado por defecto por FrankenPHP para generar automáticamente un certificado TLS, no soporta el uso de direcciones IP puras. Usar un nombre de dominio es obligatorio para usar Let's Encrypt.
|
||||
|
||||
## Despliegue
|
||||
|
||||
Copia tu proyecto en el servidor usando `git clone`, `scp` o cualquier otra herramienta que se ajuste a tus necesidades.
|
||||
Si usas GitHub, es posible que desees usar [una clave de despliegue](https://docs.github.com/es/free-pro-team@latest/developers/overview/managing-deploy-keys#deploy-keys).
|
||||
Las claves de despliegue también son [soportadas por GitLab](https://docs.gitlab.com/ee/user/project/deploy_keys/).
|
||||
|
||||
Ejemplo con Git:
|
||||
|
||||
```console
|
||||
git clone git@github.com:<usuario>/<nombre-proyecto>.git
|
||||
```
|
||||
|
||||
Ve al directorio que contiene tu proyecto (`<nombre-proyecto>`) e inicia la aplicación en modo producción:
|
||||
|
||||
```console
|
||||
docker compose up --wait
|
||||
```
|
||||
|
||||
Tu servidor está en funcionamiento y se ha generado automáticamente un certificado HTTPS para ti.
|
||||
Ve a `https://tu-dominio.ejemplo.com` y ¡disfruta!
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Docker puede tener una capa de caché, asegúrate de tener la compilación correcta para cada despliegue o vuelve a compilar tu proyecto con la opción `--no-cache` para evitar problemas de caché.
|
||||
|
||||
## Despliegue en Múltiples Nodos
|
||||
|
||||
Si deseas desplegar tu aplicación en un clúster de máquinas, puedes usar [Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/),
|
||||
que es compatible con los archivos Compose proporcionados.
|
||||
Para desplegar en Kubernetes, consulta [el gráfico Helm proporcionado con API Platform](https://api-platform.com/docs/deployment/kubernetes/), que usa FrankenPHP.
|
||||
160
docs/es/static.md
Normal file
160
docs/es/static.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Crear una Compilación Estática
|
||||
|
||||
En lugar de usar una instalación local de la biblioteca PHP,
|
||||
es posible crear una compilación estática o mayormente estática de FrankenPHP gracias al excelente [proyecto static-php-cli](https://github.com/crazywhalecc/static-php-cli) (a pesar de su nombre, este proyecto soporta todas las SAPI, no solo CLI).
|
||||
|
||||
Con este método, un único binario portátil contendrá el intérprete de PHP, el servidor web Caddy y FrankenPHP.
|
||||
|
||||
Los ejecutables nativos completamente estáticos no requieren dependencias y pueden ejecutarse incluso en la imagen Docker [`scratch`](https://docs.docker.com/build/building/base-images/#create-a-minimal-base-image-using-scratch).
|
||||
Sin embargo, no pueden cargar extensiones PHP dinámicas (como Xdebug) y tienen algunas limitaciones porque usan la libc musl.
|
||||
|
||||
Los binarios mayormente estáticos solo requieren `glibc` y pueden cargar extensiones dinámicas.
|
||||
|
||||
Cuando sea posible, recomendamos usar compilaciones mayormente estáticas basadas en glibc.
|
||||
|
||||
FrankenPHP también soporta [incrustar la aplicación PHP en el binario estático](embed.md).
|
||||
|
||||
## Linux
|
||||
|
||||
Proporcionamos imágenes Docker para compilar binarios Linux estáticos:
|
||||
|
||||
### Compilación Completamente Estática Basada en musl
|
||||
|
||||
Para un binario completamente estático que se ejecuta en cualquier distribución Linux sin dependencias pero que no soporta carga dinámica de extensiones:
|
||||
|
||||
```console
|
||||
docker buildx bake --load static-builder-musl
|
||||
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-musl
|
||||
```
|
||||
|
||||
Para un mejor rendimiento en escenarios altamente concurrentes, considera usar el asignador [mimalloc](https://github.com/microsoft/mimalloc).
|
||||
|
||||
```console
|
||||
docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl
|
||||
```
|
||||
|
||||
### Compilación Mayormente Estática Basada en glibc (Con Soporte para Extensiones Dinámicas)
|
||||
|
||||
Para un binario que soporta la carga dinámica de extensiones PHP mientras tiene las extensiones seleccionadas compiladas estáticamente:
|
||||
|
||||
```console
|
||||
docker buildx bake --load static-builder-gnu
|
||||
docker cp $(docker create --name static-builder-gnu dunglas/frankenphp:static-builder-gnu):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-gnu
|
||||
```
|
||||
|
||||
Este binario soporta todas las versiones de glibc 2.17 y superiores, pero no se ejecuta en sistemas basados en musl (como Alpine Linux).
|
||||
|
||||
El binario resultante (mayormente estático excepto por `glibc`) se llama `frankenphp` y está disponible en el directorio actual.
|
||||
|
||||
Si deseas compilar el binario estático sin Docker, consulta las instrucciones para macOS, que también funcionan para Linux.
|
||||
|
||||
### Extensiones Personalizadas
|
||||
|
||||
Por omisión, se compilan las extensiones PHP más populares.
|
||||
|
||||
Para reducir el tamaño del binario y disminuir la superficie de ataque, puedes elegir la lista de extensiones a compilar usando el ARG de Docker `PHP_EXTENSIONS`.
|
||||
|
||||
Por ejemplo, ejecuta el siguiente comando para compilar solo la extensión `opcache`:
|
||||
|
||||
```console
|
||||
docker buildx bake --load --set static-builder-musl.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder-musl
|
||||
# ...
|
||||
```
|
||||
|
||||
Para agregar bibliotecas que habiliten funcionalidades adicionales a las extensiones que has habilitado, puedes pasar el ARG de Docker `PHP_EXTENSION_LIBS`:
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder-musl.args.PHP_EXTENSIONS=gd \
|
||||
--set static-builder-musl.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \
|
||||
static-builder-musl
|
||||
```
|
||||
|
||||
### Módulos Adicionales de Caddy
|
||||
|
||||
Para agregar módulos adicionales de Caddy o pasar otros argumentos a [xcaddy](https://github.com/caddyserver/xcaddy), usa el ARG de Docker `XCADDY_ARGS`:
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder-musl.args.XCADDY_ARGS="--with github.com/darkweak/souin/plugins/caddy --with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" \
|
||||
static-builder-musl
|
||||
```
|
||||
|
||||
En este ejemplo, agregamos el módulo de caché HTTP [Souin](https://souin.io) para Caddy, así como los módulos [cbrotli](https://github.com/dunglas/caddy-cbrotli), [Mercure](https://mercure.rocks) y [Vulcain](https://vulcain.rocks).
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Los módulos cbrotli, Mercure y Vulcain están incluidos por omisión si `XCADDY_ARGS` está vacío o no está configurado.
|
||||
> Si personalizas el valor de `XCADDY_ARGS`, debes incluirlos explícitamente si deseas que estén incluidos.
|
||||
|
||||
Consulta también cómo [personalizar la compilación](#personalizando-la-compilación).
|
||||
|
||||
### Token de GitHub
|
||||
|
||||
Si alcanzas el límite de tasa de la API de GitHub, establece un Token de Acceso Personal de GitHub en una variable de entorno llamada `GITHUB_TOKEN`:
|
||||
|
||||
```console
|
||||
GITHUB_TOKEN="xxx" docker --load buildx bake static-builder-musl
|
||||
# ...
|
||||
```
|
||||
|
||||
## macOS
|
||||
|
||||
Ejecuta el siguiente script para crear un binario estático para macOS (debes tener [Homebrew](https://brew.sh/) instalado):
|
||||
|
||||
```console
|
||||
git clone https://github.com/php/frankenphp
|
||||
cd frankenphp
|
||||
./build-static.sh
|
||||
```
|
||||
|
||||
Nota: este script también funciona en Linux (y probablemente en otros Unix) y es usado internamente por las imágenes Docker que proporcionamos.
|
||||
|
||||
## Personalizando la Compilación
|
||||
|
||||
Las siguientes variables de entorno pueden pasarse a `docker build` y al script `build-static.sh` para personalizar la compilación estática:
|
||||
|
||||
- `FRANKENPHP_VERSION`: la versión de FrankenPHP a usar
|
||||
- `PHP_VERSION`: la versión de PHP a usar
|
||||
- `PHP_EXTENSIONS`: las extensiones PHP a compilar ([lista de extensiones soportadas](https://static-php.dev/en/guide/extensions.html))
|
||||
- `PHP_EXTENSION_LIBS`: bibliotecas adicionales a compilar que añaden funcionalidades a las extensiones
|
||||
- `XCADDY_ARGS`: argumentos a pasar a [xcaddy](https://github.com/caddyserver/xcaddy), por ejemplo para agregar módulos adicionales de Caddy
|
||||
- `EMBED`: ruta de la aplicación PHP a incrustar en el binario
|
||||
- `CLEAN`: cuando está establecido, libphp y todas sus dependencias se compilan desde cero (sin caché)
|
||||
- `NO_COMPRESS`: no comprimir el binario resultante usando UPX
|
||||
- `DEBUG_SYMBOLS`: cuando está establecido, los símbolos de depuración no se eliminarán y se añadirán al binario
|
||||
- `MIMALLOC`: (experimental, solo Linux) reemplaza mallocng de musl por [mimalloc](https://github.com/microsoft/mimalloc) para mejorar el rendimiento. Solo recomendamos usar esto para compilaciones orientadas a musl; para glibc, preferimos deshabilitar esta opción y usar [`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html) cuando ejecutes tu binario.
|
||||
- `RELEASE`: (solo para mantenedores) cuando está establecido, el binario resultante se subirá a GitHub
|
||||
|
||||
## Extensiones
|
||||
|
||||
Con los binarios basados en glibc o macOS, puedes cargar extensiones PHP dinámicamente. Sin embargo, estas extensiones deberán ser compiladas con soporte ZTS.
|
||||
Dado que la mayoría de los gestores de paquetes no ofrecen actualmente versiones ZTS de sus extensiones, tendrás que compilarlas tú mismo.
|
||||
|
||||
Para esto, puedes compilar y ejecutar el contenedor Docker `static-builder-gnu`, acceder a él y compilar las extensiones con `./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config`.
|
||||
|
||||
Pasos de ejemplo para [la extensión Xdebug](https://xdebug.org):
|
||||
|
||||
```console
|
||||
docker build -t gnu-ext -f static-builder-gnu.Dockerfile --build-arg FRANKENPHP_VERSION=1.0 .
|
||||
docker create --name static-builder-gnu -it gnu-ext /bin/sh
|
||||
docker start static-builder-gnu
|
||||
docker exec -it static-builder-gnu /bin/sh
|
||||
cd /go/src/app/dist/static-php-cli/buildroot/bin
|
||||
git clone https://github.com/xdebug/xdebug.git && cd xdebug
|
||||
source scl_source enable devtoolset-10
|
||||
../phpize
|
||||
./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config
|
||||
make
|
||||
exit
|
||||
docker cp static-builder-gnu:/go/src/app/dist/static-php-cli/buildroot/bin/xdebug/modules/xdebug.so xdebug-zts.so
|
||||
docker cp static-builder-gnu:/go/src/app/dist/frankenphp-linux-$(uname -m) ./frankenphp
|
||||
docker stop static-builder-gnu
|
||||
docker rm static-builder-gnu
|
||||
docker rmi gnu-ext
|
||||
```
|
||||
|
||||
Esto creará `frankenphp` y `xdebug-zts.so` en el directorio actual.
|
||||
Si mueves `xdebug-zts.so` a tu directorio de extensiones, agrega `zend_extension=xdebug-zts.so` a tu php.ini y ejecuta FrankenPHP, cargará Xdebug.
|
||||
59
docs/es/wordpress.md
Normal file
59
docs/es/wordpress.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# WordPress
|
||||
|
||||
Ejecute [WordPress](https://wordpress.org/) con FrankenPHP para disfrutar de una pila moderna y de alto rendimiento con HTTPS automático, HTTP/3 y compresión Zstandard.
|
||||
|
||||
## Instalación Mínima
|
||||
|
||||
1. [Descargue WordPress](https://wordpress.org/download/)
|
||||
2. Extraiga el archivo ZIP y abra una terminal en el directorio extraído
|
||||
3. Ejecute:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
4. Vaya a `http://localhost/wp-admin/` y siga las instrucciones de instalación
|
||||
5. ¡Listo!
|
||||
|
||||
Para una configuración lista para producción, prefiera usar `frankenphp run` con un `Caddyfile` como este:
|
||||
|
||||
```caddyfile
|
||||
example.com
|
||||
|
||||
php_server
|
||||
encode zstd br gzip
|
||||
log
|
||||
```
|
||||
|
||||
## Hot Reload
|
||||
|
||||
Para usar la función de [Hot reload](hot-reload.md) con WordPress, active [Mercure](mercure.md) y agregue la subdirectiva `hot_reload` a la directiva `php_server` en su `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
localhost
|
||||
|
||||
mercure {
|
||||
anonymous
|
||||
}
|
||||
|
||||
php_server {
|
||||
hot_reload
|
||||
}
|
||||
```
|
||||
|
||||
Luego, agregue el código necesario para cargar las bibliotecas JavaScript en el archivo `functions.php` de su tema de WordPress:
|
||||
|
||||
```php
|
||||
function hot_reload() {
|
||||
?>
|
||||
<?php if (isset($_SERVER['FRANKENPHP_HOT_RELOAD'])): ?>
|
||||
<meta name="frankenphp-hot-reload:url" content="<?=$_SERVER['FRANKENPHP_HOT_RELOAD']?>">
|
||||
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
|
||||
<?php endif ?>
|
||||
<?php
|
||||
}
|
||||
add_action('wp_head', 'hot_reload');
|
||||
```
|
||||
|
||||
Finalmente, ejecute `frankenphp run` desde el directorio raíz de WordPress.
|
||||
192
docs/es/worker.md
Normal file
192
docs/es/worker.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# Usando los Workers de FrankenPHP
|
||||
|
||||
Inicia tu aplicación una vez y manténla en memoria.
|
||||
FrankenPHP gestionará las peticiones entrantes en unos pocos milisegundos.
|
||||
|
||||
## Iniciando Scripts de Worker
|
||||
|
||||
### Docker
|
||||
|
||||
Establece el valor de la variable de entorno `FRANKENPHP_CONFIG` a `worker /ruta/a/tu/script/worker.php`:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker /app/ruta/a/tu/script/worker.php" \
|
||||
-v $PWD:/app \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
### Binario Autónomo
|
||||
|
||||
Usa la opción `--worker` del comando `php-server` para servir el contenido del directorio actual usando un worker:
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /ruta/a/tu/script/worker.php
|
||||
```
|
||||
|
||||
Si tu aplicación PHP está [incrustada en el binario](embed.md), puedes agregar un `Caddyfile` personalizado en el directorio raíz de la aplicación.
|
||||
Será usado automáticamente.
|
||||
|
||||
También es posible [reiniciar el worker al detectar cambios en archivos](config.md#watching-for-file-changes) con la opción `--watch`.
|
||||
El siguiente comando activará un reinicio si algún archivo que termine en `.php` en el directorio `/ruta/a/tu/app/` o sus subdirectorios es modificado:
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /ruta/a/tu/script/worker.php --watch="/ruta/a/tu/app/**/*.php"
|
||||
```
|
||||
|
||||
Esta función se utiliza frecuentemente en combinación con [hot reloading](hot-reload.md).
|
||||
|
||||
## Symfony Runtime
|
||||
|
||||
> [!TIP]
|
||||
> La siguiente sección es necesaria solo para versiones anteriores a Symfony 7.4, donde se introdujo soporte nativo para el modo worker de FrankenPHP.
|
||||
|
||||
El modo worker de FrankenPHP es soportado por el [Componente Runtime de Symfony](https://symfony.com/doc/current/components/runtime.html).
|
||||
Para iniciar cualquier aplicación Symfony en un worker, instala el paquete FrankenPHP de [PHP Runtime](https://github.com/php-runtime/runtime):
|
||||
|
||||
```console
|
||||
composer require runtime/frankenphp-symfony
|
||||
```
|
||||
|
||||
Inicia tu servidor de aplicación definiendo la variable de entorno `APP_RUNTIME` para usar el Runtime de FrankenPHP Symfony:
|
||||
|
||||
```console
|
||||
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
|
||||
```
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
Consulta [la documentación dedicada](laravel.md#laravel-octane).
|
||||
|
||||
## Aplicaciones Personalizadas
|
||||
|
||||
El siguiente ejemplo muestra cómo crear tu propio script de worker sin depender de una biblioteca de terceros:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// public/index.php
|
||||
|
||||
// Prevenir la terminación del script de worker cuando una conexión de cliente se interrumpe
|
||||
ignore_user_abort(true);
|
||||
|
||||
// Iniciar tu aplicación
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
$myApp = new \App\Kernel();
|
||||
$myApp->boot();
|
||||
|
||||
// Manejador fuera del bucle para mejor rendimiento (menos trabajo)
|
||||
$handler = static function () use ($myApp) {
|
||||
try {
|
||||
// Llamado cuando se recibe una petición,
|
||||
// las superglobales, php://input y similares se reinician
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
} catch (\Throwable $exception) {
|
||||
// `set_exception_handler` se llama solo cuando el script de worker termina,
|
||||
// lo cual puede no ser lo que esperas, así que captura y maneja excepciones aquí
|
||||
(new \MyCustomExceptionHandler)->handleException($exception);
|
||||
}
|
||||
};
|
||||
|
||||
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
|
||||
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
|
||||
$keepRunning = \frankenphp_handle_request($handler);
|
||||
|
||||
// Haz algo después de enviar la respuesta HTTP
|
||||
$myApp->terminate();
|
||||
|
||||
// Llama al recolector de basura para reducir las posibilidades de que se active en medio de la generación de una página
|
||||
gc_collect_cycles();
|
||||
|
||||
if (!$keepRunning) break;
|
||||
}
|
||||
|
||||
// Limpieza
|
||||
$myApp->shutdown();
|
||||
```
|
||||
|
||||
Luego, inicia tu aplicación y usa la variable de entorno `FRANKENPHP_CONFIG` para configurar tu worker:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
|
||||
-v $PWD:/app \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
Por omisión, se inician 2 workers por CPU.
|
||||
También puedes configurar el número de workers a iniciar:
|
||||
|
||||
```console
|
||||
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
|
||||
```
|
||||
|
||||
### Reiniciar el Worker Después de un Número Determinado de Peticiones
|
||||
|
||||
Como PHP no fue diseñado originalmente para procesos de larga duración, aún hay muchas bibliotecas y códigos heredados que generan fugas de memoria.
|
||||
Una solución para usar este tipo de código en modo worker es reiniciar el script de worker después de procesar un cierto número de peticiones:
|
||||
|
||||
El fragmento de worker anterior permite configurar un número máximo de peticiones a manejar estableciendo una variable de entorno llamada `MAX_REQUESTS`.
|
||||
|
||||
### Reiniciar Workers Manualmente
|
||||
|
||||
Aunque es posible reiniciar workers [al detectar cambios en archivos](config.md#watching-for-file-changes), también es posible reiniciar todos los workers
|
||||
de manera controlada a través de la [API de administración de Caddy](https://caddyserver.com/docs/api). Si el admin está habilitado en tu
|
||||
[Caddyfile](config.md#caddyfile-config), puedes activar el endpoint de reinicio con una simple petición POST como esta:
|
||||
|
||||
```console
|
||||
curl -X POST http://localhost:2019/frankenphp/workers/restart
|
||||
```
|
||||
|
||||
### Fallos en Workers
|
||||
|
||||
Si un script de worker falla con un código de salida distinto de cero, FrankenPHP lo reiniciará con una estrategia de retroceso exponencial.
|
||||
Si el script de worker permanece activo más tiempo que el último retroceso * 2,
|
||||
no penalizará al script de worker y lo reiniciará nuevamente.
|
||||
Sin embargo, si el script de worker continúa fallando con un código de salida distinto de cero en un corto período de tiempo
|
||||
(por ejemplo, tener un error tipográfico en un script), FrankenPHP fallará con el error: `too many consecutive failures`.
|
||||
|
||||
El número de fallos consecutivos puede configurarse en tu [Caddyfile](config.md#caddyfile-config) con la opción `max_consecutive_failures`:
|
||||
|
||||
```caddyfile
|
||||
frankenphp {
|
||||
worker {
|
||||
# ...
|
||||
max_consecutive_failures 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Comportamiento de las Superglobales
|
||||
|
||||
Las [superglobales de PHP](https://www.php.net/manual/es/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET`...)
|
||||
se comportan de la siguiente manera:
|
||||
|
||||
- antes de la primera llamada a `frankenphp_handle_request()`, las superglobales contienen valores vinculados al script de worker en sí
|
||||
- durante y después de la llamada a `frankenphp_handle_request()`, las superglobales contienen valores generados a partir de la petición HTTP procesada; cada llamada a `frankenphp_handle_request()` cambia los valores de las superglobales
|
||||
|
||||
Para acceder a las superglobales del script de worker dentro de la retrollamada, debes copiarlas e importar la copia en el ámbito de la retrollamada:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Copia la superglobal $_SERVER del worker antes de la primera llamada a frankenphp_handle_request()
|
||||
$workerServer = $_SERVER;
|
||||
|
||||
$handler = static function () use ($workerServer) {
|
||||
var_dump($_SERVER); // $_SERVER vinculado a la petición
|
||||
var_dump($workerServer); // $_SERVER del script de worker
|
||||
};
|
||||
|
||||
// ...
|
||||
```
|
||||
71
docs/es/x-sendfile.md
Normal file
71
docs/es/x-sendfile.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Sirviendo archivos estáticos grandes de manera eficiente (`X-Sendfile`/`X-Accel-Redirect`)
|
||||
|
||||
Normalmente, los archivos estáticos pueden ser servidos directamente por el servidor web,
|
||||
pero a veces es necesario ejecutar código PHP antes de enviarlos:
|
||||
control de acceso, estadísticas, encabezados HTTP personalizados...
|
||||
|
||||
Desafortunadamente, usar PHP para servir archivos estáticos grandes es ineficiente en comparación con
|
||||
el uso directo del servidor web (sobrecarga de memoria, rendimiento reducido...).
|
||||
|
||||
FrankenPHP permite delegar el envío de archivos estáticos al servidor web
|
||||
**después** de ejecutar código PHP personalizado.
|
||||
|
||||
Para hacerlo, tu aplicación PHP simplemente necesita definir un encabezado HTTP personalizado
|
||||
que contenga la ruta del archivo a servir. FrankenPHP se encarga del resto.
|
||||
|
||||
Esta funcionalidad es conocida como **`X-Sendfile`** para Apache y **`X-Accel-Redirect`** para NGINX.
|
||||
|
||||
En los siguientes ejemplos, asumimos que el directorio raíz del proyecto es `public/`
|
||||
y que queremos usar PHP para servir archivos almacenados fuera del directorio `public/`,
|
||||
desde un directorio llamado `private-files/`.
|
||||
|
||||
## Configuración
|
||||
|
||||
Primero, agrega la siguiente configuración a tu `Caddyfile` para habilitar esta funcionalidad:
|
||||
|
||||
```patch
|
||||
root public/
|
||||
# ...
|
||||
|
||||
+ # Necesario para Symfony, Laravel y otros proyectos que usan el componente Symfony HttpFoundation
|
||||
+ request_header X-Sendfile-Type x-accel-redirect
|
||||
+ request_header X-Accel-Mapping ../private-files=/private-files
|
||||
+
|
||||
+ intercept {
|
||||
+ @accel header X-Accel-Redirect *
|
||||
+ handle_response @accel {
|
||||
+ root private-files/
|
||||
+ rewrite * {resp.header.X-Accel-Redirect}
|
||||
+ method * GET
|
||||
+
|
||||
+ # Elimina el encabezado X-Accel-Redirect establecido por PHP para mayor seguridad
|
||||
+ header -X-Accel-Redirect
|
||||
+
|
||||
+ file_server
|
||||
+ }
|
||||
+ }
|
||||
|
||||
php_server
|
||||
```
|
||||
|
||||
## PHP puro
|
||||
|
||||
Establece la ruta relativa del archivo (desde `private-files/`) como valor del encabezado `X-Accel-Redirect`:
|
||||
|
||||
```php
|
||||
header('X-Accel-Redirect: file.txt');
|
||||
```
|
||||
|
||||
## Proyectos que usan el componente Symfony HttpFoundation (Symfony, Laravel, Drupal...)
|
||||
|
||||
Symfony HttpFoundation [soporta nativamente esta funcionalidad](https://symfony.com/doc/current/components/http_foundation.html#serving-files).
|
||||
Determinará automáticamente el valor correcto para el encabezado `X-Accel-Redirect` y lo agregará a la respuesta.
|
||||
|
||||
```php
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
BinaryFileResponse::trustXSendfileTypeHeader();
|
||||
$response = new BinaryFileResponse(__DIR__.'/../private-files/file.txt');
|
||||
|
||||
// ...
|
||||
```
|
||||
@@ -1,19 +1,40 @@
|
||||
# Configuration
|
||||
|
||||
FrankenPHP, Caddy ainsi que les modules Mercure et Vulcain peuvent être configurés en utilisant [les formats pris en charge par Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
FrankenPHP, Caddy ainsi que les modules [Mercure](mercure.md) et [Vulcain](https://vulcain.rocks) peuvent être configurés à l'aide [des formats pris en charge par Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
|
||||
Dans [les images Docker](docker.md), le `Caddyfile` est situé dans `/etc/frankenphp/Caddyfile`.
|
||||
Le binaire statique cherchera le `Caddyfile` dans le répertoire dans lequel il est démarré.
|
||||
Le format le plus courant est le `Caddyfile`, un format texte simple et facilement lisible par les humains.
|
||||
Par défaut, FrankenPHP recherchera un `Caddyfile` dans le répertoire courant.
|
||||
Vous pouvez spécifier un chemin personnalisé avec l'option `-c` ou `--config`.
|
||||
|
||||
Un `Caddyfile` minimal pour servir une application PHP est présenté ci-dessous :
|
||||
|
||||
```caddyfile
|
||||
# Le nom d'hôte auquel répondre
|
||||
localhost
|
||||
|
||||
# Optionnellement, le répertoire à partir duquel servir les fichiers, sinon le répertoire courant sera utilisé par défaut
|
||||
#root public/
|
||||
php_server
|
||||
```
|
||||
|
||||
Un `Caddyfile` plus avancé, activant davantage de fonctionnalités et fournissant des variables d'environnement pratiques, est disponible [dans le dépôt FrankenPHP](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) et avec les images Docker.
|
||||
|
||||
PHP lui-même peut être configuré [en utilisant un fichier `php.ini`](https://www.php.net/manual/fr/configuration.file.php).
|
||||
|
||||
L'interpréteur PHP cherchera dans les emplacements suivants :
|
||||
Selon votre méthode d'installation, FrankenPHP et l'interpréteur PHP chercheront les fichiers de configuration aux emplacements décrits ci-dessous.
|
||||
|
||||
Docker :
|
||||
## Docker
|
||||
|
||||
- php.ini : `/usr/local/etc/php/php.ini` Aucun php.ini n'est fourni par défaut.
|
||||
- fichiers de configuration supplémentaires : `/usr/local/etc/php/conf.d/*.ini`
|
||||
- extensions php : `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
FrankenPHP :
|
||||
|
||||
- `/etc/frankenphp/Caddyfile` : le fichier de configuration principal
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile` : fichiers de configuration additionnels qui sont chargés automatiquement
|
||||
|
||||
PHP :
|
||||
|
||||
- `php.ini` : `/usr/local/etc/php/php.ini` (aucun `php.ini` n'est fourni par défaut)
|
||||
- Fichiers de configuration supplémentaires : `/usr/local/etc/php/conf.d/*.ini`
|
||||
- Extensions PHP : `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
- Vous devriez copier un modèle officiel fourni par le projet PHP :
|
||||
|
||||
```dockerfile
|
||||
@@ -26,17 +47,29 @@ RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
```
|
||||
|
||||
Installation de FrankenPHP (.rpm ou .deb) :
|
||||
## Packages RPM et Debian
|
||||
|
||||
- php.ini : `/etc/frankenphp/php.ini` Un fichier php.ini avec des préréglages de production est fourni par défaut.
|
||||
FrankenPHP :
|
||||
|
||||
- `/etc/frankenphp/Caddyfile` : le fichier de configuration principal
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile` : fichiers de configuration additionnels qui sont chargés automatiquement
|
||||
|
||||
PHP :
|
||||
|
||||
- `php.ini` : `/etc/php-zts/php.ini` (un fichier `php.ini` avec des préréglages de production est fourni par défaut)
|
||||
- fichiers de configuration supplémentaires : `/etc/php-zts/conf.d/*.ini`
|
||||
|
||||
## Binaire statique
|
||||
|
||||
FrankenPHP :
|
||||
|
||||
- Dans le répertoire de travail actuel : `Caddyfile`
|
||||
|
||||
PHP :
|
||||
|
||||
- `php.ini` : Le répertoire dans lequel `frankenphp run` ou `frankenphp php-server` est exécuté, puis `/etc/frankenphp/php.ini`
|
||||
- fichiers de configuration supplémentaires : `/etc/frankenphp/php.d/*.ini`
|
||||
- extensions php : `/usr/lib/frankenphp/modules/`
|
||||
|
||||
Binaire statique :
|
||||
|
||||
- php.ini : Le répertoire dans lequel `frankenphp run` ou `frankenphp php-server` est exécuté, puis `/etc/frankenphp/php.ini`
|
||||
- fichiers de configuration supplémentaires : `/etc/frankenphp/php.d/*.ini`
|
||||
- extensions php : ne peuvent pas être chargées
|
||||
- Extensions PHP : ne peuvent pas être chargées, intégrez-les au binaire lui-même
|
||||
- copiez l'un des fichiers `php.ini-production` ou `php.ini-development` fournis [dans les sources de PHP](https://github.com/php/php-src/).
|
||||
|
||||
## Configuration du Caddyfile
|
||||
@@ -47,44 +80,44 @@ Exemple minimal :
|
||||
|
||||
```caddyfile
|
||||
localhost {
|
||||
# Activer la compression (optionnel)
|
||||
encode zstd br gzip
|
||||
# Exécuter les fichiers PHP dans le répertoire courant et servir les assets
|
||||
php_server
|
||||
# Activer la compression (optionnel)
|
||||
encode zstd br gzip
|
||||
# Exécuter les fichiers PHP dans le répertoire courant et servir les assets
|
||||
php_server
|
||||
}
|
||||
```
|
||||
|
||||
Vous pouvez également configurer explicitement FrankenPHP en utilisant l'option globale :
|
||||
L'[option globale](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp` peut être utilisée pour configurer FrankenPHP.
|
||||
Vous pouvez également configurer explicitement FrankenPHP en utilisant l'[option globale](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp` :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Définit le nombre de threads PHP à démarrer. Par défaut : 2x le nombre de CPUs disponibles.
|
||||
max_threads <num_threads> # Limite le nombre de threads PHP supplémentaires qui peuvent être démarrés au moment de l'exécution. Valeur par défaut : num_threads. Peut être mis à 'auto'.
|
||||
max_wait_time <duration> # Définit le temps maximum pendant lequel une requête peut attendre un thread PHP libre avant d'être interrompue. Valeur par défaut : désactivé.
|
||||
php_ini <key> <value> Définit une directive php.ini. Peut être utilisé plusieurs fois pour définir plusieurs directives.
|
||||
worker {
|
||||
file <path> # Définit le chemin vers le script worker.
|
||||
num <num> # Définit le nombre de threads PHP à démarrer, par défaut 2x le nombre de CPUs disponibles.
|
||||
env <key> <value> # Définit une variable d'environnement supplémentaire avec la valeur donnée. Peut être spécifié plusieurs fois pour régler plusieurs variables d'environnement.
|
||||
watch <path> # Définit le chemin d'accès à surveiller pour les modifications de fichiers. Peut être spécifié plusieurs fois pour plusieurs chemins.
|
||||
name <name> # Définit le nom du worker, utilisé dans les journaux et les métriques. Défaut : chemin absolu du fichier du worker
|
||||
max_consecutive_failures <num> # Définit le nombre maximum d'échecs consécutifs avant que le worker ne soit considéré comme défaillant, -1 signifie que le worker redémarre toujours. Par défaut : 6.
|
||||
}
|
||||
}
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Définit le nombre de threads PHP à démarrer. Par défaut : 2x le nombre de CPUs disponibles.
|
||||
max_threads <num_threads> # Limite le nombre de threads PHP supplémentaires qui peuvent être démarrés au moment de l'exécution. Valeur par défaut : num_threads. Peut être mis à 'auto'.
|
||||
max_wait_time <duration> # Définit le temps maximum pendant lequel une requête peut attendre un thread PHP libre avant d'être interrompue. Valeur par défaut : désactivé.
|
||||
max_idle_time <duration> # Définit le temps maximum pendant lequel un thread auto-dimensionné peut être inactif avant d'être désactivé. Par défaut : 5s.
|
||||
php_ini <key> <value> # Définit une directive php.ini. Peut être utilisé plusieurs fois pour définir plusieurs directives.
|
||||
worker {
|
||||
file <path> # Définit le chemin vers le script worker.
|
||||
num <num> # Définit le nombre de threads PHP à démarrer, par défaut 2x le nombre de CPUs disponibles.
|
||||
env <key> <value> # Définit une variable d'environnement supplémentaire avec la valeur donnée. Peut être spécifié plusieurs fois pour régler plusieurs variables d'environnement.
|
||||
watch <path> # Définit le chemin d'accès à surveiller pour les modifications de fichiers. Peut être spécifié plusieurs fois pour plusieurs chemins.
|
||||
name <name> # Définit le nom du worker, utilisé dans les journaux et les métriques. Par défaut : chemin absolu du fichier du worker
|
||||
max_consecutive_failures <num> # Définit le nombre maximum d'échecs consécutifs avant que le worker ne soit considéré comme défaillant, -1 signifie que le worker redémarre toujours. Par défaut : 6.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Vous pouvez également utiliser la forme courte de l'option worker en une seule ligne :
|
||||
Vous pouvez également utiliser la forme courte de l'option `worker` en une seule ligne :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker <file> <num>
|
||||
}
|
||||
frankenphp {
|
||||
worker <file> <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
@@ -95,48 +128,48 @@ Vous pouvez aussi définir plusieurs workers si vous servez plusieurs applicatio
|
||||
```caddyfile
|
||||
app.example.com {
|
||||
root /path/to/app/public
|
||||
php_server {
|
||||
root /path/to/app/public # permet une meilleure mise en cache
|
||||
worker index.php <num>
|
||||
}
|
||||
php_server {
|
||||
root /path/to/app/public # permet une meilleure mise en cache
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
other.example.com {
|
||||
root /path/to/other/public
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
}
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
L'utilisation de la directive `php_server` est généralement suffisante,
|
||||
mais si vous avez besoin d'un contrôle total, vous pouvez utiliser la directive `php`, qui permet un plus grand niveau de finesse dans la configuration.
|
||||
L'utilisation de la directive `php_server` est généralement ce dont vous avez besoin,
|
||||
mais si vous avez besoin d'un contrôle total, vous pouvez utiliser la sous-directive `php`.
|
||||
La directive `php` transmet toutes les entrées à PHP, au lieu de vérifier d'abord si
|
||||
c'est un fichier PHP ou pas. En savoir plus à ce sujet dans la [page performances](performance.md#try_files).
|
||||
c'est un fichier PHP ou pas. En savoir plus à ce sujet dans la [documentation liée aux performances](performance.md#try_files).
|
||||
|
||||
Utiliser la directive `php_server` est équivalent à cette configuration :
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
# Ajoute un slash final pour les requêtes de répertoire
|
||||
@canonicalPath {
|
||||
file {path}/index.php
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
# Si le fichier demandé n'existe pas, essayer les fichiers index
|
||||
@indexFiles file {
|
||||
try_files {path} {path}/index.php index.php
|
||||
split_path .php
|
||||
}
|
||||
rewrite @indexFiles {http.matchers.file.relative}
|
||||
# FrankenPHP!
|
||||
@phpFiles path *.php
|
||||
php @phpFiles
|
||||
file_server
|
||||
# Ajoute un slash final pour les requêtes de répertoire
|
||||
@canonicalPath {
|
||||
file {path}/index.php
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
# Si le fichier demandé n'existe pas, essayer les fichiers index
|
||||
@indexFiles file {
|
||||
try_files {path} {path}/index.php index.php
|
||||
split_path .php
|
||||
}
|
||||
rewrite @indexFiles {http.matchers.file.relative}
|
||||
# FrankenPHP!
|
||||
@phpFiles path *.php
|
||||
php @phpFiles
|
||||
file_server
|
||||
}
|
||||
```
|
||||
|
||||
@@ -144,19 +177,20 @@ Les directives `php_server` et `php` disposent des options suivantes :
|
||||
|
||||
```caddyfile
|
||||
php_server [<matcher>] {
|
||||
root <directory> # Définit le dossier racine du le site. Par défaut : valeur de la directive `root` parente.
|
||||
split_path <delim...> # Définit les sous-chaînes pour diviser l'URI en deux parties. La première sous-chaîne correspondante sera utilisée pour séparer le "path info" du chemin. La première partie est suffixée avec la sous-chaîne correspondante et sera considérée comme le nom réel de la ressource (script CGI). La seconde partie sera définie comme PATH_INFO pour utilisation par le script. Par défaut : `.php`
|
||||
resolve_root_symlink false # Désactive la résolution du répertoire `root` vers sa valeur réelle en évaluant un lien symbolique, s'il existe (activé par défaut).
|
||||
env <key> <value> # Définit une variable d'environnement supplémentaire avec la valeur donnée. Peut être spécifié plusieurs fois pour plusieurs variables d'environnement.
|
||||
file_server off # Désactive la directive file_server intégrée.
|
||||
worker { # Crée un worker spécifique à ce serveur. Peut être spécifié plusieurs fois pour plusieurs workers.
|
||||
file <path> # Définit le chemin vers le script worker, peut être relatif à la racine du php_server
|
||||
num <num> # Définit le nombre de threads PHP à démarrer, par défaut 2x le nombre de CPUs disponibles
|
||||
name <name> # Définit le nom du worker, utilisé dans les journaux et les métriques. Défaut : chemin absolu du fichier du worker. Commence toujours par m# lorsqu'il est défini dans un bloc php_server.
|
||||
watch <path> # Définit le chemin d'accès à surveiller pour les modifications de fichiers. Peut être spécifié plusieurs fois pour plusieurs chemins.
|
||||
env <key> <value> # Définit une variable d'environnement supplémentaire avec la valeur donnée. Peut être spécifié plusieurs fois pour plusieurs variables d'environnement. Les variables d'environnement pour ce worker sont également héritées du parent php_server, mais peuvent être écrasées ici.
|
||||
}
|
||||
worker <other_file> <num> # Peut également utiliser la forme courte comme dans le bloc frankenphp global.
|
||||
root <directory> # Définit le dossier racine du site. Par défaut : la directive `root`.
|
||||
split_path <delim...> # Définit les sous-chaînes pour diviser l'URI en deux parties. La première sous-chaîne correspondante sera utilisée pour séparer le "path info" du chemin. La première partie est suffixée avec la sous-chaîne correspondante et sera considérée comme le nom réel de la ressource (script CGI). La seconde partie sera définie comme PATH_INFO pour utilisation par le script. Par défaut : `.php`
|
||||
resolve_root_symlink false # Désactive la résolution du répertoire `root` vers sa valeur réelle en évaluant un lien symbolique, s'il existe (activé par défaut).
|
||||
env <key> <value> # Définit une variable d'environnement supplémentaire avec la valeur donnée. Peut être spécifié plusieurs fois pour plusieurs variables d'environnement.
|
||||
file_server off # Désactive la directive file_server intégrée.
|
||||
worker { # Crée un worker spécifique à ce serveur. Peut être spécifié plusieurs fois pour plusieurs workers.
|
||||
file <path> # Définit le chemin vers le script worker, peut être relatif à la racine du php_server
|
||||
num <num> # Définit le nombre de threads PHP à démarrer, par défaut 2x le nombre de CPU disponibles
|
||||
name <name> # Définit le nom du worker, utilisé dans les journaux et les métriques. Par défaut : chemin absolu du fichier du worker. Commence toujours par m# lorsqu'il est défini dans un bloc php_server.
|
||||
watch <path> # Définit le chemin d'accès à surveiller pour les modifications de fichiers. Peut être spécifié plusieurs fois pour plusieurs chemins.
|
||||
env <key> <value> # Définit une variable d'environnement supplémentaire avec la valeur donnée. Peut être spécifié plusieurs fois pour plusieurs variables d'environnement. Les variables d'environnement pour ce worker sont également héritées du parent php_server, mais peuvent être écrasées ici.
|
||||
match <path> # fait correspondre le worker à un modèle de chemin. Écrase try_files et ne peut être utilisé que dans la directive php_server.
|
||||
}
|
||||
worker <other_file> <num> # Peut également utiliser la forme courte comme dans le bloc frankenphp global.
|
||||
}
|
||||
```
|
||||
|
||||
@@ -179,9 +213,11 @@ Ceci est utile pour les environnements de développement.
|
||||
}
|
||||
```
|
||||
|
||||
Si le répertoire `watch` n'est pas précisé, il se rabattra sur `./**/*.{php,yaml,yml,twig,env}`,
|
||||
qui surveille tous les fichiers `.php`, `.yaml`, `.yml`, `.twig` et `.env` dans le répertoire et les sous-répertoires
|
||||
où le processus FrankenPHP a été lancé. Vous pouvez également spécifier un ou plusieurs répertoires via une commande
|
||||
Cette fonctionnalité est souvent utilisée en combinaison avec [le rechargement à chaud](hot-reload.md).
|
||||
|
||||
Si le répertoire `watch` n'est pas précisé, il se rabattra sur `./**/*.{env,php,twig,yaml,yml}`,
|
||||
qui surveille tous les fichiers `.env`, `.php`, `.twig`, `.yaml` et `.yml` dans le répertoire et les sous-répertoires
|
||||
où le processus FrankenPHP a été lancé. Vous pouvez également spécifier un ou plusieurs répertoires via un
|
||||
[motif de nom de fichier shell](https://pkg.go.dev/path/filepath#Match) :
|
||||
|
||||
```caddyfile
|
||||
@@ -203,36 +239,27 @@ où le processus FrankenPHP a été lancé. Vous pouvez également spécifier un
|
||||
- Si vous avez défini plusieurs workers, ils seront tous redémarrés lorsqu'un fichier est modifié.
|
||||
- Méfiez-vous des fichiers créés au moment de l'exécution (comme les logs) car ils peuvent provoquer des redémarrages intempestifs du worker.
|
||||
|
||||
La surveillance des fichiers est basé sur [e-dant/watcher](https://github.com/e-dant/watcher).
|
||||
La surveillance des fichiers est basée sur [e-dant/watcher](https://github.com/e-dant/watcher).
|
||||
|
||||
### Full Duplex (HTTP/1)
|
||||
## Faire correspondre le Worker à un chemin
|
||||
|
||||
Lors de l'utilisation de HTTP/1.x, il peut être souhaitable d'activer le mode full-duplex pour permettre l'écriture d'une réponse avant que le corps entier
|
||||
n'ait été lu. (par exemple : WebSocket, événements envoyés par le serveur, etc.)
|
||||
Dans les applications PHP traditionnelles, les scripts sont toujours placés dans le répertoire public. C'est également vrai pour les scripts worker, qui sont traités comme n'importe quel autre script PHP. Si vous souhaitez plutôt placer le script worker en dehors du répertoire public, vous pouvez le faire via la directive `match`.
|
||||
|
||||
Il s'agit d'une configuration optionnelle qui doit être ajoutée aux options globales dans le fichier `Caddyfile` :
|
||||
La directive `match` est une alternative optimisée à `try_files` disponible uniquement à l'intérieur de `php_server` et `php`. L'exemple suivant servira toujours un fichier dans le répertoire public s'il est présent et transmettra sinon la requête au worker correspondant au modèle de chemin.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
frankenphp {
|
||||
php_server {
|
||||
worker {
|
||||
file /path/to/worker.php # le fichier peut être en dehors du chemin public
|
||||
match /api/* # toutes les requêtes commençant par /api/ seront traitées par ce worker
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> L'activation de cette option peut entraîner un blocage (deadlock) des anciens clients HTTP/1.x qui ne supportent pas le full-duplex.
|
||||
> Cela peut aussi être configuré en utilisant la variable d'environnement `CADDY_GLOBAL_OPTIONS` :
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
Vous trouverez plus d'informations sur ce paramètre dans la [documentation Caddy](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
|
||||
|
||||
## Variables d'environnement
|
||||
|
||||
Les variables d'environnement suivantes peuvent être utilisées pour insérer des directives Caddy dans le `Caddyfile` sans le modifier :
|
||||
@@ -242,7 +269,7 @@ Les variables d'environnement suivantes peuvent être utilisées pour insérer d
|
||||
- `CADDY_GLOBAL_OPTIONS` : injecte [des options globales](https://caddyserver.com/docs/caddyfile/options)
|
||||
- `FRANKENPHP_CONFIG` : insère la configuration sous la directive `frankenphp`
|
||||
|
||||
Comme pour les SAPI FPM et CLI, les variables d'environnement ne sont exposées par défaut dans la superglobale `$_SERVER`.
|
||||
Comme pour les SAPI FPM et CLI, les variables d'environnement sont exposées par défaut dans la superglobale `$_SERVER`.
|
||||
|
||||
La valeur `S` de [la directive `variables_order` de PHP](https://www.php.net/manual/fr/ini.core.php#ini.variables-order) est toujours équivalente à `ES`, que `E` soit défini ailleurs dans cette directive ou non.
|
||||
|
||||
@@ -269,7 +296,43 @@ Vous pouvez également modifier la configuration de PHP en utilisant la directiv
|
||||
}
|
||||
```
|
||||
|
||||
## Activer le mode debug
|
||||
### Désactiver HTTPS
|
||||
|
||||
Par défaut, FrankenPHP activera automatiquement HTTPS pour tous les noms d'hôte, y compris `localhost`. Si vous souhaitez désactiver HTTPS (par exemple dans un environnement de développement), vous pouvez définir la variable d'environnement `SERVER_NAME` à `http://` ou `:80` :
|
||||
|
||||
Alternativement, vous pouvez utiliser toutes les autres méthodes décrites dans la [documentation Caddy](https://caddyserver.com/docs/automatic-https#activation).
|
||||
|
||||
Si vous souhaitez utiliser HTTPS avec l'adresse IP `127.0.0.1` au lieu du nom d'hôte `localhost`, veuillez lire la section [problèmes connus](known-issues.md#using-https127001-with-docker).
|
||||
|
||||
### Full Duplex (HTTP/1)
|
||||
|
||||
Lors de l'utilisation de HTTP/1.x, il peut être souhaitable d'activer le mode full-duplex pour permettre l'écriture d'une réponse avant que le corps entier
|
||||
n'ait été lu. (par exemple : [Mercure](mercure.md), WebSocket, Server-Sent Events, etc.)
|
||||
|
||||
Il s'agit d'une configuration facultative qui doit être ajoutée aux options globales dans le `Caddyfile` :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> L'activation de cette option peut entraîner un blocage (deadlock) des anciens clients HTTP/1.x qui ne supportent pas le full-duplex.
|
||||
> Cela peut aussi être configuré en utilisant la variable d'environnement `CADDY_GLOBAL_OPTIONS` :
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
Vous trouverez plus d'informations sur ce paramètre dans la [documentation Caddy](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
|
||||
|
||||
## Activer le mode Debug
|
||||
|
||||
Lors de l'utilisation de l'image Docker, définissez la variable d'environnement `CADDY_GLOBAL_OPTIONS` sur `debug` pour activer le mode debug :
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ Les images Docker de [FrankenPHP](https://hub.docker.com/r/dunglas/frankenphp) s
|
||||
|
||||
Des variantes pour PHP 8.2, 8.3, 8.4 et 8.5 sont disponibles. [Parcourir les tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
Les tags suivent le pattern suivant: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
Les tags suivent le pattern suivant : `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
|
||||
- `<frankenphp-version>` et `<php-version>` sont repsectivement les numéros de version de FrankenPHP et PHP, allant de majeur (e.g. `1`), mineur (e.g. `1.2`) à des versions correctives (e.g. `1.2.3`).
|
||||
- `<os>` est soit `trixie` (pour Debian Trixie), `bookworm` (pour Debian Bookworm) ou `alpine` (pour la dernière version stable d'Alpine).
|
||||
- `<frankenphp-version>` et `<php-version>` sont respectivement les numéros de version de FrankenPHP et PHP, allant de majeur (e.g. `1`), mineur (e.g. `1.2`) à des versions correctives (e.g. `1.2.3`).
|
||||
- `<os>` est soit `trixie` (pour Debian Trixie), `bookworm` (pour Debian Bookworm), ou `alpine` (pour la dernière version stable d'Alpine).
|
||||
|
||||
[Parcourir les tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
@@ -28,10 +28,13 @@ docker build -t my-php-app .
|
||||
docker run -it --rm --name my-running-app my-php-app
|
||||
```
|
||||
|
||||
## Comment ajuster la configuration
|
||||
|
||||
Pour une meilleure expérience initiale, un [`Caddyfile` par défaut](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) contenant des variables d'environnement communément utilisées est fourni dans l'image.
|
||||
|
||||
## Comment installer plus d'extensions PHP
|
||||
|
||||
Le script [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) est fourni dans l'image de base.
|
||||
Il est facile d'ajouter des extensions PHP supplémentaires :
|
||||
Le script [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) est fourni dans l'image de base. L'ajout d'extensions PHP supplémentaires se fait de cette manière :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
@@ -79,7 +82,7 @@ FROM dunglas/frankenphp AS runner
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
```
|
||||
|
||||
L'image builder fournie par FrankenPHP contient une version compilée de `libphp`.
|
||||
L'image `builder` fournie par FrankenPHP contient une version compilée de `libphp`.
|
||||
[Les images builder](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) sont fournies pour toutes les versions de FrankenPHP et PHP, à la fois pour Debian et Alpine.
|
||||
|
||||
> [!TIP]
|
||||
@@ -156,18 +159,17 @@ RUN \
|
||||
useradd ${USER}; \
|
||||
# Ajouter la capacité supplémentaire de se lier aux ports 80 et 443
|
||||
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
|
||||
# Donner l'accès en écriture à /data/caddy et /config/caddy
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
# Donner l'accès en écriture à /config/caddy et /data/caddy
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
### Exécution sans capacité
|
||||
|
||||
Même lorsqu'il s'exécute en tant qu'utilisateur autre que root, FrankenPHP a besoin de la capacité `CAP_NET_BIND_SERVICE`
|
||||
pour que son serveur utilise les ports privilégiés (80 et 443).
|
||||
Même lorsqu'il s'exécute en tant qu'utilisateur non-root, FrankenPHP a besoin de la capacité `CAP_NET_BIND_SERVICE` pour lier le serveur web sur les ports privilégiés (80 et 443).
|
||||
|
||||
Si vous exposez FrankenPHP sur un port non privilégié (à partir de 1024), il est possible de faire fonctionner le serveur web avec un utilisateur qui n'est pas root, et sans avoir besoin d'aucune capacité.
|
||||
Si vous exposez FrankenPHP sur un port non privilégié (1024 et au-delà), il est possible d'exécuter le serveur web en tant qu'utilisateur non-root, et sans avoir besoin d'aucune capacité :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
@@ -177,16 +179,16 @@ ARG USER=appuser
|
||||
RUN \
|
||||
# Utiliser "adduser -D ${USER}" pour les distros basées sur Alpine
|
||||
useradd ${USER}; \
|
||||
# Supprimer la capacité par défaut \
|
||||
# Supprimer la capacité par défaut
|
||||
setcap -r /usr/local/bin/frankenphp; \
|
||||
# Donner un accès en écriture à /data/caddy et /config/caddy \
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
# Donner un accès en écriture à /config/caddy et /data/caddy
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
Ensuite, définissez la variable d'environnement `SERVER_NAME` pour utiliser un port non privilégié.
|
||||
Exemple `:8000`
|
||||
Exemple : `:8000`
|
||||
|
||||
## Mises à jour
|
||||
|
||||
@@ -195,9 +197,80 @@ Les images Docker sont construites :
|
||||
- lorsqu'une nouvelle version est taguée
|
||||
- tous les jours à 4h UTC, si de nouvelles versions des images officielles PHP sont disponibles
|
||||
|
||||
## Durcir la sécurité des images
|
||||
|
||||
Pour réduire davantage la surface d'attaque et la taille de vos images Docker FrankenPHP, il est également possible de les construire sur une image [Google distroless](https://github.com/GoogleContainerTools/distroless) ou [Docker hardened](https://www.docker.com/products/hardened-images).
|
||||
|
||||
> [!WARNING]
|
||||
> Ces images de base minimales n'incluent pas de shell ou de gestionnaire de paquets, ce qui rend le débogage plus difficile. Elles sont donc recommandées uniquement pour la production si la sécurité est une priorité.
|
||||
|
||||
Lors de l'ajout d'extensions PHP supplémentaires, vous aurez besoin d'une étape de build intermédiaire :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp AS builder
|
||||
|
||||
# Ajoutez ici des extensions PHP supplémentaires
|
||||
RUN install-php-extensions pdo_mysql pdo_pgsql #...
|
||||
|
||||
# Copiez les bibliothèques partagées de frankenphp et toutes les extensions installées vers un emplacement temporaire
|
||||
# Vous pouvez également effectuer cette étape manuellement en analysant la sortie ldd du binaire frankenphp et de chaque fichier .so d'extension
|
||||
RUN apt-get update && apt-get install -y libtree && \
|
||||
EXT_DIR="$(php -r 'echo ini_get("extension_dir");')" && \
|
||||
FRANKENPHP_BIN="$(which frankenphp)"; \
|
||||
LIBS_TMP_DIR="/tmp/libs"; \
|
||||
mkdir -p "$LIBS_TMP_DIR"; \
|
||||
for target in "$FRANKENPHP_BIN" $(find "$EXT_DIR" -maxdepth 2 -type f -name "*.so"); do \
|
||||
libtree -pv "$target" | sed 's/.*── \(.*\) \[.*/\1/' | grep -v "^$target" | while IFS= read -r lib; do \
|
||||
[ -z "$lib" ] && continue; \
|
||||
base=$(basename "$lib"); \
|
||||
destfile="$LIBS_TMP_DIR/$base"; \
|
||||
if [ ! -f "$destfile" ]; then \
|
||||
cp "$lib" "$destfile"; \
|
||||
fi; \
|
||||
done; \
|
||||
done
|
||||
|
||||
|
||||
# Image de base Debian distroless, assurez-vous que c'est la même version de Debian que l'image de base
|
||||
FROM gcr.io/distroless/base-debian13
|
||||
# Alternative d'image Docker renforcée
|
||||
# FROM dhi.io/debian:13
|
||||
|
||||
# Emplacement de votre application et du Caddyfile à copier dans le conteneur
|
||||
ARG PATH_TO_APP="."
|
||||
ARG PATH_TO_CADDYFILE="./Caddyfile"
|
||||
|
||||
# Copiez votre application dans /app
|
||||
# Pour un durcissement supplémentaire, assurez-vous que seuls les chemins accessibles en écriture sont détenus par l'utilisateur non-root
|
||||
COPY --chown=nonroot:nonroot "$PATH_TO_APP" /app
|
||||
COPY "$PATH_TO_CADDYFILE" /etc/caddy/Caddyfile
|
||||
|
||||
# Copiez frankenphp et les bibliothèques nécessaires
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
|
||||
COPY --from=builder /tmp/libs /usr/lib
|
||||
|
||||
# Copiez les fichiers de configuration php.ini
|
||||
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
|
||||
COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
|
||||
|
||||
# Répertoires de données Caddy — doivent être accessibles en écriture pour l'utilisateur non-root, même sur un système de fichiers racine en lecture seule
|
||||
ENV XDG_CONFIG_HOME=/config \
|
||||
XDG_DATA_HOME=/data
|
||||
COPY --from=builder --chown=nonroot:nonroot /data/caddy /data/caddy
|
||||
COPY --from=builder --chown=nonroot:nonroot /config/caddy /config/caddy
|
||||
|
||||
USER nonroot
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Point d'entrée pour exécuter frankenphp avec le Caddyfile fourni
|
||||
ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "-c", "/etc/caddy/Caddyfile"]
|
||||
```
|
||||
|
||||
## Versions de développement
|
||||
|
||||
Les versions de développement sont disponibles dans le dépôt Docker [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev). Un nouveau build est déclenché chaque fois qu'un commit est poussé sur la branche principale du dépôt GitHub.
|
||||
|
||||
Les tags `latest*` pointent vers la tête de la branche `main`.
|
||||
Les tags sous la forme `sha-<hash-du-commit-git>` sont également disponibles.
|
||||
Les tags sous la forme `sha-<git-commit-hash>` sont également disponibles.
|
||||
|
||||
172
docs/fr/extension-workers.md
Normal file
172
docs/fr/extension-workers.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Workers d'extension
|
||||
|
||||
Les Workers d'extension permettent à votre [extension FrankenPHP](https://frankenphp.dev/docs/extensions/) de gérer un pool dédié de threads PHP pour exécuter des tâches en arrière-plan, gérer des événements asynchrones ou implémenter des protocoles personnalisés. Cela se révèle utile pour les systèmes de files d'attente, les event listeners, les planificateurs, etc.
|
||||
|
||||
## Enregistrement du Worker
|
||||
|
||||
### Enregistrement statique
|
||||
|
||||
Si vous n'avez pas besoin de rendre le worker configurable par l'utilisateur (chemin de script fixe, nombre de threads fixe), vous pouvez simplement enregistrer le worker dans la fonction `init()`.
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"github.com/dunglas/frankenphp"
|
||||
"github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
// Handle global pour communiquer avec le pool de workers
|
||||
var worker frankenphp.Workers
|
||||
|
||||
func init() {
|
||||
// Enregistre le worker lorsque le module est chargé.
|
||||
worker = caddy.RegisterWorkers(
|
||||
"my-internal-worker", // Nom unique
|
||||
"worker.php", // Chemin du script (relatif à l'exécution ou absolu)
|
||||
2, // Nombre de threads fixe
|
||||
// Hooks de cycle de vie optionnels
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
// Logique de configuration globale...
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Dans un module Caddy (configurable par l'utilisateur)
|
||||
|
||||
Si vous prévoyez de partager votre extension (comme une file d'attente générique ou un écouteur d'événements), vous devriez l'envelopper dans un module Caddy. Cela permet aux utilisateurs de configurer le chemin du script et le nombre de threads via leur `Caddyfile`. Cela nécessite d'implémenter l'interface `caddy.Provisioner` et de parser le Caddyfile ([voir un exemple](https://github.com/dunglas/frankenphp-queue/blob/989120d394d66dd6c8e2101cac73dd622fade334/caddy.go)).
|
||||
|
||||
### Dans une application Go pure (intégration)
|
||||
|
||||
Si vous [intégrez FrankenPHP dans une application Go standard sans Caddy](https://pkg.go.dev/github.com/dunglas/frankenphp#example-ServeHTTP), vous pouvez enregistrer des workers d'extension en utilisant `frankenphp.WithExtensionWorkers` lors de l'initialisation des options.
|
||||
|
||||
## Interaction avec les Workers
|
||||
|
||||
Une fois le pool de workers actif, vous pouvez lui envoyer des tâches. Cela peut être fait à l'intérieur de [fonctions natives exportées vers PHP](https://frankenphp.dev/docs/extensions/#writing-the-extension), ou à partir de toute logique Go telle qu'un planificateur cron, un écouteur d'événements (MQTT, Kafka), ou toute autre goroutine.
|
||||
|
||||
### Mode sans tête : `SendMessage`
|
||||
|
||||
Utilisez `SendMessage` pour passer des données brutes directement à votre script worker. C'est idéal pour les files d'attente ou les commandes simples.
|
||||
|
||||
#### Exemple : Une extension de file d'attente asynchrone
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_queue_push(mixed $data): bool
|
||||
func my_queue_push(data *C.zval) bool {
|
||||
// 1. S'assurer que le worker est prêt
|
||||
if worker == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. Envoyer au worker en arrière-plan
|
||||
_, err := worker.SendMessage(
|
||||
context.Background(), // Contexte Go standard
|
||||
unsafe.Pointer(data), // Données à passer au worker
|
||||
nil, // http.ResponseWriter optionnel
|
||||
)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
```
|
||||
|
||||
### Émulation HTTP : `SendRequest`
|
||||
|
||||
Utilisez `SendRequest` si votre extension doit invoquer un script PHP qui s'attend à un environnement web standard (remplir `$_SERVER`, `$_GET`, etc.).
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_worker_http_request(string $path): string
|
||||
func my_worker_http_request(path *C.zend_string) unsafe.Pointer {
|
||||
// 1. Préparer la requête et l'enregistreur
|
||||
url := frankenphp.GoString(unsafe.Pointer(path))
|
||||
req, _ := http.NewRequest("GET", url, http.NoBody)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// 2. Envoyer au worker
|
||||
if err := worker.SendRequest(rr, req); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3. Retourner la réponse capturée
|
||||
return frankenphp.PHPString(rr.Body.String(), false)
|
||||
}
|
||||
```
|
||||
|
||||
## Script Worker
|
||||
|
||||
Le script worker PHP s'exécute dans une boucle et peut gérer à la fois les messages bruts et les requêtes HTTP.
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Gérer à la fois les messages bruts et les requêtes HTTP dans la même boucle
|
||||
$handler = function ($payload = null) {
|
||||
// Cas 1 : Mode Message
|
||||
if ($payload !== null) {
|
||||
return "Received payload: " . $payload;
|
||||
}
|
||||
|
||||
// Cas 2 : Mode HTTP (les superglobales PHP standards sont peuplées)
|
||||
echo "Hello from page: " . $_SERVER['REQUEST_URI'];
|
||||
};
|
||||
|
||||
while (frankenphp_handle_request($handler)) {
|
||||
gc_collect_cycles();
|
||||
}
|
||||
```
|
||||
|
||||
## Hooks de Cycle de Vie
|
||||
|
||||
FrankenPHP fournit des hooks pour exécuter du code Go à des points spécifiques du cycle de vie.
|
||||
|
||||
| Type de Hook | Nom de l'Option | Signature | Contexte et Cas d'Utilisation |
|
||||
| :--------- | :--------------------------- | :------------------- | :--------------------------------------------------------------------- |
|
||||
| **Serveur** | `WithWorkerOnServerStartup` | `func()` | Configuration globale. Exécuté **Une fois**. Exemple : Connexion à NATS/Redis. |
|
||||
| **Serveur** | `WithWorkerOnServerShutdown` | `func()` | Nettoyage global. Exécuté **Une fois**. Exemple : Fermeture des connexions partagées. |
|
||||
| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuration par thread. Appelé lorsqu'un thread démarre. Reçoit l'ID du Thread. |
|
||||
| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Nettoyage par thread. Reçoit l'ID du Thread. |
|
||||
|
||||
### Exemple
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dunglas/frankenphp"
|
||||
frankenphpCaddy "github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
workerHandle = frankenphpCaddy.RegisterWorkers(
|
||||
"my-worker", "worker.php", 2,
|
||||
|
||||
// Démarrage du Serveur (Global)
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
fmt.Println("Extension : Démarrage du serveur...")
|
||||
}),
|
||||
|
||||
// Thread Prêt (Par Thread)
|
||||
// Note : La fonction accepte un entier représentant l'ID du Thread
|
||||
frankenphp.WithWorkerOnReady(func(id int) {
|
||||
fmt.Printf("Extension : Le thread worker #%d est prêt.\n", id)
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
150
docs/fr/hot-reload.md
Normal file
150
docs/fr/hot-reload.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Hot Reload
|
||||
|
||||
FrankenPHP inclut une fonctionnalité de **hot reload** intégrée, conçue pour améliorer considérablement l'expérience développeur.
|
||||
|
||||

|
||||
|
||||
Cette fonctionnalité offre un workflow similaire au **Hot Module Replacement (HMR)** présent dans les outils JavaScript modernes (comme Vite ou webpack).
|
||||
Au lieu de rafraîchir manuellement le navigateur après chaque modification de fichier (code PHP, templates, fichiers JavaScript et CSS...),
|
||||
FrankenPHP met à jour le contenu de la page en temps réel.
|
||||
|
||||
Le Hot Reload fonctionne nativement avec WordPress, Laravel, Symfony et toute autre application ou framework PHP.
|
||||
|
||||
Lorsqu'il est activé, FrankenPHP surveille votre répertoire de travail actuel pour détecter les modifications du système de fichiers.
|
||||
Quand un fichier est modifié, il envoie une mise à jour [Mercure](mercure.md) au navigateur.
|
||||
|
||||
Selon votre configuration, le navigateur va soit :
|
||||
|
||||
- **Transformer le DOM** (en préservant la position de défilement et l'état des champs de saisie) si [Idiomorph](https://github.com/bigskysoftware/idiomorph) est chargé.
|
||||
- **Recharger la page** (rechargement standard) si Idiomorph n'est pas présent.
|
||||
|
||||
## Configuration
|
||||
|
||||
Pour activer le hot reload, activez Mercure, puis ajoutez la sous-directive `hot_reload` à la directive `php_server` dans votre `Caddyfile`.
|
||||
|
||||
> [!WARNING]
|
||||
|
||||
> Cette fonctionnalité est destinée **uniquement aux environnements de développement**.
|
||||
> N'activez pas `hot_reload` en production, car cette fonctionnalité n'est pas sécurisée (expose des détails internes sensibles) et ralentit l'application.
|
||||
|
||||
```caddyfile
|
||||
localhost
|
||||
|
||||
mercure {
|
||||
anonymous
|
||||
}
|
||||
|
||||
root public/
|
||||
php_server {
|
||||
hot_reload
|
||||
}
|
||||
```
|
||||
|
||||
Par défaut, FrankenPHP surveillera tous les fichiers du répertoire de travail actuel correspondant au motif glob suivant : `./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}`
|
||||
|
||||
Il est possible de définir explicitement les fichiers à surveiller en utilisant un motif glob :
|
||||
|
||||
```caddyfile
|
||||
localhost
|
||||
|
||||
mercure {
|
||||
anonymous
|
||||
}
|
||||
|
||||
root public/
|
||||
php_server {
|
||||
hot_reload src/**/*{.php,.js} config/**/*.yaml
|
||||
}
|
||||
```
|
||||
|
||||
Utilisez la forme longue de `hot_reload` pour spécifier le *topic* Mercure à utiliser ainsi que les répertoires ou fichiers à surveiller :
|
||||
|
||||
```caddyfile
|
||||
localhost
|
||||
|
||||
mercure {
|
||||
anonymous
|
||||
}
|
||||
|
||||
root public/
|
||||
php_server {
|
||||
hot_reload {
|
||||
topic hot-reload-topic
|
||||
watch src/**/*.php
|
||||
watch assets/**/*.{ts,json}
|
||||
watch templates/
|
||||
watch public/css/
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Intégration côté client
|
||||
|
||||
Le serveur détecte les modifications et publie les modifications automatiquement. Le navigateur doit s'abonner à ces événements pour mettre à jour la page.
|
||||
FrankenPHP expose l'URL du Hub Mercure à utiliser pour s'abonner aux modifications de fichiers via la variable d'environnement `$_SERVER['FRANKENPHP_HOT_RELOAD']`.
|
||||
|
||||
La bibliothèque JavaScript [frankenphp-hot-reload](https://www.npmjs.com/package/frankenphp-hot-reload) gére la logique côté client.
|
||||
Pour l'utiliser, ajoutez le code suivant à votre gabarit (*layout*) principal :
|
||||
|
||||
```php
|
||||
<!DOCTYPE html>
|
||||
<title>FrankenPHP Hot Reload</title>
|
||||
<?php if (isset($_SERVER['FRANKENPHP_HOT_RELOAD'])): ?>
|
||||
<meta name="frankenphp-hot-reload:url" content="<?=$_SERVER['FRANKENPHP_HOT_RELOAD']?>">
|
||||
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
|
||||
<?php endif ?>
|
||||
```
|
||||
|
||||
La bibliothèque s'abonnera automatiquement au hub Mercure, récupérera l'URL actuelle en arrière-plan lorsqu'une modification de fichier est détectée et transformera le DOM.
|
||||
Elle est disponible en tant que package [npm](https://www.npmjs.com/package/frankenphp-hot-reload) et sur [GitHub](https://github.com/dunglas/frankenphp-hot-reload).
|
||||
|
||||
Alternativement, vous pouvez implémenter votre propre logique côté client en vous abonnant directement au hub Mercure en utilisant la classe JavaScript native `EventSource`.
|
||||
|
||||
### Conserver les nœuds DOM existants
|
||||
|
||||
Dans de rares cas, comme lors de l'utilisation d'outils de développement tels que [la *web debug toolbar* de Symfony](https://github.com/symfony/symfony/pull/62970),
|
||||
vous pouvez souhaiter conserver des nœuds DOM spécifiques.
|
||||
Pour ce faire, ajoutez l'attribut `data-frankenphp-hot-reload-preserve` à l'élément HTML concerné :
|
||||
|
||||
```html
|
||||
<div data-frankenphp-hot-reload-preserve><!-- Ma barre de développement --></div>
|
||||
```
|
||||
|
||||
## Mode Worker
|
||||
|
||||
Si vous exécutez votre application en [mode Worker](worker.md), le script de votre application reste en mémoire.
|
||||
Cela signifie que les modifications de votre code PHP ne seront pas reflétées immédiatement, même si le navigateur recharge la page.
|
||||
|
||||
Pour une meilleure expérience de développement, combinez `hot_reload` avec [la sous-directive `watch` dans la directive `worker`](config.md#surveillance-des-modifications-de-fichier).
|
||||
|
||||
- `hot_reload` : rafraîchit le **navigateur** lorsque les fichiers changent
|
||||
- `worker.watch` : redémarre le worker lorsque les fichiers changent
|
||||
|
||||
```caddy
|
||||
localhost
|
||||
|
||||
mercure {
|
||||
anonymous
|
||||
}
|
||||
|
||||
root public/
|
||||
php_server {
|
||||
hot_reload
|
||||
worker {
|
||||
file /path/to/my_worker.php
|
||||
watch
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Comment ça fonctionne
|
||||
|
||||
1. **Surveillance** : FrankenPHP surveille le système de fichiers pour les modifications en utilisant [la bibliothèque `e-dant/watcher`](https://github.com/e-dant/watcher) en interne (nous avons contribué à son binding Go).
|
||||
2. **Redémarrage (mode Worker)** : si `watch` est activé dans la configuration du worker, le worker PHP est redémarré pour charger le nouveau code.
|
||||
3. **Envoi** : un payload JSON contenant la liste des fichiers modifiés est envoyé au [hub Mercure](https://mercure.rocks) intégré.
|
||||
4. **Réception** : le navigateur, à l'écoute via la bibliothèque JavaScript, reçoit l'événement Mercure.
|
||||
5. **Mise à jour** :
|
||||
|
||||
- Si **Idiomorph** est détecté, il récupère le contenu mis à jour et transforme le HTML actuel pour correspondre au nouvel état, appliquant les modifications instantanément sans perdre l'état.
|
||||
- Sinon, `window.location.reload()` est appelé pour rafraîchir la page.
|
||||
@@ -5,26 +5,26 @@ Cependant, il est possible d'améliorer considérablement les performances en ut
|
||||
|
||||
## Nombre de threads et de workers
|
||||
|
||||
Par défaut, FrankenPHP démarre deux fois plus de threads et de workers (en mode worker) que le nombre de CPU disponibles.
|
||||
Par défaut, FrankenPHP démarre deux fois plus de threads et de workers (en mode worker) que le nombre de cœurs de CPU disponibles.
|
||||
|
||||
Les valeurs appropriées dépendent fortement de la manière dont votre application est écrite, de ce qu'elle fait et de votre matériel.
|
||||
Nous recommandons vivement de modifier ces valeurs.
|
||||
Nous recommandons vivement de modifier ces valeurs. Pour une stabilité optimale du système, il est recommandé d'avoir `num_threads` x `memory_limit` < `available_memory`.
|
||||
|
||||
Pour trouver les bonnes valeurs, il est souhaitable d'effectuer des tests de charge simulant le trafic réel.
|
||||
Pour trouver les bonnes valeurs, il est préférable d'effectuer des tests de charge simulant le trafic réel.
|
||||
[k6](https://k6.io) et [Gatling](https://gatling.io) sont de bons outils pour cela.
|
||||
|
||||
Pour configurer le nombre de threads, utilisez l'option `num_threads` des directives `php_server` et `php`.
|
||||
Pour changer le nombre de travailleurs, utilisez l'option `num` de la section `worker` de la directive `frankenphp`.
|
||||
Pour changer le nombre de workers, utilisez l'option `num` de la section `worker` de la directive `frankenphp`.
|
||||
|
||||
### `max_threads`
|
||||
|
||||
Bien qu'il soit toujours préférable de savoir exactement à quoi ressemblera votre trafic, les applications réelles
|
||||
ont tendance à être plus imprévisibles. Le paramètre `max_threads` permet à FrankenPHP de créer automatiquement des threads supplémentaires au moment de l'exécution, jusqu'à la limite spécifiée.
|
||||
ont tendance à être plus imprévisibles. La [configuration](config.md#configuration-du-caddyfile) `max_threads` permet à FrankenPHP de créer automatiquement des threads supplémentaires au moment de l'exécution, jusqu'à la limite spécifiée.
|
||||
`max_threads` peut vous aider à déterminer le nombre de threads dont vous avez besoin pour gérer votre trafic et peut rendre le serveur plus résistant aux pics de latence.
|
||||
Si elle est fixée à `auto`, la limite sera estimée en fonction de la valeur de `memory_limit` dans votre `php.ini`. Si ce n'est pas possible,
|
||||
`auto` prendra par défaut 2x `num_threads`. Gardez à l'esprit que `auto` peut fortement sous-estimer le nombre de threads nécessaires.
|
||||
`max_threads` est similaire à [pm.max_children](https://www.php.net/manual/en/install.fpm.configuration.php#pm.max-children) de PHP FPM. La principale différence est que FrankenPHP utilise des threads au lieu de
|
||||
processus et les délègue automatiquement à différents scripts de travail et au `mode classique` selon les besoins.
|
||||
processus et les délègue automatiquement à différents scripts worker et au 'mode classique' selon les besoins.
|
||||
|
||||
## Mode worker
|
||||
|
||||
@@ -34,18 +34,18 @@ vous devez créer un script worker et vous assurer que l'application n'a pas de
|
||||
|
||||
## Ne pas utiliser musl
|
||||
|
||||
Les binaires statiques que nous fournissons, ainsi que la variante Alpine Linux des images Docker officielles, utilisent [la bibliothèque musl](https://musl.libc.org).
|
||||
La variante Alpine Linux des images Docker officielles et les binaires par défaut que nous fournissons utilisent [la bibliothèque musl](https://musl.libc.org).
|
||||
|
||||
PHP est connu pour être [significativement plus lent](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381) lorsqu'il utilise cette bibliothèque C alternative au lieu de la bibliothèque GNU traditionnelle,
|
||||
surtout lorsqu'il est compilé en mode ZTS (_thread-safe_), ce qui est nécessaire pour FrankenPHP.
|
||||
PHP est connu pour être [plus lent](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381) lorsqu'il utilise cette bibliothèque C alternative au lieu de la bibliothèque GNU traditionnelle,
|
||||
surtout lorsqu'il est compilé en mode ZTS (_thread-safe_), ce qui est nécessaire pour FrankenPHP. La différence peut être significative dans un environnement fortement multithreadé.
|
||||
|
||||
En outre, [certains bogues ne se produisent que lors de l'utilisation de musl](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl).
|
||||
|
||||
Dans les environnements de production, nous recommandons fortement d'utiliser la glibc.
|
||||
Dans les environnements de production, nous recommandons d'utiliser FrankenPHP lié à glibc, compilé avec un niveau d'optimisation approprié.
|
||||
|
||||
Cela peut être réalisé en utilisant les images Docker Debian (par défaut) et [en compilant FrankenPHP à partir des sources](compile.md).
|
||||
Cela peut être réalisé en utilisant les images Docker Debian, en utilisant [les paquets .deb, .rpm ou .apk proposés par l'un de nos mainteneurs](https://pkgs.henderkes.com), ou en [compilant FrankenPHP à partir des sources](compile.md).
|
||||
|
||||
Alternativement, nous fournissons des binaires statiques compilés avec [l'allocateur mimalloc](https://github.com/microsoft/mimalloc), ce qui rend FrankenPHP+musl plus rapide (mais toujours plus lent que FrankenPHP+glibc).
|
||||
Pour des conteneurs plus légers ou plus sécurisés, vous pourriez envisager [une image Debian renforcée](docker.md#hardening-images) plutôt qu'Alpine.
|
||||
|
||||
## Configuration du runtime Go
|
||||
|
||||
@@ -84,11 +84,23 @@ vous pouvez les désactiver en définissant explicitement `try_files` comme ceci
|
||||
```caddyfile
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
root /root/to/your/app # l'ajout explicite de la racine ici permet une meilleure mise en cache
|
||||
root /root/to/your/app # l'ajout explicite de la racine ici permet une meilleure mise en cache
|
||||
}
|
||||
```
|
||||
|
||||
Cela permet de réduire considérablement le nombre d'opérations inutiles sur les fichiers.
|
||||
Un équivalent worker de la configuration précédente serait :
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
php_server { # utilisez "php" au lieu de "php_server" si vous n'avez pas du tout besoin du serveur de fichiers
|
||||
root /root/to/your/app
|
||||
worker /path/to/worker.php {
|
||||
match * # envoie toutes les requêtes directement au worker
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Une approche alternative avec 0 opérations inutiles sur le système de fichiers serait d'utiliser la directive `php`
|
||||
et de diviser les fichiers de PHP par chemin. Cette approche fonctionne bien si votre application entière est servie par un seul fichier d'entrée.
|
||||
@@ -105,10 +117,10 @@ route {
|
||||
root /root/to/your/app
|
||||
}
|
||||
|
||||
# tout ce qui n'est pas dans /assets est géré par votre index ou votre fichier PHP worker
|
||||
# tout ce qui n'est pas dans /assets est géré par votre fichier PHP d'index ou worker
|
||||
rewrite index.php
|
||||
php {
|
||||
root /root/to/your/app # l'ajout explicite de la racine ici permet une meilleure mise en cache
|
||||
root /root/to/your/app # l'ajout explicite de la racine ici permet une meilleure mise en cache
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -155,3 +167,32 @@ En particulier :
|
||||
|
||||
Pour plus de détails, lisez [l'entrée de la documentation dédiée de Symfony](https://symfony.com/doc/current/performance.html)
|
||||
(la plupart des conseils sont utiles même si vous n'utilisez pas Symfony).
|
||||
|
||||
## Division du pool de threads
|
||||
|
||||
Il est courant que les applications interagissent avec des services externes lents, comme une
|
||||
API qui a tendance à être peu fiable sous forte charge ou qui met constamment plus de 10 secondes à répondre.
|
||||
Dans de tels cas, il peut être bénéfique de diviser le pool de threads pour avoir des pools "lents" dédiés.
|
||||
Cela empêche les points d'accès lents de consommer toutes les ressources/threads du serveur et
|
||||
limite la concurrence des requêtes se dirigeant vers le point d'accès lent, à l'instar d'un
|
||||
pool de connexions.
|
||||
|
||||
```caddyfile
|
||||
example.com {
|
||||
php_server {
|
||||
root /app/public # la racine de votre application
|
||||
worker index.php {
|
||||
match /slow-endpoint/* # toutes les requêtes avec le chemin /slow-endpoint/* sont gérées par ce pool de threads
|
||||
num 1 # minimum 1 thread pour les requêtes correspondant à /slow-endpoint/*
|
||||
max_threads 20 # autorise jusqu'à 20 threads pour les requêtes correspondant à /slow-endpoint/*, si nécessaire
|
||||
}
|
||||
worker index.php {
|
||||
match * # toutes les autres requêtes sont gérées séparément
|
||||
num 1 # minimum 1 thread pour les autres requêtes, même si les points d'accès lents commencent à bloquer
|
||||
max_threads 20 # autorise jusqu'à 20 threads pour les autres requêtes, si nécessaire
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
De manière générale, il est également conseillé de gérer les points d'accès très lents de manière asynchrone, en utilisant des mécanismes pertinents tels que les files d'attente de messages.
|
||||
|
||||
@@ -19,22 +19,24 @@ docker run \
|
||||
|
||||
### Binaire autonome
|
||||
|
||||
Utilisez l'option --worker de la commande php-server pour servir le contenu du répertoire courant en utilisant un worker :
|
||||
Utilisez l'option `--worker` de la commande `php-server` pour servir le contenu du répertoire courant en utilisant un worker :
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php
|
||||
```
|
||||
|
||||
Si votre application PHP est [intégrée dans le binaire](embed.md), vous pouvez également ajouter un `Caddyfile` personnalisé dans le répertoire racine de l'application.
|
||||
Si votre application PHP est [intégrée dans le binaire](embed.md), vous pouvez ajouter un `Caddyfile` personnalisé dans le répertoire racine de l'application.
|
||||
Il sera utilisé automatiquement.
|
||||
|
||||
Il est également possible de [redémarrer le worker en cas de changement de fichier](config.md#surveillance-des-modifications-de-fichier) avec l'option `--watch`.
|
||||
Il est également possible de [redémarrer le worker en cas de changement de fichier](config.md#watching-for-file-changes) avec l'option `--watch`.
|
||||
La commande suivante déclenchera un redémarrage si un fichier se terminant par `.php` dans le répertoire `/path/to/your/app/` ou ses sous-répertoires est modifié :
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
|
||||
```
|
||||
|
||||
Cette fonctionnalité se combine très bien avec le [rechargement à chaud](hot-reload.md).
|
||||
|
||||
## Runtime Symfony
|
||||
|
||||
> [!TIP]
|
||||
@@ -70,20 +72,23 @@ L'exemple suivant montre comment créer votre propre script worker sans dépendr
|
||||
<?php
|
||||
// public/index.php
|
||||
|
||||
// Empêcher la terminaison du script worker lorsqu'une connexion client est interrompue
|
||||
ignore_user_abort(true);
|
||||
|
||||
// Démarrer votre application
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
$myApp = new \App\Kernel();
|
||||
$myApp->boot();
|
||||
|
||||
// En dehors de la boucle pour de meilleures performances (moins de travail effectué)
|
||||
// Déclarer le handler en dehors de la boucle pour de meilleures performances (moins de travail effectué)
|
||||
$handler = static function () use ($myApp) {
|
||||
// Appelé lorsqu'une requête est reçue,
|
||||
// les superglobales, php://input, etc., sont réinitialisés
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
try {
|
||||
// Appelé lorsqu'une requête est reçue,
|
||||
// les superglobales, php://input, etc., sont réinitialisés
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
} catch (\Throwable $exception) {
|
||||
// `set_exception_handler` est appelé uniquement lorsque le script worker se termine,
|
||||
// ce qui peut ne pas être ce que vous attendez, alors interceptez et gérez les exceptions ici
|
||||
(new \MyCustomExceptionHandler)->handleException($exception);
|
||||
}
|
||||
};
|
||||
|
||||
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
|
||||
@@ -133,27 +138,23 @@ Le code du worker précédent permet de configurer un nombre maximal de requête
|
||||
|
||||
### Redémarrer les workers manuellement
|
||||
|
||||
Bien qu'il soit possible de redémarrer les workers [en cas de changement de fichier](config.md#surveillance-des-modifications-de-fichier),
|
||||
Bien qu'il soit possible de redémarrer les workers [en cas de changement de fichier](config.md#watching-for-file-changes),
|
||||
il est également possible de redémarrer tous les workers de manière élégante via l'[API Admin de Caddy](https://caddyserver.com/docs/api).
|
||||
Si l'administration est activée dans votre [Caddyfile](config.md#configuration-du-caddyfile), vous pouvez envoyer un ping
|
||||
Si l'administration est activée dans votre [Caddyfile](config.md#caddyfile-config), vous pouvez envoyer un ping
|
||||
à l'endpoint de redémarrage avec une simple requête POST comme celle-ci :
|
||||
|
||||
```console
|
||||
curl -X POST http://localhost:2019/frankenphp/workers/restart
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> C'est une fonctionnalité expérimentale et peut être modifiée ou supprimée dans le futur.
|
||||
|
||||
### Worker Failures
|
||||
### Échecs des workers
|
||||
|
||||
Si un script de worker se plante avec un code de sortie non nul, FrankenPHP le redémarre avec une stratégie de backoff exponentielle.
|
||||
Si le script worker reste en place plus longtemps que le dernier backoff \* 2, FrankenPHP ne pénalisera pas le script et le redémarrera à nouveau.
|
||||
Toutefois, si le script de worker continue d'échouer avec un code de sortie non nul dans un court laps de temps
|
||||
(par exemple, une faute de frappe dans un script), FrankenPHP plantera avec l'erreur : `too many consecutive failures` (trop d'échecs consécutifs).
|
||||
|
||||
Le nombre d'échecs consécutifs peut être configuré dans votre [Caddyfile](config.md#configuration-du-caddyfile) avec l'option `max_consecutive_failures` :
|
||||
Le nombre d'échecs consécutifs peut être configuré dans votre [Caddyfile](config.md#caddyfile-config) avec l'option `max_consecutive_failures` :
|
||||
|
||||
```caddyfile
|
||||
frankenphp {
|
||||
@@ -185,4 +186,3 @@ $handler = static function () use ($workerServer) {
|
||||
};
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
FrankenPHP includes a built-in **hot reload** feature designed to vastly improve the developer experience.
|
||||
|
||||

|
||||

|
||||
|
||||
This feature provides a workflow similar to **Hot Module Replacement (HMR)** found in modern JavaScript tooling (like Vite or webpack).
|
||||
Instead of manually refreshing the browser after every file change (PHP code, templates, JavaScript and CSS files...),
|
||||
FrankenPHP updates the content in real-time.
|
||||
This feature provides a workflow similar to **Hot Module Replacement (HMR)** in modern JavaScript tooling such as Vite or webpack.
|
||||
Instead of manually refreshing the browser after every file change (PHP code, templates, JavaScript, and CSS files...),
|
||||
FrankenPHP updates the page content in real-time.
|
||||
|
||||
Hot Reload natively works with WordPress, Laravel, Symfony, and any other PHP application or framework.
|
||||
|
||||
@@ -23,9 +23,10 @@ Depending on your setup, the browser will either:
|
||||
To enable hot reloading, enable Mercure, then add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> This feature is intended for **development environments only**.
|
||||
> Do not enable `hot_reload` in production, as watching the filesystem incurs performance overhead and exposes internal endpoints.
|
||||
|
||||
> Do not enable `hot_reload` in production, as this feature is not secure (exposes sensitive internal details) and slows down the application.
|
||||
>
|
||||
```caddyfile
|
||||
localhost
|
||||
|
||||
@@ -41,7 +42,7 @@ php_server {
|
||||
|
||||
By default, FrankenPHP will watch all files in the current working directory matching this glob pattern: `./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}`
|
||||
|
||||
It's possible to explicitly set the files to watch using the glob syntax:
|
||||
It's possible to set the files to watch using the glob syntax explicitly:
|
||||
|
||||
```caddyfile
|
||||
localhost
|
||||
@@ -56,7 +57,7 @@ php_server {
|
||||
}
|
||||
```
|
||||
|
||||
Use the long form to specify the Mercure topic to use as well as which directories or files to watch by providing paths to the `hot_reload` option:
|
||||
Use the long form of `hot_reload` to specify the Mercure topic to use, as well as which directories or files to watch:
|
||||
|
||||
```caddyfile
|
||||
localhost
|
||||
@@ -95,12 +96,22 @@ To use it, add the following to your main layout:
|
||||
<?php endif ?>
|
||||
```
|
||||
|
||||
The library will automatically subscribe to the Mercure hub, fetch the current URL in the background when a file change is detected and morph the DOM.
|
||||
It is available as a [npm](https://www.npmjs.com/package/frankenphp-hot-reload) package and on [GitHub](https://github.com/dunglas/frankenphp-hot-reload).
|
||||
The library will automatically subscribe to the Mercure hub, fetch the current URL in the background when a file change is detected, and morph the DOM.
|
||||
It is available as an [npm](https://www.npmjs.com/package/frankenphp-hot-reload) package and on [GitHub](https://github.com/dunglas/frankenphp-hot-reload).
|
||||
|
||||
Alternatively, you can implement your own client-side logic by subscribing directly to the Mercure hub using the `EventSource` native JavaScript class.
|
||||
|
||||
### Worker Mode
|
||||
### Preserving Existing DOM Nodes
|
||||
|
||||
In rare cases, such as when using development tools [like the Symfony web debug toolbar](https://github.com/symfony/symfony/pull/62970),
|
||||
you may want to preserve specific DOM nodes.
|
||||
To do so, add the `data-frankenphp-hot-reload-preserve` attribute to the relevant HTML element:
|
||||
|
||||
```html
|
||||
<div data-frankenphp-hot-reload-preserve><!-- My debug bar --></div>
|
||||
```
|
||||
|
||||
## Worker Mode
|
||||
|
||||
If you are running your application in [Worker Mode](https://frankenphp.dev/docs/worker/), your application script remains in memory.
|
||||
This means changes to your PHP code will not be reflected immediately, even if the browser reloads.
|
||||
@@ -127,7 +138,7 @@ php_server {
|
||||
}
|
||||
```
|
||||
|
||||
### How it works
|
||||
## How It Works
|
||||
|
||||
1. **Watch**: FrankenPHP monitors the filesystem for modifications using [the `e-dant/watcher` library](https://github.com/e-dant/watcher) under the hood (we contributed the Go binding).
|
||||
2. **Restart (Worker Mode)**: if `watch` is enabled in the worker config, the PHP worker is restarted to load the new code.
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
# 設定
|
||||
|
||||
FrankenPHP、Caddy、そしてMercureやVulcainモジュールは、[Caddyでサポートされる形式](https://caddyserver.com/docs/getting-started#your-first-config)を使用して設定できます。
|
||||
FrankenPHP、Caddy、そして[Mercure](mercure.md)や[Vulcain](https://vulcain.rocks)モジュールは、[Caddyでサポートされる形式](https://caddyserver.com/docs/getting-started#your-first-config)を使用して設定できます。
|
||||
|
||||
[Dockerイメージ](docker.md)では、`Caddyfile`は`/etc/frankenphp/Caddyfile`に配置されています。
|
||||
静的バイナリは、`frankenphp run`コマンドを実行したディレクトリ内の`Caddyfile`を参照します。
|
||||
また、`-c`または`--config`オプションでカスタムのパスを指定できます。
|
||||
最も一般的な形式は`Caddyfile`で、シンプルで人間が読めるテキスト形式です。
|
||||
デフォルトでは、FrankenPHPは現在のディレクトリにある`Caddyfile`を探します。
|
||||
`-c`または`--config`オプションでカスタムパスを指定できます。
|
||||
|
||||
PHP自体の設定は[`php.ini` ファイルを使用](https://www.php.net/manual/en/configuration.file.php)して行えます。
|
||||
PHPアプリケーションを配信するための最小限の`Caddyfile`を以下に示します:
|
||||
|
||||
インストール方法に応じて、PHPインタープリターは上記いずれかの場所にある設定ファイルを参照します。
|
||||
```caddyfile
|
||||
# レスポンスするホスト名
|
||||
localhost
|
||||
|
||||
# オプションで、ファイルを配信するディレクトリ。指定しない場合は現在のディレクトリがデフォルト
|
||||
#root public/
|
||||
php_server
|
||||
```
|
||||
|
||||
より多くの機能を有効にし、便利な環境変数を提供するより高度な`Caddyfile`は、[FrankenPHPリポジトリ](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile)およびDockerイメージに同梱されています。
|
||||
|
||||
PHP自体は、[`php.ini` ファイルを使用](https://www.php.net/manual/en/configuration.file.php)して設定できます。
|
||||
|
||||
インストール方法に応じて、FrankenPHPとPHPインタープリターは以下の場所に記載された設定ファイルを探します。
|
||||
|
||||
## Docker
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: メインの設定ファイル
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: 自動的にロードされる追加の設定ファイル
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/usr/local/etc/php/php.ini`(デフォルトでは`php.ini`は含まれていません)
|
||||
- 追加の設定ファイル: `/usr/local/etc/php/conf.d/*.ini`
|
||||
- PHP拡張モジュール: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
@@ -20,25 +40,37 @@ PHP自体の設定は[`php.ini` ファイルを使用](https://www.php.net/manua
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# 本番環境:
|
||||
# Production:
|
||||
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
|
||||
|
||||
# または開発環境:
|
||||
# Or development:
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
```
|
||||
|
||||
## RPMおよびDebianパッケージ
|
||||
|
||||
- `php.ini`: `/etc/frankenphp/php.ini`(本番環境向けのプリセットの`php.ini`ファイルがデフォルトで提供されます)
|
||||
- 追加の設定ファイル: `/etc/frankenphp/php.d/*.ini`
|
||||
- PHP拡張モジュール: `/usr/lib/frankenphp/modules/`
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: メインの設定ファイル
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: 自動的にロードされる追加の設定ファイル
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/etc/php-zts/php.ini`(本番環境向けのプリセットの`php.ini`ファイルがデフォルトで提供されます)
|
||||
- 追加の設定ファイル: `/etc/php-zts/conf.d/*.ini`
|
||||
|
||||
## 静的バイナリ
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- 現在の作業ディレクトリ: `Caddyfile`
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `frankenphp run`または`frankenphp php-server`を実行したディレクトリ内、なければ`/etc/frankenphp/php.ini`を参照
|
||||
- 追加の設定ファイル: `/etc/frankenphp/php.d/*.ini`
|
||||
- PHP拡張モジュール: ロードできません、バイナリ自体にバンドルする必要があります
|
||||
- [PHPソース](https://github.com/php/php-src/)で提供される`php.ini-production`または`php.ini-development`のいずれかをコピーしてください
|
||||
- [PHPソース](https://github.com/php/php-src/)で提供される`php.ini-production`または`php.ini-development`のいずれかをコピーしてください。
|
||||
|
||||
## Caddyfileの設定
|
||||
|
||||
@@ -55,8 +87,7 @@ localhost {
|
||||
}
|
||||
```
|
||||
|
||||
グローバルオプションを使用してFrankenPHPを明示的に設定することもできます:
|
||||
`frankenphp`の[グローバルオプション](https://caddyserver.com/docs/caddyfile/concepts#global-options)を使用してFrankenPHPを構成できます。
|
||||
FrankenPHPは、`frankenphp`の[グローバルオプション](https://caddyserver.com/docs/caddyfile/concepts#global-options)を使用して明示的に設定することもできます:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
@@ -64,6 +95,7 @@ localhost {
|
||||
num_threads <num_threads> # 開始するPHPスレッド数を設定します。デフォルト: 利用可能なCPU数の2倍。
|
||||
max_threads <num_threads> # 実行時に追加で開始できるPHPスレッドの最大数を制限します。デフォルト: num_threads。 'auto'を設定可能。
|
||||
max_wait_time <duration> # リクエストがタイムアウトする前にPHPのスレッドが空くのを待つ最大時間を設定します。デフォルト: 無効。
|
||||
max_idle_time <duration> # 自動スケーリングされたスレッドが非アクティブ化されるまでにアイドル状態である最大時間を設定します。デフォルト: 5秒。
|
||||
php_ini <key> <value> # php.iniのディレクティブを設定します。複数のディレクティブを設定するために何度でも使用できます。
|
||||
worker {
|
||||
file <path> # ワーカースクリプトのパスを設定します。
|
||||
@@ -152,7 +184,7 @@ php_server [<matcher>] {
|
||||
file_server off # 組み込みのfile_serverディレクティブを無効にします。
|
||||
worker { # このサーバー固有のワーカーを作成します。複数のワーカーに対して複数回指定できます。
|
||||
file <path> # ワーカースクリプトへのパスを設定します。php_serverのルートからの相対パスとなります。
|
||||
num <num> # 起動するPHPスレッド数を設定します。デフォルトは利用可能なスレッド数の 2 倍です。
|
||||
num <num> # 起動するPHPスレッド数を設定します。デフォルトは利用可能なCPU数の2倍です。
|
||||
name <name> # ログとメトリクスで使用されるワーカーの名前を設定します。デフォルト: ワーカーファイルの絶対パス。php_server ブロックで定義されている場合は、常にm#で始まります。
|
||||
watch <path> # ファイルの変更を監視するパスを設定する。複数のパスに対して複数回指定することができる。
|
||||
env <key> <value> # 追加の環境変数を指定された値に設定する。複数の環境変数を指定する場合は、複数回指定することができます。このワーカーの環境変数もphp_serverの親から継承されますが、 ここで上書きすることもできます。
|
||||
@@ -181,8 +213,10 @@ PHPファイルに変更を加えても即座には反映されません。
|
||||
}
|
||||
```
|
||||
|
||||
`watch`ディレクトリが指定されていない場合、`./**/*.{php,yaml,yml,twig,env}`にフォールバックします。
|
||||
これは、FrankenPHPプロセスが開始されたディレクトリおよびそのサブディレクトリ内のすべての`.php`、`.yaml`、`.yml`、`.twig`、`.env`ファイルすべてを監視します。
|
||||
この機能は、[ホットリロード](hot-reload.md)と組み合わせてよく使用されます。
|
||||
|
||||
`watch`ディレクトリが指定されていない場合、`./**/*.{env,php,twig,yaml,yml}`にフォールバックします。
|
||||
これは、FrankenPHPプロセスが開始されたディレクトリおよびそのサブディレクトリ内のすべての`.env`、`.php`、`.twig`、`.yaml`、`.yml`ファイルすべてを監視します。
|
||||
代わりに、[シェルのファイル名パターン](https://pkg.go.dev/path/filepath#Match)を使用して
|
||||
1つ以上のディレクトリを指定することもできます:
|
||||
|
||||
@@ -230,34 +264,6 @@ PHPファイルに変更を加えても即座には反映されません。
|
||||
}
|
||||
```
|
||||
|
||||
### フルデュプレックス(HTTP/1)
|
||||
|
||||
HTTP/1.xを使用する場合、全体のボディが読み取られる前にレスポンスを書き込めるようにするため、
|
||||
フルデュプレックスモードを有効にすることが望ましい場合があります(例:WebSocket、Server-Sent Eventsなど)。
|
||||
|
||||
これは明示的に有効化する必要がある設定で、`Caddyfile`のグローバルオプションに追加する必要があります:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> このオプションを有効にすると、フルデュプレックスをサポートしない古いHTTP/1.xクライアントでデッドロックが発生する可能性があります。
|
||||
> これは`CADDY_GLOBAL_OPTIONS`環境設定を使用しても設定できます:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
この設定の詳細については、[Caddyドキュメント](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex)をご覧ください。
|
||||
|
||||
## 環境変数
|
||||
|
||||
以下の環境変数を使用することで、`Caddyfile`を直接変更せずにCaddyディレクティブを注入できます:
|
||||
@@ -284,7 +290,7 @@ FPM や CLI SAPI と同様に、環境変数はデフォルトで`$_SERVER`ス
|
||||
frankenphp {
|
||||
php_ini memory_limit 256M
|
||||
|
||||
# または
|
||||
# or
|
||||
|
||||
php_ini {
|
||||
memory_limit 256M
|
||||
@@ -294,6 +300,43 @@ FPM や CLI SAPI と同様に、環境変数はデフォルトで`$_SERVER`ス
|
||||
}
|
||||
```
|
||||
|
||||
### HTTPSの無効化
|
||||
|
||||
デフォルトでは、FrankenPHPは`localhost`を含むすべてのホスト名に対してHTTPSを自動的に有効にします。
|
||||
HTTPSを無効にしたい場合(例えば開発環境で)、`SERVER_NAME`環境変数を`http://`または`:80`に設定できます:
|
||||
|
||||
または、[Caddyのドキュメント](https://caddyserver.com/docs/automatic-https#activation)に記載されている他のすべての方法を使用することもできます。
|
||||
|
||||
`localhost`ホスト名の代わりに`127.0.0.1` IPアドレスでHTTPSを使用したい場合は、[既知の問題](known-issues.md#using-https127001-with-docker)セクションを読んでください。
|
||||
|
||||
### フルデュプレックス(HTTP/1)
|
||||
|
||||
HTTP/1.xを使用する場合、全体のボディが読み取られる前にレスポンスを書き込めるようにするため、
|
||||
フルデュプレックスモードを有効にすることが望ましい場合があります(例:[Mercure](mercure.md)、WebSocket、Server-Sent Eventsなど)。
|
||||
|
||||
これは明示的に有効化する必要がある設定で、`Caddyfile`のグローバルオプションに追加する必要があります:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> このオプションを有効にすると、フルデュプレックスをサポートしない古いHTTP/1.xクライアントでデッドロックが発生する可能性があります。
|
||||
> これは`CADDY_GLOBAL_OPTIONS`環境設定を使用しても設定できます:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
この設定の詳細については、[Caddyドキュメント](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex)をご覧ください。
|
||||
|
||||
## デバッグモードの有効化
|
||||
|
||||
Dockerイメージを使用する場合、`CADDY_GLOBAL_OPTIONS`環境変数に`debug`を設定するとデバッグモードが有効になります:
|
||||
|
||||
@@ -7,7 +7,7 @@ PHP 8.2、8.3、8.4、8.5向けのバリアントが提供されています。
|
||||
タグは次のパターンに従います:`dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
|
||||
- `<frankenphp-version>`および`<php-version>`は、それぞれFrankenPHPおよびPHPのバージョン番号で、メジャー(例:`1`)、マイナー(例:`1.2`)からパッチバージョン(例:`1.2.3`)まであります。
|
||||
- `<os>`は`bookworm`(Debian Bookworm用)または`alpine`(Alpine最新安定版用)のいずれかです。
|
||||
- `<os>`は`trixie`(Debian Trixie用)、`bookworm`(Debian Bookworm用)、または`alpine`(Alpine最新安定版用)のいずれかです。
|
||||
|
||||
[タグを閲覧](https://hub.docker.com/r/dunglas/frankenphp/tags)。
|
||||
|
||||
@@ -28,6 +28,10 @@ docker build -t my-php-app .
|
||||
docker run -it --rm --name my-running-app my-php-app
|
||||
```
|
||||
|
||||
## 設定を調整する方法
|
||||
|
||||
利便性のため、役立つ環境変数を含む[デフォルトの`Caddyfile`](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile)がイメージに含まれています。
|
||||
|
||||
## PHP拡張モジュールの追加インストール方法
|
||||
|
||||
ベースイメージには[`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer)スクリプトが含まれており、
|
||||
@@ -36,7 +40,7 @@ docker run -it --rm --name my-running-app my-php-app
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# 追加の拡張モジュールをここに追加:
|
||||
# ここに追加の拡張モジュールを追加:
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
@@ -156,8 +160,8 @@ RUN \
|
||||
useradd ${USER}; \
|
||||
# ポート 80 や 443 にバインドするための追加ケーパビリティを追加
|
||||
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
|
||||
# /data/caddy および /config/caddy への書き込み権限を付与
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
# /config/caddy および /data/caddy への書き込み権限を付与
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
@@ -180,8 +184,8 @@ RUN \
|
||||
useradd ${USER}; \
|
||||
# デフォルトのケーパビリティを削除
|
||||
setcap -r /usr/local/bin/frankenphp; \
|
||||
# /data/caddy と /config/caddy への書き込み権限を付与
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
# /config/caddy と /data/caddy への書き込み権限を付与
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
@@ -196,10 +200,80 @@ Dockerイメージは以下のタイミングでビルドされます:
|
||||
- 新しいリリースがタグ付けされたとき
|
||||
- 公式PHPイメージに新しいバージョンがある場合、毎日UTC午前4時に自動ビルド
|
||||
|
||||
## イメージの強化
|
||||
|
||||
FrankenPHP Dockerイメージの攻撃対象領域とサイズをさらに削減するために、[Google distroless](https://github.com/GoogleContainerTools/distroless)または[Docker hardened](https://www.docker.com/products/hardened-images)イメージをベースにビルドすることも可能です。
|
||||
|
||||
> [!WARNING]
|
||||
> これらの最小限のベースイメージにはシェルやパッケージマネージャーが含まれていないため、デバッグがより困難になります。そのため、セキュリティが最優先される本番環境でのみ推奨されます。
|
||||
|
||||
追加のPHP拡張機能を追加する場合は、中間ビルドステージが必要になります:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp AS builder
|
||||
|
||||
# ここに追加のPHP拡張機能を追加
|
||||
RUN install-php-extensions pdo_mysql pdo_pgsql #...
|
||||
|
||||
# frankenphpとインストールされているすべての拡張機能の共有ライブラリを一時的な場所にコピー
|
||||
# この手順は、frankenphpバイナリおよび各拡張機能の.soファイルのldd出力を分析することで手動で行うこともできます
|
||||
RUN apt-get update && apt-get install -y libtree && \
|
||||
EXT_DIR="$(php -r 'echo ini_get("extension_dir");')" && \
|
||||
FRANKENPHP_BIN="$(which frankenphp)"; \
|
||||
LIBS_TMP_DIR="/tmp/libs"; \
|
||||
mkdir -p "$LIBS_TMP_DIR"; \
|
||||
for target in "$FRANKENPHP_BIN" $(find "$EXT_DIR" -maxdepth 2 -type f -name "*.so"); do \
|
||||
libtree -pv "$target" | sed 's/.*── \(.*\) \[.*/\1/' | grep -v "^$target" | while IFS= read -r lib; do \
|
||||
[ -z "$lib" ] && continue; \
|
||||
base=$(basename "$lib"); \
|
||||
destfile="$LIBS_TMP_DIR/$base"; \
|
||||
if [ ! -f "$destfile" ]; then \
|
||||
cp "$lib" "$destfile"; \
|
||||
fi; \
|
||||
done; \
|
||||
done
|
||||
|
||||
|
||||
# Distroless Debianベースイメージ。ベースイメージと同じDebianバージョンであることを確認してください
|
||||
FROM gcr.io/distroless/base-debian13
|
||||
# Docker hardened イメージの代替
|
||||
# FROM dhi.io/debian:13
|
||||
|
||||
# コンテナにコピーするアプリケーションとCaddyfileの場所
|
||||
ARG PATH_TO_APP="."
|
||||
ARG PATH_TO_CADDYFILE="./Caddyfile"
|
||||
|
||||
# アプリケーションを/appにコピー
|
||||
# さらに強化するために、書き込み可能なパスのみが非rootユーザーに所有されていることを確認してください
|
||||
COPY --chown=nonroot:nonroot "$PATH_TO_APP" /app
|
||||
COPY "$PATH_TO_CADDYFILE" /etc/caddy/Caddyfile
|
||||
|
||||
# frankenphpと必要なライブラリをコピー
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
|
||||
COPY --from=builder /tmp/libs /usr/lib
|
||||
|
||||
# php.ini設定ファイルをコピー
|
||||
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
|
||||
COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
|
||||
|
||||
# Caddyデータディレクトリ — 読み取り専用のルートファイルシステム上でも、非rootユーザーが書き込み可能である必要があります
|
||||
ENV XDG_CONFIG_HOME=/config \
|
||||
XDG_DATA_HOME=/data
|
||||
COPY --from=builder --chown=nonroot:nonroot /data/caddy /data/caddy
|
||||
COPY --from=builder --chown=nonroot:nonroot /config/caddy /config/caddy
|
||||
|
||||
USER nonroot
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 指定されたCaddyfileでfrankenphpを実行するエントリポイント
|
||||
ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "-c", "/etc/caddy/Caddyfile"]
|
||||
```
|
||||
|
||||
## 開発版
|
||||
|
||||
開発版は[`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev)Dockerリポジトリで利用できます。
|
||||
GitHubリポジトリのmainブランチにコミットがpushされるたびに新しいビルドが実行されます。
|
||||
GitHubリポジトリの`main`ブランチにコミットがpushされるたびに新しいビルドが実行されます。
|
||||
|
||||
`latest*`タグは`main`ブランチのヘッドを指しており、
|
||||
`sha-<git-commit-hash>` 形式のタグも利用可能です。
|
||||
`latest*`タグは`main`ブランチのヘッドを指しており、`sha-<git-commit-hash>` 形式のタグも利用可能です。
|
||||
|
||||
172
docs/ja/extension-workers.md
Normal file
172
docs/ja/extension-workers.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# 拡張ワーカー
|
||||
|
||||
拡張ワーカーは、[FrankenPHP拡張機能](https://frankenphp.dev/docs/extensions/)がバックグラウンドタスクの実行、非同期イベントの処理、またはカスタムプロトコルの実装のために、PHPスレッドの専用プールを管理できるようにします。キューシステム、イベントリスナー、スケジューラーなどに役立ちます。
|
||||
|
||||
## ワーカーの登録
|
||||
|
||||
### 静的登録
|
||||
|
||||
ワーカーをユーザーが構成可能にする必要がない場合(固定スクリプトパス、固定スレッド数)、`init()` 関数でワーカーを登録するだけです。
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"github.com/dunglas/frankenphp"
|
||||
"github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
// ワーカープールと通信するためのグローバルハンドル
|
||||
var worker frankenphp.Workers
|
||||
|
||||
func init() {
|
||||
// モジュールがロードされたときにワーカーを登録します。
|
||||
worker = caddy.RegisterWorkers(
|
||||
"my-internal-worker", // ユニークな名前
|
||||
"worker.php", // スクリプトパス(実行場所からの相対パス、または絶対パス)
|
||||
2, // 固定スレッド数
|
||||
// オプションのライフサイクルフック
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
// グローバルなセットアップロジック...
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Caddyモジュール内 (ユーザーが構成可能)
|
||||
|
||||
拡張機能を共有する予定がある場合(一般的なキューやイベントリスナーなど)、Caddyモジュールにラップする必要があります。これにより、ユーザーは `Caddyfile` を介してスクリプトパスとスレッド数を構成できます([例を見る](https://github.com/dunglas/frankenphp-queue/blob/989120d394d66dd6c8e2101cac73dd622fade334/caddy.go))。
|
||||
|
||||
### 純粋なGoアプリケーション内 (組み込み)
|
||||
|
||||
[Caddyなしで標準GoアプリケーションにFrankenPHPを組み込む](https://pkg.go.dev/github.com/dunglas/frankenphp#example-ServeHTTP)場合、初期化オプションで `frankenphp.WithExtensionWorkers` を使用して拡張ワーカーを登録できます。
|
||||
|
||||
## ワーカーとの対話
|
||||
|
||||
ワーカープールがアクティブになったら、タスクをディスパッチできます。これは、[PHPにエクスポートされたネイティブ関数](https://frankenphp.dev/docs/extensions/#writing-the-extension)内、またはGoのロジック(cronスケジューラー、イベントリスナー(MQTT、Kafka)、その他のゴルーチンなど)から実行できます。
|
||||
|
||||
### ヘッドレスモード: `SendMessage`
|
||||
|
||||
`SendMessage` を使用して、生データをワーカーのスクリプトに直接渡します。これはキューや単純なコマンドに最適です。
|
||||
|
||||
#### 例: 非同期キュー拡張機能
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_queue_push(mixed $data): bool
|
||||
func my_queue_push(data *C.zval) bool {
|
||||
// 1. ワーカーが準備できていることを確認する
|
||||
if worker == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. バックグラウンドワーカーにディスパッチする
|
||||
_, err := worker.SendMessage(
|
||||
context.Background(), // 標準のGoコンテキスト
|
||||
unsafe.Pointer(data), // ワーカーに渡すデータ
|
||||
nil, // オプションのhttp.ResponseWriter
|
||||
)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
```
|
||||
|
||||
### HTTPエミュレーション: `SendRequest`
|
||||
|
||||
拡張機能が標準のウェブ環境(`$_SERVER`、`$_GET` など)を期待するPHPスクリプトを呼び出す必要がある場合は、`SendRequest` を使用します。
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_worker_http_request(string $path): string
|
||||
func my_worker_http_request(path *C.zend_string) unsafe.Pointer {
|
||||
// 1. リクエストとレコーダーを準備する
|
||||
url := frankenphp.GoString(unsafe.Pointer(path))
|
||||
req, _ := http.NewRequest("GET", url, http.NoBody)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// 2. ワーカーにディスパッチする
|
||||
if err := worker.SendRequest(rr, req); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3. キャプチャされたレスポンスを返す
|
||||
return frankenphp.PHPString(rr.Body.String(), false)
|
||||
}
|
||||
```
|
||||
|
||||
## ワーカーのスクリプト
|
||||
|
||||
PHPワーカーのスクリプトはループで実行され、生メッセージとHTTPリクエストの両方を処理できます。
|
||||
|
||||
```php
|
||||
<?php
|
||||
// 同じループで生のメッセージとHTTPリクエストの両方を処理する
|
||||
$handler = function ($payload = null) {
|
||||
// ケース1: メッセージモード
|
||||
if ($payload !== null) {
|
||||
return "Received payload: " . $payload;
|
||||
}
|
||||
|
||||
// ケース2: HTTPモード(標準のPHPスーパーグローバルが設定される)
|
||||
echo "Hello from page: " . $_SERVER['REQUEST_URI'];
|
||||
};
|
||||
|
||||
while (frankenphp_handle_request($handler)) {
|
||||
gc_collect_cycles();
|
||||
}
|
||||
```
|
||||
|
||||
## ライフサイクルフック
|
||||
|
||||
FrankenPHPは、ライフサイクルの特定の時点でGoコードを実行するためのフックを提供します。
|
||||
|
||||
| フックタイプ | オプション名 | シグネチャ | コンテキストと使用例 |
|
||||
| :--------- | :-------------------------- | :------------------ | :--------------------------------------------------------------------- |
|
||||
| **サーバー** | `WithWorkerOnServerStartup` | `func()` | グローバルなセットアップ。**一度だけ**実行されます。例: NATS/Redisへの接続。 |
|
||||
| **サーバー** | `WithWorkerOnServerShutdown` | `func()` | グローバルなクリーンアップ。**一度だけ**実行されます。例: 共有接続のクローズ。 |
|
||||
| **スレッド** | `WithWorkerOnReady` | `func(threadID int)` | スレッドごとのセットアップ。スレッドが開始したときに呼び出されます。スレッドIDを受け取ります。 |
|
||||
| **スレッド** | `WithWorkerOnShutdown` | `func(threadID int)` | スレッドごとのクリーンアップ。スレッドIDを受け取ります。 |
|
||||
|
||||
### 例
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dunglas/frankenphp"
|
||||
frankenphpCaddy "github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
workerHandle = frankenphpCaddy.RegisterWorkers(
|
||||
"my-worker", "worker.php", 2,
|
||||
|
||||
// サーバー起動時 (グローバル)
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
fmt.Println("Extension: Server starting up...")
|
||||
}),
|
||||
|
||||
// スレッド準備完了時 (スレッドごと)
|
||||
// 注: この関数はスレッドIDを表す整数を受け入れます
|
||||
frankenphp.WithWorkerOnReady(func(id int) {
|
||||
fmt.Printf("Extension: Worker thread #%d is ready.\n", id)
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -43,9 +43,9 @@ PHPは、従来のGNUライブラリの代わりにこの代替Cライブラリ
|
||||
|
||||
本番環境では、glibcにリンクされたFrankenPHPを使用することをお勧めします。
|
||||
|
||||
これは、Debian Dockerイメージ(デフォルト)を使用するか、[リリースページ](https://github.com/php/frankenphp/releases)から -gnu サフィックス付きバイナリをダウンロードするか、あるいは[FrankenPHPをソースからコンパイル](compile.md)することで実現できます。
|
||||
これは、Debian Dockerイメージを使用するか、[公式パッケージ (.deb, .rpm, .apk)](https://pkgs.henderkes.com) を使用するか、あるいは[FrankenPHPをソースからコンパイル](compile.md)することで実現できます。
|
||||
|
||||
または、[mimalloc allocator](https://github.com/microsoft/mimalloc)でコンパイルされた静的muslバイナリも提供しており、これによりスレッド環境での問題を軽減できます。
|
||||
より軽量で安全なコンテナのためには、Alpineよりも[強化されたDebianイメージ](docker.md#hardening-images)を検討することをお勧めします。
|
||||
|
||||
## Go Runtime設定
|
||||
|
||||
@@ -89,6 +89,18 @@ php_server {
|
||||
```
|
||||
|
||||
これにより、不要なファイルの操作の回数を大幅に削減できます。
|
||||
上記の構成をワーカーモードに相当させると、次のようになります:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
php_server { # file server が全く不要な場合は "php_server" の代わりに "php" を使用します
|
||||
root /root/to/your/app
|
||||
worker /path/to/worker.php {
|
||||
match * # すべてのリクエストを直接ワーカーに送信します
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
ファイルシステムへの不要な操作を完全にゼロにする代替アプローチとして、`php`ディレクティブを使用し、
|
||||
パスによってPHPファイルとそれ以外を分ける方法があります。アプリケーション全体が1つのエントリーファイルで提供される場合、この方法は有効です。
|
||||
@@ -155,3 +167,29 @@ FrankenPHPは公式のPHPインタープリターを使用しています。
|
||||
|
||||
詳細については、[Symfonyの専用ドキュメントエントリ](https://symfony.com/doc/current/performance.html)をお読みください
|
||||
(Symfonyを使用していなくても、多くのヒントが役立ちます)。
|
||||
|
||||
## スレッドプールの分割
|
||||
|
||||
アプリケーションが、高負荷時に不安定になったり、常に10秒以上応答にかかるAPIのような遅い外部サービスと連携することはよくあります。
|
||||
このような場合、スレッドプールを分割して専用の「遅い」プールを持つことが有益です。
|
||||
これにより、遅いエンドポイントがすべてのサーバーリソース/スレッドを消費するのを防ぎ、コネクションプールと同様に、遅いエンドポイントへのリクエストの同時実行数を制限できます。
|
||||
|
||||
```caddyfile
|
||||
example.com {
|
||||
php_server {
|
||||
root /app/public # アプリケーションのルート
|
||||
worker index.php {
|
||||
match /slow-endpoint/* # パスが /slow-endpoint/* のすべてリクエストはこのスレッドプールによって処理されます
|
||||
num 1 # /slow-endpoint/* に一致するリクエストに対しては最低1スレッド
|
||||
max_threads 20 # 必要に応じて、/slow-endpoint/* に一致するリクエストに対して最大20スレッドまで許可します
|
||||
}
|
||||
worker index.php {
|
||||
match * # 他のすべてのリクエストは個別に処理されます
|
||||
num 1 # 遅いエンドポイントがハングし始めても、他のリクエストには最低1スレッド
|
||||
max_threads 20 # 必要に応じて、他のリクエストに対して最大20スレッドまで許可します
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
一般的に、メッセージキューなどの適切なメカニズムを使用して、非常に遅いエンドポイントを非同期的に処理することも推奨されます。
|
||||
|
||||
@@ -35,8 +35,13 @@ PHPアプリが[バイナリに埋め込まれている](embed.md)場合は、
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
|
||||
```
|
||||
|
||||
この機能は、[ホットリロード](hot-reload.md)と組み合わせてよく使用されます。
|
||||
|
||||
## Symfonyランタイム
|
||||
|
||||
> [!TIP]
|
||||
> 以下のセクションは、FrankenPHPワーカーモードのネイティブサポートが導入されたSymfony 7.4より前のバージョンでのみ必要です。
|
||||
|
||||
FrankenPHPのワーカーモードは[Symfony Runtime Component](https://symfony.com/doc/current/components/runtime.html)によってサポートされています。
|
||||
ワーカーでSymfonyアプリケーションを開始するには、FrankenPHP用の[PHP Runtime](https://github.com/php-runtime/runtime)パッケージをインストールします:
|
||||
|
||||
@@ -67,30 +72,33 @@ docker run \
|
||||
<?php
|
||||
// public/index.php
|
||||
|
||||
// クライアント接続が中断されたときのワーカースクリプト終了を防ぐ
|
||||
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);
|
||||
try {
|
||||
// リクエストを受信すると呼び出され、
|
||||
// スーパーグローバル、php://inputなどがリセットされます。
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
} catch (\Throwable $exception) {
|
||||
// `set_exception_handler`はワーカースクリプトが終了するときにのみ呼び出されるため、
|
||||
// 予期しない動作になる可能性があります。そのため、ここで例外をキャッチして処理します。
|
||||
(new \MyCustomExceptionHandler)->handleException($exception);
|
||||
}
|
||||
};
|
||||
|
||||
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
|
||||
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
|
||||
$keepRunning = \frankenphp_handle_request($handler);
|
||||
|
||||
// HTTPレスポンスの送信後に何か処理を行います
|
||||
// HTTPレスポンス送信後に何らかの処理を実行
|
||||
$myApp->terminate();
|
||||
|
||||
// ページ生成の途中でガベージコレクタが起動する可能性を減らすために、ここでガベージコレクタを明示的に呼び出す。
|
||||
// ページ生成中にガベージコレクタが起動する可能性を減らすため、ここでガベージコレクタを明示的に呼び出す
|
||||
gc_collect_cycles();
|
||||
|
||||
if (!$keepRunning) break;
|
||||
@@ -178,4 +186,3 @@ $handler = static function () use ($workerServer) {
|
||||
};
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
@@ -1,28 +1,42 @@
|
||||
# Configuração
|
||||
|
||||
FrankenPHP, Caddy, bem como os módulos Mercure e Vulcain, podem ser configurados
|
||||
usando
|
||||
[os formatos suportados pelo Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
FrankenPHP, Caddy, bem como os módulos [Mercure](mercure.md) e [Vulcain](https://vulcain.rocks), podem ser configurados usando [os formatos suportados pelo Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
|
||||
Nas [imagens Docker](docker.md), o `Caddyfile` está localizado em
|
||||
`/etc/frankenphp/Caddyfile`.
|
||||
O binário estático também procurará pelo `Caddyfile` no diretório onde o comando
|
||||
`frankenphp run` é executado.
|
||||
O formato mais comum é o `Caddyfile`, que é um formato de texto simples e legível por humanos.
|
||||
Por padrão, o FrankenPHP procurará por um `Caddyfile` no diretório atual.
|
||||
Você pode especificar um caminho personalizado com a opção `-c` ou `--config`.
|
||||
|
||||
O próprio PHP pode ser configurado
|
||||
[usando um arquivo `php.ini`](https://www.php.net/manual/pt_BR/configuration.file.php).
|
||||
Um `Caddyfile` mínimo para servir uma aplicação PHP é mostrado abaixo:
|
||||
|
||||
Dependendo do seu método de instalação, o interpretador PHP procurará por
|
||||
arquivos de configuração nos locais descritos acima.
|
||||
```caddyfile
|
||||
# The hostname to respond to
|
||||
localhost
|
||||
|
||||
# Optionally, the directory to serve files from, otherwise defaults to the current directory
|
||||
#root public/
|
||||
php_server
|
||||
```
|
||||
|
||||
Um `Caddyfile` mais avançado, que habilita mais recursos e fornece variáveis de ambiente convenientes, é disponibilizado [no repositório FrankenPHP](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile),
|
||||
e com as imagens Docker.
|
||||
|
||||
O próprio PHP pode ser configurado [usando um arquivo `php.ini`](https://www.php.net/manual/en/configuration.file.php).
|
||||
|
||||
Dependendo do seu método de instalação, o FrankenPHP e o interpretador PHP procurarão por arquivos de configuração nos locais descritos abaixo.
|
||||
|
||||
## Docker
|
||||
|
||||
- `php.ini`: `/usr/local/etc/php/php.ini` (nenhum `php.ini` é fornecido por
|
||||
padrão);
|
||||
- Arquivos de configuração adicionais: `/usr/local/etc/php/conf.d/*.ini`;
|
||||
- Extensões PHP: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`;
|
||||
- Você deve copiar um template oficial fornecido pelo projeto PHP:
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: o arquivo de configuração principal
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: arquivos de configuração adicionais que são carregados automaticamente
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/usr/local/etc/php/php.ini` (nenhum `php.ini` é fornecido por padrão)
|
||||
- arquivos de configuração adicionais: `/usr/local/etc/php/conf.d/*.ini`
|
||||
- extensões PHP: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
- Você deve copiar um modelo oficial fornecido pelo projeto PHP:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
@@ -36,125 +50,127 @@ RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
|
||||
## Pacotes RPM e Debian
|
||||
|
||||
- `php.ini`: `/etc/frankenphp/php.ini` (um arquivo `php.ini` com configurações
|
||||
de produção é fornecido por padrão);
|
||||
- Arquivos de configuração adicionais: `/etc/frankenphp/php.d/*.ini`;
|
||||
- Extensões PHP: `/usr/lib/frankenphp/modules/`.
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: o arquivo de configuração principal
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: arquivos de configuração adicionais que são carregados automaticamente
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/etc/php-zts/php.ini` (um arquivo `php.ini` com predefinições de produção é fornecido por padrão)
|
||||
- arquivos de configuração adicionais: `/etc/php-zts/conf.d/*.ini`
|
||||
|
||||
## Binário estático
|
||||
|
||||
- `php.ini`: O diretório no qual `frankenphp run` ou `frankenphp php-server` é
|
||||
executado e, em seguida, `/etc/frankenphp/php.ini`;
|
||||
- Arquivos de configuração adicionais: `/etc/frankenphp/php.d/*.ini`;
|
||||
- Extensões PHP: não podem ser carregadas, empacote-as no próprio binário;
|
||||
- Copie um dos arquivos `php.ini-production` ou `php.ini-development` fornecidos
|
||||
[no código-fonte do PHP](https://github.com/php/php-src/).
|
||||
FrankenPHP:
|
||||
|
||||
- No diretório de trabalho atual: `Caddyfile`
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: O diretório no qual `frankenphp run` ou `frankenphp php-server` é executado, e então `/etc/frankenphp/php.ini`
|
||||
- arquivos de configuração adicionais: `/etc/frankenphp/php.d/*.ini`
|
||||
- extensões PHP: não podem ser carregadas, inclua-as no próprio binário
|
||||
- copie um dos arquivos `php.ini-production` ou `php.ini-development` fornecidos [nas fontes do PHP](https://github.com/php/php-src/).
|
||||
|
||||
## Configuração do Caddyfile
|
||||
|
||||
As [diretivas HTTP](https://caddyserver.com/docs/caddyfile/concepts#directives)
|
||||
`php_server` ou `php` podem ser usadas dentro dos blocos de site para servir sua
|
||||
aplicação PHP.
|
||||
As [diretivas HTTP](https://caddyserver.com/docs/caddyfile/concepts#directives) `php_server` ou `php` podem ser usadas dentro dos blocos de site para servir sua aplicação PHP.
|
||||
|
||||
Exemplo mínimo:
|
||||
|
||||
```caddyfile
|
||||
localhost {
|
||||
# Habilita compressão (opcional)
|
||||
encode zstd br gzip
|
||||
# Executa arquivos PHP no diretório atual e serve assets
|
||||
php_server
|
||||
# Habilita compressão (opcional)
|
||||
encode zstd br gzip
|
||||
# Executa arquivos PHP no diretório atual e serve assets
|
||||
php_server
|
||||
}
|
||||
```
|
||||
|
||||
Você também pode configurar explicitamente o FrankenPHP usando a opção global:
|
||||
A [opção global](https://caddyserver.com/docs/caddyfile/concepts#global-options)
|
||||
`frankenphp` pode ser usada para configurar o FrankenPHP.
|
||||
Você também pode configurar explicitamente o FrankenPHP usando a [opção global](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Define o número de threads PHP a serem iniciadas. Padrão: 2x o número de CPUs disponíveis.
|
||||
max_threads <num_threads> # Limita o número de threads PHP adicionais que podem ser iniciadas em tempo de execução. Padrão: num_threads. Pode ser definido como 'auto'.
|
||||
max_wait_time <duracao> # Define o tempo máximo que uma requisição pode esperar por uma thread PHP livre antes de atingir o tempo limite. Padrão: disabled.
|
||||
php_ini <chave> <valor> # Define uma diretiva php.ini. Pode ser usada várias vezes para definir múltiplas diretivas.
|
||||
worker {
|
||||
file <caminho> # Define o caminho para o worker script.
|
||||
num <num> # Define o número de threads PHP a serem iniciadas, o padrão é 2x o número de CPUs disponíveis.
|
||||
env <chave> <valor> # Define uma variável de ambiente extra para o valor fornecido. Pode ser especificada mais de uma vez para múltiplas variáveis de ambiente.
|
||||
watch <caminho> # Define o caminho para monitorar alterações em arquivos. Pode ser especificada mais de uma vez para múltiplos caminhos.
|
||||
name <nome> # Define o nome do worker, usado em logs e métricas. Padrão: caminho absoluto do arquivo do worker.
|
||||
max_consecutive_failures <num> # Define o número máximo de falhas consecutivas antes do worker ser considerado inoperante. -1 significa que o worker sempre reiniciará. Padrão: 6.
|
||||
}
|
||||
}
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Define o número de threads PHP a serem iniciadas. Padrão: 2x o número de CPUs disponíveis.
|
||||
max_threads <num_threads> # Limita o número de threads PHP adicionais que podem ser iniciadas em tempo de execução. Padrão: num_threads. Pode ser definido como 'auto'.
|
||||
max_wait_time <duration> # Define o tempo máximo que uma requisição pode esperar por uma thread PHP livre antes de atingir o tempo limite. Padrão: desabilitado.
|
||||
max_idle_time <duration> # Define o tempo máximo que uma thread autoscaled pode ficar ociosa antes de ser desativada. Padrão: 5s.
|
||||
php_ini <key> <value> # Define uma diretiva php.ini. Pode ser usada várias vezes para definir múltiplas diretivas.
|
||||
worker {
|
||||
file <path> # Define o caminho para o worker script.
|
||||
num <num> # Define o número de threads PHP a serem iniciadas, o padrão é 2x o número de CPUs disponíveis.
|
||||
env <key> <value> # Define uma variável de ambiente extra para o valor fornecido. Pode ser especificada mais de uma vez para múltiplas variáveis de ambiente.
|
||||
watch <path> # Define o caminho para monitorar alterações em arquivos. Pode ser especificada mais de uma vez para múltiplos caminhos.
|
||||
name <name> # Define o nome do worker, usado em logs e métricas. Padrão: caminho absoluto do arquivo do worker
|
||||
max_consecutive_failures <num> # Define o número máximo de falhas consecutivas antes do worker ser considerado não saudável. -1 significa que o worker sempre reiniciará. Padrão: 6.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Alternativamente, você pode usar a forma abreviada de uma linha da opção
|
||||
`worker`:
|
||||
Alternativamente, você pode usar a forma abreviada de uma linha da opção `worker`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker <arquivo> <num>
|
||||
}
|
||||
frankenphp {
|
||||
worker <file> <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Você também pode definir vários workers se servir várias aplicações no mesmo
|
||||
servidor:
|
||||
Você também pode definir múltiplos workers se servir múltiplas aplicações no mesmo servidor:
|
||||
|
||||
```caddyfile
|
||||
app.example.com {
|
||||
root /caminho/para/aplicacao/public
|
||||
php_server {
|
||||
root /caminho/para/aplicacao/public # permite melhor armazenamento em cache
|
||||
worker index.php <num>
|
||||
}
|
||||
root /path/to/app/public
|
||||
php_server {
|
||||
root /path/to/app/public # permite melhor cache
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
outra.example.com {
|
||||
root /caminho/para/outra/aplicacao/public
|
||||
php_server {
|
||||
root /caminho/para/outra/aplicacao/public
|
||||
worker index.php <num>
|
||||
}
|
||||
other.example.com {
|
||||
root /path/to/other/public
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Usar a diretiva `php_server` geralmente é o que você precisa, mas se precisar de
|
||||
controle total, você pode usar a diretiva `php` de mais baixo nível.
|
||||
Usar a diretiva `php_server` é geralmente o que você precisa,
|
||||
mas se precisar de controle total, você pode usar a diretiva `php` de mais baixo nível.
|
||||
A diretiva `php` passa toda a entrada para o PHP, em vez de primeiro verificar
|
||||
se é um arquivo PHP ou não.
|
||||
Leia mais sobre isso na [página de desempenho](performance.md#try_files).
|
||||
se é um arquivo PHP ou não. Leia mais sobre isso na [página de desempenho](performance.md#try_files).
|
||||
|
||||
Usar a diretiva `php_server` é equivalente a esta configuração:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
# Adiciona barra final para requisições de diretório
|
||||
@canonicalPath {
|
||||
file {path}/index.php
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
# Se o arquivo requisitado não existir, tenta os arquivos index
|
||||
@indexFiles file {
|
||||
try_files {path} {path}/index.php index.php
|
||||
split_path .php
|
||||
}
|
||||
rewrite @indexFiles {http.matchers.file.relative}
|
||||
|
||||
# FrankenPHP!
|
||||
@phpFiles path *.php
|
||||
php @phpFiles
|
||||
file_server
|
||||
# Adiciona barra final para requisições de diretório
|
||||
@canonicalPath {
|
||||
file {path}/index.php
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
# Se o arquivo requisitado não existir, tenta os arquivos index
|
||||
@indexFiles file {
|
||||
try_files {path} {path}/index.php index.php
|
||||
split_path .php
|
||||
}
|
||||
rewrite @indexFiles {http.matchers.file.relative}
|
||||
# FrankenPHP!
|
||||
@phpFiles path *.php
|
||||
php @phpFiles
|
||||
file_server
|
||||
}
|
||||
```
|
||||
|
||||
@@ -162,20 +178,20 @@ As diretivas `php_server` e `php` têm as seguintes opções:
|
||||
|
||||
```caddyfile
|
||||
php_server [<matcher>] {
|
||||
root <directory> # Define a pasta raiz para o site. Padrão: diretiva `root`.
|
||||
split_path <delim...> # Define as substrings para dividir o URI em duas partes. A primeira substring correspondente será usada para separar as "informações de caminho" do caminho. A primeira parte é sufixada com a substring correspondente e será assumida como o nome real do recurso (script CGI). A segunda parte será definida como PATH_INFO para o script usar. Padrão: `.php`
|
||||
resolve_root_symlink false # Desabilita a resolução do diretório `root` para seu valor real avaliando um link simbólico, se houver (habilitado por padrão).
|
||||
env <chave> <valor> # Define uma variável de ambiente extra para o valor fornecido. Pode ser especificada mais de uma vez para múltiplas variáveis de ambiente.
|
||||
file_server off # Desabilita a diretiva interna file_server.
|
||||
worker { # Cria um worker específico para este servidor. Pode ser especificada mais de uma vez para múltiplos workers.
|
||||
file <caminho> # Define o caminho para o worker script, pode ser relativo à raiz do php_server.
|
||||
num <num> # Define o número de threads PHP a serem iniciadas, o padrão é 2x o número de threads disponíveis.
|
||||
name <nome> # Define o nome do worker, usado em logs e métricas. Padrão: caminho absoluto do arquivo do worker. Sempre começa com m# quando definido em um bloco php_server.
|
||||
watch <caminho> # Define o caminho para monitorar alterações em arquivos. Pode ser especificada mais de uma vez para múltiplos caminhos.
|
||||
env <chave> <valor> # Define uma variável de ambiente extra para o valor fornecido. Pode ser especificada mais de uma vez para múltiplas variáveis de ambiente. As variáveis de ambiente para este worker também são herdadas do pai do php_server, mas podem ser sobrescritas aqui.
|
||||
match <caminho> # Corresponde o worker a um padrão de caminho. Substitui try_files e só pode ser usada na diretiva php_server.
|
||||
}
|
||||
worker <outro_arquivo> <num> # Também pode usar a forma abreviada, como no bloco global frankenphp.
|
||||
root <directory> # Define a pasta raiz para o site. Padrão: diretiva `root`.
|
||||
split_path <delim...> # Define as substrings para dividir o URI em duas partes. A primeira substring correspondente será usada para separar as "informações de caminho" do caminho. A primeira parte é sufixada com a substring correspondente e será assumida como o nome real do recurso (script CGI). A segunda parte será definida como PATH_INFO para o script usar. Padrão: `.php`
|
||||
resolve_root_symlink false # Desabilita a resolução do diretório `root` para seu valor real avaliando um link simbólico, se houver (habilitado por padrão).
|
||||
env <key> <value> # Define uma variável de ambiente extra para o valor fornecido. Pode ser especificada mais de uma vez para múltiplas variáveis de ambiente.
|
||||
file_server off # Desabilita a diretiva interna file_server.
|
||||
worker { # Cria um worker específico para este servidor. Pode ser especificada mais de uma vez para múltiplos workers.
|
||||
file <path> # Define o caminho para o worker script, pode ser relativo à raiz do php_server
|
||||
num <num> # Define o número de threads PHP a serem iniciadas, o padrão é 2x o número de CPUs disponíveis.
|
||||
name <name> # Define o nome para o worker, usado em logs e métricas. Padrão: caminho absoluto do arquivo do worker. Sempre começa com m# quando definido em um bloco php_server.
|
||||
watch <path> # Define o caminho para monitorar alterações em arquivos. Pode ser especificada mais de uma vez para múltiplos caminhos.
|
||||
env <key> <value> # Define uma variável de ambiente extra para o valor fornecido. Pode ser especificada mais de uma vez para múltiplas variáveis de ambiente. As variáveis de ambiente para este worker também são herdadas do pai do php_server, mas podem ser sobrescritas aqui.
|
||||
match <path> # Corresponde o worker a um padrão de caminho. Sobrescreve try_files e só pode ser usada na diretiva php_server.
|
||||
}
|
||||
worker <other_file> <num> # Também pode usar a forma abreviada, como no bloco global frankenphp.
|
||||
}
|
||||
```
|
||||
|
||||
@@ -184,145 +200,91 @@ php_server [<matcher>] {
|
||||
Como os workers inicializam sua aplicação apenas uma vez e a mantêm na memória,
|
||||
quaisquer alterações nos seus arquivos PHP não serão refletidas imediatamente.
|
||||
|
||||
Os workers podem ser reiniciados em caso de alterações em arquivos por meio da
|
||||
diretiva `watch`.
|
||||
Os workers podem ser reiniciados em caso de alterações em arquivos por meio da diretiva `watch`.
|
||||
Isso é útil para ambientes de desenvolvimento.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /caminho/para/aplicacao/public/worker.php
|
||||
watch
|
||||
}
|
||||
}
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Se o diretório `watch` não for especificado, ele usará o valor padrão
|
||||
`./**/*.{php,yaml,yml,twig,env}`,
|
||||
que monitora todos os arquivos `.php`, `.yaml`, `.yml`, `.twig` e `.env` no
|
||||
diretório e subdiretórios onde o processo FrankenPHP foi iniciado.
|
||||
Você também pode especificar um ou mais diretórios por meio de um
|
||||
Esta funcionalidade é frequentemente usada em combinação com [recarregamento a quente (hot reload)](hot-reload.md).
|
||||
|
||||
Se o diretório `watch` não for especificado, ele usará o valor padrão `./**/*.{env,php,twig,yaml,yml}`,
|
||||
que monitora todos os arquivos `.env`, `.php`, `.twig`, `.yaml` e `.yml` no diretório e subdiretórios
|
||||
onde o processo FrankenPHP foi iniciado. Você também pode especificar um ou mais diretórios por meio de um
|
||||
[padrão de nome de arquivo shell](https://pkg.go.dev/path/filepath#Match):
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /caminho/para/aplicacao/public/worker.php
|
||||
watch /caminho/para/aplicacao # monitora todos os arquivos em todos os subdiretórios de /caminho/para/aplicacao
|
||||
watch /caminho/para/aplicacao/*.php # monitora arquivos terminados em .php em /caminho/para/aplicacao
|
||||
watch /caminho/para/aplicacao/**/*.php # monitora arquivos PHP em /caminho/para/aplicacao e subdiretórios
|
||||
watch /caminho/para/aplicacao/**/*.{php,twig} # monitora arquivos PHP e Twig em /caminho/para/aplicacao e subdiretórios
|
||||
}
|
||||
}
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch /path/to/app # monitora todos os arquivos em todos os subdiretórios de /path/to/app
|
||||
watch /path/to/app/*.php # monitora arquivos terminados em .php em /path/to/app
|
||||
watch /path/to/app/**/*.php # monitora arquivos PHP em /path/to/app e subdiretórios
|
||||
watch /path/to/app/**/*.{php,twig} # monitora arquivos PHP e Twig em /path/to/app e subdiretórios
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- O padrão `**` significa monitoramento recursivo
|
||||
- Diretórios também podem ser relativos (ao local de início do processo
|
||||
FrankenPHP)
|
||||
- Se você tiver vários workers definidos, todos eles serão reiniciados quando um
|
||||
arquivo for alterado
|
||||
- Tenha cuidado ao monitorar arquivos criados em tempo de execução (como logs),
|
||||
pois eles podem causar reinicializações indesejadas de workers.
|
||||
- Diretórios também podem ser relativos (ao local de início do processo FrankenPHP)
|
||||
- Se você tiver múltiplos workers definidos, todos eles serão reiniciados quando um arquivo for alterado
|
||||
- Tenha cuidado ao monitorar arquivos que são criados em tempo de execução (como logs), pois eles podem causar reinicializações indesejadas de workers.
|
||||
|
||||
O monitor de arquivos é baseado no
|
||||
[e-dant/watcher](https://github.com/e-dant/watcher).
|
||||
O monitor de arquivos é baseado em [e-dant/watcher](https://github.com/e-dant/watcher).
|
||||
|
||||
## Correspondendo o worker a um caminho
|
||||
|
||||
Em aplicações PHP tradicionais, os scripts são sempre colocados no diretório
|
||||
público.
|
||||
Isso também se aplica aos worker scripts, que são tratados como qualquer outro
|
||||
script PHP.
|
||||
Se você quiser colocar o worker script fora do diretório público, pode fazê-lo
|
||||
por meio da diretiva `match`.
|
||||
Em aplicações PHP tradicionais, scripts são sempre colocados no diretório público.
|
||||
Isso também é verdade para worker scripts, que são tratados como qualquer outro script PHP.
|
||||
Se você quiser, em vez disso, colocar o worker script fora do diretório público, pode fazê-lo via a diretiva `match`.
|
||||
|
||||
A diretiva `match` é uma alternativa otimizada ao `try_files`, disponível apenas
|
||||
dentro do `php_server` e do `php`.
|
||||
A diretiva `match` é uma alternativa otimizada para `try_files`, disponível apenas dentro de `php_server` e `php`.
|
||||
O exemplo a seguir sempre servirá um arquivo no diretório público, se presente,
|
||||
e, caso contrário, encaminhará a requisição para o worker que corresponde ao
|
||||
padrão de caminho.
|
||||
e, caso contrário, encaminhará a requisição para o worker que corresponde ao padrão de caminho.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
php_server {
|
||||
worker {
|
||||
file /caminho/para/worker.php # arquivo pode estar fora do caminho público
|
||||
match /api/* # todas as requisições que começam com /api/ serão tratadas por este worker
|
||||
}
|
||||
}
|
||||
}
|
||||
frankenphp {
|
||||
php_server {
|
||||
worker {
|
||||
file /path/to/worker.php # o arquivo pode estar fora do caminho público
|
||||
match /api/* # todas as requisições que começam com /api/ serão tratadas por este worker
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Full Duplex (HTTP/1)
|
||||
|
||||
Ao usar HTTP/1.x, pode ser desejável habilitar o modo full-duplex para permitir
|
||||
a gravação de uma resposta antes que todo o corpo tenha sido lido.
|
||||
(por exemplo: WebSocket, Server-Sent Events, etc.)
|
||||
|
||||
Esta é uma configuração opcional que precisa ser adicionada às opções globais no
|
||||
`Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Habilitar esta opção pode causar deadlock em clientes HTTP/1.x antigos que não
|
||||
> suportam full-duplex.
|
||||
> Isso também pode ser configurado usando a configuração de ambiente
|
||||
> `CADDY_GLOBAL_OPTIONS`:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
Você pode encontrar mais informações sobre esta configuração na
|
||||
[documentação do Caddy](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
|
||||
|
||||
## Variáveis de ambiente
|
||||
|
||||
As seguintes variáveis de ambiente podem ser usadas para injetar diretivas Caddy
|
||||
no `Caddyfile` sem modificá-lo:
|
||||
As seguintes variáveis de ambiente podem ser usadas para injetar diretivas Caddy no `Caddyfile` sem modificá-lo:
|
||||
|
||||
- `SERVER_NAME`: altera
|
||||
[os endereços nos quais escutar](https://caddyserver.com/docs/caddyfile/concepts#addresses),
|
||||
os nomes de host fornecidos também serão usados para o certificado TLS gerado;
|
||||
- `SERVER_ROOT`: altera o diretório raiz do site, o padrão é `public/`;
|
||||
- `CADDY_GLOBAL_OPTIONS`: injeta
|
||||
[opções globais](https://caddyserver.com/docs/caddyfile/options);
|
||||
- `FRANKENPHP_CONFIG`: injeta a configuração sob a diretiva `frankenphp`.
|
||||
- `SERVER_NAME`: altera [os endereços nos quais escutar](https://caddyserver.com/docs/caddyfile/concepts#addresses), os nomes de host fornecidos também serão usados para o certificado TLS gerado
|
||||
- `SERVER_ROOT`: altera o diretório raiz do site, o padrão é `public/`
|
||||
- `CADDY_GLOBAL_OPTIONS`: injeta [opções globais](https://caddyserver.com/docs/caddyfile/options)
|
||||
- `FRANKENPHP_CONFIG`: injeta a configuração sob a diretiva `frankenphp`
|
||||
|
||||
Quanto às SAPIs FPM e CLI, as variáveis de ambiente são expostas por padrão na
|
||||
superglobal `$_SERVER`.
|
||||
Assim como para as SAPIs FPM e CLI, as variáveis de ambiente são expostas por padrão na superglobal `$_SERVER`.
|
||||
|
||||
O valor `S` da
|
||||
[diretiva `variables_order` do PHP](https://www.php.net/manual/pt_BR/ini.core.php#ini.variables-order)
|
||||
é sempre equivalente a `ES`, independentemente da colocação de `E` em outra
|
||||
parte desta diretiva.
|
||||
O valor `S` da [diretiva `variables_order` do PHP](https://www.php.net/manual/en/ini.core.php#ini.variables-order) é sempre equivalente a `ES` independentemente da colocação de `E` em outra parte desta diretiva.
|
||||
|
||||
## Configuração do PHP
|
||||
|
||||
Para carregar
|
||||
[arquivos de configuração adicionais do PHP](https://www.php.net/manual/pt_BR/configuration.file.php#configuration.file.scan),
|
||||
Para carregar [arquivos de configuração adicionais do PHP](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan),
|
||||
a variável de ambiente `PHP_INI_SCAN_DIR` pode ser usada.
|
||||
Quando definida, o PHP carregará todos os arquivos com a extensão `.ini`
|
||||
presentes nos diretórios fornecidos.
|
||||
Quando definida, o PHP carregará todos os arquivos com a extensão `.ini` presentes nos diretórios fornecidos.
|
||||
|
||||
Você também pode alterar a configuração do PHP usando a diretiva `php_ini` no
|
||||
`Caddyfile`:
|
||||
Você também pode alterar a configuração do PHP usando a diretiva `php_ini` no `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
@@ -339,10 +301,46 @@ Você também pode alterar a configuração do PHP usando a diretiva `php_ini` n
|
||||
}
|
||||
```
|
||||
|
||||
### Desabilitando HTTPS
|
||||
|
||||
Por padrão, o FrankenPHP habilitará automaticamente o HTTPS para todos os nomes de host, incluindo `localhost`.
|
||||
Se você quiser desabilitar o HTTPS (por exemplo, em um ambiente de desenvolvimento), você pode definir a variável de ambiente `SERVER_NAME` para `http://` ou `:80`:
|
||||
|
||||
Alternativamente, você pode usar todos os outros métodos descritos na [documentação do Caddy](https://caddyserver.com/docs/automatic-https#activation).
|
||||
|
||||
Se você quiser usar HTTPS com o endereço IP `127.0.0.1` em vez do nome de host `localhost`, por favor, leia a seção de [problemas conhecidos](known-issues.md#using-https127001-with-docker).
|
||||
|
||||
### Full Duplex (HTTP/1)
|
||||
|
||||
Ao usar HTTP/1.x, pode ser desejável habilitar o modo full-duplex para permitir a gravação de uma resposta antes que o corpo inteiro
|
||||
tenha sido lido. (por exemplo: [Mercure](mercure.md), WebSocket, Server-Sent Events, etc.)
|
||||
|
||||
Esta é uma configuração de adesão que precisa ser adicionada às opções globais no `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Habilitar esta opção pode causar deadlock em clientes HTTP/1.x antigos que não suportam full-duplex.
|
||||
> Isso também pode ser configurado usando a configuração de ambiente `CADDY_GLOBAL_OPTIONS`:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
Você pode encontrar mais informações sobre esta configuração na [documentação do Caddy](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
|
||||
|
||||
## Habilitar o modo de depuração
|
||||
|
||||
Ao usar a imagem Docker, defina a variável de ambiente `CADDY_GLOBAL_OPTIONS`
|
||||
como `debug` para habilitar o modo de depuração:
|
||||
Ao usar a imagem Docker, defina a variável de ambiente `CADDY_GLOBAL_OPTIONS` como `debug` para habilitar o modo de depuração:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public \
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
# Construindo uma imagem Docker personalizada
|
||||
# Construindo Imagens Docker Personalizadas
|
||||
|
||||
[As imagens Docker do FrankenPHP](https://hub.docker.com/r/dunglas/frankenphp)
|
||||
são baseadas em [imagens oficiais do PHP](https://hub.docker.com/_/php/).
|
||||
Variantes do Debian e do Alpine Linux são fornecidas para arquiteturas
|
||||
populares.
|
||||
[As imagens Docker do FrankenPHP](https://hub.docker.com/r/dunglas/frankenphp) são baseadas em [imagens oficiais do PHP](https://hub.docker.com/_/php/).
|
||||
Variantes do Debian e do Alpine Linux são fornecidas para arquiteturas populares.
|
||||
Variantes do Debian são recomendadas.
|
||||
|
||||
Variantes para PHP 8.2, 8.3, 8.4 e 8.5 são fornecidas.
|
||||
|
||||
As tags seguem este padrão:
|
||||
`dunglas/frankenphp:<versao-do-frankenphp>-php<versao-do-php>-<so>`.
|
||||
As tags seguem este padrão: `dunglas/frankenphp:<versao-do-frankenphp>-php<versao-do-php>-<os>`
|
||||
|
||||
- `<versao-do-frankenphp>` e `<versao-do-php>` são números de versão do
|
||||
FrankenPHP e do PHP, respectivamente, variando de maior (ex.: `1`), menor
|
||||
(ex.: `1.2`) a versões de patch (ex.: `1.2.3`).
|
||||
- `<so>` é `bookworm` (para Debian Bookworm) ou `alpine` (para a versão estável
|
||||
mais recente do Alpine).
|
||||
- `<versao-do-frankenphp>` e `<versao-do-php>` são números de versão do FrankenPHP e do PHP, respectivamente, variando de maior (ex.: `1`), menor (ex.: `1.2`) a versões de patch (ex.: `1.2.3`).
|
||||
- `<os>` é `trixie` (para Debian Trixie), `bookworm` (para Debian Bookworm) ou `alpine` (para a versão estável mais recente do Alpine).
|
||||
|
||||
[Navegue pelas tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
@@ -36,11 +30,14 @@ docker build -t minha-app-php .
|
||||
docker run -it --rm --name minha-app-rodando minha-app-php
|
||||
```
|
||||
|
||||
## Como ajustar a configuração
|
||||
|
||||
Para sua conveniência, [um `Caddyfile` padrão](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) contendo
|
||||
variáveis de ambiente úteis é fornecido na imagem.
|
||||
|
||||
## Como instalar mais extensões PHP
|
||||
|
||||
O script
|
||||
[`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer)
|
||||
é fornecido na imagem base.
|
||||
O script [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) é fornecido na imagem base.
|
||||
Adicionar extensões PHP adicionais é simples:
|
||||
|
||||
```dockerfile
|
||||
@@ -57,12 +54,9 @@ RUN install-php-extensions \
|
||||
|
||||
## Como instalar mais módulos Caddy
|
||||
|
||||
O FrankenPHP é construído sobre o Caddy, e todos os
|
||||
[módulos Caddy](https://caddyserver.com/docs/modules/) podem ser usados com o
|
||||
FrankenPHP.
|
||||
O FrankenPHP é construído sobre o Caddy, e todos os [módulos Caddy](https://caddyserver.com/docs/modules/) podem ser usados com o FrankenPHP.
|
||||
|
||||
A maneira mais fácil de instalar módulos Caddy personalizados é usar o
|
||||
[xcaddy](https://github.com/caddyserver/xcaddy):
|
||||
A maneira mais fácil de instalar módulos Caddy personalizados é usar o [xcaddy](https://github.com/caddyserver/xcaddy):
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp:builder AS builder
|
||||
@@ -78,8 +72,8 @@ RUN CGO_ENABLED=1 \
|
||||
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
|
||||
xcaddy build \
|
||||
--output /usr/local/bin/frankenphp \
|
||||
--with github.com/php/frankenphp=./ \
|
||||
--with github.com/php/frankenphp/caddy=./caddy/ \
|
||||
--with github.com/dunglas/frankenphp=./ \
|
||||
--with github.com/dunglas/frankenphp/caddy=./caddy/ \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
# Mercure e Vulcain estão incluídos na compilação oficial, mas sinta-se
|
||||
# à vontade para removê-los
|
||||
@@ -93,11 +87,8 @@ FROM dunglas/frankenphp AS runner
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
```
|
||||
|
||||
A imagem `builder` fornecida pelo FrankenPHP contém uma versão compilada da
|
||||
`libphp`.
|
||||
[Imagens de builder](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder)
|
||||
são fornecidas para todas as versões do FrankenPHP e do PHP, tanto para Debian
|
||||
quanto para Alpine.
|
||||
A imagem `builder` fornecida pelo FrankenPHP contém uma versão compilada da `libphp`.
|
||||
[Imagens de builder](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) são fornecidas para todas as versões do FrankenPHP e do PHP, tanto para Debian quanto para Alpine.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
@@ -106,8 +97,7 @@ quanto para Alpine.
|
||||
|
||||
## Habilitando o modo worker por padrão
|
||||
|
||||
Defina a variável de ambiente `FRANKENPHP_CONFIG` para iniciar o FrankenPHP com
|
||||
um worker script:
|
||||
Defina a variável de ambiente `FRANKENPHP_CONFIG` para iniciar o FrankenPHP com um worker script:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
@@ -119,8 +109,7 @@ ENV FRANKENPHP_CONFIG="worker ./public/index.php"
|
||||
|
||||
## Usando um volume em desenvolvimento
|
||||
|
||||
Para desenvolver facilmente com o FrankenPHP, monte o diretório do seu host que
|
||||
contém o código-fonte da aplicação como um volume no contêiner Docker:
|
||||
Para desenvolver facilmente com o FrankenPHP, monte o diretório do seu host que contém o código-fonte da aplicação como um volume no contêiner Docker:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty minha-app-php
|
||||
@@ -138,9 +127,9 @@ Com o Docker Compose:
|
||||
services:
|
||||
php:
|
||||
image: dunglas/frankenphp
|
||||
# Descomente a linha a seguir se quiser usar um Dockerfile personalizado
|
||||
# descomente a linha a seguir se quiser usar um Dockerfile personalizado
|
||||
#build: .
|
||||
# Descomente a linha a seguir se quiser executar isso em um ambiente de
|
||||
# descomente a linha a seguir se quiser executar isso em um ambiente de
|
||||
# produção
|
||||
# restart: always
|
||||
ports:
|
||||
@@ -151,7 +140,7 @@ services:
|
||||
- ./:/app/public
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
# Comente a linha a seguir em produção, isso permite ter logs legíveis em
|
||||
# comente a linha a seguir em produção, isso permite ter logs legíveis em
|
||||
# desenvolvimento
|
||||
tty: true
|
||||
|
||||
@@ -183,15 +172,13 @@ RUN \
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
### Executando sem capacidades adicionais
|
||||
### Executando sem capacidades
|
||||
|
||||
Mesmo executando sem root, o FrankenPHP precisa do recurso
|
||||
`CAP_NET_BIND_SERVICE` para vincular o servidor web em portas privilegiadas (80
|
||||
e 443).
|
||||
Mesmo executando sem root, o FrankenPHP precisa do recurso `CAP_NET_BIND_SERVICE` para vincular o
|
||||
servidor web em portas privilegiadas (80 e 443).
|
||||
|
||||
Se você expor o FrankenPHP em uma porta não privilegiada (1024 e acima), é
|
||||
possível executar o servidor web como um usuário não root e sem a necessidade de
|
||||
nenhuma capacidade adicional:
|
||||
Se você expor o FrankenPHP em uma porta não privilegiada (1024 e acima), é possível executar
|
||||
o servidor web como um usuário não root e sem a necessidade de nenhuma capacidade:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
@@ -209,24 +196,94 @@ RUN \
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
Em seguida, defina a variável de ambiente `SERVER_NAME` para usar uma porta sem
|
||||
privilégios.
|
||||
Em seguida, defina a variável de ambiente `SERVER_NAME` para usar uma porta sem privilégios.
|
||||
Exemplo: `:8000`
|
||||
|
||||
## Atualizações
|
||||
|
||||
As imagens Docker são construídas:
|
||||
|
||||
- Quando uma tag de uma nova versão é criada;
|
||||
- Diariamente às 4h UTC, se novas versões das imagens oficiais do PHP estiverem
|
||||
disponíveis.
|
||||
- quando uma nova release é marcada (tagueada)
|
||||
- diariamente às 4h UTC, se novas versões das imagens oficiais do PHP estiverem disponíveis
|
||||
|
||||
## Versões de desenvolvimento
|
||||
## Endurecendo Imagens
|
||||
|
||||
As versões de desenvolvimento estão disponíveis no repositório Docker
|
||||
[`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev).
|
||||
Uma nova construção é acionada sempre que um commit é enviado para o branch
|
||||
principal do repositório do GitHub.
|
||||
Para reduzir ainda mais a superfície de ataque e o tamanho das suas imagens Docker do FrankenPHP, também é possível construí-las sobre uma
|
||||
[imagem Google distroless](https://github.com/GoogleContainerTools/distroless) ou
|
||||
[Docker hardened](https://www.docker.com/products/hardened-images).
|
||||
|
||||
> [!WARNING]
|
||||
> Essas imagens base mínimas não incluem um shell ou gerenciador de pacotes, o que torna a depuração mais difícil.
|
||||
> Elas são, portanto, recomendadas apenas para produção se a segurança for uma alta prioridade.
|
||||
|
||||
Ao adicionar extensões PHP adicionais, você precisará de uma etapa de build intermediária:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp AS builder
|
||||
|
||||
# Adicione extensões PHP adicionais aqui
|
||||
RUN install-php-extensions pdo_mysql pdo_pgsql #...
|
||||
|
||||
# Copia as bibliotecas compartilhadas do frankenphp e todas as extensões instaladas para um local temporário
|
||||
# Você também pode fazer esta etapa manualmente analisando a saída ldd do binário frankenphp e de cada arquivo .so da extensão
|
||||
RUN apt-get update && apt-get install -y libtree && \
|
||||
EXT_DIR="$(php -r 'echo ini_get("extension_dir");')" && \
|
||||
FRANKENPHP_BIN="$(which frankenphp)"; \
|
||||
LIBS_TMP_DIR="/tmp/libs"; \
|
||||
mkdir -p "$LIBS_TMP_DIR"; \
|
||||
for target in "$FRANKENPHP_BIN" $(find "$EXT_DIR" -maxdepth 2 -type f -name "*.so"); do \
|
||||
libtree -pv "$target" | sed 's/.*── \(.*\) \[.*/\1/' | grep -v "^$target" | while IFS= read -r lib; do \
|
||||
[ -z "$lib" ] && continue; \
|
||||
base=$(basename "$lib"); \
|
||||
destfile="$LIBS_TMP_DIR/$base"; \
|
||||
if [ ! -f "$destfile" ]; then \
|
||||
cp "$lib" "$destfile"; \
|
||||
fi; \
|
||||
done; \
|
||||
done
|
||||
|
||||
|
||||
# Imagem base debian distroless, certifique-se de que esta é a mesma versão debian da imagem base
|
||||
FROM gcr.io/distroless/base-debian13
|
||||
# Alternativa de imagem Docker endurecida
|
||||
# FROM dhi.io/debian:13
|
||||
|
||||
# Localização do seu aplicativo e Caddyfile a serem copiados para o contêiner
|
||||
ARG PATH_TO_APP="."
|
||||
ARG PATH_TO_CADDYFILE="./Caddyfile"
|
||||
|
||||
# Copia seu aplicativo para /app
|
||||
# Para um endurecimento adicional, certifique-se de que apenas os caminhos graváveis são de propriedade do usuário não-root
|
||||
COPY --chown=nonroot:nonroot "$PATH_TO_APP" /app
|
||||
COPY "$PATH_TO_CADDYFILE" /etc/caddy/Caddyfile
|
||||
|
||||
# Copia frankenphp e bibliotecas necessárias
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
|
||||
COPY --from=builder /tmp/libs /usr/lib
|
||||
|
||||
# Copia arquivos de configuração php.ini
|
||||
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
|
||||
COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
|
||||
|
||||
# Diretórios de dados do Caddy — devem ser graváveis para o usuário não-root, mesmo em um sistema de arquivos raiz somente leitura
|
||||
ENV XDG_CONFIG_HOME=/config \
|
||||
XDG_DATA_HOME=/data
|
||||
COPY --from=builder --chown=nonroot:nonroot /data/caddy /data/caddy
|
||||
COPY --from=builder --chown=nonroot:nonroot /config/caddy /config/caddy
|
||||
|
||||
USER nonroot
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Ponto de entrada para executar o frankenphp com o Caddyfile fornecido
|
||||
ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "-c", "/etc/caddy/Caddyfile"]
|
||||
```
|
||||
|
||||
## Versões de Desenvolvimento
|
||||
|
||||
As versões de desenvolvimento estão disponíveis no repositório Docker [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev).
|
||||
Uma nova construção é acionada sempre que um commit é enviado para o branch principal do repositório do GitHub.
|
||||
|
||||
As tags `latest*` apontam para o HEAD do branch `main`.
|
||||
Tags no formato `sha-<git-commit-hash>` também estão disponíveis.
|
||||
|
||||
172
docs/pt-br/extension-workers.md
Normal file
172
docs/pt-br/extension-workers.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Workers de Extensão
|
||||
|
||||
Os Workers de Extensão permitem que sua [extensão FrankenPHP](https://frankenphp.dev/docs/extensions/) gerencie um pool dedicado de threads PHP para executar tarefas em segundo plano, lidar com eventos assíncronos ou implementar protocolos personalizados. Útil para sistemas de fila, listeners de eventos, agendadores, etc.
|
||||
|
||||
## Registrando o Worker
|
||||
|
||||
### Registro Estático
|
||||
|
||||
Se você não precisa que o worker seja configurável pelo usuário (caminho de script fixo, número de threads fixo), você pode simplesmente registrar o worker na função `init()`.
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"github.com/dunglas/frankenphp"
|
||||
"github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
// Handle global para comunicar com o pool de workers
|
||||
var worker frankenphp.Workers
|
||||
|
||||
func init() {
|
||||
// Registra o worker quando o módulo é carregado.
|
||||
worker = caddy.RegisterWorkers(
|
||||
"my-internal-worker", // Nome único
|
||||
"worker.php", // Caminho do script (relativo à execução ou absoluto)
|
||||
2, // Contagem fixa de threads
|
||||
// Hooks de ciclo de vida opcionais
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
// Lógica de configuração global...
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Em um Módulo Caddy (Configurável pelo usuário)
|
||||
|
||||
Se você planeja compartilhar sua extensão (como uma fila genérica ou um listener de eventos), você deve encapsulá-la em um módulo Caddy. Isso permite que os usuários configurem o caminho do script e a contagem de threads através do seu `Caddyfile`. Isso exige a implementação da interface `caddy.Provisioner` e a análise do Caddyfile ([veja um exemplo](https://github.com/dunglas/frankenphp-queue/blob/989120d394d66dd6c8e2101cac73dd622fade334/caddy.go)).
|
||||
|
||||
### Em uma Aplicação Go Pura (Embedagem)
|
||||
|
||||
Se você está [embedando o FrankenPHP em uma aplicação Go padrão sem caddy](https://pkg.go.dev/github.com/dunglas/frankenphp#example-ServeHTTP), você pode registrar workers de extensão usando `frankenphp.WithExtensionWorkers` ao inicializar as opções.
|
||||
|
||||
## Interagindo com Workers
|
||||
|
||||
Assim que o pool de workers estiver ativo, você pode despachar tarefas para ele. Isso pode ser feito dentro de [funções nativas exportadas para PHP](https://frankenphp.dev/docs/extensions/#writing-the-extension), ou de qualquer lógica Go, como um agendador cron, um listener de eventos (MQTT, Kafka), ou qualquer outra goroutine.
|
||||
|
||||
### Modo Headless: `SendMessage`
|
||||
|
||||
Use `SendMessage` para passar dados brutos diretamente para o seu script worker. Isso é ideal para filas ou comandos simples.
|
||||
|
||||
#### Exemplo: Uma Extensão de Fila Assíncrona
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_queue_push(mixed $data): bool
|
||||
func my_queue_push(data *C.zval) bool {
|
||||
// 1. Garante que o worker esteja pronto
|
||||
if worker == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. Despacha para o worker em segundo plano
|
||||
_, err := worker.SendMessage(
|
||||
context.Background(), // Contexto Go padrão
|
||||
unsafe.Pointer(data), // Dados a serem passados para o worker
|
||||
nil, // http.ResponseWriter opcional
|
||||
)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
```
|
||||
|
||||
### Emulação HTTP: `SendRequest`
|
||||
|
||||
Use `SendRequest` se sua extensão precisar invocar um script PHP que espera um ambiente web padrão (populando `$_SERVER`, `$_GET`, etc.).
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_worker_http_request(string $path): string
|
||||
func my_worker_http_request(path *C.zend_string) unsafe.Pointer {
|
||||
// 1. Prepara a requisição e o gravador
|
||||
url := frankenphp.GoString(unsafe.Pointer(path))
|
||||
req, _ := http.NewRequest("GET", url, http.NoBody)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// 2. Despacha para o worker
|
||||
if err := worker.SendRequest(rr, req); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3. Retorna a resposta capturada
|
||||
return frankenphp.PHPString(rr.Body.String(), false)
|
||||
}
|
||||
```
|
||||
|
||||
## Script do Worker
|
||||
|
||||
O script PHP do worker é executado em um loop e pode lidar tanto com mensagens brutas quanto com requisições HTTP.
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Lida tanto com mensagens brutas quanto com requisições HTTP no mesmo loop
|
||||
$handler = function ($payload = null) {
|
||||
// Caso 1: Modo de Mensagem
|
||||
if ($payload !== null) {
|
||||
return "Received payload: " . $payload;
|
||||
}
|
||||
|
||||
// Caso 2: Modo HTTP (superglobais PHP padrão são populadas)
|
||||
echo "Hello from page: " . $_SERVER['REQUEST_URI'];
|
||||
};
|
||||
|
||||
while (frankenphp_handle_request($handler)) {
|
||||
gc_collect_cycles();
|
||||
}
|
||||
```
|
||||
|
||||
## Hooks de Ciclo de Vida
|
||||
|
||||
FrankenPHP oferece hooks para executar código Go em pontos específicos do ciclo de vida.
|
||||
|
||||
| Tipo de Hook | Nome da Opção | Assinatura | Contexto e Caso de Uso |
|
||||
| :--------- | :--------------------------- | :------------------- | :------------------------------------------------------------------------- |
|
||||
| **Servidor** | `WithWorkerOnServerStartup` | `func()` | Configuração global. Executado **Uma Vez**. Exemplo: Conectar ao NATS/Redis. |
|
||||
| **Servidor** | `WithWorkerOnServerShutdown` | `func()` | Limpeza global. Executado **Uma Vez**. Exemplo: Fechar conexões compartilhadas. |
|
||||
| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuração por thread. Chamado quando um thread inicia. Recebe o ID do Thread. |
|
||||
| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Limpeza por thread. Recebe o ID do Thread. |
|
||||
|
||||
### Exemplo
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dunglas/frankenphp"
|
||||
frankenphpCaddy "github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
workerHandle = frankenphpCaddy.RegisterWorkers(
|
||||
"my-worker", "worker.php", 2,
|
||||
|
||||
// Inicialização do Servidor (Global)
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
fmt.Println("Extensão: Servidor iniciando...")
|
||||
}),
|
||||
|
||||
// Thread Pronta (Por Thread)
|
||||
// Nota: A função aceita um inteiro representando o ID do Thread
|
||||
frankenphp.WithWorkerOnReady(func(id int) {
|
||||
fmt.Printf("Extensão: Thread worker #%d está pronta.\n", id)
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -5,15 +5,14 @@ facilidade de uso.
|
||||
No entanto, é possível melhorar substancialmente o desempenho usando uma
|
||||
configuração apropriada.
|
||||
|
||||
## Número de threads e workers
|
||||
## Número de Threads e Workers
|
||||
|
||||
Por padrão, o FrankenPHP inicia 2 vezes mais threads e workers (no modo worker)
|
||||
do que a quantidade de CPU disponível.
|
||||
do que o número de núcleos de CPU disponíveis.
|
||||
|
||||
Os valores apropriados dependem muito de como sua aplicação foi escrita, do que
|
||||
ela faz e do seu hardware.
|
||||
Recomendamos fortemente alterar esses valores.
|
||||
Para melhor estabilidade do sistema, recomenda-se ter `num_threads` x
|
||||
Recomendamos fortemente alterar esses valores. Para melhor estabilidade do sistema, recomenda-se ter `num_threads` x
|
||||
`memory_limit` < `available_memory`.
|
||||
|
||||
Para encontrar os valores corretos, é melhor executar testes de carga simulando
|
||||
@@ -37,15 +36,15 @@ limite especificado.
|
||||
lidar com seu tráfego e pode tornar o servidor mais resiliente a picos de
|
||||
latência.
|
||||
Se definido como `auto`, o limite será estimado com base no `memory_limit` em
|
||||
seu `php.ini`.
|
||||
Caso contrário, `auto` assumirá como padrão o valor 2x `num_threads`.
|
||||
seu `php.ini`. Se não for possível fazer isso,
|
||||
`auto` assumirá como padrão 2x `num_threads`.
|
||||
Lembre-se de que `auto` pode subestimar bastante o número de threads
|
||||
necessárias.
|
||||
`max_threads` é semelhante ao
|
||||
[pm.max_children](https://www.php.net/manual/pt_BR/install.fpm.configuration.php#pm.max-children)
|
||||
do PHP FPM.
|
||||
A principal diferença é que o FrankenPHP usa threads em vez de processos e as
|
||||
delega automaticamente entre diferentes worker scripts e o modo clássico,
|
||||
delega automaticamente entre diferentes worker scripts e o 'classic mode',
|
||||
conforme necessário.
|
||||
|
||||
## Modo worker
|
||||
@@ -55,31 +54,28 @@ aplicação precisa ser adaptada para ser compatível com este modo: você preci
|
||||
criar um worker script e garantir que a aplicação não esteja com vazamento de
|
||||
memória.
|
||||
|
||||
## Não use `musl`
|
||||
## Não use musl
|
||||
|
||||
A variante Alpine Linux das imagens oficiais do Docker e os binários padrão que
|
||||
fornecemos usam [a biblioteca C `musl`](https://musl.libc.org).
|
||||
fornecemos usam [a biblioteca C musl](https://musl.libc.org).
|
||||
|
||||
O PHP é conhecido por ser
|
||||
[mais lento](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381)
|
||||
ao usar esta biblioteca C alternativa em vez da biblioteca GNU tradicional,
|
||||
especialmente quando compilado no modo ZTS (thread-safe), necessário para o
|
||||
FrankenPHP.
|
||||
A diferença pode ser significativa em um ambiente com muitas threads.
|
||||
FrankenPHP. A diferença pode ser significativa em um ambiente com muitas threads.
|
||||
|
||||
Além disso,
|
||||
[alguns bugs só acontecem ao usar `musl`](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl).
|
||||
[alguns bugs só acontecem ao usar musl](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl).
|
||||
|
||||
Em ambientes de produção, recomendamos o uso do FrankenPHP vinculado à `glibc`.
|
||||
Em ambientes de produção, recomendamos o uso do FrankenPHP vinculado à glibc, compilado com um nível de otimização apropriado.
|
||||
|
||||
Isso pode ser feito usando as imagens Docker do Debian (o padrão), baixando o
|
||||
binário com sufixo -gnu de nossos
|
||||
[Lançamentos](https://github.com/php/frankenphp/releases) ou
|
||||
Isso pode ser feito usando as imagens Docker do Debian, usando
|
||||
[nossos pacotes .deb, .rpm ou .apk dos mantenedores](https://pkgs.henderkes.com), ou
|
||||
[compilando o FrankenPHP a partir do código-fonte](compile.md).
|
||||
|
||||
Como alternativa, fornecemos binários `musl` estáticos compilados com
|
||||
[o alocador `mimalloc`](https://github.com/microsoft/mimalloc), o que alivia os
|
||||
problemas em cenários com threads.
|
||||
Para contêineres mais leves ou seguros, você pode considerar
|
||||
[uma imagem Debian reforçada](docker.md#hardening-images) em vez de Alpine.
|
||||
|
||||
## Configuração do runtime do Go
|
||||
|
||||
@@ -116,28 +112,39 @@ php_server {
|
||||
## `try_files`
|
||||
|
||||
Além de arquivos estáticos e arquivos PHP, `php_server` também tentará servir o
|
||||
arquivo index da sua aplicação e os arquivos index de diretório (`/path/` ->
|
||||
arquivo de índice da sua aplicação e os arquivos de índice de diretório (`/path/` ->
|
||||
`/path/index.php`).
|
||||
Se você não precisa de arquivos index de diretório, pode desativá-los definindo
|
||||
Se você não precisa de arquivos de índice de diretório, pode desativá-los definindo
|
||||
explicitamente `try_files` assim:
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
root /raiz/da/sua/aplicacao # adicionar explicitamente a raiz aqui permite um melhor armazenamento em cache
|
||||
root /root/to/your/app # adicionar explicitamente o root aqui permite um melhor cache
|
||||
}
|
||||
```
|
||||
|
||||
Isso pode reduzir significativamente o número de operações desnecessárias com
|
||||
arquivos.
|
||||
Um equivalente no modo worker da configuração anterior seria:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
php_server { # use "php" em vez de "php_server" se você não precisar do servidor de arquivos
|
||||
root /root/to/your/app
|
||||
worker /path/to/worker.php {
|
||||
match * # envia todas as requisições diretamente para o worker
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Uma abordagem alternativa com 0 operações desnecessárias no sistema de arquivos
|
||||
seria usar a diretiva `php` e separar os arquivos estáticos do PHP usando
|
||||
caminhos.
|
||||
seria usar a diretiva `php` e dividir os arquivos estáticos dos arquivos PHP por caminho.
|
||||
Essa abordagem funciona bem se toda a sua aplicação for servida por um arquivo
|
||||
de entrada.
|
||||
Um exemplo de [configuração](config.md#configuracao-do-caddyfile) que serve
|
||||
arquivos estáticos a partir de uma pasta `/assets` poderia ser assim:
|
||||
arquivos estáticos atrás de uma pasta `/assets` poderia ser assim:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
@@ -145,15 +152,15 @@ route {
|
||||
path /assets/*
|
||||
}
|
||||
|
||||
# tudo a partir de /assets é gerenciado pelo servidor de arquivos
|
||||
# tudo o que está em /assets é gerenciado pelo servidor de arquivos
|
||||
file_server @assets {
|
||||
root /raiz/da/sua/aplicacao
|
||||
root /root/to/your/app
|
||||
}
|
||||
|
||||
# tudo o que não está em /assets é gerenciado pelo seu arquivo index ou worker PHP
|
||||
# tudo o que não está em /assets é gerenciado pelo seu arquivo de índice ou worker PHP
|
||||
rewrite index.php
|
||||
php {
|
||||
root /raiz/da/sua/aplicacao # adicionar explicitamente a raiz aqui permite um melhor armazenamento em cache
|
||||
root /root/to/your/app # adicionar explicitamente o root aqui permite um melhor cache
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -213,3 +220,32 @@ Em particular:
|
||||
Para mais detalhes, leia
|
||||
[a entrada dedicada na documentação do Symfony](https://symfony.com/doc/current/performance.html)
|
||||
(a maioria das dicas é útil mesmo se você não usa o Symfony).
|
||||
|
||||
## Dividindo o Pool de Threads
|
||||
|
||||
É comum que aplicações interajam com serviços externos lentos, como uma
|
||||
API que tende a ser instável sob alta carga ou que consistentemente leva mais de 10 segundos para responder.
|
||||
Nesses casos, pode ser benéfico dividir o pool de threads para ter pools "lentos" dedicados.
|
||||
Isso impede que os endpoints lentos consumam todos os recursos/threads do servidor e
|
||||
limita a concorrência de requisições direcionadas ao endpoint lento, semelhante a um
|
||||
pool de conexões.
|
||||
|
||||
```caddyfile
|
||||
example.com {
|
||||
php_server {
|
||||
root /app/public # a raiz da sua aplicação
|
||||
worker index.php {
|
||||
match /slow-endpoint/* # todas as requisições com caminho /slow-endpoint/* são tratadas por este pool de threads
|
||||
num 1 # mínimo de 1 thread para requisições que correspondem a /slow-endpoint/*
|
||||
max_threads 20 # permite até 20 threads para requisições que correspondem a /slow-endpoint/*, se necessário
|
||||
}
|
||||
worker index.php {
|
||||
match * # todas as outras requisições são tratadas separadamente
|
||||
num 1 # mínimo de 1 thread para outras requisições, mesmo que os endpoints lentos comecem a travar
|
||||
max_threads 20 # permite até 20 threads para outras requisições, se necessário
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Geralmente, também é aconselhável lidar com endpoints muito lentos de forma assíncrona, usando mecanismos relevantes como filas de mensagens.
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
# Usando workers do FrankenPHP
|
||||
# Usando Workers do FrankenPHP
|
||||
|
||||
Inicialize sua aplicação uma vez e mantenha-a na memória.
|
||||
O FrankenPHP processará as requisições recebidas em poucos milissegundos.
|
||||
|
||||
## Iniciando worker scripts
|
||||
## Iniciando Worker Scripts
|
||||
|
||||
### Docker
|
||||
|
||||
Defina o valor da variável de ambiente `FRANKENPHP_CONFIG` como
|
||||
`worker /caminho/para/seu/worker/script.php`:
|
||||
Defina o valor da variável de ambiente `FRANKENPHP_CONFIG` como `worker /path/to/your/worker/script.php`:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker /app/caminho/para/seu/worker/script.php" \
|
||||
-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
|
||||
@@ -20,41 +19,37 @@ docker run \
|
||||
|
||||
### Binário independente
|
||||
|
||||
Use a opção `--worker` do comando `php-server` para servir o conteúdo do
|
||||
diretório atual usando um worker:
|
||||
Use a opção `--worker` do comando `php-server` para servir o conteúdo do diretório atual usando um worker:
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /caminho/para/seu/worker/script.php
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php
|
||||
```
|
||||
|
||||
Se a sua aplicação PHP estiver [embutida no binário](embed.md), você pode
|
||||
adicionar um `Caddyfile` personalizado no diretório raiz da aplicação.
|
||||
Se a sua aplicação PHP estiver [embutida no binário](embed.md), você pode adicionar um `Caddyfile` personalizado no diretório raiz da aplicação.
|
||||
Ele será usado automaticamente.
|
||||
|
||||
Também é possível
|
||||
[reiniciar o worker em caso de alterações em arquivos](config.md#monitorando-alteracoes-em-arquivos)
|
||||
com a opção `--watch`.
|
||||
O comando a seguir acionará uma reinicialização se qualquer arquivo terminado em
|
||||
`.php` no diretório `/caminho/para/sua/aplicacao/` ou subdiretórios for
|
||||
modificado:
|
||||
Também é possível [reiniciar o worker em caso de alterações em arquivos](config.md#watching-for-file-changes) com a opção `--watch`.
|
||||
O comando a seguir acionará uma reinicialização se qualquer arquivo terminado em `.php` no diretório `/path/to/your/app/` ou subdiretórios for modificado:
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /caminho/para/seu/worker/script.php --watch="/caminho/para/sua/aplicacao/**/*.php"
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
|
||||
```
|
||||
|
||||
Este recurso é frequentemente usado em combinação com [hot reloading](hot-reload.md).
|
||||
|
||||
## Symfony Runtime
|
||||
|
||||
O modo worker do FrankenPHP é suportado pelo
|
||||
[Componente Symfony Runtime](https://symfony.com/doc/current/components/runtime.html).
|
||||
Para iniciar qualquer aplicação Symfony em um worker, instale o pacote
|
||||
FrankenPHP do [PHP Runtime](https://github.com/php-runtime/runtime):
|
||||
> [!TIP]
|
||||
> A seção a seguir é necessária apenas antes do Symfony 7.4, onde o suporte nativo para o modo worker do FrankenPHP foi introduzido.
|
||||
|
||||
O modo worker do FrankenPHP é suportado pelo [Componente Symfony Runtime](https://symfony.com/doc/current/components/runtime.html).
|
||||
Para iniciar qualquer aplicação Symfony em um worker, instale o pacote FrankenPHP do [PHP Runtime](https://github.com/php-runtime/runtime):
|
||||
|
||||
```console
|
||||
composer require runtime/frankenphp-symfony
|
||||
```
|
||||
|
||||
Inicie seu servidor de aplicações definindo a variável de ambiente `APP_RUNTIME`
|
||||
para usar o Symfony Runtime do FrankenPHP:
|
||||
Inicie seu servidor de aplicações definindo a variável de ambiente `APP_RUNTIME` para usar o Symfony Runtime do FrankenPHP:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
@@ -71,17 +66,12 @@ Consulte [a documentação dedicada](laravel.md#laravel-octane).
|
||||
|
||||
## Aplicações personalizadas
|
||||
|
||||
O exemplo a seguir mostra como criar seu próprio worker script sem depender de
|
||||
uma biblioteca de terceiros:
|
||||
O exemplo a seguir mostra como criar seu próprio worker script sem depender de uma biblioteca de terceiros:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// public/index.php
|
||||
|
||||
// Impede o encerramento do worker script quando uma conexão do cliente for
|
||||
// interrompida
|
||||
ignore_user_abort(true);
|
||||
|
||||
// Inicializa a aplicação
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
@@ -90,9 +80,15 @@ $myApp->boot();
|
||||
|
||||
// Manipulador fora do loop para melhor desempenho (fazendo menos trabalho)
|
||||
$handler = static function () use ($myApp) {
|
||||
// Chamado quando uma requisição é recebida,
|
||||
// superglobals, php://input e similares são redefinidos
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
try {
|
||||
// Chamado quando uma requisição é recebida,
|
||||
// superglobais, php://input e similares são redefinidos
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
} catch (\Throwable $exception) {
|
||||
// `set_exception_handler` é chamado apenas quando o worker script termina,
|
||||
// o que pode não ser o que você espera, então capture e trate exceções aqui
|
||||
(new \MyCustomExceptionHandler)->handleException($exception);
|
||||
}
|
||||
};
|
||||
|
||||
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
|
||||
@@ -102,8 +98,7 @@ for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests
|
||||
// Faz algo depois de enviar a resposta HTTP
|
||||
$myApp->terminate();
|
||||
|
||||
// Chama o coletor de lixo para reduzir as chances de ele ser acionado no
|
||||
// meio da geração de uma página
|
||||
// Chama o coletor de lixo para reduzir as chances de ele ser acionado no meio da geração de uma página
|
||||
gc_collect_cycles();
|
||||
|
||||
if (!$keepRunning) break;
|
||||
@@ -113,8 +108,7 @@ for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests
|
||||
$myApp->shutdown();
|
||||
```
|
||||
|
||||
Em seguida, inicie sua aplicação e use a variável de ambiente
|
||||
`FRANKENPHP_CONFIG` para configurar seu worker:
|
||||
Em seguida, inicie sua aplicação e use a variável de ambiente `FRANKENPHP_CONFIG` para configurar seu worker:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
@@ -135,44 +129,28 @@ docker run \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
### Reiniciar o worker após um certo número de requisições
|
||||
### Reiniciar o Worker Após um Certo Número de Requisições
|
||||
|
||||
Como o PHP não foi originalmente projetado para processos de longa duração,
|
||||
ainda existem muitas bibliotecas e códigos legados que vazam memória.
|
||||
Uma solução alternativa para usar esse tipo de código no modo worker é reiniciar
|
||||
o worker script após processar um certo número de requisições:
|
||||
Como o PHP não foi originalmente projetado para processos de longa duração, ainda existem muitas bibliotecas e códigos legados que vazam memória.
|
||||
Uma solução alternativa para usar esse tipo de código no modo worker é reiniciar o worker script após processar um certo número de requisições:
|
||||
|
||||
O trecho de código de worker anterior permite configurar um número máximo de
|
||||
requisições a serem processadas, definindo uma variável de ambiente chamada
|
||||
`MAX_REQUESTS`.
|
||||
O trecho de código de worker anterior permite configurar um número máximo de requisições a serem processadas, definindo uma variável de ambiente chamada `MAX_REQUESTS`.
|
||||
|
||||
### Reiniciar os workers manualmente
|
||||
### Reiniciar os Workers Manualmente
|
||||
|
||||
Embora seja possível reiniciar os workers
|
||||
[em alterações de arquivo](config.md#monitorando-alteracoes-em-arquivos), também
|
||||
é possível reiniciar todos os workers graciosamente por meio da
|
||||
[API de administração do Caddy](https://caddyserver.com/docs/api).
|
||||
Se o administrador estiver habilitado no seu
|
||||
[Caddyfile](config.md#configuracao-do-caddyfile), você pode executar ping no
|
||||
endpoint de reinicialização com uma simples requisição POST como esta:
|
||||
Embora seja possível reiniciar os workers [em alterações de arquivo](config.md#watching-for-file-changes), também é possível reiniciar todos os workers graciosamente por meio da [API de administração do Caddy](https://caddyserver.com/docs/api). Se o administrador estiver habilitado no seu [Caddyfile](config.md#caddyfile-config), você pode acionar o endpoint de reinicialização com uma simples requisição POST como esta:
|
||||
|
||||
```console
|
||||
curl -X POST http://localhost:2019/frankenphp/workers/restart
|
||||
```
|
||||
|
||||
### Falhas de worker
|
||||
### Falhas de Worker
|
||||
|
||||
Se um worker script travar com um código de saída diferente de zero, o
|
||||
FrankenPHP o reiniciará com uma estratégia de backoff exponencial.
|
||||
Se o worker script permanecer ativo por mais tempo do que o último backoff \* 2,
|
||||
ele não irá penalizar o worker script e reiniciá-lo novamente.
|
||||
No entanto, se o worker script continuar a falhar com um código de saída
|
||||
diferente de zero em um curto período de tempo (por exemplo, com um erro de
|
||||
digitação em um script), o FrankenPHP travará com o erro:
|
||||
`too many consecutive failures` (muitas falhas consecutivas).
|
||||
Se um worker script travar com um código de saída diferente de zero, o FrankenPHP o reiniciará com uma estratégia de backoff exponencial.
|
||||
Se o worker script permanecer ativo por mais tempo do que o último backoff \* 2, ele não irá penalizar o worker script e reiniciá-lo novamente.
|
||||
No entanto, se o worker script continuar a falhar com um código de saída diferente de zero em um curto período de tempo (por exemplo, com um erro de digitação em um script), o FrankenPHP travará com o erro: `too many consecutive failures`.
|
||||
|
||||
O número de falhas consecutivas pode ser configurado no seu
|
||||
[Caddyfile](config.md#caddyfile-config) com a opção `max_consecutive_failures`:
|
||||
O número de falhas consecutivas pode ser configurado no seu [Caddyfile](config.md#caddyfile-config) com a opção `max_consecutive_failures`:
|
||||
|
||||
```caddyfile
|
||||
frankenphp {
|
||||
@@ -183,21 +161,14 @@ frankenphp {
|
||||
}
|
||||
```
|
||||
|
||||
## Comportamento das superglobais
|
||||
## Comportamento das Superglobais
|
||||
|
||||
As
|
||||
[superglobais do PHP](https://www.php.net/manual/pt_BR/language.variables.superglobals.php)
|
||||
(`$_SERVER`, `$_ENV`, `$_GET`...) se comportam da seguinte maneira:
|
||||
As [superglobais do PHP](https://www.php.net/manual/pt_BR/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET`...) se comportam da seguinte maneira:
|
||||
|
||||
- antes da primeira chamada para `frankenphp_handle_request()`, as superglobais
|
||||
contêm valores vinculados ao próprio worker script.
|
||||
- durante e após a chamada para `frankenphp_handle_request()`, as superglobais
|
||||
contêm valores gerados a partir da requisição HTTP processada.
|
||||
Cada chamada para `frankenphp_handle_request()` altera os valores das
|
||||
superglobais.
|
||||
- antes da primeira chamada para `frankenphp_handle_request()`, as superglobais contêm valores vinculados ao próprio worker script.
|
||||
- durante e após a chamada para `frankenphp_handle_request()`, as superglobais contêm valores gerados a partir da requisição HTTP processada. Cada chamada para `frankenphp_handle_request()` altera os valores das superglobais.
|
||||
|
||||
Para acessar as superglobais do worker script dentro do retorno de chamada, você
|
||||
deve copiá-las e importar a cópia para o escopo do retorno de chamada:
|
||||
Para acessar as superglobais do worker script dentro do retorno de chamada, você deve copiá-las e importar a cópia para o escopo do retorno de chamada:
|
||||
|
||||
```php
|
||||
<?php
|
||||
@@ -211,4 +182,3 @@ $handler = static function () use ($workerServer) {
|
||||
};
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
@@ -1,47 +1,80 @@
|
||||
# Конфигурация
|
||||
# Конфигурация
|
||||
|
||||
FrankenPHP, Caddy, а также модули Mercure и Vulcain могут быть настроены с использованием [конфигурационных форматов, поддерживаемых Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
FrankenPHP, Caddy, а также модули [Mercure](mercure.md) и [Vulcain](https://vulcain.rocks) могут быть настроены с использованием [форматов, поддерживаемых Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
|
||||
В [Docker-образах](docker.md) файл `Caddyfile` находится по пути `/etc/frankenphp/Caddyfile`.
|
||||
Статический бинарный файл будет искать `Caddyfile` в директории запуска.
|
||||
Наиболее распространенным форматом является `Caddyfile`, который представляет собой простой, удобочитаемый текстовый формат.
|
||||
По умолчанию FrankenPHP будет искать `Caddyfile` в текущей директории.
|
||||
Вы можете указать пользовательский путь с помощью опции `-c` или `--config`.
|
||||
|
||||
Ниже показан минимальный `Caddyfile` для обслуживания PHP-приложения:
|
||||
|
||||
```caddyfile
|
||||
# Хостнейм, на который будет отвечать сервер
|
||||
localhost
|
||||
|
||||
# Опционально, директория для обслуживания файлов, иначе по умолчанию используется текущая директория
|
||||
#root public/
|
||||
php_server
|
||||
```
|
||||
|
||||
Более продвинутый `Caddyfile`, включающий дополнительные функции и предоставляющий удобные переменные окружения, можно найти [в репозитории FrankenPHP](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile), а также в Docker-образах.
|
||||
|
||||
PHP можно настроить [с помощью файла `php.ini`](https://www.php.net/manual/en/configuration.file.php).
|
||||
|
||||
PHP-интерпретатор будет искать в следующих местах:
|
||||
В зависимости от метода установки, FrankenPHP и PHP-интерпретатор будут искать конфигурационные файлы в местах, описанных ниже.
|
||||
|
||||
Docker:
|
||||
## Docker
|
||||
|
||||
- php.ini: `/usr/local/etc/php/php.ini` По умолчанию php.ini не предоставляется.
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: основной файл конфигурации
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: дополнительные файлы конфигурации, которые загружаются автоматически
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/usr/local/etc/php/php.ini` (по умолчанию `php.ini` не предоставляется)
|
||||
- дополнительные файлы конфигурации: `/usr/local/etc/php/conf.d/*.ini`
|
||||
- расширения php: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
- расширения PHP: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
- Вы должны скопировать официальный шаблон, предоставляемый проектом PHP:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# Для production:
|
||||
# Production:
|
||||
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
|
||||
|
||||
# Или для development:
|
||||
# Или development:
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
```
|
||||
|
||||
Установка FrankenPHP (.rpm или .deb):
|
||||
## RPM и Debian пакеты
|
||||
|
||||
- php.ini: `/etc/frankenphp/php.ini` По умолчанию предоставляется файл php.ini с производственными настройками.
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: основной файл конфигурации
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: дополнительные файлы конфигурации, которые загружаются автоматически
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/etc/php-zts/php.ini` (по умолчанию предоставляется файл `php.ini` с производственными настройками)
|
||||
- дополнительные файлы конфигурации: `/etc/php-zts/conf.d/*.ini`
|
||||
|
||||
## Статический бинарный файл
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- В текущей рабочей директории: `Caddyfile`
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: Директория, в которой выполняется `frankenphp run` или `frankenphp php-server`, затем `/etc/frankenphp/php.ini`
|
||||
- дополнительные файлы конфигурации: `/etc/frankenphp/php.d/*.ini`
|
||||
- расширения php: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
Статический бинарный файл:
|
||||
|
||||
- php.ini: Директория, в которой выполняется `frankenphp run` или `frankenphp php-server`, затем `/etc/frankenphp/php.ini`
|
||||
- дополнительные файлы конфигурации: `/etc/frankenphp/php.d/*.ini`
|
||||
- расширения php: не могут быть загружены
|
||||
- расширения PHP: не могут быть загружены, их следует встраивать в сам бинарный файл
|
||||
- скопируйте один из шаблонов `php.ini-production` или `php.ini-development`, предоставленных [в исходниках PHP](https://github.com/php/php-src/).
|
||||
|
||||
## Конфигурация Caddyfile
|
||||
|
||||
[HTTP-директивы](https://caddyserver.com/docs/caddyfile/concepts#directives) `php_server` или `php` могут быть использованы в блоках сайта для обработки вашего PHP-приложения.
|
||||
[HTTP-директивы](https://caddyserver.com/docs/caddyfile/concepts#directives) `php_server` или `php` могут быть использованы в блоках сайта для обслуживания вашего PHP-приложения.
|
||||
|
||||
Минимальный пример:
|
||||
|
||||
@@ -54,18 +87,23 @@ localhost {
|
||||
}
|
||||
```
|
||||
|
||||
Вы также можете явно настроить FrankenPHP с помощью глобальной опции:
|
||||
[Глобальная опция](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp` может быть использована для настройки FrankenPHP.
|
||||
Вы также можете явно настроить FrankenPHP, используя [глобальную опцию](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Указывает количество потоков PHP. По умолчанию: 2x от числа доступных CPU.
|
||||
num_threads <num_threads> # Устанавливает количество запускаемых потоков PHP. По умолчанию: 2x от числа доступных CPU.
|
||||
max_threads <num_threads> # Ограничивает количество дополнительных потоков PHP, которые могут быть запущены во время выполнения. По умолчанию: num_threads. Может быть установлено в 'auto'.
|
||||
max_wait_time <duration> # Устанавливает максимальное время, в течение которого запрос может ожидать свободный поток PHP до истечения таймаута. По умолчанию: отключено.
|
||||
max_idle_time <duration> # Устанавливает максимальное время бездействия автоматически масштабируемого потока до его деактивации. По умолчанию: 5s.
|
||||
php_ini <key> <value> # Устанавливает директиву php.ini. Может использоваться несколько раз для установки нескольких директив.
|
||||
worker {
|
||||
file <path> # Указывает путь к worker-скрипту.
|
||||
num <num> # Указывает количество потоков PHP. По умолчанию: 2x от числа доступных CPU.
|
||||
env <key> <value> # Устанавливает дополнительную переменную окружения. Можно указать несколько раз для разных переменных.
|
||||
watch <path> # Указывает путь для отслеживания изменений файлов.Можно указать несколько раз для разных путей.
|
||||
file <path> # Устанавливает путь к worker-скрипту.
|
||||
num <num> # Устанавливает количество запускаемых потоков PHP, по умолчанию 2x от числа доступных CPU.
|
||||
env <key> <value> # Устанавливает дополнительную переменную окружения с заданным значением. Может быть указано несколько раз для нескольких переменных окружения.
|
||||
watch <path> # Устанавливает путь для отслеживания изменений файлов. Может быть указано несколько раз для нескольких путей.
|
||||
name <name> # Устанавливает имя worker, используемое в логах и метриках. По умолчанию: абсолютный путь к файлу worker.
|
||||
max_consecutive_failures <num> # Устанавливает максимальное количество последовательных сбоев, после которых worker считается неработоспособным; -1 означает, что worker всегда будет перезапускаться. По умолчанию: 6.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,7 +127,7 @@ localhost {
|
||||
|
||||
```caddyfile
|
||||
app.example.com {
|
||||
root /path/to/app/public
|
||||
root /path/to/app/public
|
||||
php_server {
|
||||
root /path/to/app/public # позволяет лучше кэшировать
|
||||
worker index.php <num>
|
||||
@@ -97,7 +135,7 @@ app.example.com {
|
||||
}
|
||||
|
||||
other.example.com {
|
||||
root /path/to/other/public
|
||||
root /path/to/other/public
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
@@ -107,7 +145,10 @@ other.example.com {
|
||||
# ...
|
||||
```
|
||||
|
||||
Использование директивы `php_server` — это то, что нужно в большинстве случаев. Однако если требуется полный контроль, вы можете использовать более низкоуровневую директиву `php`:
|
||||
Использование директивы `php_server` — это то, что нужно в большинстве случаев,
|
||||
но если требуется полный контроль, вы можете использовать более низкоуровневую директиву `php`.
|
||||
Директива `php` передает все входные данные PHP, не проверяя предварительно,
|
||||
является ли это PHP-файлом или нет. Подробнее об этом читайте на [странице производительности](performance.md#try_files).
|
||||
|
||||
Использование директивы `php_server` эквивалентно следующей конфигурации:
|
||||
|
||||
@@ -136,27 +177,30 @@ route {
|
||||
|
||||
```caddyfile
|
||||
php_server [<matcher>] {
|
||||
root <directory> # Указывает корневую директорию сайта. По умолчанию: директива `root`.
|
||||
split_path <delim...> # Устанавливает подстроки для разделения URI на две части. Первая часть будет использована как имя ресурса (CGI-скрипта), вторая часть — как PATH_INFO. По умолчанию: `.php`.
|
||||
resolve_root_symlink false # Отключает разрешение символьных ссылок для `root` (включено по умолчанию).
|
||||
env <key> <value> # Устанавливает дополнительные переменные окружения. Можно указать несколько раз для разных переменных.
|
||||
root <directory> # Устанавливает корневую папку для сайта. По умолчанию: директива `root`.
|
||||
split_path <delim...> # Устанавливает подстроки для разделения URI на две части. Первая совпадающая подстрока будет использована для разделения "path info" от пути. Первая часть будет дополнена совпадающей подстрокой и будет считаться фактическим именем ресурса (CGI-скрипта). Вторая часть будет установлена как PATH_INFO для использования скриптом. По умолчанию: `.php`.
|
||||
resolve_root_symlink false # Отключает разрешение директории `root` до ее фактического значения путем оценки символической ссылки, если таковая существует (включено по умолчанию).
|
||||
env <key> <value> # Устанавливает дополнительную переменную окружения с заданным значением. Может быть указано несколько раз для нескольких переменных окружения.
|
||||
file_server off # Отключает встроенную директиву file_server.
|
||||
worker { # Создает worker, специфичный для этого сервера. Можно указать несколько раз для разных workers.
|
||||
file <path> # Указывает путь к worker-скрипту, может быть относительным к корню php_server
|
||||
num <num> # Указывает количество потоков PHP. По умолчанию: 2x от числа доступных CPU.
|
||||
worker { # Создает worker, специфичный для этого сервера. Может быть указано несколько раз для нескольких workers.
|
||||
file <path> # Устанавливает путь к worker-скрипту, может быть относительным к корню php_server
|
||||
num <num> # Устанавливает количество запускаемых потоков PHP, по умолчанию 2x от числа доступных CPU.
|
||||
name <name> # Устанавливает имя для worker, используемое в логах и метриках. По умолчанию: абсолютный путь к файлу worker. Всегда начинается с m# при определении в блоке php_server.
|
||||
watch <path> # Указывает путь для отслеживания изменений файлов. Можно указать несколько раз для разных путей.
|
||||
env <key> <value> # Устанавливает дополнительную переменную окружения. Можно указать несколько раз для разных переменных. Переменные окружения для этого worker также наследуются от родительского php_server, но могут быть переопределены здесь.
|
||||
watch <path> # Устанавливает путь для отслеживания изменений файлов. Может быть указано несколько раз для нескольких путей.
|
||||
env <key> <value> # Устанавливает дополнительную переменную окружения с заданным значением. Может быть указано несколько раз для нескольких переменных окружения. Переменные окружения для этого worker также наследуются от родительского php_server, но могут быть переопределены здесь.
|
||||
match <path> # сопоставляет worker с шаблоном пути. Переопределяет try_files и может использоваться только в директиве php_server.
|
||||
}
|
||||
worker <other_file> <num> # Также можно использовать краткую форму как в глобальном блоке frankenphp.
|
||||
worker <other_file> <num> # Также можно использовать краткую форму, как и в глобальном блоке frankenphp.
|
||||
}
|
||||
```
|
||||
|
||||
### Отслеживание изменений файлов
|
||||
|
||||
Поскольку workers запускают ваше приложение только один раз и держат его в памяти, изменения в PHP-файлах не будут применяться сразу.
|
||||
Поскольку workers запускают ваше приложение только один раз и держат его в памяти, любые изменения
|
||||
в ваших PHP-файлах не будут отражены немедленно.
|
||||
|
||||
Для разработки можно настроить перезапуск workers при изменении файлов с помощью директивы `watch`:
|
||||
Вместо этого workers могут быть перезапущены при изменении файлов с помощью директивы `watch`.
|
||||
Это полезно для сред разработки.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
@@ -169,8 +213,12 @@ php_server [<matcher>] {
|
||||
}
|
||||
```
|
||||
|
||||
Если директория для `watch` не указана, по умолчанию будет использоваться путь `./**/*.{php,yaml,yml,twig,env}`,
|
||||
который отслеживает все файлы с расширениями `.php`, `.yaml`, `.yml`, `.twig` и `.env` в директории, где был запущен процесс FrankenPHP, и во всех её поддиректориях. Вы также можете указать одну или несколько директорий с использованием [шаблона имён файлов](https://pkg.go.dev/path/filepath#Match):
|
||||
Эта функция часто используется в сочетании с [горячей перезагрузкой](hot-reload.md).
|
||||
|
||||
Если директория для `watch` не указана, по умолчанию будет использоваться `./**/*.{env,php,twig,yaml,yml}`,
|
||||
который отслеживает все файлы с расширениями `.env`, `.php`, `.twig`, `.yaml` и `.yml` в директории и поддиректориях,
|
||||
где был запущен процесс FrankenPHP. Вы также можете указать одну или несколько директорий с использованием
|
||||
[шаблона имён файлов оболочки](https://pkg.go.dev/path/filepath#Match):
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
@@ -187,17 +235,84 @@ php_server [<matcher>] {
|
||||
```
|
||||
|
||||
- Шаблон `**` указывает на рекурсивное отслеживание.
|
||||
- Директории могут быть указаны относительно директории запуска FrankenPHP.
|
||||
- Директории также могут быть относительными (к месту запуска процесса FrankenPHP).
|
||||
- Если у вас определено несколько workers, все они будут перезапущены при изменении файлов.
|
||||
- Избегайте отслеживания файлов, создаваемых во время выполнения (например, логов), так как это может вызвать нежелательные перезапуски.
|
||||
- Будьте осторожны с отслеживанием файлов, создаваемых во время выполнения (например, логов), так как это может вызвать нежелательные перезапуски workers.
|
||||
|
||||
Механизм отслеживания файлов основан на [e-dant/watcher](https://github.com/e-dant/watcher).
|
||||
|
||||
## Сопоставление Worker с путем
|
||||
|
||||
В традиционных PHP-приложениях скрипты всегда размещаются в публичной директории.
|
||||
Это также верно для worker-скриптов, которые обрабатываются как любые другие PHP-скрипты.
|
||||
Если вы хотите разместить worker-скрипт вне публичной директории, вы можете сделать это с помощью директивы `match`.
|
||||
|
||||
Директива `match` является оптимизированной альтернативой `try_files`, доступной только внутри `php_server` и `php`.
|
||||
Следующий пример всегда будет обслуживать файл в публичной директории, если он присутствует,
|
||||
и в противном случае будет перенаправлять запрос worker, соответствующему шаблону пути.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
php_server {
|
||||
worker {
|
||||
file /path/to/worker.php # файл может находиться вне публичного пути
|
||||
match /api/* # все запросы, начинающиеся с /api/, будут обрабатываться этим worker
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
Следующие переменные окружения могут быть использованы для внедрения директив Caddy в `Caddyfile` без его изменения:
|
||||
|
||||
- `SERVER_NAME`: изменение [адресов для прослушивания](https://caddyserver.com/docs/caddyfile/concepts#addresses); предоставленные хостнеймы также будут использованы для генерации TLS-сертификата
|
||||
- `SERVER_ROOT`: изменение корневой директории сайта, по умолчанию `public/`
|
||||
- `CADDY_GLOBAL_OPTIONS`: внедрение [глобальных опций](https://caddyserver.com/docs/caddyfile/options)
|
||||
- `FRANKENPHP_CONFIG`: внедрение конфигурации под директиву `frankenphp`
|
||||
|
||||
Как и для FPM и CLI SAPIs, переменные окружения по умолчанию доступны в суперглобальной переменной `$_SERVER`.
|
||||
|
||||
Значение `S` в [директиве PHP `variables_order`](https://www.php.net/manual/en/ini.core.php#ini.variables-order) всегда эквивалентно `ES`, независимо от того, где расположена `E` в этой директиве.
|
||||
|
||||
## Конфигурация PHP
|
||||
|
||||
Для загрузки [дополнительных конфигурационных файлов PHP](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan) можно использовать переменную окружения `PHP_INI_SCAN_DIR`.
|
||||
Если она установлена, PHP загрузит все файлы с расширением `.ini`, находящиеся в указанных директориях.
|
||||
|
||||
Вы также можете изменить конфигурацию PHP с помощью директивы `php_ini` в `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
php_ini memory_limit 256M
|
||||
|
||||
# или
|
||||
|
||||
php_ini {
|
||||
memory_limit 256M
|
||||
max_execution_time 15
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Отключение HTTPS
|
||||
|
||||
По умолчанию FrankenPHP автоматически включает HTTPS для всех хостнеймов, включая `localhost`.
|
||||
Если вы хотите отключить HTTPS (например, в среде разработки), вы можете установить переменную окружения `SERVER_NAME` в `http://` или `:80`:
|
||||
|
||||
В качестве альтернативы вы можете использовать все другие методы, описанные в [документации Caddy](https://caddyserver.com/docs/automatic-https#activation).
|
||||
|
||||
Если вы хотите использовать HTTPS с IP-адресом `127.0.0.1` вместо хостнейма `localhost`, пожалуйста, ознакомьтесь с разделом [известные проблемы](known-issues.md#using-https127001-with-docker).
|
||||
|
||||
### Полный дуплекс (HTTP/1)
|
||||
|
||||
При использовании HTTP/1.x можно включить режим полного дуплекса, чтобы разрешить запись ответа до завершения чтения тела запроса (например, для WebSocket, Server-Sent Events и т.д.).
|
||||
При использовании HTTP/1.x может быть желательно включить режим полного дуплекса, чтобы разрешить запись ответа до завершения чтения всего тела запроса. (например: [Mercure](mercure.md), WebSocket, Server-Sent Events и т.д.)
|
||||
|
||||
Эта опция включается вручную и должна быть добавлена в глобальные настройки `Caddyfile`:
|
||||
Это опция, которую необходимо добавить в глобальные настройки в `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
@@ -210,7 +325,7 @@ php_server [<matcher>] {
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Включение этой опции может привести к зависанию устаревших HTTP/1.x клиентов, которые не поддерживают полный дуплекс.
|
||||
> Настройка также доступна через переменную окружения `CADDY_GLOBAL_OPTIONS`:
|
||||
> Это также можно настроить с помощью переменной окружения `CADDY_GLOBAL_OPTIONS`:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
@@ -220,23 +335,6 @@ CADDY_GLOBAL_OPTIONS="servers {
|
||||
|
||||
Дополнительную информацию об этой настройке можно найти в [документации Caddy](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
Следующие переменные окружения могут быть использованы для добавления директив в `Caddyfile` без его изменения:
|
||||
|
||||
- `SERVER_NAME`: изменение [адресов для прослушивания](https://caddyserver.com/docs/caddyfile/concepts#addresses); предоставленные хостнеймы также будут использованы для генерации TLS-сертификата.
|
||||
- `CADDY_GLOBAL_OPTIONS`: добавление [глобальных опций](https://caddyserver.com/docs/caddyfile/options).
|
||||
- `FRANKENPHP_CONFIG`: добавление конфигурации в директиву `frankenphp`.
|
||||
|
||||
Как и для FPM и CLI SAPIs, переменные окружения по умолчанию доступны в суперглобальной переменной `$_SERVER`.
|
||||
|
||||
Значение `S` в [директиве PHP `variables_order`](https://www.php.net/manual/en/ini.core.php#ini.variables-order) всегда эквивалентно `ES`, независимо от того, где расположена `E` в этой директиве.
|
||||
|
||||
## Конфигурация PHP
|
||||
|
||||
Для загрузки [дополнительных конфигурационных файлов PHP](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan) можно использовать переменную окружения `PHP_INI_SCAN_DIR`.
|
||||
Если она установлена, PHP загрузит все файлы с расширением `.ini`, находящиеся в указанных директориях.
|
||||
|
||||
## Включение режима отладки
|
||||
|
||||
При использовании Docker-образа установите переменную окружения `CADDY_GLOBAL_OPTIONS` в `debug`, чтобы включить режим отладки:
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
# Создание кастомных Docker-образов
|
||||
|
||||
[Docker-образы FrankenPHP](https://hub.docker.com/r/dunglas/frankenphp) основаны на [официальных PHP-образах](https://hub.docker.com/_/php/). Доступны варианты для Debian и Alpine Linux для популярных архитектур. Рекомендуется использовать Debian-варианты.
|
||||
[Docker-образы FrankenPHP](https://hub.docker.com/r/dunglas/frankenphp) основаны на [официальных PHP-образах](https://hub.docker.com/_/php/).
|
||||
Доступны варианты для Debian и Alpine Linux для популярных архитектур.
|
||||
Рекомендуется использовать Debian-варианты.
|
||||
|
||||
Доступны версии для PHP 8.2, 8.3, 8.4 и 8.5.
|
||||
|
||||
Теги следуют следующему шаблону: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`.
|
||||
|
||||
- `<frankenphp-version>` и `<php-version>` — версии FrankenPHP и PHP соответственно: от основных (например, `1`) до минорных (например, `1.2`) и патч-версий (например, `1.2.3`).
|
||||
- `<frankenphp-version>` и `<php-version>` — версии FrankenPHP и PHP соответственно, начиная с мажорных (например, `1`), минорных (например, `1.2`) и заканчивая патч-версиями (например, `1.2.3`).
|
||||
- `<os>` может быть `trixie` (для Debian Trixie), `bookworm` (для Debian Bookworm) или `alpine` (для последней стабильной версии Alpine).
|
||||
|
||||
[Просмотреть доступные теги](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
@@ -28,9 +30,13 @@ docker build -t my-php-app .
|
||||
docker run -it --rm --name my-running-app my-php-app
|
||||
```
|
||||
|
||||
## Как настроить конфигурацию
|
||||
|
||||
Для удобства в образе предоставлен [Caddyfile по умолчанию](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile), содержащий полезные переменные окружения.
|
||||
|
||||
## Как установить дополнительные PHP-расширения
|
||||
|
||||
Скрипт [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) включён в базовый образ. Установка дополнительных PHP-расширений осуществляется просто:
|
||||
Скрипт [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) предоставляется в базовом образе. Установка дополнительных PHP-расширений осуществляется просто:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
@@ -78,16 +84,17 @@ FROM dunglas/frankenphp AS runner
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
```
|
||||
|
||||
Образ `builder`, предоставляемый FrankenPHP, содержит скомпилированную версию `libphp`.
|
||||
Образ `builder`, предоставляемый FrankenPHP, содержит скомпилированную версию `libphp`.
|
||||
[Образы builder](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) доступны для всех версий FrankenPHP и PHP, как для Debian, так и для Alpine.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Если вы используете Alpine Linux и Symfony, возможно, потребуется [увеличить размер стека](compile.md#использование-xcaddy).
|
||||
> Если вы используете Alpine Linux и Symfony,
|
||||
> возможно, потребуется [увеличить размер стека](compile.md#использование-xcaddy).
|
||||
|
||||
## Активировать worker режим по умолчанию
|
||||
|
||||
Установите переменную окружения `FRANKENPHP_CONFIG`, чтобы запускать FrankenPHP с Worker-скриптом:
|
||||
Установите переменную окружения `FRANKENPHP_CONFIG`, чтобы запускать FrankenPHP с рабочим скриптом:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
@@ -107,7 +114,7 @@ docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-a
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Опция `--tty` позволяет видеть удобочитаемые логи вместо JSON-формата.
|
||||
> Опция `--tty` позволяет получать удобочитаемые логи вместо JSON-формата.
|
||||
|
||||
С использованием Docker Compose:
|
||||
|
||||
@@ -119,7 +126,7 @@ services:
|
||||
image: dunglas/frankenphp
|
||||
# раскомментируйте следующую строку, если хотите использовать собственный Dockerfile
|
||||
#build: .
|
||||
# раскомментируйте следующую строку, если вы запускаете это в продакшн среде
|
||||
# раскомментируйте следующую строку для работы в production-окружении
|
||||
# restart: always
|
||||
ports:
|
||||
- "80:80" # HTTP
|
||||
@@ -129,10 +136,10 @@ services:
|
||||
- ./:/app/public
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
# закомментируйте следующую строку в продакшн среде, она позволяет получать удобочитаемые логи в режиме разработки
|
||||
# закомментируйте следующую строку в production — в dev она обеспечивает удобочитаемые логи
|
||||
tty: true
|
||||
|
||||
# Томы, необходимые для сертификатов и конфигурации Caddy
|
||||
# Тома, необходимые для сертификатов и конфигурации Caddy
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
@@ -140,7 +147,7 @@ volumes:
|
||||
|
||||
## Запуск под обычным пользователем
|
||||
|
||||
FrankenPHP поддерживает запуск под обычным пользователем в Docker.
|
||||
FrankenPHP может запускаться под обычным пользователем в Docker.
|
||||
|
||||
Пример `Dockerfile` для этого:
|
||||
|
||||
@@ -152,10 +159,10 @@ ARG USER=appuser
|
||||
RUN \
|
||||
# Для дистрибутивов на основе Alpine используйте "adduser -D ${USER}"
|
||||
useradd ${USER}; \
|
||||
# Добавьте возможность привязываться к портам 80 и 443
|
||||
# Добавить возможность привязки к портам 80 и 443
|
||||
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
|
||||
# Дайте права на запись для /data/caddy и /config/caddy
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
# Предоставить доступ на запись в /config/caddy и /data/caddy
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
@@ -172,30 +179,102 @@ FROM dunglas/frankenphp
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Для Alpine-дистрибутивов используйте команду "adduser -D ${USER}"
|
||||
# Для дистрибутивов на основе Alpine используйте "adduser -D ${USER}"
|
||||
useradd ${USER}; \
|
||||
# Удалите стандартные возможности
|
||||
# Удалить стандартные возможности
|
||||
setcap -r /usr/local/bin/frankenphp; \
|
||||
# Дайте права на запись для /data/caddy и /config/caddy
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
# Предоставить доступ на запись в /config/caddy и /data/caddy
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
Затем установите переменную окружения `SERVER_NAME`, чтобы использовать непривилегированный порт.
|
||||
Затем установите переменную окружения `SERVER_NAME`, чтобы использовать непривилегированный порт.
|
||||
Пример: `:8000`.
|
||||
|
||||
## Обновления
|
||||
|
||||
Docker-образы обновляются:
|
||||
Docker-образы собираются:
|
||||
|
||||
- при выпуске новой версии;
|
||||
- ежедневно в 4 утра UTC, если доступны новые версии официальных PHP-образов.
|
||||
|
||||
## Укрепление образов
|
||||
|
||||
Чтобы ещё больше уменьшить поверхность атаки и размер ваших Docker-образов FrankenPHP, также возможно создавать их на основе [Google Distroless](https://github.com/GoogleContainerTools/distroless) или [Docker Hardened](https://www.docker.com/products/hardened-images) образов.
|
||||
|
||||
> [!WARNING]
|
||||
> Эти минимальные базовые образы не включают оболочку или менеджер пакетов, что значительно усложняет отладку.
|
||||
> Поэтому они рекомендуются только для продакшена, если безопасность является высоким приоритетом.
|
||||
|
||||
При добавлении дополнительных PHP-расширений вам потребуется промежуточная стадия сборки:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp AS builder
|
||||
|
||||
# Добавьте дополнительные PHP-расширения здесь
|
||||
RUN install-php-extensions pdo_mysql pdo_pgsql #...
|
||||
|
||||
# Скопировать общие библиотеки frankenphp и всех установленных расширений во временное место
|
||||
# Этот шаг можно также выполнить вручную, проанализировав вывод ldd для бинарного файла frankenphp и каждого файла расширения .so
|
||||
RUN apt-get update && apt-get install -y libtree && \
|
||||
EXT_DIR="$(php -r 'echo ini_get("extension_dir");')" && \
|
||||
FRANKENPHP_BIN="$(which frankenphp)"; \
|
||||
LIBS_TMP_DIR="/tmp/libs"; \
|
||||
mkdir -p "$LIBS_TMP_DIR"; \
|
||||
for target in "$FRANKENPHP_BIN" $(find "$EXT_DIR" -maxdepth 2 -type f -name "*.so"); do \
|
||||
libtree -pv "$target" | sed 's/.*── \(.*\) \[.*/\1/' | grep -v "^$target" | while IFS= read -r lib; do \
|
||||
[ -z "$lib" ] && continue; \
|
||||
base=$(basename "$lib"); \
|
||||
destfile="$LIBS_TMP_DIR/$base"; \
|
||||
if [ ! -f "$destfile" ]; then \
|
||||
cp "$lib" "$destfile"; \
|
||||
fi; \
|
||||
done; \
|
||||
done
|
||||
|
||||
|
||||
# Базовый образ Distroless Debian — убедитесь, что версия Debian совпадает с базовым образом
|
||||
FROM gcr.io/distroless/base-debian13
|
||||
# Альтернатива: Docker Hardened Image
|
||||
# FROM dhi.io/debian:13
|
||||
|
||||
# Путь к приложению и Caddyfile для копирования в контейнер
|
||||
ARG PATH_TO_APP="."
|
||||
ARG PATH_TO_CADDYFILE="./Caddyfile"
|
||||
|
||||
# Скопировать приложение в /app
|
||||
# Для дополнительного усиления убедитесь, что только записываемые пути принадлежат пользователю nonroot
|
||||
COPY --chown=nonroot:nonroot "$PATH_TO_APP" /app
|
||||
COPY "$PATH_TO_CADDYFILE" /etc/caddy/Caddyfile
|
||||
|
||||
# Скопировать frankenphp и необходимые библиотеки
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
|
||||
COPY --from=builder /tmp/libs /usr/lib
|
||||
|
||||
# Скопировать конфигурационные файлы php.ini
|
||||
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
|
||||
COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
|
||||
|
||||
# Директории данных Caddy — должны быть доступны для записи пользователю nonroot даже в файловой системе только для чтения
|
||||
ENV XDG_CONFIG_HOME=/config \
|
||||
XDG_DATA_HOME=/data
|
||||
COPY --from=builder --chown=nonroot:nonroot /data/caddy /data/caddy
|
||||
COPY --from=builder --chown=nonroot:nonroot /config/caddy /config/caddy
|
||||
|
||||
USER nonroot
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Точка входа для запуска frankenphp с указанным Caddyfile
|
||||
ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "-c", "/etc/caddy/Caddyfile"]
|
||||
```
|
||||
|
||||
## Версии для разработки
|
||||
|
||||
Версии для разработки доступны в Docker-репозитории [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev).
|
||||
Сборка запускается автоматически при каждом коммите в основную ветку GitHub-репозитория
|
||||
Версии для разработки доступны в Docker-репозитории [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev).
|
||||
Сборка запускается автоматически при каждом коммите в основную ветку GitHub-репозитория.
|
||||
|
||||
Теги с префиксом `latest*` указывают на актуальное состояние ветки `main`.
|
||||
Теги `latest*` указывают на голову ветки `main`.
|
||||
Также доступны теги в формате `sha-<git-commit-hash>`.
|
||||
|
||||
172
docs/ru/extension-workers.md
Normal file
172
docs/ru/extension-workers.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Расширение Workers
|
||||
|
||||
Расширение Workers позволяет вашему [расширению FrankenPHP](https://frankenphp.dev/docs/extensions/) управлять выделенным пулом PHP-потоков для выполнения фоновых задач, обработки асинхронных событий или реализации пользовательских протоколов. Полезно для систем очередей, слушателей событий, планировщиков и т. д.
|
||||
|
||||
## Регистрация Worker-а
|
||||
|
||||
### Статическая регистрация
|
||||
|
||||
Если вам не нужно делать Worker-а настраиваемым пользователем (фиксированный путь к скрипту, фиксированное количество потоков), вы можете просто зарегистрировать Worker в функции `init()`.
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"github.com/dunglas/frankenphp"
|
||||
"github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
// Глобальный дескриптор для связи с пулом Worker-ов
|
||||
var worker frankenphp.Workers
|
||||
|
||||
func init() {
|
||||
// Зарегистрировать Worker при загрузке модуля.
|
||||
worker = caddy.RegisterWorkers(
|
||||
"my-internal-worker", // Уникальное имя
|
||||
"worker.php", // Путь к скрипту (относительный или абсолютный)
|
||||
2, // Фиксированное количество потоков
|
||||
// Дополнительные хуки жизненного цикла
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
// Глобальная логика настройки...
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### В модуле Caddy (настраивается пользователем)
|
||||
|
||||
Если вы планируете делиться своим расширением (например, универсальной очередью или слушателем событий), вам следует обернуть его в модуль Caddy. Это позволит пользователям настраивать путь к скрипту и количество потоков через свой `Caddyfile`. Это требует реализации интерфейса `caddy.Provisioner` и парсинга Caddyfile ([см. пример](https://github.com/dunglas/frankenphp-queue/blob/989120d394d66dd6c8e2101cac73dd622fade334/caddy.go)).
|
||||
|
||||
### В чистом Go-приложении (встраивание)
|
||||
|
||||
Если вы [встраиваете FrankenPHP в стандартное Go-приложение без Caddy](https://pkg.go.dev/github.com/dunglas/frankenphp#example-ServeHTTP), вы можете зарегистрировать Worker-ы расширения, используя `frankenphp.WithExtensionWorkers` при инициализации опций.
|
||||
|
||||
## Взаимодействие с Worker-ами
|
||||
|
||||
Как только пул Worker-ов активен, вы можете отправлять ему задачи. Это можно сделать внутри [нативных функций, экспортированных в PHP](https://frankenphp.dev/docs/extensions/#writing-the-extension), или из любой Go-логики, такой как планировщик cron, слушатель событий (MQTT, Kafka) или любая другая горутина.
|
||||
|
||||
### Безголовый режим: `SendMessage`
|
||||
|
||||
Используйте `SendMessage` для прямой передачи необработанных данных вашему скрипту Worker-а. Это идеально подходит для очередей или простых команд.
|
||||
|
||||
#### Пример: Расширение асинхронной очереди
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_queue_push(mixed $data): bool
|
||||
func my_queue_push(data *C.zval) bool {
|
||||
// 1. Убедитесь, что Worker готов
|
||||
if worker == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. Отправить фоновому Worker-у
|
||||
_, err := worker.SendMessage(
|
||||
context.Background(), // Стандартный Go-контекст
|
||||
unsafe.Pointer(data), // Данные для передачи Worker-у
|
||||
nil, // Опциональный http.ResponseWriter
|
||||
)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
```
|
||||
|
||||
### Эмуляция HTTP: `SendRequest`
|
||||
|
||||
Используйте `SendRequest`, если ваше расширение должно вызвать PHP-скрипт, который ожидает стандартную веб-среду (заполнение `$_SERVER`, `$_GET` и т. д.).
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_worker_http_request(string $path): string
|
||||
func my_worker_http_request(path *C.zend_string) unsafe.Pointer {
|
||||
// 1. Подготовьте запрос и рекордер
|
||||
url := frankenphp.GoString(unsafe.Pointer(path))
|
||||
req, _ := http.NewRequest("GET", url, http.NoBody)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// 2. Отправить Worker-у
|
||||
if err := worker.SendRequest(rr, req); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3. Вернуть захваченный ответ
|
||||
return frankenphp.PHPString(rr.Body.String(), false)
|
||||
}
|
||||
```
|
||||
|
||||
## Скрипт Worker-а
|
||||
|
||||
PHP-скрипт Worker-а выполняется в цикле и может обрабатывать как необработанные сообщения, так и HTTP-запросы.
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Обрабатывать как необработанные сообщения, так и HTTP-запросы в одном цикле
|
||||
$handler = function ($payload = null) {
|
||||
// Случай 1: Режим сообщений
|
||||
if ($payload !== null) {
|
||||
return "Received payload: " . $payload;
|
||||
}
|
||||
|
||||
// Случай 2: Режим HTTP (заполняются стандартные суперглобальные переменные PHP)
|
||||
echo "Hello from page: " . $_SERVER['REQUEST_URI'];
|
||||
};
|
||||
|
||||
while (frankenphp_handle_request($handler)) {
|
||||
gc_collect_cycles();
|
||||
}
|
||||
```
|
||||
|
||||
## Хуки жизненного цикла
|
||||
|
||||
FrankenPHP предоставляет хуки для выполнения Go-кода в определенные моменты жизненного цикла.
|
||||
|
||||
| Тип хука | Имя опции | Подпись | Контекст и вариант использования |
|
||||
| :-------- | :--------------------------- | :------------------- | :-------------------------------------------------------------------- |
|
||||
| **Сервер** | `WithWorkerOnServerStartup` | `func()` | Глобальная настройка. Выполняется **один раз**. Пример: Подключение к NATS/Redis. |
|
||||
| **Сервер** | `WithWorkerOnServerShutdown` | `func()` | Глобальная очистка. Выполняется **один раз**. Пример: Закрытие общих соединений. |
|
||||
| **Поток** | `WithWorkerOnReady` | `func(threadID int)` | Настройка для каждого потока. Вызывается при запуске потока. Получает ID потока. |
|
||||
| **Поток** | `WithWorkerOnShutdown` | `func(threadID int)` | Очистка для каждого потока. Получает ID потока. |
|
||||
|
||||
### Пример
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dunglas/frankenphp"
|
||||
frankenphpCaddy "github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
workerHandle = frankenphpCaddy.RegisterWorkers(
|
||||
"my-worker", "worker.php", 2,
|
||||
|
||||
// Запуск сервера (Глобальный)
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
fmt.Println("Extension: Server starting up...")
|
||||
}),
|
||||
|
||||
// Поток готов (Для каждого потока)
|
||||
// Примечание: Функция принимает целое число, представляющее ID потока
|
||||
frankenphp.WithWorkerOnReady(func(id int) {
|
||||
fmt.Printf("Extension: Worker thread #%d is ready.\n", id)
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -1,52 +1,50 @@
|
||||
# Производительность
|
||||
|
||||
По умолчанию FrankenPHP предлагает хороший баланс между производительностью и удобством использования.
|
||||
Однако, используя подходящую конфигурацию, можно существенно улучшить производительность.
|
||||
По умолчанию FrankenPHP стремится предложить хороший компромисс между производительностью и простотой использования. Однако, используя подходящую конфигурацию, можно существенно улучшить производительность.
|
||||
|
||||
## Количество потоков и worker-скриптов
|
||||
## Количество потоков и воркеров
|
||||
|
||||
По умолчанию FrankenPHP запускает потоков и worker-скриптов (в worker режиме) вдвое больше, чем количество доступных процессорных ядер.
|
||||
По умолчанию FrankenPHP запускает в 2 раза больше потоков и воркеров (в режиме воркера), чем доступное количество ядер процессора.
|
||||
|
||||
Оптимальные значения зависят от структуры вашего приложения, его функциональности и аппаратного обеспечения.
|
||||
Мы настоятельно рекомендуем изменить эти значения.
|
||||
Подходящие значения сильно зависят от того, как написано ваше приложение, что оно делает, и от вашего оборудования. Мы настоятельно рекомендуем изменить эти значения. Для лучшей стабильности системы рекомендуется, чтобы `num_threads` x `memory_limit` < `available_memory`.
|
||||
|
||||
Чтобы найти подходящие параметры, лучше всего провести нагрузочные тесты, имитирующие реальный трафик.
|
||||
Хорошими инструментами для этого являются [k6](https://k6.io) и [Gatling](https://gatling.io).
|
||||
Чтобы найти подходящие значения, лучше всего провести нагрузочные тесты, имитирующие реальный трафик. [k6](https://k6.io) и [Gatling](https://gatling.io) являются хорошими инструментами для этого.
|
||||
|
||||
Чтобы настроить количество потоков, используйте опцию `num_threads` в директивах `php_server` и `php`.
|
||||
Для изменения количества worker-скриптов используйте опцию `num` в секции `worker` директивы `frankenphp`.
|
||||
Чтобы настроить количество потоков, используйте опцию `num_threads` директив `php_server` и `php`. Для изменения количества воркеров используйте опцию `num` секции `worker` директивы `frankenphp`.
|
||||
|
||||
## Worker режим
|
||||
### `max_threads`
|
||||
|
||||
Включение [Worker режима](worker.md) значительно улучшает производительность,
|
||||
но ваше приложение должно быть адаптировано для совместимости с этим режимом:
|
||||
необходимо создать worker-скрипт и убедиться, что приложение не имеет утечек памяти.
|
||||
Хотя всегда лучше точно знать, как будет выглядеть ваш трафик, реальные приложения, как правило, более непредсказуемы. [Конфигурация](config.md#конфигурация-caddyfile) `max_threads` позволяет FrankenPHP автоматически создавать дополнительные потоки во время выполнения до указанного предела. `max_threads` может помочь вам определить, сколько потоков требуется для обработки вашего трафика, и может сделать сервер более устойчивым к скачкам задержки. Если установлено значение `auto`, лимит будет оценен на основе `memory_limit` в вашем `php.ini`. Если это невозможно, `auto` вместо этого будет по умолчанию равно 2x `num_threads`. Имейте в виду, что `auto` может сильно недооценивать необходимое количество потоков. `max_threads` похожа на [pm.max_children](https://www.php.net/manual/en/install.fpm.configuration.php#pm.max-children) в PHP FPM. Основное отличие состоит в том, что FrankenPHP использует потоки вместо процессов и автоматически распределяет их между различными скриптами воркеров и 'классическим режимом' по мере необходимости.
|
||||
|
||||
## Режим воркера
|
||||
|
||||
Включение [режима воркера](worker.md) значительно улучшает производительность, но ваше приложение должно быть адаптировано для совместимости с этим режимом: необходимо создать скрипт воркера и убедиться, что приложение не имеет утечек памяти.
|
||||
|
||||
## Избегайте использования musl
|
||||
|
||||
Статические бинарники, которые мы предоставляем, а также Alpine Linux-вариант официальных Docker-образов используют [библиотеку musl libc](https://musl.libc.org).
|
||||
Вариант официальных Docker-образов для Alpine Linux и предоставляемые нами бинарные файлы по умолчанию используют [библиотеку musl libc](https://musl.libc.org).
|
||||
|
||||
Известно, что PHP [значительно медленнее работает](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381) с этой библиотекой по сравнению с традиционной GNU libc, особенно при компиляции в ZTS режиме (потокобезопасный режим), который требуется для FrankenPHP.
|
||||
Известно, что PHP [работает медленнее](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381) при использовании этой альтернативной библиотеки C вместо традиционной библиотеки GNU, особенно при компиляции в режиме ZTS (потокобезопасном режиме), который требуется для FrankenPHP. Разница может быть существенной в сильно многопоточной среде.
|
||||
|
||||
Кроме того, [некоторые ошибки проявляются исключительно при использовании musl](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl).
|
||||
|
||||
В производственной среде настоятельно рекомендуется использовать glibc.
|
||||
В производственных средах мы рекомендуем использовать FrankenPHP, скомпилированный с glibc и с соответствующим уровнем оптимизации.
|
||||
|
||||
Это можно сделать, используя Debian Docker-образы (по умолчанию) и [компилируя FrankenPHP из исходников](compile.md).
|
||||
Этого можно достичь, используя Docker-образы Debian, используя [пакеты .deb, .rpm или .apk от наших мейнтейнеров](https://pkgs.henderkes.com) или [компилируя FrankenPHP из исходников](compile.md).
|
||||
|
||||
В качестве альтернативы мы предоставляем статические бинарники, скомпилированные с [аллокатором mimalloc](https://github.com/microsoft/mimalloc), что делает FrankenPHP+musl быстрее (но всё же медленнее, чем FrankenPHP+glibc).
|
||||
Для более компактных или безопасных контейнеров вы можете рассмотреть [усиленный образ Debian](docker.md#hardening-images) вместо Alpine.
|
||||
|
||||
## Настройка среды выполнения Go
|
||||
|
||||
FrankenPHP написан на языке Go.
|
||||
|
||||
В большинстве случаев среда выполнения Go не требует особой настройки, но в некоторых ситуациях специфическая конфигурация может улучшить производительность.
|
||||
В целом, среда выполнения Go не требует какой-либо особой настройки, но при определенных обстоятельствах специфическая конфигурация улучшает производительность.
|
||||
|
||||
Рекомендуется установить переменную окружения `GODEBUG` в значение `cgocheck=0` (по умолчанию в Docker-образах FrankenPHP).
|
||||
Вероятно, вы захотите установить переменную окружения `GODEBUG` в значение `cgocheck=0` (по умолчанию в Docker-образах FrankenPHP).
|
||||
|
||||
Если вы запускаете FrankenPHP в контейнерах (Docker, Kubernetes, LXC и т.д.) и ограничиваете доступную память, установите переменную окружения `GOMEMLIMIT` в значение доступного объёма памяти.
|
||||
Если вы запускаете FrankenPHP в контейнерах (Docker, Kubernetes, LXC...) и ограничиваете память, доступную для контейнеров, установите переменную окружения `GOMEMLIMIT` в значение доступного объёма памяти.
|
||||
|
||||
Для более детальной информации ознакомьтесь с [документацией Go по этой теме](https://pkg.go.dev/runtime#hdr-Environment_Variables).
|
||||
Для получения более подробной информации [страница документации Go, посвященная этой теме](https://pkg.go.dev/runtime#hdr-Environment_Variables), обязательна к прочтению, чтобы максимально эффективно использовать среду выполнения.
|
||||
|
||||
## `file_server`
|
||||
|
||||
@@ -60,17 +58,60 @@ php_server {
|
||||
}
|
||||
```
|
||||
|
||||
## `try_files`
|
||||
|
||||
Помимо статических файлов и файлов PHP, `php_server` также попытается обслужить индекс вашего приложения и индексные файлы директорий (`/path/` -> `/path/index.php`). Если вам не нужны индексы директорий, вы можете отключить их, явно определив `try_files` следующим образом:
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
root /root/to/your/app # явное указание корневой директории здесь обеспечивает лучшее кеширование
|
||||
}
|
||||
```
|
||||
|
||||
Это может значительно сократить количество ненужных файловых операций. Эквивалент предыдущей конфигурации для воркера будет следующим:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
php_server { # используйте "php" вместо "php_server", если вам вообще не нужен файловый сервер
|
||||
root /root/to/your/app
|
||||
worker /path/to/worker.php {
|
||||
match * # отправить все запросы напрямую воркеру
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Альтернативный подход с 0 ненужных операций файловой системы заключается в использовании директивы `php` и разделении файлов от PHP по пути. Этот подход хорошо работает, если все ваше приложение обслуживается одним входным файлом. Пример [конфигурации](config.md#конфигурация-caddyfile), которая обслуживает статические файлы за папкой `/assets`, может выглядеть так:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
@assets {
|
||||
path /assets/*
|
||||
}
|
||||
|
||||
# всё, что находится за /assets, обрабатывается файловым сервером
|
||||
file_server @assets {
|
||||
root /root/to/your/app
|
||||
}
|
||||
|
||||
# всё, что не находится в /assets, обрабатывается вашим индексным или воркер PHP-файлом
|
||||
rewrite index.php
|
||||
php {
|
||||
root /root/to/your/app # явное указание корневой директории здесь обеспечивает лучшее кеширование
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Плейсхолдеры
|
||||
|
||||
Вы можете использовать [плейсхолдеры](https://caddyserver.com/docs/conventions#placeholders) в директивах `root` и `env`.
|
||||
Однако это предотвращает кеширование значений и существенно снижает производительность.
|
||||
Вы можете использовать [плейсхолдеры](https://caddyserver.com/docs/conventions#placeholders) в директивах `root` и `env`. Однако это предотвращает кеширование значений и существенно снижает производительность.
|
||||
|
||||
По возможности избегайте использования плейсхолдеров в этих директивах.
|
||||
|
||||
## `resolve_root_symlink`
|
||||
|
||||
По умолчанию, если корневая директория документа является символьной ссылкой, она автоматически разрешается FrankenPHP (это необходимо для корректной работы PHP).
|
||||
Если корневая директория документа не является символьной ссылкой, вы можете отключить эту функцию.
|
||||
По умолчанию, если корневая директория документа является символьной ссылкой, она автоматически разрешается FrankenPHP (это необходимо для корректной работы PHP). Если корневая директория документа не является символьной ссылкой, вы можете отключить эту функцию.
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
@@ -78,18 +119,15 @@ php_server {
|
||||
}
|
||||
```
|
||||
|
||||
Это улучшит производительность, если директива `root` содержит [плейсхолдеры](https://caddyserver.com/docs/conventions#placeholders).
|
||||
В остальных случаях прирост производительности будет минимальным.
|
||||
Это улучшит производительность, если директива `root` содержит [плейсхолдеры](https://caddyserver.com/docs/conventions#placeholders). В остальных случаях прирост производительности будет минимальным.
|
||||
|
||||
## Логи
|
||||
|
||||
Логирование, безусловно, полезно, но требует операций ввода-вывода и выделения памяти, что значительно снижает производительность.
|
||||
Убедитесь, что вы [правильно настроили уровень логирования](https://caddyserver.com/docs/caddyfile/options#log) и логируете только необходимое.
|
||||
Логирование, безусловно, очень полезно, но, по определению, оно требует операций ввода-вывода и выделения памяти, что значительно снижает производительность. Убедитесь, что вы [правильно настроили уровень логирования](https://caddyserver.com/docs/caddyfile/options#log) и логируете только то, что необходимо.
|
||||
|
||||
## Производительность PHP
|
||||
|
||||
FrankenPHP использует официальный интерпретатор PHP.
|
||||
Все стандартные оптимизации производительности PHP применимы к FrankenPHP.
|
||||
FrankenPHP использует официальный интерпретатор PHP. Все обычные оптимизации производительности, связанные с PHP, применимы к FrankenPHP.
|
||||
|
||||
В частности:
|
||||
|
||||
@@ -98,4 +136,28 @@ FrankenPHP использует официальный интерпретато
|
||||
- убедитесь, что кеш `realpath` достаточно велик для нужд вашего приложения;
|
||||
- используйте [предварительную загрузку](https://www.php.net/manual/en/opcache.preloading.php).
|
||||
|
||||
Для более детальной информации ознакомьтесь с [документацией Symfony о производительности](https://symfony.com/doc/current/performance.html) (большинство советов полезны даже если вы не используете Symfony).
|
||||
Для получения более подробной информации ознакомьтесь с [разделом документации Symfony, посвященным производительности](https://symfony.com/doc/current/performance.html) (большинство советов полезны, даже если вы не используете Symfony).
|
||||
|
||||
## Разделение пула потоков
|
||||
|
||||
Приложениям часто приходится взаимодействовать с медленными внешними службами, такими как API, который имеет тенденцию быть ненадежным при высокой нагрузке или постоянно отвечает в течение 10+ секунд. В таких случаях может быть полезно разделить пул потоков, чтобы иметь выделенные "медленные" пулы. Это предотвращает потребление медленными конечными точками всех ресурсов/потоков сервера и ограничивает параллелизм запросов, направляемых к медленной конечной точке, подобно пулу соединений.
|
||||
|
||||
```caddyfile
|
||||
example.com {
|
||||
php_server {
|
||||
root /app/public # корень вашего приложения
|
||||
worker index.php {
|
||||
match /slow-endpoint/* # все запросы к /slow-endpoint/* обрабатываются этим пулом потоков
|
||||
num 1 # минимум 1 поток для запросов к /slow-endpoint/*
|
||||
max_threads 20 # при необходимости разрешить до 20 потоков для запросов к /slow-endpoint/*
|
||||
}
|
||||
worker index.php {
|
||||
match * # все остальные запросы обрабатываются отдельно
|
||||
num 1 # минимум 1 поток для остальных запросов, даже если медленные конечные точки начинают зависать
|
||||
max_threads 20 # при необходимости разрешить до 20 потоков для остальных запросов
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
В целом, также рекомендуется обрабатывать очень медленные конечные точки асинхронно, используя соответствующие механизмы, такие как очереди сообщений.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Worker режим в FrankenPHP
|
||||
# Использование воркеров FrankenPHP
|
||||
|
||||
Загрузите приложение один раз и держите его в памяти.
|
||||
FrankenPHP обрабатывает входящие запросы за несколько миллисекунд.
|
||||
Загрузите приложение один раз и держите его в памяти.
|
||||
FrankenPHP будет обрабатывать входящие запросы за несколько миллисекунд.
|
||||
|
||||
## Запуск worker-скриптов
|
||||
|
||||
@@ -19,25 +19,30 @@ docker run \
|
||||
|
||||
### Автономный бинарный файл
|
||||
|
||||
Используйте опцию `--worker` команды `php-server`, чтобы обслуживать содержимое текущей директории через worker-скрипт:
|
||||
Используйте опцию `--worker` команды `php-server` для обслуживания содержимого текущей директории в режиме воркера:
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php
|
||||
```
|
||||
|
||||
Если ваше PHP-приложение [встроено в бинарник](embed.md), вы можете добавить пользовательский `Caddyfile` в корневую директорию приложения.
|
||||
Если ваше PHP-приложение [встроено в бинарник](embed.md), вы можете добавить пользовательский `Caddyfile` в корневую директорию приложения.
|
||||
Он будет использоваться автоматически.
|
||||
|
||||
Также можно настроить [автоматический перезапуск worker-скрипта при изменении файлов](config.md#отслеживание-изменений-файлов) с помощью опции `--watch`.
|
||||
Также можно [перезапускать воркер при изменении файлов](config.md#watching-for-file-changes) с помощью опции `--watch`.
|
||||
Следующая команда выполнит перезапуск, если будет изменён любой файл с расширением `.php` в директории `/path/to/your/app/` или её поддиректориях:
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
|
||||
```
|
||||
|
||||
Эта функция часто используется в сочетании с [горячей перезагрузкой](hot-reload.md).
|
||||
|
||||
## Symfony Runtime
|
||||
|
||||
Worker режим FrankenPHP поддерживается компонентом [Symfony Runtime](https://symfony.com/doc/current/components/runtime.html).
|
||||
> [!TIP]
|
||||
> Следующий раздел актуален только для версий Symfony до 7.4, где была введена нативная поддержка режима воркеров FrankenPHP.
|
||||
|
||||
Worker режим FrankenPHP поддерживается компонентом [Symfony Runtime](https://symfony.com/doc/current/components/runtime.html).
|
||||
Чтобы запустить любое Symfony-приложение в worker режиме, установите пакет FrankenPHP для [PHP Runtime](https://github.com/php-runtime/runtime):
|
||||
|
||||
```console
|
||||
@@ -57,7 +62,7 @@ docker run \
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
Подробнее см. в [документации](laravel.md#laravel-octane).
|
||||
Подробнее см. в [отдельной документации](laravel.md#laravel-octane).
|
||||
|
||||
## Пользовательские приложения
|
||||
|
||||
@@ -67,20 +72,23 @@ docker run \
|
||||
<?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);
|
||||
try {
|
||||
// Выполняется при получении запроса,
|
||||
// суперглобальные переменные, php://input и прочие сбрасываются
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
} catch (\Throwable $exception) {
|
||||
// `set_exception_handler` вызывается только когда worker-скрипт завершается,
|
||||
// что может быть не тем, что вы ожидаете, поэтому перехватывайте и обрабатывайте исключения здесь
|
||||
(new \MyCustomExceptionHandler)->handleException($exception);
|
||||
}
|
||||
};
|
||||
|
||||
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
|
||||
@@ -96,11 +104,11 @@ for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests
|
||||
if (!$keepRunning) break;
|
||||
}
|
||||
|
||||
// Завершение
|
||||
// Очистка
|
||||
$myApp->shutdown();
|
||||
```
|
||||
|
||||
Запустите приложение, настроив worker-скрипт с помощью переменной окружения `FRANKENPHP_CONFIG`:
|
||||
Запустите ваше приложение и используйте переменную окружения `FRANKENPHP_CONFIG` для настройки вашего воркера:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
@@ -110,10 +118,7 @@ docker run \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
## Настройка количества worker-скриптов
|
||||
|
||||
По умолчанию запускается по 2 worker-скрипта на каждый CPU.
|
||||
Вы можете задать своё значение:
|
||||
По умолчанию запускается 2 воркера на каждый CPU. Вы также можете настроить количество запускаемых воркеров:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
@@ -125,35 +130,54 @@ docker run \
|
||||
|
||||
### Перезапуск worker-скрипта после определённого количества запросов
|
||||
|
||||
PHP изначально не предназначался для долгоживущих процессов, поэтому некоторые библиотеки и устаревший код могут приводить к утечкам памяти.
|
||||
Для этого можно настроить автоматический перезапуск worker-скрипта после обработки определённого количества запросов.
|
||||
PHP изначально не предназначался для долгоживущих процессов, поэтому есть ещё много библиотек и устаревшего кода, которые вызывают утечки памяти.
|
||||
Обходной путь для использования такого кода в режиме воркера состоит в перезапуске скрипта воркера после обработки определённого количества запросов:
|
||||
|
||||
В предыдущем примере максимальное количество запросов задаётся с помощью переменной окружения `MAX_REQUESTS`.
|
||||
Предыдущий сниппет воркера позволяет настроить максимальное количество запросов для обработки, установив переменную окружения с именем `MAX_REQUESTS`.
|
||||
|
||||
### Перезапуск воркеров вручную
|
||||
|
||||
Хотя можно перезапускать воркеры [при изменении файлов](config.md#watching-for-file-changes), также можно корректно перезапустить все воркеры через [API администрирования Caddy](https://caddyserver.com/docs/api). Если администрирование включено в вашем [Caddyfile](config.md#caddyfile-config), вы можете отправить запрос POST на конечную точку перезапуска следующим образом:
|
||||
|
||||
```console
|
||||
curl -X POST http://localhost:2019/frankenphp/workers/restart
|
||||
```
|
||||
|
||||
### Сбои worker-скрипта
|
||||
|
||||
Если worker-скрипт завершится с ненулевым кодом выхода, FrankenPHP перезапустит его с использованием экспоненциальной задержки.
|
||||
Если worker-скрипт проработает дольше, чем время последней задержки \* 2, он будет считаться стабильным, и задержка сбросится.
|
||||
Однако, если worker-скрипт продолжает завершаться с ненулевым кодом выхода в течение короткого промежутка времени (например, из-за опечатки в коде), FrankenPHP завершит работу с ошибкой: `too many consecutive failures`.
|
||||
Если worker-скрипт завершится с ненулевым кодом выхода, FrankenPHP перезапустит его со стратегией экспоненциальной задержки.
|
||||
Если скрипт воркера остается активным дольше, чем время последней задержки \* 2, FrankenPHP не будет применять штраф к worker-скрипту и перезапустит его снова.
|
||||
Однако, если worker-скрипт продолжает завершаться с ненулевым кодом выхода в течение короткого промежутка времени
|
||||
(например, из-за опечатки в скрипте), FrankenPHP завершит работу с ошибкой: `too many consecutive failures`.
|
||||
|
||||
Количество последовательных сбоев можно настроить в вашем [Caddyfile](config.md#caddyfile-config) с помощью опции `max_consecutive_failures`:
|
||||
|
||||
```caddyfile
|
||||
frankenphp {
|
||||
worker {
|
||||
# ...
|
||||
max_consecutive_failures 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Поведение суперглобальных переменных
|
||||
|
||||
[PHP суперглобальные переменные](https://www.php.net/manual/en/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET` и т.д.) ведут себя следующим образом:
|
||||
[PHP суперглобальные переменные](https://www.php.net/manual/en/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET`...) ведут себя следующим образом:
|
||||
|
||||
- до первого вызова `frankenphp_handle_request()` суперглобальные переменные содержат значения, связанные с самим worker-скриптом
|
||||
- во время и после вызова `frankenphp_handle_request()` суперглобальные переменные содержат значения, сгенерированные на основе обработанного HTTP-запроса, каждый вызов изменяет значения суперглобальных переменных
|
||||
- до первого вызова `frankenphp_handle_request()`, суперглобальные переменные содержат значения, связанные с самим worker-скриптом
|
||||
- во время и после вызова `frankenphp_handle_request()`, суперглобальные переменные содержат значения, сгенерированные на основе обработанного HTTP-запроса, каждый вызов `frankenphp_handle_request()` изменяет значения суперглобальных переменных
|
||||
|
||||
Чтобы получить доступ к суперглобальным переменным worker-скрипта внутри колбэка, необходимо скопировать их и импортировать копию в область видимости колбэка:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Копирование $_SERVER worker-скрипта перед первым вызовом frankenphp_handle_request()
|
||||
// Копирование суперглобальной переменной $_SERVER воркера перед первым вызовом frankenphp_handle_request()
|
||||
$workerServer = $_SERVER;
|
||||
|
||||
$handler = static function () use ($workerServer) {
|
||||
var_dump($_SERVER); // $_SERVER для запроса
|
||||
var_dump($_SERVER); // $_SERVER, привязанный к запросу
|
||||
var_dump($workerServer); // $_SERVER worker-скрипта
|
||||
};
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
@@ -1,42 +1,73 @@
|
||||
# Konfigürasyon
|
||||
# Konfigürasyon
|
||||
|
||||
FrankenPHP, Caddy'nin yanı sıra Mercure ve Vulcain modülleri [Caddy tarafından desteklenen formatlar](https://caddyserver.com/docs/getting-started#your-first-config) kullanılarak yapılandırılabilir.
|
||||
FrankenPHP, Caddy'nin yanı sıra [Mercure](mercure.md) ve [Vulcain](https://vulcain.rocks) modülleri [Caddy tarafından desteklenen formatlar](https://caddyserver.com/docs/getting-started#your-first-config) kullanılarak yapılandırılabilir.
|
||||
|
||||
Docker imajlarında] (docker.md), `Caddyfile` `/etc/frankenphp/Caddyfile` adresinde bulunur.
|
||||
Statik ikili, başlatıldığı dizinde `Caddyfile` dosyasını arayacaktır.
|
||||
En yaygın format, basit, insan tarafından okunabilir bir metin formatı olan `Caddyfile`'dır. Varsayılan olarak, FrankenPHP mevcut dizinde bir `Caddyfile` arar. Özel bir yol belirtmek için `-c` veya `--config` seçeneğini kullanabilirsiniz.
|
||||
|
||||
PHP'nin kendisi [bir `php.ini` dosyası kullanılarak yapılandırılabilir](https://www.php.net/manual/tr/configuration.file.php).
|
||||
Bir PHP uygulamasını sunmak için minimal bir `Caddyfile` aşağıda gösterilmiştir:
|
||||
|
||||
PHP yorumlayıcısı aşağıdaki konumlarda arama yapacaktır:
|
||||
```caddyfile
|
||||
# Yanıt verilecek ana bilgisayar adı
|
||||
localhost
|
||||
|
||||
Docker:
|
||||
# İsteğe bağlı olarak, dosyaların sunulacağı dizin, aksi takdirde mevcut dizin varsayılan olarak kullanılır
|
||||
#root public/
|
||||
php_server
|
||||
```
|
||||
|
||||
- php.ini: `/usr/local/etc/php/php.ini` Varsayılan olarak php.ini sağlanmaz.
|
||||
Daha fazla özellik sağlayan ve kullanışlı ortam değişkenleri sunan daha gelişmiş bir `Caddyfile`, [FrankenPHP deposunda](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) ve Docker imajlarıyla birlikte sağlanır.
|
||||
|
||||
PHP'nin kendisi [bir `php.ini` dosyası kullanılarak yapılandırılabilir](https://www.php.net/manual/en/configuration.file.php).
|
||||
|
||||
Kurulum yönteminize bağlı olarak, FrankenPHP ve PHP yorumlayıcısı, aşağıda açıklanan konumlardaki yapılandırma dosyalarını arayacaktır.
|
||||
|
||||
## Docker
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: ana yapılandırma dosyası
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: otomatik olarak yüklenen ek yapılandırma dosyaları
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/usr/local/etc/php/php.ini` (varsayılan olarak bir `php.ini` sağlanmaz)
|
||||
- ek yapılandırma dosyaları: `/usr/local/etc/php/conf.d/*.ini`
|
||||
- php uzantıları: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
- PHP uzantıları: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
- PHP projesi tarafından sağlanan resmi bir şablonu kopyalamalısınız:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# Developement:
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
|
||||
# Veya production:
|
||||
# Production:
|
||||
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
|
||||
|
||||
# Veya development:
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
```
|
||||
|
||||
FrankenPHP kurulumu (.rpm veya .deb):
|
||||
## RPM ve Debian paketleri
|
||||
|
||||
- php.ini: `/etc/frankenphp/php.ini` Varsayılan olarak üretim ön ayarlarına sahip bir php.ini dosyası sağlanır.
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: ana yapılandırma dosyası
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: otomatik olarak yüklenen ek yapılandırma dosyaları
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/etc/php-zts/php.ini` (varsayılan olarak üretim ön ayarlarına sahip bir `php.ini` dosyası sağlanır)
|
||||
- ek yapılandırma dosyaları: `/etc/php-zts/conf.d/*.ini`
|
||||
|
||||
## Statik ikili
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- Mevcut çalışma dizininde: `Caddyfile`
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `frankenphp run` veya `frankenphp php-server` komutunun çalıştırıldığı dizin, ardından `/etc/frankenphp/php.ini`
|
||||
- ek yapılandırma dosyaları: `/etc/frankenphp/php.d/*.ini`
|
||||
- php uzantıları: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
Statik ikili:
|
||||
|
||||
- php.ini: `frankenphp run` veya `frankenphp php-server` komutunun çalıştırıldığı dizin, ardından `/etc/frankenphp/php.ini`
|
||||
- ek yapılandırma dosyaları: `/etc/frankenphp/php.d/*.ini`
|
||||
- php uzantıları: yüklenemez
|
||||
- PHP uzantıları: yüklenemez, bunları doğrudan ikili dosyaya dahil edin
|
||||
- [PHP kaynak kodu](https://github.com/php/php-src/) ile birlikte verilen `php.ini-production` veya `php.ini-development` dosyalarından birini kopyalayın.
|
||||
|
||||
## Caddyfile Konfigürasyonu
|
||||
@@ -54,17 +85,23 @@ localhost {
|
||||
}
|
||||
```
|
||||
|
||||
FrankenPHP'yi global seçenek kullanarak açıkça yapılandırabilirsiniz:
|
||||
`frankenphp` [global seçenek](https://caddyserver.com/docs/caddyfile/concepts#global-options) FrankenPHP'yi yapılandırmak için kullanılabilir.
|
||||
Alternatif olarak, FrankenPHP'yi [global seçenek](https://caddyserver.com/docs/caddyfile/concepts#global-options) olan `frankenphp` kullanarak açıkça yapılandırabilirsiniz:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Başlatılacak PHP iş parçacığı sayısını ayarlar. Varsayılan: Mevcut CPU çekirdek sayısının 2 katı.
|
||||
num_threads <num_threads> # Başlatılacak PHP iş parçacığı sayısını ayarlar. Varsayılan: Mevcut CPU'ların 2 katı.
|
||||
max_threads <num_threads> # Çalışma zamanında başlatılabilecek ek PHP iş parçacığı sayısını sınırlar. Varsayılan: num_threads. 'auto' olarak ayarlanabilir.
|
||||
max_wait_time <duration> # Bir isteğin boş bir PHP iş parçacığı bekleme süresinin zaman aşımına uğramadan önceki maksimum süresini ayarlar. Varsayılan: devre dışı.
|
||||
max_idle_time <duration> # Otomatik ölçeklenen bir iş parçacığının devre dışı bırakılmadan önce ne kadar süre boş kalabileceğini ayarlar. Varsayılan: 5s.
|
||||
php_ini <key> <value> # Bir php.ini yönergesi ayarlar. Birden fazla yönerge ayarlamak için birden fazla kez kullanılabilir.
|
||||
worker {
|
||||
file <path> # Çalışan komut dosyasının yolunu ayarlar.
|
||||
num <num> # Başlatılacak PHP iş parçacığı sayısını ayarlar, varsayılan değer mevcut CPU çekirdek sayısının 2 katıdır.
|
||||
num <num> # Başlatılacak PHP iş parçacığı sayısını ayarlar, varsayılan değer mevcut CPU'ların 2 katıdır.
|
||||
env <key> <value> # Ek bir ortam değişkenini verilen değere ayarlar. Birden fazla ortam değişkeni için birden fazla kez belirtilebilir.
|
||||
watch <path> # Dosya değişikliklerini izlemek için yolu ayarlar. Birden fazla yol için birden fazla kez belirtilebilir.
|
||||
name <name> # İşçinin adını ayarlar, günlüklerde ve metriklerde kullanılır. Varsayılan: işçi dosyasının mutlak yolu
|
||||
max_consecutive_failures <num> # İşçinin sağlıksız kabul edilmeden önceki maksimum ardışık hata sayısını ayarlar, -1 işçinin her zaman yeniden başlayacağı anlamına gelir. Varsayılan: 6.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +125,7 @@ Aynı sunucuda birden fazla uygulamaya hizmet veriyorsanız birden fazla işçi
|
||||
|
||||
```caddyfile
|
||||
app.example.com {
|
||||
root /path/to/app/public
|
||||
root /path/to/app/public
|
||||
php_server {
|
||||
root /path/to/app/public # daha iyi önbelleğe almayı sağlar
|
||||
worker index.php <num>
|
||||
@@ -96,7 +133,7 @@ app.example.com {
|
||||
}
|
||||
|
||||
other.example.com {
|
||||
root /path/to/other/public
|
||||
root /path/to/other/public
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
@@ -107,13 +144,14 @@ other.example.com {
|
||||
```
|
||||
|
||||
Genellikle ihtiyacınız olan şey `php_server` yönergesini kullanmaktır,
|
||||
ancak tam kontrole ihtiyacınız varsa, daha düşük seviyeli `php` yönergesini kullanabilirsiniz:
|
||||
ancak tam kontrole ihtiyacınız varsa, daha düşük seviyeli `php` yönergesini kullanabilirsiniz.
|
||||
`php` yönergesi, önce bir PHP dosyası olup olmadığını kontrol etmek yerine tüm girdiyi PHP'ye iletir. Daha fazla bilgiyi [performans sayfasında](performance.md#try_files) okuyun.
|
||||
|
||||
php_server` yönergesini kullanmak bu yapılandırmay ile aynıdır:
|
||||
`php_server` yönergesini kullanmak bu yapılandırmayla aynıdır:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
# Dizin istekleri için sondaki eğik çizgiyi, diğer adıyla taksim işaretini ekleyin
|
||||
# Dizin istekleri için sondaki eğik çizgiyi ekleyin
|
||||
@canonicalPath {
|
||||
file {path}/index.php
|
||||
not path */
|
||||
@@ -132,33 +170,96 @@ route {
|
||||
}
|
||||
```
|
||||
|
||||
php_server`ve`php` yönergeleri aşağıdaki seçeneklere sahiptir:
|
||||
`php_server` ve `php` yönergeleri aşağıdaki seçeneklere sahiptir:
|
||||
|
||||
```caddyfile
|
||||
php_server [<matcher>] {
|
||||
root <directory> # Sitenin kök klasörünü ayarlar. Öntanımlı: `root` yönergesi.
|
||||
root <directory> # Sitenin kök klasörünü ayarlar. Varsayılan: `root` yönergesi.
|
||||
split_path <delim...> # URI'yi iki parçaya bölmek için alt dizgeleri ayarlar. İlk eşleşen alt dizge "yol bilgisini" yoldan ayırmak için kullanılır. İlk parça eşleşen alt dizeyle sonlandırılır ve gerçek kaynak (CGI betiği) adı olarak kabul edilir. İkinci parça betiğin kullanması için PATH_INFO olarak ayarlanacaktır. Varsayılan: `.php`
|
||||
resolve_root_symlink false # Varsa, sembolik bir bağlantıyı değerlendirerek `root` dizininin gerçek değerine çözümlenmesini devre dışı bırakır (varsayılan olarak etkindir).
|
||||
env <key> <value> # Ek bir ortam değişkenini verilen değere ayarlar. Birden fazla ortam değişkeni için birden fazla kez belirtilebilir.
|
||||
file_server off # Yerleşik file_server yönergesini devre dışı bırakır.
|
||||
worker { # Bu sunucuya özgü bir worker oluşturur. Birden fazla worker için birden fazla kez belirtilebilir.
|
||||
file <path> # Worker betiğinin yolunu ayarlar, php_server köküne göre göreceli olabilir
|
||||
num <num> # Başlatılacak PHP iş parçacığı sayısını ayarlar, varsayılan değer mevcut CPU çekirdek sayısının 2 katıdır
|
||||
num <num> # Başlatılacak PHP iş parçacığı sayısını ayarlar, varsayılan değer mevcut CPU'ların 2 katıdır
|
||||
name <name> # Worker için günlüklerde ve metriklerde kullanılan bir ad ayarlar. Varsayılan: worker dosyasının mutlak yolu. Bir php_server bloğunda tanımlandığında her zaman m# ile başlar.
|
||||
watch <path> # Dosya değişikliklerini izlemek için yolu ayarlar. Birden fazla yol için birden fazla kez belirtilebilir.
|
||||
env <key> <value> # Ek bir ortam değişkenini verilen değere ayarlar. Birden fazla ortam değişkeni için birden fazla kez belirtilebilir. Bu worker için ortam değişkenleri ayrıca php_server üst öğesinden devralınır, ancak burada geçersiz kılınabilir.
|
||||
match <path> # İşçiyi bir yol desenine eşleştirir. try_files'ı geçersiz kılar ve yalnızca php_server yönergesinde kullanılabilir.
|
||||
}
|
||||
worker <other_file> <num> # Global frankenphp bloğundaki gibi kısa formu da kullanabilirsiniz.
|
||||
}
|
||||
```
|
||||
|
||||
### Dosya Değişikliklerini İzleme
|
||||
|
||||
Workers yalnızca uygulamanızı bir kez başlatır ve bellekte tutar, bu nedenle PHP dosyalarınızdaki herhangi bir değişiklik hemen yansımaz.
|
||||
|
||||
Bunun yerine işçiler, `watch` yönergesi aracılığıyla dosya değişikliklerinde yeniden başlatılabilir. Bu, geliştirme ortamları için kullanışlıdır.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Bu özellik genellikle [hot reload](hot-reload.md) ile birlikte kullanılır.
|
||||
|
||||
Eğer `watch` dizini belirtilmezse, FrankenPHP sürecinin başlatıldığı dizin ve alt dizinlerdeki tüm `.env`, `.php`, `.twig`, `.yaml` ve `.yml` dosyalarını izleyen `./**/*.{env,php,twig,yaml,yml}` değerine geri döner. Bunun yerine, bir veya daha fazla dizini [kabuk dosya adı deseni](https://pkg.go.dev/path/filepath#Match) aracılığıyla da belirtebilirsiniz:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch /path/to/app # /path/to/app'ın tüm alt dizinlerindeki tüm dosyaları izler
|
||||
watch /path/to/app/*.php # /path/to/app'da .php ile biten dosyaları izler
|
||||
watch /path/to/app/**/*.php # /path/to/app ve alt dizinlerindeki PHP dosyalarını izler
|
||||
watch /path/to/app/**/*.{php,twig} # /path/to/app ve alt dizinlerindeki PHP ve Twig dosyalarını izler
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `**` deseni, özyinelemeli izlemeyi belirtir
|
||||
- Dizinler göreceli de olabilir (FrankenPHP sürecinin başlatıldığı yere göre)
|
||||
- Birden fazla işçi tanımladıysanız, bir dosya değiştiğinde hepsi yeniden başlatılacaktır.
|
||||
- Çalışma zamanında oluşturulan dosyaları (günlükler gibi) izlerken dikkatli olun, zira bunlar istenmeyen işçi yeniden başlatmalarına neden olabilir.
|
||||
|
||||
Dosya izleyici [e-dant/watcher](https://github.com/e-dant/watcher) üzerine kuruludur.
|
||||
|
||||
## İşçiyi Yola Eşleştirme
|
||||
|
||||
Geleneksel PHP uygulamalarında, betikler her zaman public dizininde bulunur. Bu, diğer tüm PHP betikleri gibi ele alınan işçi betikleri için de geçerlidir. İşçi betiğini public dizininin dışına koymak isterseniz, bunu `match` yönergesi aracılığıyla yapabilirsiniz.
|
||||
|
||||
`match` yönergesi, yalnızca `php_server` ve `php` içinde bulunan `try_files`'a optimize edilmiş bir alternatiftir. Aşağıdaki örnek, varsa her zaman public dizinindeki bir dosyayı sunacak, aksi takdirde isteği yol desenine uyan işçiye iletecektir.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
php_server {
|
||||
worker {
|
||||
file /path/to/worker.php # dosya public yolunun dışında olabilir
|
||||
match /api/* # /api/ ile başlayan tüm istekler bu worker tarafından işlenecektir
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ortam Değişkenleri
|
||||
|
||||
Aşağıdaki ortam değişkenleri `Caddyfile` içinde değişiklik yapmadan Caddy yönergelerini entegre etmek için kullanılabilir:
|
||||
|
||||
- `SERVER_NAME`: değiştirin [dinlenecek adresleri](https://caddyserver.com/docs/caddyfile/concepts#addresses), sağlanan ana bilgisayar adları oluşturulan TLS sertifikası için de kullanılacaktır
|
||||
- `CADDY_GLOBAL_OPTIONS`: entegre edin [global seçenekler](https://caddyserver.com/docs/caddyfile/options)
|
||||
- `FRANKENPHP_CONFIG`: `frankenphp` yönergesi altına yapılandırma entegre edin
|
||||
- `SERVER_NAME`: [dinlenecek adresleri](https://caddyserver.com/docs/caddyfile/concepts#addresses) değiştirir, sağlanan ana bilgisayar adları oluşturulan TLS sertifikası için de kullanılacaktır
|
||||
- `SERVER_ROOT`: sitenin kök dizinini değiştirir, varsayılan olarak `public/`dir
|
||||
- `CADDY_GLOBAL_OPTIONS`: [global seçenekleri](https://caddyserver.com/docs/caddyfile/options) entegre eder
|
||||
- `FRANKENPHP_CONFIG`: `frankenphp` yönergesi altına yapılandırma entegre eder
|
||||
|
||||
FPM ve CLI SAPI'lerinde olduğu gibi, ortam değişkenleri varsayılan olarak `$_SERVER` süper globalinde gösterilir.
|
||||
|
||||
@@ -166,10 +267,62 @@ FPM ve CLI SAPI'lerinde olduğu gibi, ortam değişkenleri varsayılan olarak `$
|
||||
|
||||
## PHP konfigürasyonu
|
||||
|
||||
Ek olarak [PHP yapılandırma dosyalarını](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan) yüklemek için
|
||||
Ek olarak [PHP yapılandırma dosyalarını](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan) yüklemek için,
|
||||
`PHP_INI_SCAN_DIR` ortam değişkeni kullanılabilir.
|
||||
Ayarlandığında, PHP verilen dizinlerde bulunan `.ini` uzantılı tüm dosyaları yükleyecektir.
|
||||
|
||||
PHP yapılandırmasını `Caddyfile` içindeki `php_ini` yönergesini kullanarak da değiştirebilirsiniz:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
php_ini memory_limit 256M
|
||||
|
||||
# veya
|
||||
|
||||
php_ini {
|
||||
memory_limit 256M
|
||||
max_execution_time 15
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### HTTPS'i Devre Dışı Bırakma
|
||||
|
||||
Varsayılan olarak, FrankenPHP tüm ana bilgisayar adları için (localhost dahil) HTTPS'i otomatik olarak etkinleştirir. HTTPS'i devre dışı bırakmak isterseniz (örneğin bir geliştirme ortamında), `SERVER_NAME` ortam değişkenini `http://` veya `:80` olarak ayarlayabilirsiniz:
|
||||
|
||||
Alternatif olarak, [Caddy belgelerinde](https://caddyserver.com/docs/automatic-https#activation) açıklanan diğer tüm yöntemleri kullanabilirsiniz.
|
||||
|
||||
Eğer `localhost` ana bilgisayar adı yerine `127.0.0.1` IP adresiyle HTTPS kullanmak isterseniz, lütfen [bilinen sorunlar](known-issues.md#using-https127001-with-docker) bölümünü okuyun.
|
||||
|
||||
### Tam Çift Yönlü (HTTP/1)
|
||||
|
||||
HTTP/1.x kullanırken, tüm gövde okunmadan önce bir yanıt yazmaya izin vermek için tam çift yönlü modun etkinleştirilmesi istenebilir. (örneğin: [Mercure](mercure.md), WebSocket, Sunucu Tarafından Gönderilen Olaylar vb.)
|
||||
|
||||
Bu, `Caddyfile`'daki global seçeneklere eklenmesi gereken isteğe bağlı bir yapılandırmadır:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Bu seçeneği etkinleştirmek, tam çift yönlü desteği olmayan eski HTTP/1.x istemcilerinin kilitlenmesine neden olabilir.
|
||||
> Bu, `CADDY_GLOBAL_OPTIONS` ortam yapılandırması kullanılarak da yapılandırılabilir:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
Bu ayar hakkında daha fazla bilgiyi [Caddy belgelerinde](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex) bulabilirsiniz.
|
||||
|
||||
## Hata Ayıklama Modunu Etkinleştirin
|
||||
|
||||
Docker imajını kullanırken, hata ayıklama modunu etkinleştirmek için `CADDY_GLOBAL_OPTIONS` ortam değişkenini `debug` olarak ayarlayın:
|
||||
@@ -179,4 +332,3 @@ docker run -v $PWD:/app/public \
|
||||
-e CADDY_GLOBAL_OPTIONS=debug \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
# Özel Docker İmajı Oluşturma
|
||||
|
||||
[Resmi PHP imajları](https://hub.docker.com/_/php/) temel alınarak [FrankenPHP Docker imajları](https://hub.docker.com/r/dunglas/frankenphp) hazırlanmıştır. Popüler mimariler için Debian ve Alpine Linux varyantları sağlanmıştır. Debian dağıtımı tavsiye edilir.
|
||||
[FrankenPHP Docker imajları](https://hub.docker.com/r/dunglas/frankenphp), [resmi PHP imajları](https://hub.docker.com/_/php/) temel alınarak hazırlanmıştır. Popüler mimariler için Debian ve Alpine Linux varyantları sağlanmıştır. Debian varyantları tavsiye edilir.
|
||||
|
||||
PHP 8.2, 8.3, 8.4 ve 8.5 için varyantlar sağlanmıştır. [Etiketlere göz atın](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
PHP 8.2, 8.3, 8.4 ve 8.5 için varyantlar sağlanmıştır.
|
||||
|
||||
Etiketler şu deseni takip eder: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
|
||||
- `<frankenphp-version>` ve `<php-version>`, sırasıyla FrankenPHP ve PHP'nin ana (örn. `1`), ikincil (örn. `1.2`) ve yama sürümlerine (örn. `1.2.3`) kadar değişen sürüm numaralarıdır.
|
||||
- `<os>` ise `trixie` (Debian Trixie için), `bookworm` (Debian Bookworm için) veya `alpine` (Alpine'ın en son kararlı sürümü için) olabilir.
|
||||
|
||||
[Etiketlere göz atın](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
## İmajlar Nasıl Kullanılır
|
||||
|
||||
@@ -21,15 +28,19 @@ docker build -t my-php-app .
|
||||
docker run -it --rm --name my-running-app my-php-app
|
||||
```
|
||||
|
||||
## Yapılandırma Nasıl Ayarlanır
|
||||
|
||||
Kolaylık sağlamak için, faydalı ortam değişkenleri içeren [varsayılan bir `Caddyfile`](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) imajda sağlanmıştır.
|
||||
|
||||
## Daha Fazla PHP Eklentisi Nasıl Kurulur
|
||||
|
||||
[Docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) betiği temel imajda sağlanmıştır.
|
||||
Ek PHP eklentileri eklemek ise gerçekten kolaydır:
|
||||
[`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) betiği temel imajda sağlanmıştır.
|
||||
Ek PHP eklentileri eklemek basittir:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# buraya istenilen eklentileri ekleyin:
|
||||
# ek eklentileri buraya ekleyin:
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
@@ -47,10 +58,10 @@ FrankenPHP, Caddy'nin üzerine inşa edilmiştir ve tüm [Caddy modülleri](http
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp:builder AS builder
|
||||
|
||||
# xcaddy'yi derleyen imaja kopyalayın
|
||||
# xcaddy'yi builder imajına kopyalayın
|
||||
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
|
||||
|
||||
# FrankenPHP oluşturmak için CGO etkinleştirilmelidir
|
||||
# CGO, FrankenPHP oluşturmak için etkinleştirilmelidir
|
||||
RUN CGO_ENABLED=1 \
|
||||
XCADDY_SETCAP=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
|
||||
@@ -61,10 +72,10 @@ RUN CGO_ENABLED=1 \
|
||||
--with github.com/dunglas/frankenphp=./ \
|
||||
--with github.com/dunglas/frankenphp/caddy=./caddy/ \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
# Mercure ve Vulcain resmi yapıya dahil edilmiştir, ancak bunları kaldırmaktan çekinmeyin
|
||||
# Mercure ve Vulcain resmi derlemeye dahildir, ancak bunları kaldırmaktan çekinmeyin
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Buraya ekstra Caddy modülleri ekleyin
|
||||
# Ek Caddy modüllerini buraya ekleyin
|
||||
|
||||
FROM dunglas/frankenphp AS runner
|
||||
|
||||
@@ -73,7 +84,7 @@ COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
```
|
||||
|
||||
FrankenPHP tarafından sağlanan `builder` imajı `libphp`'nin derlenmiş bir sürümünü içerir.
|
||||
[Derleyici imajları](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) hem Debian hem de Alpine için FrankenPHP ve PHP'nin tüm sürümleri için sağlanmıştır.
|
||||
[Builder imajları](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) hem Debian hem de Alpine için FrankenPHP ve PHP'nin tüm sürümleri için sağlanmıştır.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
@@ -92,9 +103,9 @@ FROM dunglas/frankenphp
|
||||
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
|
||||
```
|
||||
|
||||
## Geliştirme Sürecinde Yığın (Volume) Kullanma
|
||||
## Geliştirme Sürecinde Volume Kullanma
|
||||
|
||||
FrankenPHP ile kolayca geliştirme yapmak için, uygulamanın kaynak kodunu içeren dizini ana bilgisayarınızdan Docker konteynerine bir yığın (volume) olarak bağlayın:
|
||||
FrankenPHP ile kolayca geliştirme yapmak için, uygulamanın kaynak kodunu içeren dizini ana bilgisayarınızdan Docker konteynerine bir volume olarak bağlayın:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app
|
||||
@@ -112,9 +123,9 @@ Docker Compose ile:
|
||||
services:
|
||||
php:
|
||||
image: dunglas/frankenphp
|
||||
# özel bir Dockerfile kullanmak istiyorsanız aşağıdaki yorum satırını kaldırın
|
||||
# özel bir Dockerfile kullanmak istiyorsanız aşağıdaki satırın yorumunu kaldırın
|
||||
#build: .
|
||||
# bunu bir production ortamında çalıştırmak istiyorsanız aşağıdaki yorum satırını kaldırın
|
||||
# bunu bir üretim ortamında çalıştırmak istiyorsanız aşağıdaki satırın yorumunu kaldırın
|
||||
# restart: always
|
||||
ports:
|
||||
- "80:80" # HTTP
|
||||
@@ -124,10 +135,10 @@ services:
|
||||
- ./:/app/public
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
# production ortamda aşağıdaki satırı yorum satırı yapın, geliştirme ortamında insan tarafından okunabilir güzel günlüklere sahip olmanızı sağlar
|
||||
# üretimde aşağıdaki satırı yorum satırı yapın; geliştirme ortamında ise güzel, insan tarafından okunabilir günlükler sağlar
|
||||
tty: true
|
||||
|
||||
# Caddy sertifikaları ve yapılandırması için gereken yığınlar (volumes)
|
||||
# Caddy sertifikaları ve yapılandırması için gereken volume'ler
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
@@ -149,23 +160,120 @@ RUN \
|
||||
useradd ${USER}; \
|
||||
# 80 ve 443 numaralı bağlantı noktalarına bağlanmak için ek özellik ekleyin
|
||||
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
|
||||
# /data/caddy ve /config/caddy dosyalarına yazma erişimi verin
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy;
|
||||
# /config/caddy ve /data/caddy dosyalarına yazma erişimi verin
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
### Yetenek Olmadan Çalıştırma
|
||||
|
||||
FrankenPHP, root yetkisi olmadan çalışırken bile, web sunucusunu ayrıcalıklı bağlantı noktalarında (80 ve 443) bağlamak için `CAP_NET_BIND_SERVICE` yeteneğine ihtiyaç duyar.
|
||||
|
||||
FrankenPHP'yi ayrıcalıklı olmayan bir bağlantı noktasında (1024 ve üzeri) çalıştırırsanız, web sunucusunu root olmayan bir kullanıcı olarak ve herhangi bir yeteneğe ihtiyaç duymadan çalıştırmak mümkündür:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Alpine tabanlı dağıtımlar için "adduser -D ${USER}" kullanın
|
||||
useradd ${USER}; \
|
||||
# Varsayılan yeteneği kaldırın
|
||||
setcap -r /usr/local/bin/frankenphp; \
|
||||
# /config/caddy ve /data/caddy dosyalarına yazma erişimi verin
|
||||
chown -R ${USER}:${USER} /config/caddy /data/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
Ardından, ayrıcalıklı olmayan bir bağlantı noktası kullanmak için `SERVER_NAME` ortam değişkenini ayarlayın.
|
||||
Örnek: `:8000`
|
||||
|
||||
## Güncellemeler
|
||||
|
||||
Docker imajları oluşturulur:
|
||||
|
||||
- Yeni bir sürüm etiketlendiğinde
|
||||
- Her gün UTC ile saat 4'te Resmi PHP imajlarının yeni sürümleri mevcutsa
|
||||
- Her gün UTC ile sabah 4'te, resmi PHP imajlarının yeni sürümleri mevcutsa
|
||||
|
||||
## İmajları Sertleştirme
|
||||
|
||||
FrankenPHP Docker imajlarınızın saldırı yüzeyini ve boyutunu daha da azaltmak için, onları [Google distroless](https://github.com/GoogleContainerTools/distroless) veya [Docker hardened](https://www.docker.com/products/hardened-images) bir imaj üzerine inşa etmek de mümkündür.
|
||||
|
||||
> [!WARNING]
|
||||
> Bu minimal temel imajlar, hata ayıklamayı zorlaştıran bir kabuk veya paket yöneticisi içermez.
|
||||
> Bu nedenle, güvenlik yüksek öncelikliyse yalnızca üretim için önerilirler.
|
||||
|
||||
Ek PHP eklentileri eklerken, bir ara derleme aşamasına ihtiyacınız olacaktır:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp AS builder
|
||||
|
||||
# Ek PHP eklentilerini buraya ekleyin
|
||||
RUN install-php-extensions pdo_mysql pdo_pgsql #...
|
||||
|
||||
# frankenphp'nin paylaşılan kütüphanelerini ve kurulu tüm eklentileri geçici bir konuma kopyalayın
|
||||
# Bu adımı, frankenphp binary'sinin ve her bir eklenti .so dosyasının ldd çıktısını analiz ederek manuel olarak da yapabilirsiniz
|
||||
RUN apt-get update && apt-get install -y libtree && \
|
||||
EXT_DIR="$(php -r 'echo ini_get("extension_dir");')" && \
|
||||
FRANKENPHP_BIN="$(which frankenphp)"; \
|
||||
LIBS_TMP_DIR="/tmp/libs"; \
|
||||
mkdir -p "$LIBS_TMP_DIR"; \
|
||||
for target in "$FRANKENPHP_BIN" $(find "$EXT_DIR" -maxdepth 2 -type f -name "*.so"); do \
|
||||
libtree -pv "$target" | sed 's/.*── \(.*\) \[.*/\1/' | grep -v "^$target" | while IFS= read -r lib; do \
|
||||
[ -z "$lib" ] && continue; \
|
||||
base=$(basename "$lib"); \
|
||||
destfile="$LIBS_TMP_DIR/$base"; \
|
||||
if [ ! -f "$destfile" ]; then \
|
||||
cp "$lib" "$destfile"; \
|
||||
fi; \
|
||||
done; \
|
||||
done
|
||||
|
||||
|
||||
# Distroless debian temel imajı, bunun temel imajla aynı debian sürümü olduğundan emin olun
|
||||
FROM gcr.io/distroless/base-debian13
|
||||
# Docker hardened imaj alternatifi
|
||||
# FROM dhi.io/debian:13
|
||||
|
||||
# Uygulamanızın ve Caddyfile'ınızın konteynere kopyalanacak konumu
|
||||
ARG PATH_TO_APP="."
|
||||
ARG PATH_TO_CADDYFILE="./Caddyfile"
|
||||
|
||||
# Uygulamanızı /app'e kopyalayın
|
||||
# Daha fazla sertleştirme için, yalnızca yazılabilir yolların nonroot kullanıcısına ait olduğundan emin olun
|
||||
COPY --chown=nonroot:nonroot "$PATH_TO_APP" /app
|
||||
COPY "$PATH_TO_CADDYFILE" /etc/caddy/Caddyfile
|
||||
|
||||
# frankenphp'yi ve gerekli kütüphaneleri kopyalayın
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions
|
||||
COPY --from=builder /tmp/libs /usr/lib
|
||||
|
||||
# php.ini yapılandırma dosyalarını kopyalayın
|
||||
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
|
||||
COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
|
||||
|
||||
# Caddy veri dizinleri — salt okunur bir kök dosya sisteminde bile nonroot için yazılabilir olmalıdır
|
||||
ENV XDG_CONFIG_HOME=/config \
|
||||
XDG_DATA_HOME=/data
|
||||
COPY --from=builder --chown=nonroot:nonroot /data/caddy /data/caddy
|
||||
COPY --from=builder --chown=nonroot:nonroot /config/caddy /config/caddy
|
||||
|
||||
USER nonroot
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Sağlanan Caddyfile ile frankenphp'yi çalıştırmak için giriş noktası
|
||||
ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "-c", "/etc/caddy/Caddyfile"]
|
||||
```
|
||||
|
||||
## Geliştirme Sürümleri
|
||||
|
||||
Geliştirme sürümleri [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev) Docker deposunda mevcuttur.
|
||||
GitHub deposunun ana dalına her commit yapıldığında yeni bir derleme tetiklenir.
|
||||
GitHub deposunun `main` dalına her commit gönderildiğinde yeni bir derleme tetiklenir.
|
||||
|
||||
`latest*` etiketleri `main` dalının başına işaret eder.
|
||||
`sha-<hash-du-commit-git>` biçimindeki etiketler de kullanılabilir.
|
||||
`sha-<git-commit-hash>` biçimindeki etiketler de mevcuttur.
|
||||
|
||||
171
docs/tr/extension-workers.md
Normal file
171
docs/tr/extension-workers.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# Uzantı İşçileri
|
||||
|
||||
Uzantı İşçileri, [FrankenPHP uzantınızın](https://frankenphp.dev/docs/extensions/) arka plan görevlerini yürütmek, eşzamansız olayları işlemek veya özel protokolleri uygulamak için özel bir PHP iş parçacığı havuzunu yönetmesini sağlar. Kuyruk sistemleri, olay dinleyicileri, zamanlayıcılar vb. için kullanışlıdır.
|
||||
|
||||
## İşçiyi Kaydetme
|
||||
|
||||
### Statik Kayıt
|
||||
|
||||
İşçiyi kullanıcı tarafından yapılandırılabilir hale getirmeniz gerekmiyorsa (sabit komut dosyası yolu, sabit iş parçacığı sayısı), işçiyi `init()` fonksiyonunda basitçe kaydedebilirsiniz.
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"github.com/dunglas/frankenphp"
|
||||
"github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
// İşçi havuzuyla iletişim kurmak için genel tanıtıcı
|
||||
var worker frankenphp.Workers
|
||||
|
||||
func init() {
|
||||
// Modül yüklendiğinde işçiyi kaydet.
|
||||
worker = caddy.RegisterWorkers(
|
||||
"my-internal-worker", // Benzersiz isim
|
||||
"worker.php", // Komut dosyası yolu (çalışmaya göre veya mutlak)
|
||||
2, // Sabit iş parçacığı sayısı
|
||||
// İsteğe bağlı Yaşam Döngüsü Kancaları
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
// Genel kurulum mantığı...
|
||||
}),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Bir Caddy Modülünde (Kullanıcı tarafından yapılandırılabilir)
|
||||
|
||||
Uzantınızı paylaşmayı planlıyorsanız (genel bir kuyruk veya olay dinleyici gibi), onu bir Caddy modülüne sarmalısınız. Bu, kullanıcıların `Caddyfile` aracılığıyla komut dosyası yolunu ve iş parçacığı sayısını yapılandırmasına olanak tanır. Bu, `caddy.Provisioner` arayüzünü uygulamayı ve Caddyfile'ı ayrıştırmayı gerektirir ([bir örnek görmek için](https://github.com/dunglas/frankenphp-queue/blob/989120d394d66dd6c8e2101cac73dd622fade334/caddy.go)).
|
||||
|
||||
### Saf Bir Go Uygulamasında (Gömme)
|
||||
|
||||
FrankenPHP'yi [Caddy olmadan standart bir Go uygulamasına gömüyorsanız](https://pkg.go.dev/github.com/dunglas/frankenphp#example-ServeHTTP), seçenekleri başlatırken `frankenphp.WithExtensionWorkers` kullanarak uzantı işçilerini kaydedebilirsiniz.
|
||||
|
||||
## İşçilerle Etkileşim Kurma
|
||||
|
||||
İşçi havuzu aktif hale geldiğinde, ona görevler gönderebilirsiniz. Bu, [PHP'ye dışa aktarılan yerel fonksiyonlar](https://frankenphp.dev/docs/extensions/#writing-the-extension) içinde veya bir cron zamanlayıcı, bir olay dinleyicisi (MQTT, Kafka) veya herhangi başka bir goroutine gibi herhangi bir Go mantığından yapılabilir.
|
||||
|
||||
### Başsız Mod : `SendMessage`
|
||||
|
||||
Doğrudan işçi komut dosyanıza ham veri geçirmek için `SendMessage` kullanın. Bu, kuyruklar veya basit komutlar için idealdir.
|
||||
|
||||
#### Örnek: Asenkron Bir Kuyruk Uzantısı
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_queue_push(mixed $data): bool
|
||||
func my_queue_push(data *C.zval) bool {
|
||||
// 1. İşçinin hazır olduğundan emin olun
|
||||
if worker == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. Arka plan işçisine gönder
|
||||
_, err := worker.SendMessage(
|
||||
context.Background(), // Standart Go bağlamı
|
||||
unsafe.Pointer(data), // İşçiye iletilecek veri
|
||||
nil, // İsteğe bağlı http.ResponseWriter
|
||||
)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP Emülasyonu :`SendRequest`
|
||||
|
||||
Uzantınızın standart bir web ortamı bekleyen ( `$_SERVER`, `$_GET` vb. dolduran) bir PHP komut dosyasını çağırması gerekiyorsa `SendRequest` kullanın.
|
||||
|
||||
```go
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"unsafe"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function my_worker_http_request(string $path): string
|
||||
func my_worker_http_request(path *C.zend_string) unsafe.Pointer {
|
||||
// 1. İsteği ve kaydediciyi hazırla
|
||||
url := frankenphp.GoString(unsafe.Pointer(path))
|
||||
req, _ := http.NewRequest("GET", url, http.NoBody)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// 2. İşçiye gönder
|
||||
if err := worker.SendRequest(rr, req); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3. Yakalanan yanıtı döndür
|
||||
return frankenphp.PHPString(rr.Body.String(), false)
|
||||
}
|
||||
```
|
||||
|
||||
## İşçi Komut Dosyası
|
||||
|
||||
PHP işçi komut dosyası bir döngüde çalışır ve hem ham mesajları hem de HTTP isteklerini işleyebilir.
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Hem ham mesajları hem de HTTP isteklerini aynı döngüde işle
|
||||
$handler = function ($payload = null) {
|
||||
// Durum 1: Mesaj Modu
|
||||
if ($payload !== null) {
|
||||
return "Received payload: " . $payload;
|
||||
}
|
||||
|
||||
// Durum 2: HTTP Modu (standart PHP süper küreselleri doldurulur)
|
||||
echo "Hello from page: " . $_SERVER['REQUEST_URI'];
|
||||
};
|
||||
|
||||
while (frankenphp_handle_request($handler)) {
|
||||
gc_collect_cycles();
|
||||
}
|
||||
```
|
||||
|
||||
## Yaşam Döngüsü Kancaları
|
||||
|
||||
FrankenPHP, yaşam döngüsünün belirli noktalarında Go kodunu yürütmek için kancalar sağlar.
|
||||
|
||||
| Kanca Türü | Seçenek Adı | İmza | Bağlam ve Kullanım Durumu |
|
||||
| :--------- | :--------------------------- | :------------------- | :--------------------------------------------------------------------- |
|
||||
| **Sunucu** | `WithWorkerOnServerStartup` | `func()` | Genel kurulum. **Bir Kez** çalışır. Örnek: NATS/Redis'e bağlanma. |
|
||||
| **Sunucu** | `WithWorkerOnServerShutdown` | `func()` | Genel temizleme. **Bir Kez** çalışır. Örnek: Paylaşılan bağlantıları kapatma. |
|
||||
| **İş Parçacığı** | `WithWorkerOnReady` | `func(threadID int)` | İş parçacığı başına kurulum. Bir iş parçacığı başladığında çağrılır. İş Parçacığı Kimliğini alır. |
|
||||
| **İş Parçacığı** | `WithWorkerOnShutdown` | `func(threadID int)` | İş parçacığı başına temizleme. İş Parçacığı Kimliğini alır. |
|
||||
|
||||
### Örnek
|
||||
|
||||
```go
|
||||
package myextension
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dunglas/frankenphp"
|
||||
frankenphpCaddy "github.com/dunglas/frankenphp/caddy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
workerHandle = frankenphpCaddy.RegisterWorkers(
|
||||
"my-worker", "worker.php", 2,
|
||||
|
||||
// Sunucu Başlatma (Genel)
|
||||
frankenphp.WithWorkerOnServerStartup(func() {
|
||||
fmt.Println("Uzantı: Sunucu başlıyor...")
|
||||
}),
|
||||
|
||||
// İş Parçacığı Hazır (İş Parçacığı Başına)
|
||||
// Not: Fonksiyon, İş Parçacığı Kimliğini temsil eden bir tamsayı kabul eder
|
||||
frankenphp.WithWorkerOnReady(func(id int) {
|
||||
fmt.Printf("Uzantı: İşçi iş parçacığı #%d hazır.\n", id)
|
||||
}),
|
||||
)
|
||||
}
|
||||
195
docs/tr/performance.md
Normal file
195
docs/tr/performance.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Performans
|
||||
|
||||
Varsayılan olarak, FrankenPHP performans ve kullanım kolaylığı arasında iyi bir denge sunmaya çalışır.
|
||||
Ancak, uygun bir yapılandırma kullanılarak performansı önemli ölçüde artırmak mümkündür.
|
||||
|
||||
## İş Parçacığı ve İşçi Sayısı
|
||||
|
||||
Varsayılan olarak, FrankenPHP mevcut CPU çekirdeği sayısının 2 katı kadar iş parçacığı ve işçi (işçi modunda) başlatır.
|
||||
|
||||
Uygun değerler, uygulamanızın nasıl yazıldığına, ne yaptığına ve donanımınıza büyük ölçüde bağlıdır.
|
||||
Bu değerleri değiştirmenizi şiddetle tavsiye ederiz. En iyi sistem kararlılığı için, `num_threads` x `memory_limit` < `available_memory` olması önerilir.
|
||||
|
||||
Doğru değerleri bulmak için gerçek trafiği simüle eden yük testleri yapmak en iyisidir.
|
||||
[k6](https://k6.io) ve [Gatling](https://gatling.io) bunun için iyi araçlardır.
|
||||
|
||||
İş parçacığı sayısını yapılandırmak için `php_server` ve `php` yönergelerinin `num_threads` seçeneğini kullanın.
|
||||
İşçi sayısını değiştirmek için `frankenphp` yönergesinin `worker` bölümünün `num` seçeneğini kullanın.
|
||||
|
||||
### `max_threads`
|
||||
|
||||
Trafiğinizin neye benzeyeceğini tam olarak bilmek her zaman daha iyi olsa da, gerçek dünya uygulamaları daha
|
||||
tahmin edilemez olma eğilimindedir. `max_threads` [yapılandırması](config.md#caddyfile-konfigürasyonu), FrankenPHP'nin çalışma zamanında belirtilen sınıra kadar ek iş parçacıkları otomatik olarak oluşturmasına olanak tanır.
|
||||
`max_threads`, trafiğinizi yönetmek için kaç iş parçacığına ihtiyacınız olduğunu anlamanıza yardımcı olabilir ve sunucuyu gecikme artışlarına karşı daha dirençli hale getirebilir.
|
||||
Eğer `auto` olarak ayarlanırsa, sınır `php.ini` dosyanızdaki `memory_limit` değerine göre tahmin edilecektir. Bunu yapamazsa,
|
||||
`auto` bunun yerine varsayılan olarak 2x `num_threads` olacaktır. `auto`'nun ihtiyaç duyulan iş parçacığı sayısını büyük ölçüde küçümseyebileceğini unutmayın.
|
||||
`max_threads`, PHP FPM'nin [pm.max_children](https://www.php.net/manual/en/install.fpm.configuration.php#pm.max-children) ile benzerdir. Temel fark, FrankenPHP'nin süreçler yerine
|
||||
iş parçacıkları kullanması ve gerektiğinde bunları farklı işçi komut dosyaları ve 'klasik mod' arasında otomatik olarak devretmesidir.
|
||||
|
||||
## İşçi Modu
|
||||
|
||||
[İşçi modunu](worker.md) etkinleştirmek performansı önemli ölçüde artırır,
|
||||
ancak uygulamanızın bu modla uyumlu olacak şekilde uyarlanması gerekir:
|
||||
bir işçi komut dosyası oluşturmanız ve uygulamanın bellek sızdırmadığından emin olmanız gerekir.
|
||||
|
||||
## musl Kullanmayın
|
||||
|
||||
Resmi Docker imajlarının Alpine Linux varyantı ve sağladığımız varsayılan ikili dosyalar [musl libc](https://musl.libc.org) kullanmaktadır.
|
||||
|
||||
PHP'nin, geleneksel GNU kitaplığı yerine bu alternatif C kitaplığını kullandığında [daha yavaş olduğu](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381) bilinmektedir,
|
||||
özellikle de FrankenPHP için gerekli olan ZTS modunda (iş parçacığı güvenli) derlendiğinde. Fark, yoğun iş parçacıklı bir ortamda önemli olabilir.
|
||||
|
||||
Ayrıca, [bazı hatalar yalnızca musl kullanıldığında ortaya çıkar](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl).
|
||||
|
||||
Üretim ortamlarında, glibc'ye bağlı, uygun bir optimizasyon seviyesiyle derlenmiş FrankenPHP kullanmanızı öneririz.
|
||||
|
||||
Bu, Debian Docker imajlarını kullanarak, [bakımcılarımızın .deb, .rpm veya .apk paketlerini](https://pkgs.henderkes.com) kullanarak veya [FrankenPHP'yi kaynak koddan derleyerek](compile.md) başarılabilir.
|
||||
|
||||
Daha yalın veya daha güvenli konteynerler için Alpine yerine [güçlendirilmiş bir Debian imajı](docker.md#hardening-images) kullanmayı düşünebilirsiniz.
|
||||
|
||||
## Go Çalışma Zamanı Yapılandırması
|
||||
|
||||
FrankenPHP Go ile yazılmıştır.
|
||||
|
||||
Genel olarak, Go çalışma zamanı özel bir yapılandırma gerektirmez, ancak belirli durumlarda,
|
||||
özel yapılandırma performansı artırır.
|
||||
|
||||
Muhtemelen `GODEBUG` ortam değişkenini `cgocheck=0` olarak ayarlamak isteyeceksiniz (FrankenPHP Docker imajlarındaki varsayılan değer).
|
||||
|
||||
FrankenPHP'yi konteynerlerde (Docker, Kubernetes, LXC...) çalıştırıyorsanız ve konteynerler için ayrılan belleği sınırlıyorsanız,
|
||||
`GOMEMLIMIT` ortam değişkenini mevcut bellek miktarına ayarlayın.
|
||||
|
||||
Daha fazla ayrıntı için, [Go dokümantasyon sayfasının bu konuya ayrılmış bölümünü](https://pkg.go.dev/runtime#hdr-Environment_Variables) okumanız, çalışma zamanından en iyi şekilde yararlanmak için zorunludur.
|
||||
|
||||
## `file_server`
|
||||
|
||||
Varsayılan olarak, `php_server` yönergesi,
|
||||
kök dizinde depolanan statik dosyaları (varlıkları) sunmak için otomatik olarak bir dosya sunucusu kurar.
|
||||
|
||||
Bu özellik kullanışlıdır, ancak bir maliyeti vardır.
|
||||
Bunu devre dışı bırakmak için aşağıdaki yapılandırmayı kullanın:
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
file_server off
|
||||
}
|
||||
```
|
||||
|
||||
## `try_files`
|
||||
|
||||
Statik dosyalar ve PHP dosyaları dışında, `php_server` uygulamanızın dizin dizini ve dizin dizini dosyalarını (`/path/` -> `/path/index.php`) da sunmaya çalışacaktır. Dizin dizinlerine ihtiyacınız yoksa,
|
||||
`try_files` değerini açıkça şu şekilde tanımlayarak bunları devre dışı bırakabilirsiniz:
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
root /root/to/your/app # kökü buraya açıkça eklemek daha iyi önbelleğe alma sağlar
|
||||
}
|
||||
```
|
||||
|
||||
Bu, gereksiz dosya işlemlerinin sayısını önemli ölçüde azaltabilir.
|
||||
Önceki yapılandırmanın bir işçi eşdeğeri şöyle olacaktır:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
php_server { # dosya sunucusuna hiç ihtiyacınız yoksa "php_server" yerine "php" kullanın
|
||||
root /root/to/your/app
|
||||
worker /path/to/worker.php {
|
||||
match * # tüm istekleri doğrudan işçiye gönder
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
0 gereksiz dosya sistemi işlemiyle alternatif bir yaklaşım, bunun yerine `php` yönergesini kullanmak ve dosyaları PHP'den yola göre ayırmaktır. Bu yaklaşım, tüm uygulamanızın tek bir giriş dosyası tarafından sunulması durumunda iyi çalışır.
|
||||
Statik dosyaları bir `/assets` klasörünün arkasında sunan bir örnek [yapılandırma](config.md#caddyfile-konfigürasyonu) şöyle görünebilir:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
@assets {
|
||||
path /assets/*
|
||||
}
|
||||
|
||||
# /assets arkasındaki her şey dosya sunucusu tarafından işlenir
|
||||
file_server @assets {
|
||||
root /root/to/your/app
|
||||
}
|
||||
|
||||
# /assets içinde olmayan her şey dizininiz veya işçi PHP dosyanız tarafından işlenir
|
||||
rewrite index.php
|
||||
php {
|
||||
root /root/to/your/app # kökü buraya açıkça eklemek daha iyi önbelleğe alma sağlar
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Yer Tutucular
|
||||
|
||||
`root` ve `env` yönergelerinde [yer tutucular](https://caddyserver.com/docs/conventions#placeholders) kullanabilirsiniz.
|
||||
Ancak bu, bu değerlerin önbelleğe alınmasını engeller ve önemli bir performans maliyetiyle birlikte gelir.
|
||||
|
||||
Mümkünse, bu yönergelerde yer tutuculardan kaçının.
|
||||
|
||||
## `resolve_root_symlink`
|
||||
|
||||
Varsayılan olarak, belge kökü sembolik bir bağlantıysa, FrankenPHP tarafından otomatik olarak çözümlenir (PHP'nin düzgün çalışması için bu gereklidir).
|
||||
Belge kökü bir sembolik bağlantı değilse, bu özelliği devre dışı bırakabilirsiniz.
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
resolve_root_symlink false
|
||||
}
|
||||
```
|
||||
|
||||
Bu, `root` yönergesi [yer tutucular](https://caddyserver.com/docs/conventions#placeholders) içeriyorsa performansı artıracaktır.
|
||||
Diğer durumlarda kazanç ihmal edilebilir olacaktır.
|
||||
|
||||
## Günlükler
|
||||
|
||||
Günlük kaydı açıkça çok faydalıdır, ancak tanım gereği,
|
||||
giriş/çıkış işlemleri ve bellek ayırmaları gerektirir, bu da performansı önemli ölçüde azaltır.
|
||||
[Günlük seviyesini](https://caddyserver.com/docs/caddyfile/options#log) doğru bir şekilde ayarladığınızdan emin olun,
|
||||
ve yalnızca gerekli olanı günlüğe kaydedin.
|
||||
|
||||
## PHP Performansı
|
||||
|
||||
FrankenPHP resmi PHP yorumlayıcısını kullanır.
|
||||
Tüm olağan PHP ile ilgili performans optimizasyonları FrankenPHP ile de geçerlidir.
|
||||
|
||||
Özellikle:
|
||||
|
||||
- [OPcache](https://www.php.net/manual/en/book.opcache.php)'in kurulu, etkin ve doğru şekilde yapılandırıldığını kontrol edin
|
||||
- [Composer otomatik yükleyici optimizasyonlarını](https://getcomposer.org/doc/articles/autoloader-optimization.md) etkinleştirin
|
||||
- `realpath` önbelleğinin uygulamanızın ihtiyaçları için yeterince büyük olduğundan emin olun
|
||||
- [ön yüklemeyi](https://www.php.net/manual/en/opcache.preloading.php) kullanın
|
||||
|
||||
Daha fazla ayrıntı için, [Symfony'nin bu konuya ayrılmış dokümantasyon girişini](https://symfony.com/doc/current/performance.html) okuyun
|
||||
(ipuçlarının çoğu Symfony kullanmasanız bile faydalıdır).
|
||||
|
||||
## İş Parçacığı Havuzunu Bölme
|
||||
|
||||
Uygulamaların, yüksek yük altında güvenilmez olma eğiliminde olan veya sürekli olarak 10 saniyeden fazla yanıt veren bir
|
||||
API gibi yavaş harici hizmetlerle etkileşime girmesi yaygındır.
|
||||
Bu gibi durumlarda, özel "yavaş" havuzlara sahip olmak için iş parçacığı havuzunu bölmek faydalı olabilir.
|
||||
Bu, yavaş uç noktaların tüm sunucu kaynaklarını/iş parçacıklarını tüketmesini önler ve
|
||||
bağlantı havuzuna benzer şekilde, yavaş uç noktaya giden isteklerin eş zamanlılığını sınırlar.
|
||||
|
||||
```caddyfile
|
||||
example.com {
|
||||
php_server {
|
||||
root /app/public # uygulamanızın kök dizini
|
||||
worker index.php {
|
||||
match /slow-endpoint/* # /slow-endpoint/* yoluyla eşleşen tüm istekler bu iş parçacığı havuzu tarafından işlenir
|
||||
num 1 # /slow-endpoint/* ile eşleşen istekler için minimum 1 iş parçacığı
|
||||
max_threads 20 # /slow-endpoint/* ile eşleşen istekler için gerektiğinde 20 iş parçacığına kadar izin ver
|
||||
}
|
||||
worker index.php {
|
||||
match * # diğer tüm istekler ayrı ayrı işlenir
|
||||
num 1 # diğer istekler için minimum 1 iş parçacığı, yavaş uç noktalar asılı kalmaya başlasa bile
|
||||
max_threads 20 # diğer istekler için gerektiğinde 20 iş parçacığına kadar izin ver
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Genel olarak, çok yavaş uç noktaları, mesaj kuyrukları gibi ilgili mekanizmalar kullanarak eşzamansız olarak ele almak da tavsiye edilir.
|
||||
@@ -17,7 +17,7 @@ docker run \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
### Binary Çıktısı
|
||||
### Bağımsız İkili
|
||||
|
||||
Geçerli dizinin içeriğini bir worker kullanarak sunmak için `php-server` komutunun `--worker` seçeneğini kullanın:
|
||||
|
||||
@@ -25,11 +25,23 @@ Geçerli dizinin içeriğini bir worker kullanarak sunmak için `php-server` kom
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php
|
||||
```
|
||||
|
||||
PHP uygulamanız [binary dosyaya gömülü](embed.md) ise, uygulamanın kök dizinine özel bir `Caddyfile` ekleyebilirsiniz.
|
||||
PHP uygulamanız [ikili dosyaya gömülü](embed.md) ise, uygulamanın kök dizinine özel bir `Caddyfile` ekleyebilirsiniz.
|
||||
Otomatik olarak kullanılacaktır.
|
||||
|
||||
Dosya değişikliklerinde worker'ı yeniden başlatmak ([dosya değişikliklerini izleme](config.md#watching-for-file-changes)) `--watch` seçeneğiyle de mümkündür.
|
||||
Aşağıdaki komut, `/path/to/your/app/` dizininde veya alt dizinlerde `.php` ile biten herhangi bir dosya değiştirilirse yeniden başlatmayı tetikleyecektir:
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
|
||||
```
|
||||
|
||||
Bu özellik genellikle [hot reloading](hot-reload.md) ile birlikte kullanılır.
|
||||
|
||||
## Symfony Çalışma Zamanı
|
||||
|
||||
> [!TIP]
|
||||
> Bu bölüm, FrankenPHP worker moduna yerel desteğin sunulduğu Symfony 7.4 öncesi için gereklidir.
|
||||
|
||||
FrankenPHP'nin worker modu [Symfony Runtime Component](https://symfony.com/doc/current/components/runtime.html) tarafından desteklenmektedir.
|
||||
Herhangi bir Symfony uygulamasını bir worker'da başlatmak için [PHP Runtime](https://github.com/php-runtime/runtime)'ın FrankenPHP paketini yükleyin:
|
||||
|
||||
@@ -50,19 +62,16 @@ docker run \
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
Bkz. [ilgili doküman](laravel.md#laravel-octane).
|
||||
Bkz. [özel dokümantasyon](laravel.md#laravel-octane).
|
||||
|
||||
## Özel Uygulamalar
|
||||
|
||||
Aşağıdaki örnek, üçüncü taraf bir kütüphaneye güvenmeden kendi çalışan kodunuzu nasıl oluşturacağınızı göstermektedir:
|
||||
Aşağıdaki örnek, üçüncü taraf bir kütüphaneye güvenmeden kendi worker betiğinizi nasıl oluşturacağınızı göstermektedir:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// public/index.php
|
||||
|
||||
// Bir istemci bağlantısı kesildiğinde alt komut dosyasının sonlandırılmasını önleyin
|
||||
ignore_user_abort(true);
|
||||
|
||||
// Uygulamanızı önyükleyin
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
@@ -71,26 +80,35 @@ $myApp->boot();
|
||||
|
||||
// Daha iyi performans için döngü dışında işleyici (daha az iş yapıyor)
|
||||
$handler = static function () use ($myApp) {
|
||||
// Bir istek alındığında çağrılır,
|
||||
// superglobals, php://input ve benzerleri sıfırlanır
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
try {
|
||||
// Bir istek alındığında çağrılır,
|
||||
// süper küresel değişkenler, php://input ve benzerleri sıfırlanır
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
} catch (\Throwable $exception) {
|
||||
// `set_exception_handler` yalnızca worker betiği sona erdiğinde çağrılır,
|
||||
// bu beklediğiniz gibi olmayabilir, bu yüzden istisnaları burada yakalayın ve ele alın
|
||||
(new \MyCustomExceptionHandler)->handleException($exception);
|
||||
}
|
||||
};
|
||||
|
||||
for ($nbRequests = 0, $running = true; isset($_SERVER['MAX_REQUESTS']) && ($nbRequests < ((int)$_SERVER['MAX_REQUESTS'])) && $running; ++$nbRequests) {
|
||||
$running = \frankenphp_handle_request($handler);
|
||||
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
|
||||
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
|
||||
$keepRunning = \frankenphp_handle_request($handler);
|
||||
|
||||
// HTTP yanıtını gönderdikten sonra bir şey yapın
|
||||
$myApp->terminate();
|
||||
|
||||
// Bir sayfa oluşturmanın ortasında tetiklenme olasılığını azaltmak için çöp toplayıcıyı çağırın
|
||||
gc_collect_cycles();
|
||||
|
||||
if (!$keepRunning) break;
|
||||
}
|
||||
|
||||
// Temizleme
|
||||
$myApp->shutdown();
|
||||
```
|
||||
|
||||
Ardından, uygulamanızı başlatın ve çalışanınızı yapılandırmak için `FRANKENPHP_CONFIG` ortam değişkenini kullanın:
|
||||
Ardından, uygulamanızı başlatın ve worker'ınızı yapılandırmak için `FRANKENPHP_CONFIG` ortam değişkenini kullanın:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
@@ -113,12 +131,51 @@ docker run \
|
||||
|
||||
### Belirli Sayıda İstekten Sonra Worker'ı Yeniden Başlatın
|
||||
|
||||
<!-- textlint-disable -->
|
||||
|
||||
PHP başlangıçta uzun süreli işlemler için tasarlanmadığından, hala bellek sızdıran birçok kütüphane ve eski kod vardır.
|
||||
|
||||
<!-- textlint-enable -->
|
||||
|
||||
Bu tür kodları worker modunda kullanmak için geçici bir çözüm, belirli sayıda isteği işledikten sonra worker betiğini yeniden başlatmaktır:
|
||||
|
||||
Önceki worker kod parçacığı, `MAX_REQUESTS` adlı bir ortam değişkeni ayarlayarak işlenecek maksimum istek sayısını yapılandırmaya izin verir.
|
||||
|
||||
### Worker'ları Manuel Olarak Yeniden Başlatma
|
||||
|
||||
Worker'ları [dosya değişikliklerinde](config.md#watching-for-file-changes) yeniden başlatmak mümkünken, tüm worker'ları [Caddy admin API](https://caddyserver.com/docs/api) aracılığıyla sorunsuz bir şekilde yeniden başlatmak da mümkündür. Yönetici [Caddyfile](config.md#caddyfile-config)'ınızda etkinleştirilmişse, yeniden başlatma uç noktasına aşağıdaki gibi basit bir POST isteği gönderebilirsiniz:
|
||||
|
||||
```console
|
||||
curl -X POST http://localhost:2019/frankenphp/workers/restart
|
||||
```
|
||||
|
||||
### Worker Hataları
|
||||
|
||||
Bir worker betiği sıfır olmayan bir çıkış koduyla çökerse, FrankenPHP onu üstel bir geri çekilme (exponential backoff) stratejisiyle yeniden başlatacaktır. Worker betiği, son geri çekilme süresinin 2 katından daha uzun süre çalışır durumda kalırsa, worker betiğini cezalandırmayacak ve tekrar yeniden başlatacaktır. Ancak, worker betiği kısa bir süre içinde sıfır olmayan bir çıkış koduyla başarısız olmaya devam ederse (örneğin, bir betikte yazım hatası olması durumunda), FrankenPHP `too many consecutive failures` hatasıyla çökecektir.
|
||||
|
||||
Ardışık hata sayısı, [Caddyfile](config.md#caddyfile-config)'ınızda `max_consecutive_failures` seçeneği ile yapılandırılabilir:
|
||||
|
||||
```caddyfile
|
||||
frankenphp {
|
||||
worker {
|
||||
# ...
|
||||
max_consecutive_failures 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Süper Küresel Değişkenlerin Davranışı
|
||||
|
||||
[PHP süper küresel değişkenleri](https://www.php.net/manual/en/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET`...) aşağıdaki gibi davranır:
|
||||
|
||||
- `frankenphp_handle_request()`'e ilk çağrıdan önce, süper küresel değişkenler worker betiğinin kendisine bağlı değerleri içerir
|
||||
- `frankenphp_handle_request()` çağrısı sırasında ve sonrasında, süper küresel değişkenler işlenen HTTP isteğinden üretilen değerleri içerir, `frankenphp_handle_request()`'e yapılan her çağrı süper küresel değişken değerlerini değiştirir
|
||||
|
||||
Geri çağırım içinde worker betiğinin süper küresel değişkenlerine erişmek için, bunları kopyalamalı ve kopyayı geri çağırımın kapsamına aktarmalısınız:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// frankenphp_handle_request()'e ilk çağrıdan önce worker'ın $_SERVER süper küresel değişkenini kopyalayın
|
||||
$workerServer = $_SERVER;
|
||||
|
||||
$handler = static function () use ($workerServer) {
|
||||
var_dump($_SERVER); // HTTP isteğine bağlı $_SERVER
|
||||
var_dump($workerServer); // Worker betiğinin $_SERVER'ı
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
@@ -14,6 +14,21 @@ const LANGUAGES = [
|
||||
'ru' => 'Russian',
|
||||
'tr' => 'Turkish',
|
||||
];
|
||||
const SYSTEM_PROMPT = <<<PROMPT
|
||||
You are translating the docs of the FrankenPHP server from english to other languages.
|
||||
You will receive the english version (authoritative) and a translation (possibly incomplete or incorrect).
|
||||
Your task is to produce a corrected and complete translation in the target language.
|
||||
You must strictly follow these rules:
|
||||
- You must not change the structure of the document (headings, code blocks, etc.).
|
||||
- You must not translate code, only comments inside the code.
|
||||
- You must not translate link urls, only links texts.
|
||||
- You may translate anchors to translation pages (config.md#translated-anchor), keep existing anchors as they are.
|
||||
- You must not add or remove any content, only translate what is present.
|
||||
- You must ensure that the translation is accurate and faithful to the original meaning.
|
||||
- You must write in a natural and fluent style, appropriate for technical documentation.
|
||||
- You must use the correct terminology for technical terms in the target language, don't translate technical terms if unsure.
|
||||
- You must not include any explanations or notes, only the translated document.
|
||||
PROMPT;
|
||||
|
||||
function makeGeminiRequest(string $systemPrompt, string $userPrompt, string $model, string $apiKey, int $reties = 2): string
|
||||
{
|
||||
@@ -51,21 +66,6 @@ function makeGeminiRequest(string $systemPrompt, string $userPrompt, string $mod
|
||||
|
||||
function createPrompt(string $language, string $englishFile, string $currentTranslation): array
|
||||
{
|
||||
$systemPrompt = <<<PROMPT
|
||||
You are translating the docs of the FrankenPHP server from english to other languages.
|
||||
You will receive the english version (authoritative) and a translation (possibly incomplete or incorrect).
|
||||
Your task is to produce a corrected and complete translation in the target language.
|
||||
You must strictly follow these rules:
|
||||
- You must not change the structure of the document (headings, code blocks, etc.)
|
||||
- You must not translate code, only comments and strings inside the code.
|
||||
- You must not translate links to other documentation pages, only the link text.
|
||||
- You must not add or remove any content, only translate what is present.
|
||||
- You must ensure that the translation is accurate and faithful to the original meaning.
|
||||
- You must write in a natural and fluent style, appropriate for technical documentation.
|
||||
- You must use the correct terminology for technical terms in the target language, don't translate if unsure.
|
||||
- You must not include any explanations or notes, only the translated document.
|
||||
PROMPT;
|
||||
|
||||
$languageName = LANGUAGES[$language];
|
||||
$userPrompt = <<<PROMPT
|
||||
Here is the english version of the document:
|
||||
@@ -85,7 +85,7 @@ function createPrompt(string $language, string $englishFile, string $currentTran
|
||||
```markdown
|
||||
PROMPT;
|
||||
|
||||
return [$systemPrompt, $userPrompt];
|
||||
return [SYSTEM_PROMPT, $userPrompt];
|
||||
}
|
||||
|
||||
function sanitizeMarkdown(string $markdown): string
|
||||
|
||||
115
env.go
115
env.go
@@ -1,116 +1,37 @@
|
||||
package frankenphp
|
||||
|
||||
// #cgo nocallback frankenphp_init_persistent_string
|
||||
// #cgo noescape frankenphp_init_persistent_string
|
||||
// #include "frankenphp.h"
|
||||
// #include <Zend/zend_API.h>
|
||||
// #include "types.h"
|
||||
import "C"
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func initializeEnv() map[string]*C.zend_string {
|
||||
env := os.Environ()
|
||||
envMap := make(map[string]*C.zend_string, len(env))
|
||||
var lengthOfEnv = 0
|
||||
|
||||
for _, envVar := range env {
|
||||
//export go_init_os_env
|
||||
func go_init_os_env(mainThreadEnv *C.zend_array) {
|
||||
fullEnv := os.Environ()
|
||||
lengthOfEnv = len(fullEnv)
|
||||
|
||||
for _, envVar := range fullEnv {
|
||||
key, val, _ := strings.Cut(envVar, "=")
|
||||
envMap[key] = C.frankenphp_init_persistent_string(toUnsafeChar(val), C.size_t(len(val)))
|
||||
}
|
||||
|
||||
return envMap
|
||||
}
|
||||
|
||||
// get the main thread env or the thread specific env
|
||||
func getSandboxedEnv(thread *phpThread) map[string]*C.zend_string {
|
||||
if thread.sandboxedEnv != nil {
|
||||
return thread.sandboxedEnv
|
||||
}
|
||||
|
||||
return mainThread.sandboxedEnv
|
||||
}
|
||||
|
||||
func clearSandboxedEnv(thread *phpThread) {
|
||||
if thread.sandboxedEnv == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for key, val := range thread.sandboxedEnv {
|
||||
valInMainThread, ok := mainThread.sandboxedEnv[key]
|
||||
if !ok || val != valInMainThread {
|
||||
C.free(unsafe.Pointer(val))
|
||||
}
|
||||
}
|
||||
|
||||
thread.sandboxedEnv = nil
|
||||
}
|
||||
|
||||
// if an env var already exists, it needs to be freed
|
||||
func removeEnvFromThread(thread *phpThread, key string) {
|
||||
valueInThread, existsInThread := thread.sandboxedEnv[key]
|
||||
if !existsInThread {
|
||||
return
|
||||
}
|
||||
|
||||
valueInMainThread, ok := mainThread.sandboxedEnv[key]
|
||||
if !ok || valueInThread != valueInMainThread {
|
||||
C.free(unsafe.Pointer(valueInThread))
|
||||
}
|
||||
|
||||
delete(thread.sandboxedEnv, key)
|
||||
}
|
||||
|
||||
// copy the main thread env to the thread specific env
|
||||
func cloneSandboxedEnv(thread *phpThread) {
|
||||
if thread.sandboxedEnv != nil {
|
||||
return
|
||||
}
|
||||
thread.sandboxedEnv = make(map[string]*C.zend_string, len(mainThread.sandboxedEnv))
|
||||
for key, value := range mainThread.sandboxedEnv {
|
||||
thread.sandboxedEnv[key] = value
|
||||
zkey := newPersistentZendString(key)
|
||||
zStr := newPersistentZendString(val)
|
||||
C.__hash_update_string__(mainThreadEnv, zkey, zStr)
|
||||
}
|
||||
}
|
||||
|
||||
//export go_putenv
|
||||
func go_putenv(threadIndex C.uintptr_t, str *C.char, length C.int) C.bool {
|
||||
thread := phpThreads[threadIndex]
|
||||
envString := C.GoStringN(str, length)
|
||||
cloneSandboxedEnv(thread)
|
||||
func go_putenv(name *C.char, nameLen C.int, val *C.char, valLen C.int) C.bool {
|
||||
goName := C.GoStringN(name, nameLen)
|
||||
|
||||
// Check if '=' is present in the string
|
||||
if key, val, found := strings.Cut(envString, "="); found {
|
||||
removeEnvFromThread(thread, key)
|
||||
thread.sandboxedEnv[key] = C.frankenphp_init_persistent_string(toUnsafeChar(val), C.size_t(len(val)))
|
||||
return os.Setenv(key, val) == nil
|
||||
if val == nil {
|
||||
// If no "=" is present, unset the environment variable
|
||||
return C.bool(os.Unsetenv(goName) == nil)
|
||||
}
|
||||
|
||||
// No '=', unset the environment variable
|
||||
removeEnvFromThread(thread, envString)
|
||||
return os.Unsetenv(envString) == nil
|
||||
}
|
||||
|
||||
//export go_getfullenv
|
||||
func go_getfullenv(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
|
||||
thread := phpThreads[threadIndex]
|
||||
env := getSandboxedEnv(thread)
|
||||
|
||||
for key, val := range env {
|
||||
C.add_assoc_str_ex(trackVarsArray, toUnsafeChar(key), C.size_t(len(key)), val)
|
||||
}
|
||||
}
|
||||
|
||||
//export go_getenv
|
||||
func go_getenv(threadIndex C.uintptr_t, name *C.char) (C.bool, *C.zend_string) {
|
||||
thread := phpThreads[threadIndex]
|
||||
|
||||
// Get the environment variable value
|
||||
envValue, exists := getSandboxedEnv(thread)[C.GoString(name)]
|
||||
if !exists {
|
||||
// Environment variable does not exist
|
||||
return false, nil // Return 0 to indicate failure
|
||||
}
|
||||
|
||||
return true, envValue // Return 1 to indicate success
|
||||
goVal := C.GoStringN(val, valLen)
|
||||
return C.bool(os.Setenv(goName, goVal) == nil)
|
||||
}
|
||||
|
||||
2
ext.go
2
ext.go
@@ -1,6 +1,6 @@
|
||||
package frankenphp
|
||||
|
||||
//#include "frankenphp.h"
|
||||
// #include "frankenphp.h"
|
||||
import "C"
|
||||
import (
|
||||
"sync"
|
||||
|
||||
614
frankenphp.c
614
frankenphp.c
@@ -1,15 +1,21 @@
|
||||
#include "frankenphp.h"
|
||||
#include <SAPI.h>
|
||||
#include <Zend/zend_alloc.h>
|
||||
#include <Zend/zend_exceptions.h>
|
||||
#include <Zend/zend_interfaces.h>
|
||||
#include <Zend/zend_types.h>
|
||||
#include <errno.h>
|
||||
#include <ext/session/php_session.h>
|
||||
#include <ext/spl/spl_exceptions.h>
|
||||
#include <ext/standard/head.h>
|
||||
#ifdef HAVE_PHP_SESSION
|
||||
#include <ext/session/php_session.h>
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
#include <php.h>
|
||||
#ifdef PHP_WIN32
|
||||
#include <config.w32.h>
|
||||
#else
|
||||
#include <php_config.h>
|
||||
#endif
|
||||
#include <php_ini.h>
|
||||
#include <php_main.h>
|
||||
#include <php_output.h>
|
||||
@@ -40,7 +46,7 @@ ZEND_TSRMLS_CACHE_DEFINE()
|
||||
*
|
||||
* @see https://github.com/DataDog/dd-trace-php/pull/3169 for an example
|
||||
*/
|
||||
static const char *MODULES_TO_RELOAD[] = {"filter", "session", NULL};
|
||||
static const char *MODULES_TO_RELOAD[] = {"filter", NULL};
|
||||
|
||||
frankenphp_version frankenphp_get_version() {
|
||||
return (frankenphp_version){
|
||||
@@ -71,46 +77,12 @@ frankenphp_config frankenphp_get_config() {
|
||||
|
||||
bool should_filter_var = 0;
|
||||
bool original_user_abort_setting = 0;
|
||||
frankenphp_interned_strings_t frankenphp_strings = {0};
|
||||
HashTable *main_thread_env = NULL;
|
||||
|
||||
__thread uintptr_t thread_index;
|
||||
__thread bool is_worker_thread = false;
|
||||
__thread zval *os_environment = NULL;
|
||||
__thread HashTable *worker_ini_snapshot = NULL;
|
||||
|
||||
/* Session user handler names (same structure as PS(mod_user_names)).
|
||||
* In PHP 8.2, mod_user_names is a union with .name.ps_* access.
|
||||
* In PHP 8.3+, mod_user_names is a direct struct with .ps_* access. */
|
||||
typedef struct {
|
||||
zval ps_open;
|
||||
zval ps_close;
|
||||
zval ps_read;
|
||||
zval ps_write;
|
||||
zval ps_destroy;
|
||||
zval ps_gc;
|
||||
zval ps_create_sid;
|
||||
zval ps_validate_sid;
|
||||
zval ps_update_timestamp;
|
||||
} session_user_handlers;
|
||||
|
||||
/* Macro to access PS(mod_user_names) handlers across PHP versions */
|
||||
#if PHP_VERSION_ID >= 80300
|
||||
#define PS_MOD_USER_NAMES(handler) PS(mod_user_names).handler
|
||||
#else
|
||||
#define PS_MOD_USER_NAMES(handler) PS(mod_user_names).name.handler
|
||||
#endif
|
||||
|
||||
#define FOR_EACH_SESSION_HANDLER(op) \
|
||||
op(ps_open); \
|
||||
op(ps_close); \
|
||||
op(ps_read); \
|
||||
op(ps_write); \
|
||||
op(ps_destroy); \
|
||||
op(ps_gc); \
|
||||
op(ps_create_sid); \
|
||||
op(ps_validate_sid); \
|
||||
op(ps_update_timestamp)
|
||||
|
||||
__thread session_user_handlers *worker_session_handlers_snapshot = NULL;
|
||||
__thread HashTable *sandboxed_env = NULL;
|
||||
|
||||
void frankenphp_update_local_thread_context(bool is_worker) {
|
||||
is_worker_thread = is_worker;
|
||||
@@ -222,165 +194,52 @@ static void frankenphp_release_temporary_streams() {
|
||||
ZEND_HASH_FOREACH_END();
|
||||
}
|
||||
|
||||
/* Destructor for INI snapshot hash table entries */
|
||||
static void frankenphp_ini_snapshot_dtor(zval *zv) {
|
||||
zend_string_release((zend_string *)Z_PTR_P(zv));
|
||||
}
|
||||
|
||||
/* Save the current state of modified INI entries.
|
||||
* This captures INI values set by the framework before the worker loop. */
|
||||
static void frankenphp_snapshot_ini(void) {
|
||||
if (worker_ini_snapshot != NULL) {
|
||||
return; /* Already snapshotted */
|
||||
}
|
||||
|
||||
if (EG(modified_ini_directives) == NULL) {
|
||||
/* Allocate empty table to mark as snapshotted */
|
||||
ALLOC_HASHTABLE(worker_ini_snapshot);
|
||||
zend_hash_init(worker_ini_snapshot, 0, NULL, frankenphp_ini_snapshot_dtor,
|
||||
0);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t num_modified = zend_hash_num_elements(EG(modified_ini_directives));
|
||||
ALLOC_HASHTABLE(worker_ini_snapshot);
|
||||
zend_hash_init(worker_ini_snapshot, num_modified, NULL,
|
||||
frankenphp_ini_snapshot_dtor, 0);
|
||||
|
||||
zend_ini_entry *ini_entry;
|
||||
ZEND_HASH_FOREACH_PTR(EG(modified_ini_directives), ini_entry) {
|
||||
if (ini_entry->value) {
|
||||
zend_hash_add_ptr(worker_ini_snapshot, ini_entry->name,
|
||||
zend_string_copy(ini_entry->value));
|
||||
}
|
||||
}
|
||||
ZEND_HASH_FOREACH_END();
|
||||
}
|
||||
|
||||
/* Restore INI values to the state captured by frankenphp_snapshot_ini().
|
||||
* - Entries in snapshot with changed values: restore to snapshot value
|
||||
* - Entries not in snapshot: restore to startup default */
|
||||
static void frankenphp_restore_ini(void) {
|
||||
if (worker_ini_snapshot == NULL || EG(modified_ini_directives) == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
zend_ini_entry *ini_entry;
|
||||
zend_string *snapshot_value;
|
||||
zend_string *entry_name;
|
||||
|
||||
/* Collect entries to restore to default in a separate array.
|
||||
* We cannot call zend_restore_ini_entry() during iteration because
|
||||
* it calls zend_hash_del() on EG(modified_ini_directives). */
|
||||
uint32_t max_entries = zend_hash_num_elements(EG(modified_ini_directives));
|
||||
zend_string **entries_to_restore =
|
||||
max_entries ? emalloc(max_entries * sizeof(zend_string *)) : NULL;
|
||||
size_t restore_count = 0;
|
||||
|
||||
ZEND_HASH_FOREACH_STR_KEY_PTR(EG(modified_ini_directives), entry_name,
|
||||
ini_entry) {
|
||||
snapshot_value = zend_hash_find_ptr(worker_ini_snapshot, entry_name);
|
||||
|
||||
if (snapshot_value == NULL) {
|
||||
/* Entry was not in snapshot: collect for restore to startup default */
|
||||
entries_to_restore[restore_count++] = zend_string_copy(entry_name);
|
||||
} else if (!zend_string_equals(ini_entry->value, snapshot_value)) {
|
||||
/* Entry was in snapshot but value changed: restore to snapshot value.
|
||||
* zend_alter_ini_entry() does not delete from modified_ini_directives. */
|
||||
zend_alter_ini_entry(entry_name, snapshot_value, PHP_INI_USER,
|
||||
PHP_INI_STAGE_RUNTIME);
|
||||
}
|
||||
/* else: Entry in snapshot with same value, nothing to do */
|
||||
}
|
||||
ZEND_HASH_FOREACH_END();
|
||||
|
||||
/* Now restore entries to default outside of iteration */
|
||||
for (size_t i = 0; i < restore_count; i++) {
|
||||
zend_restore_ini_entry(entries_to_restore[i], PHP_INI_STAGE_RUNTIME);
|
||||
zend_string_release(entries_to_restore[i]);
|
||||
}
|
||||
if (entries_to_restore) {
|
||||
efree(entries_to_restore);
|
||||
}
|
||||
}
|
||||
|
||||
/* Save session user handlers set before the worker loop.
|
||||
* This allows frameworks to define custom session handlers that persist. */
|
||||
static void frankenphp_snapshot_session_handlers(void) {
|
||||
if (worker_session_handlers_snapshot != NULL) {
|
||||
return; /* Already snapshotted */
|
||||
}
|
||||
|
||||
/* Check if session module is loaded */
|
||||
if (zend_hash_str_find_ptr(&module_registry, "session",
|
||||
sizeof("session") - 1) == NULL) {
|
||||
return; /* Session module not available */
|
||||
}
|
||||
|
||||
/* Check if user session handlers are defined */
|
||||
if (Z_ISUNDEF(PS_MOD_USER_NAMES(ps_open))) {
|
||||
return; /* No user handlers to snapshot */
|
||||
}
|
||||
|
||||
worker_session_handlers_snapshot = emalloc(sizeof(session_user_handlers));
|
||||
|
||||
/* Copy each handler zval with incremented reference count */
|
||||
#define SNAPSHOT_HANDLER(h) \
|
||||
if (!Z_ISUNDEF(PS_MOD_USER_NAMES(h))) { \
|
||||
ZVAL_COPY(&worker_session_handlers_snapshot->h, &PS_MOD_USER_NAMES(h)); \
|
||||
} else { \
|
||||
ZVAL_UNDEF(&worker_session_handlers_snapshot->h); \
|
||||
}
|
||||
|
||||
FOR_EACH_SESSION_HANDLER(SNAPSHOT_HANDLER);
|
||||
|
||||
#undef SNAPSHOT_HANDLER
|
||||
}
|
||||
|
||||
/* Restore session user handlers from snapshot after RSHUTDOWN freed them. */
|
||||
static void frankenphp_restore_session_handlers(void) {
|
||||
if (worker_session_handlers_snapshot == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Restore each handler zval.
|
||||
* Session RSHUTDOWN already freed the handlers via zval_ptr_dtor and set
|
||||
* them to UNDEF, so we don't need to destroy them again. We simply copy
|
||||
* from the snapshot (which holds its own reference). */
|
||||
#define RESTORE_HANDLER(h) \
|
||||
if (!Z_ISUNDEF(worker_session_handlers_snapshot->h)) { \
|
||||
ZVAL_COPY(&PS_MOD_USER_NAMES(h), &worker_session_handlers_snapshot->h); \
|
||||
}
|
||||
|
||||
FOR_EACH_SESSION_HANDLER(RESTORE_HANDLER);
|
||||
|
||||
#undef RESTORE_HANDLER
|
||||
}
|
||||
|
||||
/* Free worker state when the worker script terminates. */
|
||||
static void frankenphp_cleanup_worker_state(void) {
|
||||
/* Free INI snapshot */
|
||||
if (worker_ini_snapshot != NULL) {
|
||||
zend_hash_destroy(worker_ini_snapshot);
|
||||
FREE_HASHTABLE(worker_ini_snapshot);
|
||||
worker_ini_snapshot = NULL;
|
||||
}
|
||||
|
||||
/* Free session handlers snapshot */
|
||||
if (worker_session_handlers_snapshot != NULL) {
|
||||
#define FREE_HANDLER(h) \
|
||||
if (!Z_ISUNDEF(worker_session_handlers_snapshot->h)) { \
|
||||
zval_ptr_dtor(&worker_session_handlers_snapshot->h); \
|
||||
}
|
||||
|
||||
FOR_EACH_SESSION_HANDLER(FREE_HANDLER);
|
||||
|
||||
#undef FREE_HANDLER
|
||||
|
||||
efree(worker_session_handlers_snapshot);
|
||||
worker_session_handlers_snapshot = NULL;
|
||||
}
|
||||
#ifdef HAVE_PHP_SESSION
|
||||
/* Reset session state between worker requests, preserving user handlers.
|
||||
* Based on php_rshutdown_session_globals() + php_rinit_session_globals(). */
|
||||
static void frankenphp_reset_session_state(void) {
|
||||
if (PS(session_status) == php_session_active) {
|
||||
php_session_flush(1);
|
||||
}
|
||||
|
||||
if (!Z_ISUNDEF(PS(http_session_vars))) {
|
||||
zval_ptr_dtor(&PS(http_session_vars));
|
||||
ZVAL_UNDEF(&PS(http_session_vars));
|
||||
}
|
||||
|
||||
if (PS(mod_data) || PS(mod_user_implemented)) {
|
||||
zend_try { PS(mod)->s_close(&PS(mod_data)); }
|
||||
zend_end_try();
|
||||
}
|
||||
|
||||
if (PS(id)) {
|
||||
zend_string_release_ex(PS(id), 0);
|
||||
PS(id) = NULL;
|
||||
}
|
||||
|
||||
if (PS(session_vars)) {
|
||||
zend_string_release_ex(PS(session_vars), 0);
|
||||
PS(session_vars) = NULL;
|
||||
}
|
||||
|
||||
/* PS(mod_user_class_name) and PS(mod_user_names) are preserved */
|
||||
|
||||
#if PHP_VERSION_ID >= 80300
|
||||
if (PS(session_started_filename)) {
|
||||
zend_string_release(PS(session_started_filename));
|
||||
PS(session_started_filename) = NULL;
|
||||
PS(session_started_lineno) = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
PS(session_status) = php_session_none;
|
||||
PS(in_save_handler) = 0;
|
||||
PS(set_handler) = 0;
|
||||
PS(mod_data) = NULL;
|
||||
PS(mod_user_is_open) = 0;
|
||||
PS(define_sid) = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Adapted from php_request_shutdown */
|
||||
static void frankenphp_worker_request_shutdown() {
|
||||
@@ -397,6 +256,10 @@ static void frankenphp_worker_request_shutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_PHP_SESSION
|
||||
frankenphp_reset_session_state();
|
||||
#endif
|
||||
|
||||
/* Shutdown output layer (send the set HTTP headers, cleanup output handlers,
|
||||
* etc.) */
|
||||
zend_try { php_output_deactivate(); }
|
||||
@@ -416,19 +279,13 @@ bool frankenphp_shutdown_dummy_request(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Snapshot INI and session handlers BEFORE shutdown.
|
||||
* The framework has set these up before the worker loop, and we want
|
||||
* to preserve them. Session RSHUTDOWN will free the handlers. */
|
||||
frankenphp_snapshot_ini();
|
||||
frankenphp_snapshot_session_handlers();
|
||||
|
||||
frankenphp_worker_request_shutdown();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void get_full_env(zval *track_vars_array) {
|
||||
go_getfullenv(thread_index, track_vars_array);
|
||||
zend_hash_copy(Z_ARR_P(track_vars_array), main_thread_env, NULL);
|
||||
}
|
||||
|
||||
/* Adapted from php_request_startup() */
|
||||
@@ -477,12 +334,6 @@ static int frankenphp_worker_request_startup() {
|
||||
|
||||
frankenphp_reset_super_globals();
|
||||
|
||||
/* Restore INI values changed during the previous request back to their
|
||||
* snapshot state (captured in frankenphp_shutdown_dummy_request).
|
||||
* This ensures framework settings persist while request-level changes
|
||||
* are reset. */
|
||||
frankenphp_restore_ini();
|
||||
|
||||
const char **module_name;
|
||||
zend_module_entry *module;
|
||||
for (module_name = MODULES_TO_RELOAD; *module_name; module_name++) {
|
||||
@@ -492,12 +343,6 @@ static int frankenphp_worker_request_startup() {
|
||||
module->request_startup_func(module->type, module->module_number);
|
||||
}
|
||||
}
|
||||
|
||||
/* Restore session handlers AFTER session RINIT.
|
||||
* Session RSHUTDOWN frees mod_user_names callbacks, so we must restore
|
||||
* them before user code runs. This must happen after RINIT because
|
||||
* session RINIT may reset some state. */
|
||||
frankenphp_restore_session_handlers();
|
||||
}
|
||||
zend_catch { retval = FAILURE; }
|
||||
zend_end_try();
|
||||
@@ -537,39 +382,72 @@ PHP_FUNCTION(frankenphp_putenv) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
if (go_putenv(thread_index, setting, (int)setting_len)) {
|
||||
RETURN_TRUE;
|
||||
} else {
|
||||
RETURN_FALSE;
|
||||
if (setting_len == 0 || setting[0] == '=') {
|
||||
zend_argument_value_error(1, "must have a valid syntax");
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
if (sandboxed_env == NULL) {
|
||||
sandboxed_env = zend_array_dup(main_thread_env);
|
||||
}
|
||||
|
||||
/* cut at null byte to stay consistent with regular putenv */
|
||||
char *null_pos = memchr(setting, '\0', setting_len);
|
||||
if (null_pos != NULL) {
|
||||
setting_len = null_pos - setting;
|
||||
}
|
||||
|
||||
/* cut the string at the first '=' */
|
||||
char *eq_pos = memchr(setting, '=', setting_len);
|
||||
bool success = true;
|
||||
|
||||
/* no '=' found, delete the variable */
|
||||
if (eq_pos == NULL) {
|
||||
success = go_putenv(setting, (int)setting_len, NULL, 0);
|
||||
if (success) {
|
||||
zend_hash_str_del(sandboxed_env, setting, setting_len);
|
||||
}
|
||||
|
||||
RETURN_BOOL(success);
|
||||
}
|
||||
|
||||
size_t name_len = eq_pos - setting;
|
||||
size_t value_len =
|
||||
(setting_len > name_len + 1) ? (setting_len - name_len - 1) : 0;
|
||||
success = go_putenv(setting, (int)name_len, eq_pos + 1, (int)value_len);
|
||||
if (success) {
|
||||
zval val = {0};
|
||||
ZVAL_STRINGL(&val, eq_pos + 1, value_len);
|
||||
zend_hash_str_update(sandboxed_env, setting, name_len, &val);
|
||||
}
|
||||
|
||||
RETURN_BOOL(success);
|
||||
} /* }}} */
|
||||
|
||||
/* {{{ Call go's getenv to prevent race conditions */
|
||||
/* {{{ Get the env from the sandboxed environment */
|
||||
PHP_FUNCTION(frankenphp_getenv) {
|
||||
char *name = NULL;
|
||||
size_t name_len = 0;
|
||||
zend_string *name = NULL;
|
||||
bool local_only = 0;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(0, 2)
|
||||
Z_PARAM_OPTIONAL
|
||||
Z_PARAM_STRING_OR_NULL(name, name_len)
|
||||
Z_PARAM_STR_OR_NULL(name)
|
||||
Z_PARAM_BOOL(local_only)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
if (!name) {
|
||||
array_init(return_value);
|
||||
get_full_env(return_value);
|
||||
HashTable *ht = sandboxed_env ? sandboxed_env : main_thread_env;
|
||||
|
||||
if (!name) {
|
||||
RETURN_ARR(zend_array_dup(ht));
|
||||
return;
|
||||
}
|
||||
|
||||
struct go_getenv_return result = go_getenv(thread_index, name);
|
||||
|
||||
if (result.r0) {
|
||||
// Return the single environment variable as a string
|
||||
RETVAL_STR(result.r1);
|
||||
zval *env_val = zend_hash_find(ht, name);
|
||||
if (env_val && Z_TYPE_P(env_val) == IS_STRING) {
|
||||
zend_string *str = Z_STR_P(env_val);
|
||||
zend_string_addref(str);
|
||||
RETVAL_STR(str);
|
||||
} else {
|
||||
// Environment variable does not exist
|
||||
RETVAL_FALSE;
|
||||
}
|
||||
} /* }}} */
|
||||
@@ -692,6 +570,11 @@ PHP_FUNCTION(frankenphp_handle_request) {
|
||||
|
||||
if (zend_call_function(&fci, &fcc) == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
|
||||
callback_ret = &retval;
|
||||
|
||||
/* pass NULL instead of the NULL zval as return value */
|
||||
if (Z_TYPE(retval) == IS_NULL) {
|
||||
callback_ret = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -842,14 +725,6 @@ static zend_module_entry frankenphp_module = {
|
||||
TOSTRING(FRANKENPHP_VERSION),
|
||||
STANDARD_MODULE_PROPERTIES};
|
||||
|
||||
static void frankenphp_request_shutdown() {
|
||||
if (is_worker_thread) {
|
||||
frankenphp_cleanup_worker_state();
|
||||
}
|
||||
php_request_shutdown((void *)0);
|
||||
frankenphp_free_request_context();
|
||||
}
|
||||
|
||||
static int frankenphp_startup(sapi_module_struct *sapi_module) {
|
||||
php_import_environment_variables = get_full_env;
|
||||
|
||||
@@ -930,72 +805,53 @@ static inline void frankenphp_register_trusted_var(zend_string *z_key,
|
||||
}
|
||||
}
|
||||
|
||||
void frankenphp_register_single(zend_string *z_key, char *value, size_t val_len,
|
||||
zval *track_vars_array) {
|
||||
HashTable *ht = Z_ARRVAL_P(track_vars_array);
|
||||
frankenphp_register_trusted_var(z_key, value, val_len, ht);
|
||||
}
|
||||
|
||||
/* Register known $_SERVER variables in bulk to avoid cgo overhead */
|
||||
void frankenphp_register_bulk(
|
||||
zval *track_vars_array, ht_key_value_pair remote_addr,
|
||||
ht_key_value_pair remote_host, ht_key_value_pair remote_port,
|
||||
ht_key_value_pair document_root, ht_key_value_pair path_info,
|
||||
ht_key_value_pair php_self, ht_key_value_pair document_uri,
|
||||
ht_key_value_pair script_filename, ht_key_value_pair script_name,
|
||||
ht_key_value_pair https, ht_key_value_pair ssl_protocol,
|
||||
ht_key_value_pair request_scheme, ht_key_value_pair server_name,
|
||||
ht_key_value_pair server_port, ht_key_value_pair content_length,
|
||||
ht_key_value_pair gateway_interface, ht_key_value_pair server_protocol,
|
||||
ht_key_value_pair server_software, ht_key_value_pair http_host,
|
||||
ht_key_value_pair auth_type, ht_key_value_pair remote_ident,
|
||||
ht_key_value_pair request_uri, ht_key_value_pair ssl_cipher) {
|
||||
void frankenphp_register_server_vars(zval *track_vars_array,
|
||||
frankenphp_server_vars vars) {
|
||||
HashTable *ht = Z_ARRVAL_P(track_vars_array);
|
||||
frankenphp_register_trusted_var(remote_addr.key, remote_addr.val,
|
||||
remote_addr.val_len, ht);
|
||||
frankenphp_register_trusted_var(remote_host.key, remote_host.val,
|
||||
remote_host.val_len, ht);
|
||||
frankenphp_register_trusted_var(remote_port.key, remote_port.val,
|
||||
remote_port.val_len, ht);
|
||||
frankenphp_register_trusted_var(document_root.key, document_root.val,
|
||||
document_root.val_len, ht);
|
||||
frankenphp_register_trusted_var(path_info.key, path_info.val,
|
||||
path_info.val_len, ht);
|
||||
frankenphp_register_trusted_var(php_self.key, php_self.val, php_self.val_len,
|
||||
ht);
|
||||
frankenphp_register_trusted_var(document_uri.key, document_uri.val,
|
||||
document_uri.val_len, ht);
|
||||
frankenphp_register_trusted_var(script_filename.key, script_filename.val,
|
||||
script_filename.val_len, ht);
|
||||
frankenphp_register_trusted_var(script_name.key, script_name.val,
|
||||
script_name.val_len, ht);
|
||||
frankenphp_register_trusted_var(https.key, https.val, https.val_len, ht);
|
||||
frankenphp_register_trusted_var(ssl_protocol.key, ssl_protocol.val,
|
||||
ssl_protocol.val_len, ht);
|
||||
frankenphp_register_trusted_var(ssl_cipher.key, ssl_cipher.val,
|
||||
ssl_cipher.val_len, ht);
|
||||
frankenphp_register_trusted_var(request_scheme.key, request_scheme.val,
|
||||
request_scheme.val_len, ht);
|
||||
frankenphp_register_trusted_var(server_name.key, server_name.val,
|
||||
server_name.val_len, ht);
|
||||
frankenphp_register_trusted_var(server_port.key, server_port.val,
|
||||
server_port.val_len, ht);
|
||||
frankenphp_register_trusted_var(content_length.key, content_length.val,
|
||||
content_length.val_len, ht);
|
||||
frankenphp_register_trusted_var(gateway_interface.key, gateway_interface.val,
|
||||
gateway_interface.val_len, ht);
|
||||
frankenphp_register_trusted_var(server_protocol.key, server_protocol.val,
|
||||
server_protocol.val_len, ht);
|
||||
frankenphp_register_trusted_var(server_software.key, server_software.val,
|
||||
server_software.val_len, ht);
|
||||
frankenphp_register_trusted_var(http_host.key, http_host.val,
|
||||
http_host.val_len, ht);
|
||||
frankenphp_register_trusted_var(auth_type.key, auth_type.val,
|
||||
auth_type.val_len, ht);
|
||||
frankenphp_register_trusted_var(remote_ident.key, remote_ident.val,
|
||||
remote_ident.val_len, ht);
|
||||
frankenphp_register_trusted_var(request_uri.key, request_uri.val,
|
||||
request_uri.val_len, ht);
|
||||
zend_hash_extend(ht, vars.total_num_vars, 0);
|
||||
|
||||
// update values with variable strings
|
||||
#define FRANKENPHP_REGISTER_VAR(name) \
|
||||
frankenphp_register_trusted_var(frankenphp_strings.name, vars.name, \
|
||||
vars.name##_len, ht)
|
||||
|
||||
FRANKENPHP_REGISTER_VAR(remote_addr);
|
||||
FRANKENPHP_REGISTER_VAR(remote_host);
|
||||
FRANKENPHP_REGISTER_VAR(remote_port);
|
||||
FRANKENPHP_REGISTER_VAR(document_root);
|
||||
FRANKENPHP_REGISTER_VAR(path_info);
|
||||
FRANKENPHP_REGISTER_VAR(php_self);
|
||||
FRANKENPHP_REGISTER_VAR(document_uri);
|
||||
FRANKENPHP_REGISTER_VAR(script_filename);
|
||||
FRANKENPHP_REGISTER_VAR(script_name);
|
||||
FRANKENPHP_REGISTER_VAR(ssl_cipher);
|
||||
FRANKENPHP_REGISTER_VAR(server_name);
|
||||
FRANKENPHP_REGISTER_VAR(server_port);
|
||||
FRANKENPHP_REGISTER_VAR(content_length);
|
||||
FRANKENPHP_REGISTER_VAR(server_protocol);
|
||||
FRANKENPHP_REGISTER_VAR(http_host);
|
||||
FRANKENPHP_REGISTER_VAR(request_uri);
|
||||
|
||||
#undef FRANKENPHP_REGISTER_VAR
|
||||
|
||||
/* update values with hard-coded zend_strings */
|
||||
zval zv;
|
||||
ZVAL_STR(&zv, frankenphp_strings.cgi11);
|
||||
zend_hash_update_ind(ht, frankenphp_strings.gateway_interface, &zv);
|
||||
ZVAL_STR(&zv, frankenphp_strings.frankenphp);
|
||||
zend_hash_update_ind(ht, frankenphp_strings.server_software, &zv);
|
||||
ZVAL_STR(&zv, vars.request_scheme);
|
||||
zend_hash_update_ind(ht, frankenphp_strings.request_scheme, &zv);
|
||||
ZVAL_STR(&zv, vars.ssl_protocol);
|
||||
zend_hash_update_ind(ht, frankenphp_strings.ssl_protocol, &zv);
|
||||
ZVAL_STR(&zv, vars.https);
|
||||
zend_hash_update_ind(ht, frankenphp_strings.https, &zv);
|
||||
|
||||
/* update values with always empty strings */
|
||||
ZVAL_EMPTY_STRING(&zv);
|
||||
zend_hash_update_ind(ht, frankenphp_strings.auth_type, &zv);
|
||||
zend_hash_update_ind(ht, frankenphp_strings.remote_ident, &zv);
|
||||
}
|
||||
|
||||
/** Create an immutable zend_string that lasts for the whole process **/
|
||||
@@ -1010,7 +866,22 @@ zend_string *frankenphp_init_persistent_string(const char *string, size_t len) {
|
||||
return z_string;
|
||||
}
|
||||
|
||||
static void
|
||||
/* initialize all hard-coded zend_strings once per process */
|
||||
static void frankenphp_init_interned_strings(void) {
|
||||
if (frankenphp_strings.remote_addr != NULL) {
|
||||
return; /* already initialized */
|
||||
}
|
||||
|
||||
#define F_INITIALIZE_FIELD(name, str) \
|
||||
frankenphp_strings.name = \
|
||||
frankenphp_init_persistent_string(str, sizeof(str) - 1);
|
||||
|
||||
FRANKENPHP_INTERNED_STRINGS_LIST(F_INITIALIZE_FIELD)
|
||||
#undef F_INITIALIZE_FIELD
|
||||
}
|
||||
|
||||
/* Register variables from SG(request_info) into $_SERVER */
|
||||
static inline void
|
||||
frankenphp_register_variable_from_request_info(zend_string *zKey, char *value,
|
||||
bool must_be_present,
|
||||
zval *track_vars_array) {
|
||||
@@ -1023,23 +894,31 @@ frankenphp_register_variable_from_request_info(zend_string *zKey, char *value,
|
||||
}
|
||||
}
|
||||
|
||||
void frankenphp_register_variables_from_request_info(
|
||||
zval *track_vars_array, zend_string *content_type,
|
||||
zend_string *path_translated, zend_string *query_string,
|
||||
zend_string *auth_user, zend_string *request_method) {
|
||||
static void
|
||||
frankenphp_register_variables_from_request_info(zval *track_vars_array) {
|
||||
frankenphp_register_variable_from_request_info(
|
||||
content_type, (char *)SG(request_info).content_type, true,
|
||||
frankenphp_strings.content_type, (char *)SG(request_info).content_type,
|
||||
true, track_vars_array);
|
||||
frankenphp_register_variable_from_request_info(
|
||||
frankenphp_strings.path_translated,
|
||||
(char *)SG(request_info).path_translated, false, track_vars_array);
|
||||
frankenphp_register_variable_from_request_info(
|
||||
frankenphp_strings.query_string, SG(request_info).query_string, true,
|
||||
track_vars_array);
|
||||
frankenphp_register_variable_from_request_info(
|
||||
path_translated, (char *)SG(request_info).path_translated, false,
|
||||
frankenphp_strings.remote_user, (char *)SG(request_info).auth_user, false,
|
||||
track_vars_array);
|
||||
frankenphp_register_variable_from_request_info(
|
||||
query_string, SG(request_info).query_string, true, track_vars_array);
|
||||
frankenphp_register_variable_from_request_info(
|
||||
auth_user, (char *)SG(request_info).auth_user, false, track_vars_array);
|
||||
frankenphp_register_variable_from_request_info(
|
||||
request_method, (char *)SG(request_info).request_method, false,
|
||||
track_vars_array);
|
||||
frankenphp_strings.request_method,
|
||||
(char *)SG(request_info).request_method, false, track_vars_array);
|
||||
}
|
||||
|
||||
/* Only hard-coded keys may be registered this way */
|
||||
void frankenphp_register_known_variable(zend_string *z_key, char *value,
|
||||
size_t val_len,
|
||||
zval *track_vars_array) {
|
||||
frankenphp_register_trusted_var(z_key, value, val_len,
|
||||
Z_ARRVAL_P(track_vars_array));
|
||||
}
|
||||
|
||||
/* variables with user-defined keys must be registered safely
|
||||
@@ -1072,34 +951,17 @@ static inline void register_server_variable_filtered(const char *key,
|
||||
static void frankenphp_register_variables(zval *track_vars_array) {
|
||||
/* https://www.php.net/manual/en/reserved.variables.server.php */
|
||||
|
||||
/* In CGI mode, we consider the environment to be a part of the server
|
||||
* variables.
|
||||
/* In CGI mode, the environment is part of the $_SERVER variables.
|
||||
* $_SERVER and $_ENV should only contain values from the original
|
||||
* environment, not values added though putenv
|
||||
*/
|
||||
zend_hash_copy(Z_ARR_P(track_vars_array), main_thread_env, NULL);
|
||||
|
||||
/* in non-worker mode we import the os environment regularly */
|
||||
if (!is_worker_thread) {
|
||||
get_full_env(track_vars_array);
|
||||
// php_import_environment_variables(track_vars_array);
|
||||
go_register_variables(thread_index, track_vars_array);
|
||||
return;
|
||||
}
|
||||
/* import CGI variables from the request context in go */
|
||||
go_register_server_variables(thread_index, track_vars_array);
|
||||
|
||||
/* In worker mode we cache the os environment */
|
||||
if (os_environment == NULL) {
|
||||
os_environment = malloc(sizeof(zval));
|
||||
if (os_environment == NULL) {
|
||||
php_error(E_ERROR, "Failed to allocate memory for os_environment");
|
||||
|
||||
return;
|
||||
}
|
||||
array_init(os_environment);
|
||||
get_full_env(os_environment);
|
||||
// php_import_environment_variables(os_environment);
|
||||
}
|
||||
zend_hash_copy(Z_ARR_P(track_vars_array), Z_ARR_P(os_environment),
|
||||
(copy_ctor_func_t)zval_add_ref);
|
||||
|
||||
go_register_variables(thread_index, track_vars_array);
|
||||
/* Some variables are already present in SG(request_info) */
|
||||
frankenphp_register_variables_from_request_info(track_vars_array);
|
||||
}
|
||||
|
||||
static void frankenphp_log_message(const char *message, int syslog_type_int) {
|
||||
@@ -1107,10 +969,12 @@ static void frankenphp_log_message(const char *message, int syslog_type_int) {
|
||||
}
|
||||
|
||||
static char *frankenphp_getenv(const char *name, size_t name_len) {
|
||||
struct go_getenv_return result = go_getenv(thread_index, (char *)name);
|
||||
HashTable *ht = sandboxed_env ? sandboxed_env : main_thread_env;
|
||||
|
||||
if (result.r0) {
|
||||
return result.r1->val;
|
||||
zval *env_val = zend_hash_str_find(ht, name, name_len);
|
||||
if (env_val && Z_TYPE_P(env_val) == IS_STRING) {
|
||||
zend_string *str = Z_STR_P(env_val);
|
||||
return ZSTR_VAL(str);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@@ -1196,6 +1060,7 @@ static void *php_thread(void *arg) {
|
||||
}
|
||||
|
||||
static void *php_main(void *arg) {
|
||||
#ifndef ZEND_WIN32
|
||||
/*
|
||||
* SIGPIPE must be masked in non-Go threads:
|
||||
* https://pkg.go.dev/os/signal#hdr-Go_programs_that_use_cgo_or_SWIG
|
||||
@@ -1208,6 +1073,7 @@ static void *php_main(void *arg) {
|
||||
perror("failed to block SIGPIPE");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
#endif
|
||||
|
||||
set_thread_name("php-main");
|
||||
|
||||
@@ -1225,6 +1091,32 @@ static void *php_main(void *arg) {
|
||||
|
||||
sapi_startup(&frankenphp_sapi_module);
|
||||
|
||||
/* TODO: adapted from https://github.com/php/php-src/pull/16958, remove when
|
||||
* merged. */
|
||||
#ifdef PHP_WIN32
|
||||
{
|
||||
const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
|
||||
HMODULE module;
|
||||
/* Use a larger buffer to support long module paths on Windows. */
|
||||
wchar_t filename[32768];
|
||||
if (GetModuleHandleExW(flags, (LPCWSTR)&frankenphp_sapi_module, &module)) {
|
||||
const DWORD filename_capacity = (DWORD)_countof(filename);
|
||||
DWORD len = GetModuleFileNameW(module, filename, filename_capacity);
|
||||
if (len > 0 && len < filename_capacity) {
|
||||
wchar_t *slash = wcsrchr(filename, L'\\');
|
||||
if (slash) {
|
||||
*slash = L'\0';
|
||||
if (!SetDllDirectoryW(filename)) {
|
||||
fprintf(stderr, "Warning: SetDllDirectoryW failed (error %lu)\n",
|
||||
GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ZEND_MAX_EXECUTION_TIMERS
|
||||
/* overwrite php.ini with custom user settings */
|
||||
char *php_ini_overrides = go_get_custom_php_ini(false);
|
||||
@@ -1238,6 +1130,15 @@ static void *php_main(void *arg) {
|
||||
frankenphp_sapi_module.ini_entries = php_ini_overrides;
|
||||
}
|
||||
|
||||
frankenphp_init_interned_strings();
|
||||
|
||||
/* take a snapshot of the environment for sandboxing */
|
||||
if (main_thread_env == NULL) {
|
||||
main_thread_env = pemalloc(sizeof(HashTable), 1);
|
||||
zend_hash_init(main_thread_env, 8, NULL, NULL, 1);
|
||||
go_init_os_env(main_thread_env);
|
||||
}
|
||||
|
||||
frankenphp_sapi_module.startup(&frankenphp_sapi_module);
|
||||
|
||||
/* check if a default filter is set in php.ini and only filter if
|
||||
@@ -1293,7 +1194,8 @@ static int frankenphp_request_startup() {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
frankenphp_request_shutdown();
|
||||
php_request_shutdown((void *)0);
|
||||
frankenphp_free_request_context();
|
||||
|
||||
return FAILURE;
|
||||
}
|
||||
@@ -1319,16 +1221,16 @@ int frankenphp_execute_script(char *file_name) {
|
||||
zend_catch { status = EG(exit_status); }
|
||||
zend_end_try();
|
||||
|
||||
// free the cached os environment before shutting down the script
|
||||
if (os_environment != NULL) {
|
||||
zval_ptr_dtor(os_environment);
|
||||
free(os_environment);
|
||||
os_environment = NULL;
|
||||
}
|
||||
|
||||
zend_destroy_file_handle(&file_handle);
|
||||
|
||||
frankenphp_request_shutdown();
|
||||
/* Reset the sandboxed environment */
|
||||
if (sandboxed_env != NULL) {
|
||||
zend_hash_release(sandboxed_env);
|
||||
sandboxed_env = NULL;
|
||||
}
|
||||
|
||||
php_request_shutdown((void *)0);
|
||||
frankenphp_free_request_context();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ package frankenphp
|
||||
|
||||
// #include <stdlib.h>
|
||||
// #include <stdint.h>
|
||||
// #include "frankenphp.h"
|
||||
// #include <php_variables.h>
|
||||
// #include <zend_llist.h>
|
||||
// #include <SAPI.h>
|
||||
// #include "frankenphp.h"
|
||||
import "C"
|
||||
import (
|
||||
"bytes"
|
||||
@@ -211,7 +211,7 @@ func calculateMaxThreads(opt *opt) (numWorkers int, _ error) {
|
||||
return numWorkers, nil
|
||||
}
|
||||
|
||||
if !maxThreadsIsSet && !numThreadsIsSet {
|
||||
if !numThreadsIsSet {
|
||||
if numWorkers >= maxProcs {
|
||||
// Start at least as many threads as workers, and keep a free thread to handle requests in non-worker mode
|
||||
opt.numThreads = numWorkers + 1
|
||||
@@ -276,6 +276,10 @@ func Init(options ...Option) error {
|
||||
|
||||
maxWaitTime = opt.maxWaitTime
|
||||
|
||||
if opt.maxIdleTime > 0 {
|
||||
maxIdleTime = opt.maxIdleTime
|
||||
}
|
||||
|
||||
workerThreadCount, err := calculateMaxThreads(opt)
|
||||
if err != nil {
|
||||
Shutdown()
|
||||
@@ -418,7 +422,7 @@ func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error
|
||||
}
|
||||
|
||||
//export go_ub_write
|
||||
func go_ub_write(threadIndex C.uintptr_t, cBuf *C.char, length C.int) (C.size_t, C.bool) {
|
||||
func go_ub_write(threadIndex C.uintptr_t, cBuf *C.char, length C.size_t) (C.size_t, C.bool) {
|
||||
thread := phpThreads[threadIndex]
|
||||
fc := thread.frankenPHPContext()
|
||||
|
||||
@@ -500,11 +504,11 @@ func go_apache_request_headers(threadIndex C.uintptr_t) (*C.go_string, C.size_t)
|
||||
return sd, C.size_t(len(fc.request.Header))
|
||||
}
|
||||
|
||||
func addHeader(ctx context.Context, fc *frankenPHPContext, cString *C.char, length C.int) {
|
||||
key, val := splitRawHeader(cString, int(length))
|
||||
func addHeader(ctx context.Context, fc *frankenPHPContext, h *C.sapi_header_struct) {
|
||||
key, val := splitRawHeader(h.header, int(h.header_len))
|
||||
if key == "" {
|
||||
if fc.logger.Enabled(ctx, slog.LevelDebug) {
|
||||
fc.logger.LogAttrs(ctx, slog.LevelDebug, "invalid header", slog.String("header", C.GoStringN(cString, length)))
|
||||
fc.logger.LogAttrs(ctx, slog.LevelDebug, "invalid header", slog.String("header", C.GoStringN(h.header, C.int(h.header_len))))
|
||||
}
|
||||
|
||||
return
|
||||
@@ -564,7 +568,7 @@ func go_write_headers(threadIndex C.uintptr_t, status C.int, headers *C.zend_lli
|
||||
for current != nil {
|
||||
h := (*C.sapi_header_struct)(unsafe.Pointer(&(current.data)))
|
||||
|
||||
addHeader(thread.context(), fc, h.header, C.int(h.header_len))
|
||||
addHeader(thread.context(), fc, h)
|
||||
current = current.next
|
||||
}
|
||||
|
||||
@@ -750,30 +754,6 @@ func go_is_context_done(threadIndex C.uintptr_t) C.bool {
|
||||
return C.bool(phpThreads[threadIndex].frankenPHPContext().isDone)
|
||||
}
|
||||
|
||||
// ExecuteScriptCLI executes the PHP script passed as parameter.
|
||||
// It returns the exit status code of the script.
|
||||
func ExecuteScriptCLI(script string, args []string) int {
|
||||
// Ensure extensions are registered before CLI execution
|
||||
registerExtensions()
|
||||
|
||||
cScript := C.CString(script)
|
||||
defer C.free(unsafe.Pointer(cScript))
|
||||
|
||||
argc, argv := convertArgs(args)
|
||||
defer freeArgs(argv)
|
||||
|
||||
return int(C.frankenphp_execute_script_cli(cScript, argc, (**C.char)(unsafe.Pointer(&argv[0])), false))
|
||||
}
|
||||
|
||||
func ExecutePHPCode(phpCode string) int {
|
||||
// Ensure extensions are registered before CLI execution
|
||||
registerExtensions()
|
||||
|
||||
cCode := C.CString(phpCode)
|
||||
defer C.free(unsafe.Pointer(cCode))
|
||||
return int(C.frankenphp_execute_script_cli(cCode, 0, nil, true))
|
||||
}
|
||||
|
||||
func convertArgs(args []string) (C.int, []*C.char) {
|
||||
argc := C.int(len(args))
|
||||
argv := make([]*C.char, argc)
|
||||
@@ -805,5 +785,6 @@ func resetGlobals() {
|
||||
workersByName = nil
|
||||
workersByPath = nil
|
||||
watcherIsEnabled = false
|
||||
maxIdleTime = defaultMaxIdleTime
|
||||
globalMu.Unlock()
|
||||
}
|
||||
|
||||
160
frankenphp.h
160
frankenphp.h
@@ -1,6 +1,46 @@
|
||||
#ifndef _FRANKENPHP_H
|
||||
#define _FRANKENPHP_H
|
||||
|
||||
#ifdef _WIN32
|
||||
// Define this to prevent windows.h from including legacy winsock.h
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
// Explicitly include Winsock2 BEFORE windows.h
|
||||
#include <windows.h>
|
||||
#include <winerror.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
// Fix for missing IntSafe functions (LongLongAdd) when building with Clang
|
||||
#ifdef __clang__
|
||||
#ifndef INTSAFE_E_ARITHMETIC_OVERFLOW
|
||||
#define INTSAFE_E_ARITHMETIC_OVERFLOW ((HRESULT)0x80070216L)
|
||||
#endif
|
||||
|
||||
#ifndef LongLongAdd
|
||||
static inline HRESULT LongLongAdd(LONGLONG llAugend, LONGLONG llAddend,
|
||||
LONGLONG *pllResult) {
|
||||
if (__builtin_add_overflow(llAugend, llAddend, pllResult)) {
|
||||
return INTSAFE_E_ARITHMETIC_OVERFLOW;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef LongLongSub
|
||||
static inline HRESULT LongLongSub(LONGLONG llMinuend, LONGLONG llSubtrahend,
|
||||
LONGLONG *pllResult) {
|
||||
if (__builtin_sub_overflow(llMinuend, llSubtrahend, pllResult)) {
|
||||
return INTSAFE_E_ARITHMETIC_OVERFLOW;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <Zend/zend_modules.h>
|
||||
#include <Zend/zend_types.h>
|
||||
#include <stdbool.h>
|
||||
@@ -17,11 +57,96 @@ typedef struct go_string {
|
||||
char *data;
|
||||
} go_string;
|
||||
|
||||
typedef struct ht_key_value_pair {
|
||||
zend_string *key;
|
||||
char *val;
|
||||
size_t val_len;
|
||||
} ht_key_value_pair;
|
||||
typedef struct frankenphp_server_vars {
|
||||
size_t total_num_vars;
|
||||
char *remote_addr;
|
||||
size_t remote_addr_len;
|
||||
char *remote_host;
|
||||
size_t remote_host_len;
|
||||
char *remote_port;
|
||||
size_t remote_port_len;
|
||||
char *document_root;
|
||||
size_t document_root_len;
|
||||
char *path_info;
|
||||
size_t path_info_len;
|
||||
char *php_self;
|
||||
size_t php_self_len;
|
||||
char *document_uri;
|
||||
size_t document_uri_len;
|
||||
char *script_filename;
|
||||
size_t script_filename_len;
|
||||
char *script_name;
|
||||
size_t script_name_len;
|
||||
char *server_name;
|
||||
size_t server_name_len;
|
||||
char *server_port;
|
||||
size_t server_port_len;
|
||||
char *content_length;
|
||||
size_t content_length_len;
|
||||
char *server_protocol;
|
||||
size_t server_protocol_len;
|
||||
char *http_host;
|
||||
size_t http_host_len;
|
||||
char *request_uri;
|
||||
size_t request_uri_len;
|
||||
char *ssl_cipher;
|
||||
size_t ssl_cipher_len;
|
||||
zend_string *request_scheme;
|
||||
zend_string *ssl_protocol;
|
||||
zend_string *https;
|
||||
} frankenphp_server_vars;
|
||||
|
||||
/**
|
||||
* Cached interned strings for memory and performance benefits
|
||||
* Add more hard-coded strings here if needed
|
||||
*/
|
||||
#define FRANKENPHP_INTERNED_STRINGS_LIST(X) \
|
||||
X(remote_addr, "REMOTE_ADDR") \
|
||||
X(remote_host, "REMOTE_HOST") \
|
||||
X(remote_port, "REMOTE_PORT") \
|
||||
X(document_root, "DOCUMENT_ROOT") \
|
||||
X(path_info, "PATH_INFO") \
|
||||
X(php_self, "PHP_SELF") \
|
||||
X(document_uri, "DOCUMENT_URI") \
|
||||
X(script_filename, "SCRIPT_FILENAME") \
|
||||
X(script_name, "SCRIPT_NAME") \
|
||||
X(https, "HTTPS") \
|
||||
X(httpsLowercase, "https") \
|
||||
X(httpLowercase, "http") \
|
||||
X(ssl_protocol, "SSL_PROTOCOL") \
|
||||
X(request_scheme, "REQUEST_SCHEME") \
|
||||
X(server_name, "SERVER_NAME") \
|
||||
X(server_port, "SERVER_PORT") \
|
||||
X(content_length, "CONTENT_LENGTH") \
|
||||
X(server_protocol, "SERVER_PROTOCOL") \
|
||||
X(http_host, "HTTP_HOST") \
|
||||
X(request_uri, "REQUEST_URI") \
|
||||
X(ssl_cipher, "SSL_CIPHER") \
|
||||
X(server_software, "SERVER_SOFTWARE") \
|
||||
X(frankenphp, "FrankenPHP") \
|
||||
X(gateway_interface, "GATEWAY_INTERFACE") \
|
||||
X(cgi11, "CGI/1.1") \
|
||||
X(auth_type, "AUTH_TYPE") \
|
||||
X(remote_ident, "REMOTE_IDENT") \
|
||||
X(content_type, "CONTENT_TYPE") \
|
||||
X(path_translated, "PATH_TRANSLATED") \
|
||||
X(query_string, "QUERY_STRING") \
|
||||
X(remote_user, "REMOTE_USER") \
|
||||
X(request_method, "REQUEST_METHOD") \
|
||||
X(tls1, "TLSv1") \
|
||||
X(tls11, "TLSv1.1") \
|
||||
X(tls12, "TLSv1.2") \
|
||||
X(tls13, "TLSv1.3") \
|
||||
X(on, "on") \
|
||||
X(empty, "")
|
||||
|
||||
typedef struct frankenphp_interned_strings_t {
|
||||
#define F_DEFINE_STRUCT_FIELD(name, str) zend_string *name;
|
||||
FRANKENPHP_INTERNED_STRINGS_LIST(F_DEFINE_STRUCT_FIELD)
|
||||
#undef F_DEFINE_STRUCT_FIELD
|
||||
} frankenphp_interned_strings_t;
|
||||
|
||||
extern frankenphp_interned_strings_t frankenphp_strings;
|
||||
|
||||
typedef struct frankenphp_version {
|
||||
unsigned char major_version;
|
||||
@@ -50,32 +175,17 @@ void frankenphp_update_local_thread_context(bool is_worker);
|
||||
int frankenphp_execute_script_cli(char *script, int argc, char **argv,
|
||||
bool eval);
|
||||
|
||||
void frankenphp_register_variables_from_request_info(
|
||||
zval *track_vars_array, zend_string *content_type,
|
||||
zend_string *path_translated, zend_string *query_string,
|
||||
zend_string *auth_user, zend_string *request_method);
|
||||
void frankenphp_register_known_variable(zend_string *z_key, char *value,
|
||||
size_t val_len, zval *track_vars_array);
|
||||
void frankenphp_register_variable_safe(char *key, char *var, size_t val_len,
|
||||
zval *track_vars_array);
|
||||
void frankenphp_register_server_vars(zval *track_vars_array,
|
||||
frankenphp_server_vars vars);
|
||||
|
||||
zend_string *frankenphp_init_persistent_string(const char *string, size_t len);
|
||||
int frankenphp_reset_opcache(void);
|
||||
int frankenphp_get_current_memory_limit();
|
||||
|
||||
void frankenphp_register_single(zend_string *z_key, char *value, size_t val_len,
|
||||
zval *track_vars_array);
|
||||
void frankenphp_register_bulk(
|
||||
zval *track_vars_array, ht_key_value_pair remote_addr,
|
||||
ht_key_value_pair remote_host, ht_key_value_pair remote_port,
|
||||
ht_key_value_pair document_root, ht_key_value_pair path_info,
|
||||
ht_key_value_pair php_self, ht_key_value_pair document_uri,
|
||||
ht_key_value_pair script_filename, ht_key_value_pair script_name,
|
||||
ht_key_value_pair https, ht_key_value_pair ssl_protocol,
|
||||
ht_key_value_pair request_scheme, ht_key_value_pair server_name,
|
||||
ht_key_value_pair server_port, ht_key_value_pair content_length,
|
||||
ht_key_value_pair gateway_interface, ht_key_value_pair server_protocol,
|
||||
ht_key_value_pair server_software, ht_key_value_pair http_host,
|
||||
ht_key_value_pair auth_type, ht_key_value_pair remote_ident,
|
||||
ht_key_value_pair request_uri, ht_key_value_pair ssl_cipher);
|
||||
|
||||
void register_extensions(zend_module_entry **m, int len);
|
||||
|
||||
#endif
|
||||
|
||||
BIN
frankenphp.ico
Normal file
BIN
frankenphp.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 256 KiB |
@@ -22,6 +22,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -140,6 +141,12 @@ func TestMain(m *testing.M) {
|
||||
slog.SetDefault(slog.New(slog.DiscardHandler))
|
||||
}
|
||||
|
||||
// setup custom environment var for TestWorkerHasOSEnvironmentVariableInSERVER
|
||||
if os.Setenv("CUSTOM_OS_ENV_VARIABLE", "custom_env_variable_value") != nil {
|
||||
fmt.Println("Failed to set environment variable for tests")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -154,6 +161,19 @@ func testHelloWorld(t *testing.T, opts *testOptions) {
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func TestEnvVarsInPhpIni(t *testing.T) {
|
||||
t.Setenv("OPCACHE_ENABLE", "0")
|
||||
|
||||
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, _ int) {
|
||||
body, _ := testGet("http://example.com/ini.php?key=opcache.enable", handler, t)
|
||||
assert.Equal(t, "opcache.enable:0", body)
|
||||
}, &testOptions{
|
||||
phpIni: map[string]string{
|
||||
"opcache.enable": "${OPCACHE_ENABLE}",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestFinishRequest_module(t *testing.T) { testFinishRequest(t, nil) }
|
||||
func TestFinishRequest_worker(t *testing.T) {
|
||||
testFinishRequest(t, &testOptions{workerScript: "finish-request.php"})
|
||||
@@ -695,11 +715,11 @@ func TestFailingWorker(t *testing.T) {
|
||||
assert.Error(t, err, "should return an immediate error if workers fail on startup")
|
||||
}
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
testEnv(t, &testOptions{nbParallelRequests: 1})
|
||||
func TestEnv_module(t *testing.T) {
|
||||
testEnv(t, &testOptions{nbParallelRequests: 1, phpIni: map[string]string{"variables_order": "EGPCS"}})
|
||||
}
|
||||
func TestEnvWorker(t *testing.T) {
|
||||
testEnv(t, &testOptions{nbParallelRequests: 1, workerScript: "env/test-env.php"})
|
||||
func TestEnv_worker(t *testing.T) {
|
||||
testEnv(t, &testOptions{nbParallelRequests: 1, workerScript: "env/test-env.php", phpIni: map[string]string{"variables_order": "EGPCS"}})
|
||||
}
|
||||
|
||||
// testEnv cannot be run in parallel due to https://github.com/golang/go/issues/63567
|
||||
@@ -714,7 +734,7 @@ func testEnv(t *testing.T, opts *testOptions) {
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
// php is not installed or other issue, use the hardcoded output below:
|
||||
stdoutStderr = []byte("Set MY_VAR successfully.\nMY_VAR = HelloWorld\nUnset MY_VAR successfully.\nMY_VAR is unset.\nMY_VAR set to empty successfully.\nMY_VAR = \nUnset NON_EXISTING_VAR successfully.\n")
|
||||
stdoutStderr = []byte("Set MY_VAR successfully.\nMY_VAR = HelloWorld\nMY_VAR not found in $_ENV.\nMY_VAR not found in $_SERVER.\nUnset MY_VAR successfully.\nMY_VAR is unset.\nMY_VAR set to empty successfully.\nMY_VAR = \nUnset NON_EXISTING_VAR successfully.\nInvalid value was not inserted.\n")
|
||||
}
|
||||
|
||||
assert.Equal(t, string(stdoutStderr), body)
|
||||
@@ -784,40 +804,6 @@ func testFileUpload(t *testing.T, opts *testOptions) {
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func TestExecuteScriptCLI(t *testing.T) {
|
||||
if _, err := os.Stat("internal/testcli/testcli"); err != nil {
|
||||
t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`")
|
||||
}
|
||||
|
||||
cmd := exec.Command("internal/testcli/testcli", "testdata/command.php", "foo", "bar")
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
assert.Error(t, err)
|
||||
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(err, &exitError) {
|
||||
assert.Equal(t, 3, exitError.ExitCode())
|
||||
}
|
||||
|
||||
stdoutStderrStr := string(stdoutStderr)
|
||||
|
||||
assert.Contains(t, stdoutStderrStr, `"foo"`)
|
||||
assert.Contains(t, stdoutStderrStr, `"bar"`)
|
||||
assert.Contains(t, stdoutStderrStr, "From the CLI")
|
||||
}
|
||||
|
||||
func TestExecuteCLICode(t *testing.T) {
|
||||
if _, err := os.Stat("internal/testcli/testcli"); err != nil {
|
||||
t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`")
|
||||
}
|
||||
|
||||
cmd := exec.Command("internal/testcli/testcli", "-r", "echo 'Hello World';")
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
assert.NoError(t, err)
|
||||
|
||||
stdoutStderrStr := string(stdoutStderr)
|
||||
assert.Equal(t, stdoutStderrStr, `Hello World`)
|
||||
}
|
||||
|
||||
func ExampleServeHTTP() {
|
||||
if err := frankenphp.Init(); err != nil {
|
||||
panic(err)
|
||||
@@ -837,15 +823,6 @@ func ExampleServeHTTP() {
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
||||
func ExampleExecuteScriptCLI() {
|
||||
if len(os.Args) <= 1 {
|
||||
log.Println("Usage: my-program script.php")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(frankenphp.ExecuteScriptCLI(os.Args[1], os.Args))
|
||||
}
|
||||
|
||||
func BenchmarkHelloWorld(b *testing.B) {
|
||||
require.NoError(b, frankenphp.Init())
|
||||
b.Cleanup(frankenphp.Shutdown)
|
||||
@@ -1143,8 +1120,8 @@ func TestSessionHandlerReset_worker(t *testing.T) {
|
||||
assert.Contains(t, body1Str, "session.save_handler=user")
|
||||
|
||||
// Request 2: Start session without setting a custom handler
|
||||
// After the fix: session.save_handler should be reset to "files"
|
||||
// and session_start() should work normally
|
||||
// The user handler from request 1 is preserved (mod_user_names persist),
|
||||
// so session_start() should work without crashing.
|
||||
resp2, err := http.Get(ts.URL + "/session-handler.php?action=start_without_handler")
|
||||
assert.NoError(t, err)
|
||||
body2, _ := io.ReadAll(resp2.Body)
|
||||
@@ -1152,13 +1129,9 @@ func TestSessionHandlerReset_worker(t *testing.T) {
|
||||
|
||||
body2Str := string(body2)
|
||||
|
||||
// session.save_handler should be reset to "files" (default)
|
||||
assert.Contains(t, body2Str, "save_handler_before=files",
|
||||
"session.save_handler INI should be reset to 'files' between requests.\nResponse: %s", body2Str)
|
||||
|
||||
// session_start() should succeed
|
||||
// session_start() should succeed (handlers are preserved)
|
||||
assert.Contains(t, body2Str, "SESSION_START_RESULT=true",
|
||||
"session_start() should succeed after INI reset.\nResponse: %s", body2Str)
|
||||
"session_start() should succeed.\nResponse: %s", body2Str)
|
||||
|
||||
// No errors or exceptions should occur
|
||||
assert.NotContains(t, body2Str, "ERROR:",
|
||||
@@ -1174,39 +1147,6 @@ func TestSessionHandlerReset_worker(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIniLeakBetweenRequests_worker(t *testing.T) {
|
||||
runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) {
|
||||
// Request 1: Change INI values
|
||||
resp1, err := http.Get(ts.URL + "/ini-leak.php?action=change_ini")
|
||||
assert.NoError(t, err)
|
||||
body1, _ := io.ReadAll(resp1.Body)
|
||||
_ = resp1.Body.Close()
|
||||
|
||||
assert.Contains(t, string(body1), "INI_CHANGED")
|
||||
|
||||
// Request 2: Check if INI values leaked from request 1
|
||||
resp2, err := http.Get(ts.URL + "/ini-leak.php?action=check_ini")
|
||||
assert.NoError(t, err)
|
||||
body2, _ := io.ReadAll(resp2.Body)
|
||||
_ = resp2.Body.Close()
|
||||
|
||||
body2Str := string(body2)
|
||||
t.Logf("Response: %s", body2Str)
|
||||
|
||||
// If INI values leak, this test will fail
|
||||
assert.Contains(t, body2Str, "NO_LEAKS",
|
||||
"INI values should not leak between requests.\nResponse: %s", body2Str)
|
||||
assert.NotContains(t, body2Str, "LEAKS_DETECTED",
|
||||
"INI leaks detected.\nResponse: %s", body2Str)
|
||||
|
||||
}, &testOptions{
|
||||
workerScript: "ini-leak.php",
|
||||
nbWorkers: 1,
|
||||
nbParallelRequests: 1,
|
||||
realServer: true,
|
||||
})
|
||||
}
|
||||
|
||||
func TestSessionHandlerPreLoopPreserved_worker(t *testing.T) {
|
||||
runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) {
|
||||
// Request 1: Check that the pre-loop session handler is preserved
|
||||
@@ -1256,58 +1196,6 @@ func TestSessionHandlerPreLoopPreserved_worker(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIniPreLoopPreserved_worker(t *testing.T) {
|
||||
runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) {
|
||||
// Request 1: Check that pre-loop INI values are present
|
||||
resp1, err := http.Get(ts.URL + "/worker-with-ini.php?action=check")
|
||||
assert.NoError(t, err)
|
||||
body1, _ := io.ReadAll(resp1.Body)
|
||||
_ = resp1.Body.Close()
|
||||
|
||||
body1Str := string(body1)
|
||||
t.Logf("Request 1 response: %s", body1Str)
|
||||
assert.Contains(t, body1Str, "precision=8",
|
||||
"Pre-loop precision should be 8")
|
||||
assert.Contains(t, body1Str, "display_errors=0",
|
||||
"Pre-loop display_errors should be 0")
|
||||
assert.Contains(t, body1Str, "PRELOOP_INI_PRESERVED",
|
||||
"Pre-loop INI values should be preserved")
|
||||
|
||||
// Request 2: Change INI values during request
|
||||
resp2, err := http.Get(ts.URL + "/worker-with-ini.php?action=change_ini")
|
||||
assert.NoError(t, err)
|
||||
body2, _ := io.ReadAll(resp2.Body)
|
||||
_ = resp2.Body.Close()
|
||||
|
||||
body2Str := string(body2)
|
||||
t.Logf("Request 2 response: %s", body2Str)
|
||||
assert.Contains(t, body2Str, "INI_CHANGED")
|
||||
assert.Contains(t, body2Str, "precision=5",
|
||||
"INI should be changed during request")
|
||||
|
||||
// Request 3: Check that pre-loop INI values are restored
|
||||
resp3, err := http.Get(ts.URL + "/worker-with-ini.php?action=check")
|
||||
assert.NoError(t, err)
|
||||
body3, _ := io.ReadAll(resp3.Body)
|
||||
_ = resp3.Body.Close()
|
||||
|
||||
body3Str := string(body3)
|
||||
t.Logf("Request 3 response: %s", body3Str)
|
||||
assert.Contains(t, body3Str, "precision=8",
|
||||
"Pre-loop precision should be restored to 8.\nResponse: %s", body3Str)
|
||||
assert.Contains(t, body3Str, "display_errors=0",
|
||||
"Pre-loop display_errors should be restored to 0.\nResponse: %s", body3Str)
|
||||
assert.Contains(t, body3Str, "PRELOOP_INI_PRESERVED",
|
||||
"Pre-loop INI values should be restored after request changes.\nResponse: %s", body3Str)
|
||||
|
||||
}, &testOptions{
|
||||
workerScript: "worker-with-ini.php",
|
||||
nbWorkers: 1,
|
||||
nbParallelRequests: 1,
|
||||
realServer: true,
|
||||
})
|
||||
}
|
||||
|
||||
func TestSessionNoLeakBetweenRequests_worker(t *testing.T) {
|
||||
runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) {
|
||||
// Client A: Set a secret value in session
|
||||
@@ -1415,3 +1303,30 @@ func TestSessionNoLeakAfterExit_worker(t *testing.T) {
|
||||
realServer: true,
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpcachePreload_module(t *testing.T) {
|
||||
testOpcachePreload(t, &testOptions{env: map[string]string{"TEST": "123"}})
|
||||
}
|
||||
|
||||
func TestOpcachePreload_worker(t *testing.T) {
|
||||
testOpcachePreload(t, &testOptions{workerScript: "preload-check.php", nbWorkers: 1, nbParallelRequests: 1, env: map[string]string{"TEST": "123"}})
|
||||
}
|
||||
|
||||
func testOpcachePreload(t *testing.T, opts *testOptions) {
|
||||
cwd, _ := os.Getwd()
|
||||
preloadScript := cwd + "/testdata/preload.php"
|
||||
|
||||
u, err := user.Current()
|
||||
require.NoError(t, err)
|
||||
|
||||
opts.phpIni = map[string]string{
|
||||
"opcache.enable": "1",
|
||||
"opcache.preload": preloadScript,
|
||||
"opcache.preload_user": u.Username,
|
||||
}
|
||||
|
||||
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
|
||||
body, _ := testGet("http://example.com/preload-check.php", handler, t)
|
||||
assert.Equal(t, "I am preloaded", body)
|
||||
}, opts)
|
||||
}
|
||||
|
||||
14
go.mod
14
go.mod
@@ -6,12 +6,12 @@ retract v1.0.0-rc.1 // Human error
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/dunglas/mercure v0.21.8
|
||||
github.com/e-dant/watcher v0.0.0-20260202035023-10268e78355f
|
||||
github.com/dunglas/mercure v0.21.11
|
||||
github.com/e-dant/watcher v0.0.0-20260223030516-06f84a1314be
|
||||
github.com/maypok86/otter/v2 v2.3.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/text v0.34.0
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.15.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@@ -45,7 +45,7 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/rs/cors v1.11.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
@@ -58,10 +58,10 @@ require (
|
||||
github.com/unrolled/secure v1.17.0 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
28
go.sum
28
go.sum
@@ -8,8 +8,8 @@ github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 h1:1yw6O62BReQ+uA1oyk9XaQTvLhcoHWmoQAgXmDFXpIY=
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145/go.mod h1:877WBceefKn14QwVVn4xRFUsHsZb9clICgdeTj4XsUg=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4 h1:4aKySrrg9G/5oRtJ3TrZLObVqxgQ9f1znCRBwEwjuVw=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.15.0 h1:gCbixa3UiG7g6WUZNVOfEEg2HTc1vR4OVdMkX8t1ZFc=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.15.0/go.mod h1:eq4wdNXxtJIS/oikeCzdX1rBzek7ANzbth041hrU8Q4=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
||||
@@ -18,12 +18,12 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dunglas/mercure v0.21.8 h1:D+SxSq0VqdB29lfMXrsvDkFvq/cTL94aKCC0R4heKV0=
|
||||
github.com/dunglas/mercure v0.21.8/go.mod h1:kt4RJpixJOcPN+x9Z53VBhpJYSdyEEzuu9/99vJIocQ=
|
||||
github.com/dunglas/mercure v0.21.11 h1:4Sd/Q77j8uh9SI5D9ZMg5sePlWs336+9CKxDQC1FV34=
|
||||
github.com/dunglas/mercure v0.21.11/go.mod h1:WPMgfqonUiO1qB+W8Tya63Ngag9ZwplGMXSOy8P/uMg=
|
||||
github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4=
|
||||
github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w=
|
||||
github.com/e-dant/watcher v0.0.0-20260202035023-10268e78355f h1:UDB5nhFRW7IOOpLk/eP1UGj7URmPimFGV+01/EG9qR8=
|
||||
github.com/e-dant/watcher v0.0.0-20260202035023-10268e78355f/go.mod h1:PmV4IVmBJVqT2NcfTGN4+sZ+qGe3PA0qkphAtOHeFG0=
|
||||
github.com/e-dant/watcher v0.0.0-20260223030516-06f84a1314be h1:vqHrvilasyJcnru/0Z4FoojsQJUIfXGVplte7JtupfY=
|
||||
github.com/e-dant/watcher v0.0.0-20260223030516-06f84a1314be/go.mod h1:PmV4IVmBJVqT2NcfTGN4+sZ+qGe3PA0qkphAtOHeFG0=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
@@ -74,8 +74,8 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
@@ -104,18 +104,18 @@ go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
|
||||
57
install.ps1
Normal file
57
install.ps1
Normal file
@@ -0,0 +1,57 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Downloads and installs the latest FrankenPHP release for Windows.
|
||||
.DESCRIPTION
|
||||
This script downloads the latest FrankenPHP Windows release from GitHub
|
||||
and extracts it to the specified directory (~\.frankenphp by default).
|
||||
|
||||
Usage as a one-liner:
|
||||
irm https://github.com/php/frankenphp/raw/refs/heads/main/install.ps1 | iex
|
||||
Custom install directory:
|
||||
$env:FRANKENPHP_INSTALL = 'C:\frankenphp'; irm https://github.com/php/frankenphp/raw/refs/heads/main/install.ps1 | iex
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ($env:FRANKENPHP_INSTALL) {
|
||||
$BinDir = $env:FRANKENPHP_INSTALL
|
||||
} else {
|
||||
$BinDir = Join-Path $HOME ".frankenphp"
|
||||
}
|
||||
|
||||
Write-Host "Downloading FrankenPHP for Windows (x64)..." -ForegroundColor Cyan
|
||||
|
||||
$tmpZip = Join-Path $env:TEMP "frankenphp-windows-$PID.zip"
|
||||
|
||||
try {
|
||||
Invoke-WebRequest -Uri "https://github.com/php/frankenphp/releases/latest/download/frankenphp-windows-x86_64.zip" -OutFile $tmpZip
|
||||
} catch {
|
||||
Write-Host "Download failed: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Extracting to $BinDir..." -ForegroundColor Cyan
|
||||
|
||||
if (-not (Test-Path $BinDir)) {
|
||||
New-Item -ItemType Directory -Path $BinDir -Force | Out-Null
|
||||
}
|
||||
|
||||
try {
|
||||
Expand-Archive -Force -Path $tmpZip -DestinationPath $BinDir
|
||||
} finally {
|
||||
Remove-Item $tmpZip -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "FrankenPHP downloaded successfully to $BinDir" -ForegroundColor Green
|
||||
|
||||
# Check if the directory is in PATH
|
||||
$inPath = $env:PATH -split ";" | Where-Object { $_ -eq $BinDir -or $_ -eq "$BinDir\" }
|
||||
if (-not $inPath) {
|
||||
Write-Host "Add $BinDir to your PATH to use frankenphp.exe globally:" -ForegroundColor Yellow
|
||||
Write-Host " [Environment]::SetEnvironmentVariable('PATH', `"$BinDir;`" + [Environment]::GetEnvironmentVariable('PATH', 'User'), 'User')" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "If you like FrankenPHP, please give it a star on GitHub: https://github.com/php/frankenphp" -ForegroundColor Cyan
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user