mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-12-24 13:38:11 +08:00
Compare commits
348 Commits
v1.1.4
...
feat/frank
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79e3a98a11 | ||
|
|
afc4463ab3 | ||
|
|
6749ddbde5 | ||
|
|
82ba882a4e | ||
|
|
4b1679e70f | ||
|
|
75ce2e22c2 | ||
|
|
5a43e9f4de | ||
|
|
5542044376 | ||
|
|
52b65311c2 | ||
|
|
2dc8048ad2 | ||
|
|
a59b649dac | ||
|
|
68a4548bf4 | ||
|
|
6f049f9a9c | ||
|
|
340b1fd1c2 | ||
|
|
c9329bd717 | ||
|
|
f54a1fa85e | ||
|
|
b4115ca9a2 | ||
|
|
14469d4a0a | ||
|
|
ee394756b1 | ||
|
|
5a260c430a | ||
|
|
b6fcab5a95 | ||
|
|
1e49586b0e | ||
|
|
b27cd1c986 | ||
|
|
c6483088c5 | ||
|
|
5a9785d0d9 | ||
|
|
c522b52804 | ||
|
|
9a8ad979e0 | ||
|
|
663aff7cc4 | ||
|
|
79f2b2347b | ||
|
|
bf5c98410b | ||
|
|
cf7541fde6 | ||
|
|
25491068df | ||
|
|
d72751b9fd | ||
|
|
8820e53819 | ||
|
|
d2b6f9e723 | ||
|
|
13fbe126ea | ||
|
|
afa7dafe1c | ||
|
|
39e22bd5e0 | ||
|
|
bbbfdb31b5 | ||
|
|
0b83602575 | ||
|
|
ecca9dc01d | ||
|
|
eb40c03a21 | ||
|
|
c2390e7c3b | ||
|
|
0d12a5162d | ||
|
|
a48db9422d | ||
|
|
ab0fcd80de | ||
|
|
2f7b987198 | ||
|
|
1d74b2caa8 | ||
|
|
92e92335e3 | ||
|
|
8f5f9e4c8b | ||
|
|
5b7fc5ec52 | ||
|
|
05220de0e3 | ||
|
|
3741782330 | ||
|
|
a6e1d3554d | ||
|
|
6f1b4f3bae | ||
|
|
cd540bda11 | ||
|
|
8125993001 | ||
|
|
8583afd83e | ||
|
|
d10a243f86 | ||
|
|
1ec37f6cc9 | ||
|
|
4ad5e870ec | ||
|
|
49d2e62996 | ||
|
|
8febee71af | ||
|
|
16814581f9 | ||
|
|
ffa52f7c8d | ||
|
|
4550027de4 | ||
|
|
7f8e43fd62 | ||
|
|
254c0a8a55 | ||
|
|
22cf94d556 | ||
|
|
a4dc93f831 | ||
|
|
c276a3f434 | ||
|
|
02a1c92b88 | ||
|
|
8092f4a35c | ||
|
|
b250bd9a07 | ||
|
|
99064ee3e1 | ||
|
|
58a728b790 | ||
|
|
66aa975d47 | ||
|
|
5e1ad5d797 | ||
|
|
96dd739064 | ||
|
|
729cf9bba1 | ||
|
|
c5752f9e3b | ||
|
|
ba36f92a35 | ||
|
|
d3589f9770 | ||
|
|
8e6a183bda | ||
|
|
855b3f93b1 | ||
|
|
f85ca1c2d2 | ||
|
|
a30ed2e9d3 | ||
|
|
565b3a9629 | ||
|
|
3701516e5e | ||
|
|
f36bd51163 | ||
|
|
45bba2101f | ||
|
|
87315a19ae | ||
|
|
3bc426482a | ||
|
|
341b0240c9 | ||
|
|
432824edf1 | ||
|
|
9cca12858b | ||
|
|
cc473ee03e | ||
|
|
93266dfcad | ||
|
|
6203d207fa | ||
|
|
424ca426cb | ||
|
|
a9cf944b62 | ||
|
|
8d9ce15849 | ||
|
|
409c0fdf5f | ||
|
|
f50248a7d2 | ||
|
|
09b8219ad4 | ||
|
|
f2bae25a78 | ||
|
|
3dd90a3071 | ||
|
|
c57f741d83 | ||
|
|
3ba4e257a1 | ||
|
|
619c903386 | ||
|
|
78824107f0 | ||
|
|
f64c0f948e | ||
|
|
db3e1a047c | ||
|
|
80f13f07ea | ||
|
|
072151dfee | ||
|
|
965fa6570c | ||
|
|
251567a617 | ||
|
|
57e7747b9b | ||
|
|
f109f0403b | ||
|
|
d407dbd498 | ||
|
|
d970309544 | ||
|
|
30bf69cbe5 | ||
|
|
f61bc180c4 | ||
|
|
9f5e7a9eaa | ||
|
|
a5ca60da44 | ||
|
|
1c097a6fdf | ||
|
|
233753ca6b | ||
|
|
9dd05b0b1b | ||
|
|
4c92633396 | ||
|
|
be2e4714f5 | ||
|
|
941f218a79 | ||
|
|
7bd6ca89b0 | ||
|
|
5342d34126 | ||
|
|
dd250e3bda | ||
|
|
7e39e0a201 | ||
|
|
05aafc7c44 | ||
|
|
eee1de147e | ||
|
|
ece420c569 | ||
|
|
2f4c8310e2 | ||
|
|
d712aed2a5 | ||
|
|
d0b259df42 | ||
|
|
0681c63bc9 | ||
|
|
92e6b48156 | ||
|
|
e53ba345a1 | ||
|
|
34dfd8789a | ||
|
|
16bb790d52 | ||
|
|
1e56edceb8 | ||
|
|
f05f3b3d13 | ||
|
|
c3031ea07f | ||
|
|
39a88c3e83 | ||
|
|
19344a0dfe | ||
|
|
5b86f2c554 | ||
|
|
fd6cc7148d | ||
|
|
72120d7a2c | ||
|
|
479ba0a063 | ||
|
|
2b7b3d1e4b | ||
|
|
ec8eea0c7d | ||
|
|
c2ca4dbf03 | ||
|
|
2276129c6d | ||
|
|
045ce00c15 | ||
|
|
43c1de2372 | ||
|
|
5a148342b0 | ||
|
|
2f93baf984 | ||
|
|
7aaea72f14 | ||
|
|
028bad3e54 | ||
|
|
851ff9976e | ||
|
|
07622be221 | ||
|
|
d8f393900b | ||
|
|
e2687dbeb9 | ||
|
|
43984c3990 | ||
|
|
e874ea8710 | ||
|
|
92f95342d1 | ||
|
|
0fc6ccc5ce | ||
|
|
13eb9e8534 | ||
|
|
cb37c3d66d | ||
|
|
f288c3688e | ||
|
|
8cf6616ed6 | ||
|
|
a3e5af523c | ||
|
|
1bebb12ad9 | ||
|
|
57bc54864e | ||
|
|
d276032e20 | ||
|
|
20eaecf325 | ||
|
|
b16b60b053 | ||
|
|
85c273543d | ||
|
|
ec99f6a761 | ||
|
|
79ab84dad7 | ||
|
|
c0e0c2d07f | ||
|
|
fbbc129e4d | ||
|
|
f592e0f47b | ||
|
|
2676bffa98 | ||
|
|
047ce0c8b2 | ||
|
|
2f3e4b650b | ||
|
|
61922473ac | ||
|
|
fc6be5d09e | ||
|
|
9e2433e39b | ||
|
|
8a199bb4d7 | ||
|
|
fd49eade1a | ||
|
|
a396e64ad6 | ||
|
|
15ad1b4cb4 | ||
|
|
ccf2af7ab2 | ||
|
|
6d123c7e66 | ||
|
|
a1797c49b0 | ||
|
|
1e279bc348 | ||
|
|
449a0e7191 | ||
|
|
08e99fc85f | ||
|
|
2d6a299dbc | ||
|
|
b4748ee110 | ||
|
|
b40c5a64a8 | ||
|
|
0c123a2563 | ||
|
|
fa64198d52 | ||
|
|
a441e22a1b | ||
|
|
7af06f18d7 | ||
|
|
9ad06f11d3 | ||
|
|
0328d0600e | ||
|
|
2538849433 | ||
|
|
843d199469 | ||
|
|
102b4d1ad0 | ||
|
|
51e4445c00 | ||
|
|
172b598f3b | ||
|
|
022b8f1094 | ||
|
|
9013614801 | ||
|
|
56d5d50ea9 | ||
|
|
75dab8f33d | ||
|
|
1c3ce114f6 | ||
|
|
e5ca97308e | ||
|
|
ee8e1b97b9 | ||
|
|
69c43ee43d | ||
|
|
dad858b697 | ||
|
|
f567318e19 | ||
|
|
afedeb9d58 | ||
|
|
d53f909d20 | ||
|
|
2532eb5887 | ||
|
|
17e57287eb | ||
|
|
ed3703a16a | ||
|
|
f43de0ccf5 | ||
|
|
e812473fe1 | ||
|
|
5ec030830a | ||
|
|
dbd3ae54af | ||
|
|
5601cc9640 | ||
|
|
aa98b8c014 | ||
|
|
cc21b4dfd3 | ||
|
|
e864142a7b | ||
|
|
cda74730ae | ||
|
|
334139ca2b | ||
|
|
ea7a514389 | ||
|
|
f1e2b3ad07 | ||
|
|
8bbd16d585 | ||
|
|
9acfb8be20 | ||
|
|
d99b16a158 | ||
|
|
e9c075a4a5 | ||
|
|
ce13140d6b | ||
|
|
f2d7e212bd | ||
|
|
95c381ec78 | ||
|
|
56d2f99548 | ||
|
|
029ce7e0ad | ||
|
|
8d9b6e755b | ||
|
|
aa585f7da0 | ||
|
|
b8e5ad16cd | ||
|
|
1e20f65e26 | ||
|
|
59f1690596 | ||
|
|
5d43fc2c8d | ||
|
|
861fd8b2fa | ||
|
|
ae31cb9995 | ||
|
|
f62244fb69 | ||
|
|
6c0292af99 | ||
|
|
a4ac4eb3fb | ||
|
|
dcf190ebcb | ||
|
|
d968334371 | ||
|
|
47257ec919 | ||
|
|
a16076e082 | ||
|
|
f5bec5c13c | ||
|
|
db12b4e113 | ||
|
|
4a8555571c | ||
|
|
d12551762f | ||
|
|
27ca1ae4f7 | ||
|
|
60e3aba981 | ||
|
|
7a524ddbd5 | ||
|
|
ac37760e37 | ||
|
|
496831329f | ||
|
|
ba58e3d829 | ||
|
|
d532772355 | ||
|
|
3ca52f5934 | ||
|
|
968176a948 | ||
|
|
2af8fd2e31 | ||
|
|
26cfcc145a | ||
|
|
fd97c977c1 | ||
|
|
6c708be99d | ||
|
|
bcc825a121 | ||
|
|
4de9abb49d | ||
|
|
93859e3149 | ||
|
|
fb23c64632 | ||
|
|
323edefc4b | ||
|
|
a6572225f6 | ||
|
|
6d5cb37647 | ||
|
|
0751f453b9 | ||
|
|
4fab5a3169 | ||
|
|
e743f6ab87 | ||
|
|
0500ebc191 | ||
|
|
b87cf4e8b9 | ||
|
|
ebdb2656b6 | ||
|
|
29d47f42c8 | ||
|
|
ae4ebd11f6 | ||
|
|
8ff6cfdda8 | ||
|
|
952dd7a79b | ||
|
|
213be22967 | ||
|
|
11e3745b8b | ||
|
|
153e7d6686 | ||
|
|
a313f3a809 | ||
|
|
e45a788824 | ||
|
|
549239d16f | ||
|
|
b47f4d3aa0 | ||
|
|
d6d1b2731c | ||
|
|
e0ccd816e6 | ||
|
|
039d021f51 | ||
|
|
6b44b532f5 | ||
|
|
c0c56a8cf8 | ||
|
|
d99ce659f6 | ||
|
|
c4aba6be02 | ||
|
|
4537f27f67 | ||
|
|
acf6d0ffc4 | ||
|
|
ce4732aa43 | ||
|
|
68c0a4c246 | ||
|
|
74e9a3c9e5 | ||
|
|
3714fdf3a1 | ||
|
|
0b4a427cac | ||
|
|
b96db939b7 | ||
|
|
ad2c18a6b7 | ||
|
|
2183f29b18 | ||
|
|
90a7b98b10 | ||
|
|
322e45c186 | ||
|
|
da342b6f2f | ||
|
|
3d065eda35 | ||
|
|
c894a92135 | ||
|
|
835ad8acb2 | ||
|
|
73e9b640d6 | ||
|
|
d01733dd3e | ||
|
|
f773c1f529 | ||
|
|
469070ce85 | ||
|
|
ea83ea6dbd | ||
|
|
a2f0eb9140 | ||
|
|
e5fcea0690 | ||
|
|
cd2049f611 | ||
|
|
3dbb3fd48d | ||
|
|
627f817b59 | ||
|
|
ea5e19ff4b | ||
|
|
12fb11eead | ||
|
|
25a858954c | ||
|
|
593233db17 |
@@ -1,14 +1,11 @@
|
||||
# ignored
|
||||
**/*
|
||||
|
||||
# authorized
|
||||
!**/Caddyfile
|
||||
!**/*.go
|
||||
!**/go.*
|
||||
!**/*.c
|
||||
!**/*.h
|
||||
!testdata/*.php
|
||||
!testdata/*.txt
|
||||
!build-static.sh
|
||||
!app.tar
|
||||
!app_checksum.txt
|
||||
/caddy/frankenphp/frankenphp
|
||||
/internal/testserver/testserver
|
||||
/internal/testcli/testcli
|
||||
/dist
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
__debug_bin
|
||||
frankenphp.dev.test
|
||||
caddy/frankenphp/Build
|
||||
*.log
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -74,7 +74,7 @@ body:
|
||||
attributes:
|
||||
label: PHP configuration
|
||||
description: |
|
||||
Please copy and paste the output of the `phpinfo()` function.
|
||||
Please copy and paste the output of the `phpinfo()` function -- remember to remove **sensitive information** like passwords, API keys, etc.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
|
||||
34
.github/actions/watcher/action.yaml
vendored
Normal file
34
.github/actions/watcher/action.yaml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: watcher
|
||||
description: Install e-dant/watcher
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Determine e-dant/watcher version
|
||||
id: determine-watcher-version
|
||||
run: echo version="$(gh release view --repo e-dant/watcher --json tagName --template '{{ .tagName }}')" >> "${GITHUB_OUTPUT}"
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
- name: Cache e-dant/watcher
|
||||
id: cache-watcher
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: watcher/target
|
||||
key: watcher-${{ runner.os }}-${{ runner.arch }}-${{ steps.determine-watcher-version.outputs.version }}-${{ env.CC && env.CC || 'gcc' }}
|
||||
- if: steps.cache-watcher.outputs.cache-hit != 'true'
|
||||
name: Compile e-dant/watcher
|
||||
run: |
|
||||
mkdir watcher
|
||||
gh release download --repo e-dant/watcher -A tar.gz -O - | tar -xz -C watcher --strip-components 1
|
||||
cd watcher
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build
|
||||
sudo cmake --install build --prefix target
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
- name: Update LD_LIBRARY_PATH
|
||||
run: |
|
||||
sudo sh -c "echo ${PWD}/watcher/target/lib > /etc/ld.so.conf.d/watcher.conf"
|
||||
sudo ldconfig
|
||||
shell: bash
|
||||
28
.github/dependabot.yaml
vendored
28
.github/dependabot.yaml
vendored
@@ -1,31 +1,33 @@
|
||||
---
|
||||
version: 2
|
||||
updates:
|
||||
-
|
||||
package-ecosystem: gomod
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
commit-message:
|
||||
prefix: chore
|
||||
-
|
||||
package-ecosystem: gomod
|
||||
groups:
|
||||
go-modules:
|
||||
patterns:
|
||||
- "*"
|
||||
- package-ecosystem: gomod
|
||||
directory: /caddy
|
||||
schedule:
|
||||
interval: weekly
|
||||
commit-message:
|
||||
prefix: chore(caddy)
|
||||
# These packages must be in sync with versions
|
||||
# used by github.com/caddyserver/caddy/v2
|
||||
ignore:
|
||||
-
|
||||
dependency-name: github.com/google/cel-go
|
||||
-
|
||||
dependency-name: github.com/quic-go/*
|
||||
-
|
||||
package-ecosystem: github-actions
|
||||
groups:
|
||||
go-modules:
|
||||
patterns:
|
||||
- "*"
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
commit-message:
|
||||
prefix: ci
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
155
.github/workflows/docker.yaml
vendored
155
.github/workflows/docker.yaml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- "docs/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -16,18 +16,20 @@ on:
|
||||
- v*.*.*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
#checkov:skip=CKV_GHA_7
|
||||
version:
|
||||
description: 'FrankenPHP version'
|
||||
description: "FrankenPHP version"
|
||||
required: false
|
||||
type: string
|
||||
schedule:
|
||||
- cron: '0 4 * * *'
|
||||
- cron: "0 4 * * *"
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
|
||||
LATEST: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && '0' || '1' }}
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
# Push if it's a scheduled job, a tag, or if we're committing to the main branch
|
||||
push: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request')) && true || false }}
|
||||
@@ -40,18 +42,19 @@ jobs:
|
||||
skip: ${{ steps.check.outputs.skip }}
|
||||
ref: ${{ steps.check.outputs.ref || (github.event_name == 'workflow_dispatch' && inputs.version) || '' }}
|
||||
steps:
|
||||
-
|
||||
name: Check PHP versions
|
||||
- name: Check PHP versions
|
||||
id: check
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PHP_82_LATEST=$(skopeo inspect docker://docker.io/library/php:8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
PHP_83_LATEST=$(skopeo inspect docker://docker.io/library/php:8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
PHP_84_LATEST=$(skopeo inspect docker://docker.io/library/php:8.4 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
{
|
||||
echo php_version="${PHP_83_LATEST},${PHP_82_LATEST}"
|
||||
echo php_version="${PHP_82_LATEST},${PHP_83_LATEST},${PHP_84_LATEST}"
|
||||
echo php82_version="${PHP_82_LATEST//./-}"
|
||||
echo php83_version="${PHP_83_LATEST//./-}"
|
||||
echo php84_version="${PHP_84_LATEST//./-}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
# Check if the Docker images must be rebuilt
|
||||
@@ -60,10 +63,11 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
|
||||
FRANKENPHP_82_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:latest-php8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
FRANKENPHP_83_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:latest-php8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
FRANKENPHP_82_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:php8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
FRANKENPHP_83_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:php8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
FRANKENPHP_84_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:php8.4 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
|
||||
if [[ "${FRANKENPHP_82_LATEST}" == "${PHP_82_LATEST}" ]] && [[ "${FRANKENPHP_83_LATEST}" == "${PHP_83_LATEST}" ]]; then
|
||||
if [[ "${FRANKENPHP_82_LATEST}" == "${PHP_82_LATEST}" ]] && [[ "${FRANKENPHP_83_LATEST}" == "${PHP_83_LATEST}" ]] && [[ "${FRANKENPHP_84_LATEST}" == "${PHP_84_LATEST}" ]]; then
|
||||
echo skip=true >> "${GITHUB_OUTPUT}"
|
||||
exit 0
|
||||
fi
|
||||
@@ -72,21 +76,19 @@ jobs:
|
||||
echo ref="$(gh release view --repo dunglas/frankenphp --json tagName --jq '.tagName')"
|
||||
echo skip=false
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
-
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ !fromJson(steps.check.outputs.skip) }}
|
||||
with:
|
||||
ref: ${{ steps.check.outputs.ref }}
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
- name: Set up Docker Buildx
|
||||
if: ${{ !fromJson(steps.check.outputs.skip) }}
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
-
|
||||
name: Create variants matrix
|
||||
- name: Create variants matrix
|
||||
if: ${{ !fromJson(steps.check.outputs.skip) }}
|
||||
id: matrix
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
METADATA="$(docker buildx bake --print | jq -c)"
|
||||
{
|
||||
echo metadata="${METADATA}"
|
||||
@@ -95,10 +97,10 @@ jobs:
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || steps.check.outputs.ref || github.sha }}
|
||||
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || steps.check.outputs.ref || 'dev' }}
|
||||
PHP_VERSION: ${{ steps.check.outputs.php_version }}
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
needs:
|
||||
- prepare
|
||||
if: ${{ !fromJson(needs.prepare.outputs.skip) }}
|
||||
@@ -108,52 +110,37 @@ jobs:
|
||||
variant: ${{ fromJson(needs.prepare.outputs.variants) }}
|
||||
platform: ${{ fromJson(needs.prepare.outputs.platforms) }}
|
||||
include:
|
||||
-
|
||||
race: ""
|
||||
qemu: true
|
||||
-
|
||||
platform: linux/amd64
|
||||
qemu: false
|
||||
- race: ""
|
||||
- platform: linux/amd64
|
||||
race: "-race" # The Go race detector is only supported on amd64
|
||||
-
|
||||
platform: linux/386
|
||||
qemu: false
|
||||
exclude:
|
||||
# arm/v6 is only available for Alpine: https://github.com/docker-library/golang/issues/502
|
||||
-
|
||||
variant: php-${{ needs.prepare.outputs.php82_version }}-bookworm
|
||||
- variant: php-${{ needs.prepare.outputs.php82_version }}-bookworm
|
||||
platform: linux/arm/v6
|
||||
-
|
||||
variant: php-${{ needs.prepare.outputs.php83_version }}-bookworm
|
||||
- variant: php-${{ needs.prepare.outputs.php83_version }}-bookworm
|
||||
platform: linux/arm/v6
|
||||
steps:
|
||||
-
|
||||
uses: actions/checkout@v4
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "sanitized_platform=${platform//\//-}" >> "${GITHUB_OUTPUT}"
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
-
|
||||
name: Set up QEMU
|
||||
if: matrix.qemu
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
version: latest
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
-
|
||||
name: Build
|
||||
- name: Build
|
||||
id: build
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
pull: true
|
||||
load: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
@@ -162,18 +149,21 @@ jobs:
|
||||
runner-${{ matrix.variant }}
|
||||
# Remove tags to prevent "can't push tagged ref [...] by digest" error
|
||||
set: |
|
||||
${{ (github.event_name == 'pull_request') && '*.args.NO_COMPRESS=1' || '' }}
|
||||
*.tags=
|
||||
*.platform=${{ matrix.platform }}
|
||||
*.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }}
|
||||
*.cache-from=type=gha,scope=refs/heads/main-${{ matrix.platform }}
|
||||
*.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }},ignore-error=true
|
||||
builder-${{ matrix.variant }}.cache-from=type=gha,scope=builder-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }}
|
||||
builder-${{ matrix.variant }}.cache-from=type=gha,scope=refs/heads/main-builder-${{ matrix.variant }}-${{ matrix.platform }}
|
||||
builder-${{ matrix.variant }}.cache-to=type=gha,scope=builder-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }},ignore-error=true
|
||||
runner-${{ matrix.variant }}.cache-from=type=gha,scope=runner-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }}
|
||||
runner-${{ matrix.variant }}.cache-from=type=gha,scope=refs/heads/main-runner-${{ matrix.variant }}-${{ matrix.platform }}
|
||||
runner-${{ matrix.variant }}.cache-to=type=gha,scope=runner-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }},ignore-error=true
|
||||
${{ fromJson(needs.prepare.outputs.push) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }}
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
VERSION: ${{ github.ref_type == 'tag' && github.ref_name || needs.prepare.outputs.ref || github.sha }}
|
||||
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref || 'dev' }}
|
||||
PHP_VERSION: ${{ needs.prepare.outputs.php_version }}
|
||||
-
|
||||
# Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600
|
||||
- # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600
|
||||
name: Export metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
run: |
|
||||
@@ -186,36 +176,33 @@ jobs:
|
||||
touch "/tmp/metadata/runner/${runnerDigest#sha256:}"
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
-
|
||||
name: Upload builder metadata
|
||||
- name: Upload builder metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metadata-builder-${{ matrix.variant }}
|
||||
name: metadata-builder-${{ matrix.variant }}-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata/builder/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
-
|
||||
name: Upload runner metadata
|
||||
- name: Upload runner metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metadata-runner-${{ matrix.variant }}
|
||||
name: metadata-runner-${{ matrix.variant }}-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata/runner/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
-
|
||||
name: Run tests
|
||||
if: ${{ !matrix.qemu && !fromJson(needs.prepare.outputs.push) }}
|
||||
- name: Run tests
|
||||
if: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
run: |
|
||||
docker run --platform=${{ matrix.platform }} --rm \
|
||||
"$(jq -r '."builder-${{ matrix.variant }}"."containerimage.config.digest"' <<< "${METADATA}")" \
|
||||
sh -c 'go test ${{ matrix.race }} -v ./... && cd caddy && go test ${{ matrix.race }} -v ./...'
|
||||
sh -c 'go test -tags ${{ matrix.race }} -v ./... && cd caddy && go test -tags nobadger,nomysql,nopgx ${{ matrix.race }} -v ./...'
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||
push:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- prepare
|
||||
- build
|
||||
@@ -224,28 +211,23 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: ${{ fromJson(needs.prepare.outputs.variants) }}
|
||||
target: ['builder', 'runner']
|
||||
target: ["builder", "runner"]
|
||||
steps:
|
||||
-
|
||||
name: Download metadata
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Download metadata
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metadata-${{ matrix.target }}-${{ matrix.variant }}
|
||||
pattern: metadata-${{ matrix.target }}-${{ matrix.variant }}-*
|
||||
path: /tmp/metadata
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
merge-multiple: true
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
# Temporary fix for https://github.com/docker/buildx/issues/2229
|
||||
version: "https://github.com/docker/buildx.git#master"
|
||||
-
|
||||
name: Login to DockerHub
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
-
|
||||
name: Create manifest list and push
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/metadata
|
||||
run: |
|
||||
set -x
|
||||
@@ -254,10 +236,9 @@ jobs:
|
||||
$(printf "${IMAGE_NAME}@sha256:%s " *)
|
||||
env:
|
||||
METADATA: ${{ needs.prepare.outputs.metadata }}
|
||||
-
|
||||
name: Inspect image
|
||||
- name: Inspect image
|
||||
run: |
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools inspect $(jq -cr '.target."${{ matrix.target }}-${{ matrix.variant }}".tags | first' <<< ${METADATA})
|
||||
docker buildx imagetools inspect $(jq -cr '.target."${{ matrix.target }}-${{ matrix.variant }}".tags | first' <<< ${METADATA})
|
||||
env:
|
||||
METADATA: ${{ needs.prepare.outputs.metadata }}
|
||||
|
||||
29
.github/workflows/lint.yaml
vendored
29
.github/workflows/lint.yaml
vendored
@@ -7,37 +7,36 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
statuses: write
|
||||
jobs:
|
||||
build:
|
||||
name: Lint Code Base
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
statuses: write
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout Code
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Lint Code Base
|
||||
uses: super-linter/super-linter@v5
|
||||
- name: Lint Code Base
|
||||
uses: super-linter/super-linter/slim@v7.4.0
|
||||
env:
|
||||
VALIDATE_ALL_CODEBASE: true
|
||||
DEFAULT_BRANCH: main
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
LINTER_RULES_PATH: /
|
||||
FILTER_REGEX_EXCLUDE: '.*C-Thread-Pool/.*'
|
||||
MARKDOWN_CONFIG_FILE: .markdown-lint.yaml
|
||||
VALIDATE_CPP: false
|
||||
VALIDATE_JSCPD: false
|
||||
VALIDATE_GO: false
|
||||
VALIDATE_GO_MODULES: false
|
||||
VALIDATE_PHP_PHPCS: false
|
||||
VALIDATE_PHP_PHPSTAN: false
|
||||
VALIDATE_PHP_PSALM: false
|
||||
VALIDATE_TERRAGRUNT: false
|
||||
VALIDATE_DOCKERFILE_HADOLINT: false
|
||||
# Prettier and StandardJS are incompatible
|
||||
VALIDATE_JAVASCRIPT_PRETTIER: false
|
||||
VALIDATE_TYPESCRIPT_PRETTIER: false
|
||||
# Conflicts with MARKDOWN
|
||||
VALIDATE_MARKDOWN_PRETTIER: false
|
||||
|
||||
104
.github/workflows/sanitizers.yaml
vendored
Normal file
104
.github/workflows/sanitizers.yaml
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
name: Sanitizers
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
GOTOOLCHAIN: local
|
||||
jobs:
|
||||
# Adapted from https://github.com/beberlei/hdrhistogram-php
|
||||
sanitizers:
|
||||
name: ${{ matrix.sanitizer }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
sanitizer: ["asan", "msan"]
|
||||
env:
|
||||
CFLAGS: -g -O0 -fsanitize=${{ matrix.sanitizer == 'asan' && 'address' || 'memory' }} -DZEND_TRACK_ARENA_ALLOC
|
||||
LDFLAGS: -fsanitize=${{ matrix.sanitizer == 'asan' && 'address' || 'memory' }}
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
USE_ZEND_ALLOC: 0
|
||||
LIBRARY_PATH: ${{ github.workspace }}/php/target/lib:${{ github.workspace }}/watcher/target/lib
|
||||
LD_LIBRARY_PATH: ${{ github.workspace }}/php/target/lib
|
||||
steps:
|
||||
- name: Remove local PHP
|
||||
run: sudo apt-get remove --purge --autoremove 'php*' 'libmemcached*'
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
- name: Determine PHP version
|
||||
id: determine-php-version
|
||||
run: |
|
||||
curl -fsSL 'https://www.php.net/releases/index.php?json&max=1&version=8.4' -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
|
||||
id: cache-php
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: php/target
|
||||
key: php-sanitizers-${{ matrix.sanitizer }}-${{ runner.arch }}-${{ steps.determine-php-version.outputs.version }}
|
||||
- if: steps.cache-php.outputs.cache-hit != 'true'
|
||||
name: Compile PHP
|
||||
run: |
|
||||
mkdir php/
|
||||
curl -fsSL "${{ steps.determine-php-version.outputs.archive }}" | tar -Jx -C php --strip-components=1
|
||||
cd php/
|
||||
./configure \
|
||||
CFLAGS="$CFLAGS" \
|
||||
LDFLAGS="$LDFLAGS" \
|
||||
--enable-debug \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--enable-option-checking=fatal \
|
||||
--disable-zend-signals \
|
||||
--without-sqlite3 \
|
||||
--without-pdo-sqlite \
|
||||
--without-libxml \
|
||||
--disable-dom \
|
||||
--disable-simplexml \
|
||||
--disable-xml \
|
||||
--disable-xmlreader \
|
||||
--disable-xmlwriter \
|
||||
--without-pcre-jit \
|
||||
--disable-opcache-jit \
|
||||
--disable-cli \
|
||||
--disable-cgi \
|
||||
--disable-phpdbg \
|
||||
--without-pear \
|
||||
--disable-mbregex \
|
||||
--enable-werror \
|
||||
${{ matrix.sanitizer == 'msan' && '--enable-memory-sanitizer' || '' }} \
|
||||
--prefix="$(pwd)/target/"
|
||||
make -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
make install
|
||||
- name: Add PHP to the PATH
|
||||
run: echo "$(pwd)/php/target/bin" >> "$GITHUB_PATH"
|
||||
- name: Install e-dant/watcher
|
||||
uses: ./.github/actions/watcher
|
||||
- name: Set Set CGO flags
|
||||
run: |
|
||||
{
|
||||
echo "CGO_CFLAGS=$CFLAGS -I${PWD}/watcher/target/include $(php-config --includes)"
|
||||
echo "CGO_LDFLAGS=$LDFLAGS $(php-config --ldflags) $(php-config --libs)"
|
||||
} >> "$GITHUB_ENV"
|
||||
- name: Compile tests
|
||||
run: go test ${{ matrix.sanitizer == 'msan' && '-tags=nowatcher' || '' }} -${{ matrix.sanitizer }} -v -x -c
|
||||
- name: Run tests
|
||||
run: ./frankenphp.dev.test -test.v
|
||||
382
.github/workflows/static.yaml
vendored
382
.github/workflows/static.yaml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- "docs/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -16,26 +16,31 @@ on:
|
||||
- v*.*.*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
#checkov:skip=CKV_GHA_7
|
||||
version:
|
||||
description: 'FrankenPHP version'
|
||||
description: "FrankenPHP version"
|
||||
required: false
|
||||
type: string
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
- cron: "0 0 * * *"
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
env:
|
||||
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
|
||||
LATEST: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && '0' || '1' }}
|
||||
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
|
||||
GOTOOLCHAIN: local
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
push: ${{ toJson((steps.check.outputs.ref || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request')) && true || false) }}
|
||||
platforms: ${{ steps.matrix.outputs.platforms }}
|
||||
metadata: ${{ steps.matrix.outputs.metadata }}
|
||||
gnu_metadata: ${{ steps.matrix.outputs.gnu_metadata }}
|
||||
ref: ${{ steps.check.outputs.ref }}
|
||||
steps:
|
||||
-
|
||||
name: Get version
|
||||
- name: Get version
|
||||
id: check
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
@@ -47,28 +52,25 @@ jobs:
|
||||
echo "ref=${ref}" >> "${GITHUB_OUTPUT}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ steps.check.outputs.ref }}
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
-
|
||||
name: Create platforms matrix
|
||||
- name: Create platforms matrix
|
||||
id: matrix
|
||||
run: |
|
||||
METADATA="$(docker buildx bake --print static-builder | jq -c)"
|
||||
METADATA="$(docker buildx bake --print static-builder-musl | jq -c)"
|
||||
GNU_METADATA="$(docker buildx bake --print static-builder-gnu | jq -c)"
|
||||
{
|
||||
echo metadata="${METADATA}"
|
||||
echo platforms="$(jq -c 'first(.target[]) | .platforms' <<< "${METADATA}")"
|
||||
echo gnu_metadata="${GNU_METADATA}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
VERSION: ${{ steps.check.outputs.ref || github.sha }}
|
||||
build-linux:
|
||||
VERSION: ${{ steps.check.outputs.ref || 'dev' }}
|
||||
build-linux-musl:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -76,192 +78,297 @@ jobs:
|
||||
debug: [false]
|
||||
mimalloc: [false]
|
||||
include:
|
||||
-
|
||||
qemu: true
|
||||
-
|
||||
platform: linux/amd64
|
||||
qemu: false
|
||||
-
|
||||
platform: linux/amd64
|
||||
qemu: false
|
||||
- platform: linux/amd64
|
||||
- platform: linux/amd64
|
||||
debug: true
|
||||
-
|
||||
platform: linux/amd64
|
||||
qemu: false
|
||||
- platform: linux/amd64
|
||||
mimalloc: true
|
||||
name: Build ${{ matrix.platform }} static binary${{ matrix.debug && ' (debug)' || '' }}${{ matrix.mimalloc && ' (mimalloc)' || '' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ prepare ]
|
||||
name: Build ${{ matrix.platform }} static musl binary${{ matrix.debug && ' (debug)' || '' }}${{ matrix.mimalloc && ' (mimalloc)' || '' }}
|
||||
runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
needs: [prepare]
|
||||
steps:
|
||||
-
|
||||
uses: actions/checkout@v4
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "sanitized_platform=${platform//\//-}" >> "${GITHUB_OUTPUT}"
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
-
|
||||
name: Set up QEMU
|
||||
if: matrix.qemu
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
version: latest
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: ${{ fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc }}
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
-
|
||||
name: Build
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Build
|
||||
id: build
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
pull: true
|
||||
load: ${{ !fromJson(needs.prepare.outputs.push) || matrix.debug || matrix.mimalloc }}
|
||||
targets: static-builder
|
||||
targets: static-builder-musl
|
||||
set: |
|
||||
${{ matrix.debug && 'static-builder.args.DEBUG_SYMBOLS=1' || '' }}
|
||||
${{ matrix.mimalloc && 'static-builder.args.MIMALLOC=1' || '' }}
|
||||
${{ matrix.debug && 'static-builder-musl.args.DEBUG_SYMBOLS=1' || '' }}
|
||||
${{ matrix.mimalloc && 'static-builder-musl.args.MIMALLOC=1' || '' }}
|
||||
${{ (github.event_name == 'pull_request' || matrix.platform == 'linux/arm64') && 'static-builder-musl.args.NO_COMPRESS=1' || '' }}
|
||||
*.tags=
|
||||
*.platform=${{ matrix.platform }}
|
||||
*.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
*.cache-from=type=gha,scope=refs/heads/main-static-builder${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
*.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }},ignore-error=true
|
||||
*.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
*.cache-from=type=gha,scope=refs/heads/main-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
*.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }},ignore-error=true
|
||||
${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }}
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref || github.sha }}
|
||||
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref || 'dev' }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
# Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600
|
||||
- # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600
|
||||
name: Export metadata
|
||||
if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc
|
||||
run: |
|
||||
mkdir -p /tmp/metadata
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
digest=$(jq -r '."static-builder"."containerimage.digest"' <<< ${METADATA})
|
||||
digest=$(jq -r '."static-builder-musl"."containerimage.digest"' <<< ${METADATA})
|
||||
touch "/tmp/metadata/${digest#sha256:}"
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
-
|
||||
name: Upload metadata
|
||||
- name: Upload metadata
|
||||
if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metadata-static-builder
|
||||
name: metadata-static-builder-musl-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
-
|
||||
name: Copy binary
|
||||
if: ${{ !fromJson(needs.prepare.outputs.push) || matrix.debug || matrix.mimalloc }}
|
||||
- name: Copy binary
|
||||
run: |
|
||||
digest=$(jq -r '."static-builder"."containerimage.config.digest"' <<< "${METADATA}")
|
||||
docker create --platform=${{ matrix.platform }} --name static-builder "${digest}"
|
||||
docker cp "static-builder:/go/src/app/dist/${BINARY}" "${BINARY}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}"
|
||||
# shellcheck disable=SC2034
|
||||
digest=$(jq -r '."static-builder-musl"."${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && 'containerimage.digest' || 'containerimage.config.digest' }}"' <<< "${METADATA}")
|
||||
docker create --platform=${{ matrix.platform }} --name static-builder-musl "${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && '${IMAGE_NAME}@${digest}' || '${digest}' }}"
|
||||
docker cp "static-builder-musl:/go/src/app/dist/${BINARY}" "${BINARY}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}"
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
BINARY: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}
|
||||
-
|
||||
name: Upload artifact
|
||||
- name: Upload artifact
|
||||
if: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
path: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
-
|
||||
name: Upload special assets
|
||||
if: fromJson(needs.prepare.outputs.push) && (matrix.debug || matrix.mimalloc) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
|
||||
run: gh release upload "${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }}" frankenphp-linux-x86_64${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} --repo dunglas/frankenphp --clobber
|
||||
- name: Upload assets
|
||||
if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
|
||||
run: gh release upload "${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }}" frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} --repo dunglas/frankenphp --clobber
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||
push:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prepare
|
||||
- build-linux
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
#if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
|
||||
steps:
|
||||
-
|
||||
name: Download metadata
|
||||
uses: actions/download-artifact@v3
|
||||
- if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
name: metadata-static-builder
|
||||
path: /tmp/metadata
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
subject-path: ${{ github.workspace }}/frankenphp-linux-*
|
||||
- name: Run sanity checks
|
||||
run: |
|
||||
"${BINARY}" version
|
||||
"${BINARY}" build-info
|
||||
"${BINARY}" list-modules | grep frankenphp
|
||||
"${BINARY}" list-modules | grep http.encoders.br
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.vulcain
|
||||
"${BINARY}" php-cli -r "echo 'Sanity check passed';"
|
||||
env:
|
||||
BINARY: ./frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
|
||||
build-linux-gnu:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: ${{ fromJson(needs.prepare.outputs.platforms) }}
|
||||
name: Build ${{ matrix.platform }} static GNU binary
|
||||
runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
needs: [prepare]
|
||||
steps:
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "sanitized_platform=${platform//\//-}" >> "${GITHUB_OUTPUT}"
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
-
|
||||
name: Login to DockerHub
|
||||
platforms: ${{ matrix.platform }}
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
-
|
||||
name: Create manifest list and push
|
||||
- name: Build
|
||||
id: build
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
pull: true
|
||||
load: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
targets: static-builder-gnu
|
||||
set: |
|
||||
${{ (github.event_name == 'pull_request' || matrix.platform == 'linux/arm64') && 'static-builder-gnu.args.NO_COMPRESS=1' || '' }}
|
||||
static-builder-gnu.args.BUILD_PACKAGES=1
|
||||
*.tags=
|
||||
*.platform=${{ matrix.platform }}
|
||||
*.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-gnu
|
||||
*.cache-from=type=gha,scope=refs/heads/main-static-builder-gnu
|
||||
*.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-gnu,ignore-error=true
|
||||
${{ fromJson(needs.prepare.outputs.push) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }}
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref || 'dev' }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600
|
||||
name: Export metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
run: |
|
||||
mkdir -p /tmp/metadata-gnu
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
digest=$(jq -r '."static-builder-gnu"."containerimage.digest"' <<< ${METADATA})
|
||||
touch "/tmp/metadata-gnu/${digest#sha256:}"
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
- name: Upload metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metadata-static-builder-gnu-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata-gnu/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
- name: Copy all frankenphp* files
|
||||
run: |
|
||||
# shellcheck disable=SC2034
|
||||
digest=$(jq -r '."static-builder-gnu"."${{ fromJson(needs.prepare.outputs.push) && 'containerimage.digest' || 'containerimage.config.digest' }}"' <<< "${METADATA}")
|
||||
container_id=$(docker create --platform=${{ matrix.platform }} "${{ fromJson(needs.prepare.outputs.push) && '${IMAGE_NAME}@${digest}' || '${digest}' }}")
|
||||
mkdir -p gh-output
|
||||
cd gh-output
|
||||
for file in $(docker run --rm "${{ fromJson(needs.prepare.outputs.push) && '${IMAGE_NAME}@${digest}' || '${digest}' }}" sh -c "ls /go/src/app/dist | grep '^frankenphp'"); do
|
||||
docker cp "${container_id}:/go/src/app/dist/${file}" "./${file}"
|
||||
done
|
||||
docker rm "${container_id}"
|
||||
mv "${BINARY}" "${BINARY}-gnu"
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
BINARY: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}
|
||||
- name: Upload artifact
|
||||
if: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}-gnu-files
|
||||
path: gh-output/*
|
||||
- name: Upload assets
|
||||
if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
|
||||
run: gh release upload "${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }}" gh-output/* --repo dunglas/frankenphp --clobber
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: ${{ github.workspace }}/gh-output/frankenphp-linux-*-gnu
|
||||
- name: Run sanity checks
|
||||
run: |
|
||||
"${BINARY}" version
|
||||
"${BINARY}" list-modules | grep frankenphp
|
||||
"${BINARY}" list-modules | grep http.encoders.br
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.vulcain
|
||||
"${BINARY}" php-cli -r "echo 'Sanity check passed';"
|
||||
env:
|
||||
BINARY: ./gh-output/frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}-gnu
|
||||
|
||||
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||
push:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- prepare
|
||||
- build-linux-musl
|
||||
- build-linux-gnu
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
steps:
|
||||
- name: Download metadata
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: metadata-static-builder-musl-*
|
||||
path: /tmp/metadata
|
||||
merge-multiple: true
|
||||
- name: Download GNU metadata
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: metadata-static-builder-gnu-*
|
||||
path: /tmp/metadata-gnu
|
||||
merge-multiple: true
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/metadata
|
||||
run: |
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools create $(jq -cr '.target."static-builder".tags | map("-t " + .) | join(" ")' <<< "${METADATA}") \
|
||||
docker buildx imagetools create $(jq -cr '.target."static-builder-musl".tags | map("-t " + .) | join(" ")' <<< "${METADATA}") \
|
||||
$(printf "${IMAGE_NAME}@sha256:%s " *)
|
||||
env:
|
||||
METADATA: ${{ needs.prepare.outputs.metadata }}
|
||||
-
|
||||
name: Inspect image
|
||||
- name: Create GNU manifest list and push
|
||||
working-directory: /tmp/metadata-gnu
|
||||
run: |
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools inspect "$(jq -cr '.target."static-builder".tags | first' <<< "${METADATA}")"
|
||||
docker buildx imagetools create $(jq -cr '.target."static-builder-gnu".tags | map("-t " + .) | join(" ")' <<< "${GNU_METADATA}") \
|
||||
$(printf "${IMAGE_NAME}@sha256:%s " *)
|
||||
env:
|
||||
METADATA: ${{ needs.prepare.outputs.metadata }}
|
||||
-
|
||||
name: Copy binary
|
||||
GNU_METADATA: ${{ needs.prepare.outputs.gnu_metadata }}
|
||||
- name: Inspect image
|
||||
run: |
|
||||
tag=$(jq -cr '.target."static-builder".tags | first' <<< "${METADATA}")
|
||||
docker cp "$(docker create --platform=linux/amd64 --name static-builder "${tag}"):/go/src/app/dist/frankenphp-linux-x86_64" frankenphp-linux-x86_64 ; docker rm static-builder
|
||||
docker cp "$(docker create --platform=linux/arm64 --name static-builder "${tag}"):/go/src/app/dist/frankenphp-linux-aarch64" frankenphp-linux-aarch64 ; docker rm static-builder
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools inspect "$(jq -cr '.target."static-builder-musl".tags | first' <<< "${METADATA}")"
|
||||
env:
|
||||
METADATA: ${{ needs.prepare.outputs.metadata }}
|
||||
-
|
||||
name: Upload asset
|
||||
if: needs.prepare.outputs.ref || github.ref_type == 'tag'
|
||||
run: gh release upload "${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }}" frankenphp-linux-x86_64 frankenphp-linux-aarch64 --repo dunglas/frankenphp --clobber
|
||||
- name: Inspect GNU image
|
||||
run: |
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools inspect "$(jq -cr '.target."static-builder-gnu".tags | first' <<< "${GNU_METADATA}")-gnu"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GNU_METADATA: ${{ needs.prepare.outputs.gnu_metadata }}
|
||||
|
||||
build-mac:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: ['arm64', 'x86_64']
|
||||
platform: ["arm64", "x86_64"]
|
||||
name: Build macOS ${{ matrix.platform }} binaries
|
||||
runs-on: ${{ matrix.platform == 'arm64' && 'macos-14' || 'macos-13' }}
|
||||
needs: [ prepare ]
|
||||
needs: [prepare]
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
steps:
|
||||
-
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
-
|
||||
uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: "1.24"
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
-
|
||||
name: Set FRANKENPHP_VERSION
|
||||
- name: Set FRANKENPHP_VERSION
|
||||
run: |
|
||||
if [ "${GITHUB_REF_TYPE}" == "tag" ]; then
|
||||
export FRANKENPHP_VERSION=${GITHUB_REF_NAME:1}
|
||||
@@ -272,16 +379,31 @@ jobs:
|
||||
fi
|
||||
|
||||
echo "FRANKENPHP_VERSION=${FRANKENPHP_VERSION}" >> "${GITHUB_ENV}"
|
||||
-
|
||||
name: Build FrankenPHP
|
||||
- name: Build FrankenPHP
|
||||
run: ./build-static.sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE: ${{ (needs.prepare.outputs.ref || github.ref_type == 'tag') && '1' || '' }}
|
||||
-
|
||||
name: Upload artifact
|
||||
NO_COMPRESS: ${{ github.event_name == 'pull_request' && '1' || '' }}
|
||||
- if: needs.prepare.outputs.ref || github.ref_type == 'tag'
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: ${{ github.workspace }}/dist/frankenphp-mac-*
|
||||
- name: Upload artifact
|
||||
if: github.ref_type == 'branch'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frankenphp-mac-${{ matrix.platform }}
|
||||
path: dist/frankenphp-mac-${{ matrix.platform }}
|
||||
- name: Run sanity checks
|
||||
run: |
|
||||
"${BINARY}" version
|
||||
"${BINARY}" build-info
|
||||
"${BINARY}" list-modules | grep frankenphp
|
||||
"${BINARY}" list-modules | grep http.encoders.br
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.vulcain
|
||||
"${BINARY}" php-cli -r "echo 'Sanity check passed';"
|
||||
env:
|
||||
BINARY: dist/frankenphp-mac-${{ matrix.platform }}
|
||||
|
||||
106
.github/workflows/tests.yaml
vendored
106
.github/workflows/tests.yaml
vendored
@@ -5,33 +5,37 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- "docs/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- "docs/**"
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
GOTOOLCHAIN: local
|
||||
GOEXPERIMENT: cgocheck2
|
||||
jobs:
|
||||
tests:
|
||||
tests-linux:
|
||||
name: Tests (Linux, PHP ${{ matrix.php-versions }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.2', '8.3']
|
||||
php-versions: ["8.2", "8.3", "8.4"]
|
||||
env:
|
||||
GOEXPERIMENT: cgocheck2
|
||||
GOMAXPROCS: 10
|
||||
LIBRARY_PATH: ${{ github.workspace }}/watcher/target/lib
|
||||
steps:
|
||||
-
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
uses: actions/setup-go@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: "1.24"
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
-
|
||||
uses: shivammathur/setup-php@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
ini-file: development
|
||||
@@ -39,37 +43,69 @@ jobs:
|
||||
tools: none
|
||||
env:
|
||||
phpts: ts
|
||||
-
|
||||
name: Set CGO flags
|
||||
run: |
|
||||
echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
|
||||
-
|
||||
name: Build
|
||||
run: go build
|
||||
-
|
||||
name: Build testcli binary
|
||||
debug: true
|
||||
- name: Install e-dant/watcher
|
||||
uses: ./.github/actions/watcher
|
||||
- name: Set CGO flags
|
||||
run: echo "CGO_CFLAGS=-I${PWD}/watcher/target/include $(php-config --includes)" >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
run: go build
|
||||
- name: Build testcli binary
|
||||
working-directory: internal/testcli/
|
||||
run: go build
|
||||
-
|
||||
name: Run library tests
|
||||
- name: Run library tests
|
||||
run: go test -race -v ./...
|
||||
-
|
||||
name: Run Caddy module tests
|
||||
- name: Run Caddy module tests
|
||||
working-directory: caddy/
|
||||
run: go test -race -v ./...
|
||||
-
|
||||
name: Build the server
|
||||
run: go test -tags nobadger,nomysql,nopgx -race -v ./...
|
||||
- name: Run Fuzzing Tests
|
||||
working-directory: caddy/
|
||||
run: go test -fuzz FuzzRequest -fuzztime 20s
|
||||
- name: Build the server
|
||||
working-directory: caddy/frankenphp/
|
||||
run: go build
|
||||
-
|
||||
name: Start the server
|
||||
- name: Start the server
|
||||
working-directory: testdata/
|
||||
run: sudo ../caddy/frankenphp/frankenphp start
|
||||
-
|
||||
name: Run integrations tests
|
||||
- name: Run integrations tests
|
||||
run: ./reload_test.sh
|
||||
-
|
||||
name: Lint Go code
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
- name: Lint Go code
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
if: matrix.php-versions == '8.4'
|
||||
with:
|
||||
version: latest
|
||||
tests-mac:
|
||||
name: Tests (macOS, PHP 8.4)
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.4
|
||||
ini-file: development
|
||||
coverage: none
|
||||
tools: none
|
||||
env:
|
||||
phpts: ts
|
||||
debug: true
|
||||
- name: Set Set CGO flags
|
||||
run: |
|
||||
{
|
||||
echo "CGO_CFLAGS=-I/opt/homebrew/include/ $(php-config --includes)"
|
||||
echo "CGO_LDFLAGS=-L/opt/homebrew/lib/ $(php-config --ldflags) $(php-config --libs)"
|
||||
} >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
run: go build -tags nowatcher
|
||||
- name: Run library tests
|
||||
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 ./...
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -2,7 +2,11 @@
|
||||
/internal/testserver/testserver
|
||||
/internal/testcli/testcli
|
||||
/dist
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
__debug_bin
|
||||
frankenphp.test
|
||||
frankenphp.dev.test
|
||||
caddy/frankenphp/Build
|
||||
package/etc/php.ini
|
||||
*.log
|
||||
|
||||
7
.golangci.yaml
Normal file
7
.golangci.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
version: "2"
|
||||
run:
|
||||
build-tags:
|
||||
- nobadger
|
||||
- nomysql
|
||||
- nopgx
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Johan Hanssen Seferidis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,70 +0,0 @@
|
||||
[](https://github.com/Pithikos/C-Thread-Pool/actions?query=workflow%3Atests+branch%3Amaster)
|
||||
|
||||
|
||||
# C Thread Pool
|
||||
|
||||
This is a minimal but advanced threadpool implementation.
|
||||
|
||||
* ANCI C and POSIX compliant
|
||||
* Pause/resume/wait as you like
|
||||
* Simple easy-to-digest API
|
||||
* Well tested
|
||||
|
||||
The threadpool is under MIT license. Notice that this project took a considerable amount of work and sacrifice of my free time and the reason I give it for free (even for commercial use) is so when you become rich and wealthy you don't forget about us open-source creatures of the night. Cheers!
|
||||
|
||||
If this project reduced your development time feel free to buy me a coffee.
|
||||
|
||||
[](https://www.paypal.me/seferidis)
|
||||
|
||||
|
||||
## Run an example
|
||||
|
||||
The library is not precompiled so you have to compile it with your project. The thread pool
|
||||
uses POSIX threads so if you compile with gcc on Linux you have to use the flag `-pthread` like this:
|
||||
|
||||
gcc example.c thpool.c -D THPOOL_DEBUG -pthread -o example
|
||||
|
||||
|
||||
Then run the executable like this:
|
||||
|
||||
./example
|
||||
|
||||
|
||||
## Basic usage
|
||||
|
||||
1. Include the header in your source file: `#include "thpool.h"`
|
||||
2. Create a thread pool with number of threads you want: `threadpool thpool = thpool_init(4);`
|
||||
3. Add work to the pool: `thpool_add_work(thpool, (void*)function_p, (void*)arg_p);`
|
||||
|
||||
The workers(threads) will start their work automatically as fast as there is new work
|
||||
in the pool. If you want to wait for all added work to be finished before continuing
|
||||
you can use `thpool_wait(thpool);`. If you want to destroy the pool you can use
|
||||
`thpool_destroy(thpool);`.
|
||||
|
||||
|
||||
## API
|
||||
|
||||
For a deeper look into the documentation check in the [thpool.h](https://github.com/Pithikos/C-Thread-Pool/blob/master/thpool.h) file. Below is a fast practical overview.
|
||||
|
||||
| Function example | Description |
|
||||
|---------------------------------|---------------------------------------------------------------------|
|
||||
| ***thpool_init(4)*** | Will return a new threadpool with `4` threads. |
|
||||
| ***thpool_add_work(thpool, (void*)function_p, (void*)arg_p)*** | Will add new work to the pool. Work is simply a function. You can pass a single argument to the function if you wish. If not, `NULL` should be passed. |
|
||||
| ***thpool_wait(thpool)*** | Will wait for all jobs (both in queue and currently running) to finish. |
|
||||
| ***thpool_destroy(thpool)*** | This will destroy the threadpool. If jobs are currently being executed, then it will wait for them to finish. |
|
||||
| ***thpool_pause(thpool)*** | All threads in the threadpool will pause no matter if they are idle or executing work. |
|
||||
| ***thpool_resume(thpool)*** | If the threadpool is paused, then all threads will resume from where they were. |
|
||||
| ***thpool_num_threads_working(thpool)*** | Will return the number of currently working threads. |
|
||||
|
||||
|
||||
## Contribution
|
||||
|
||||
You are very welcome to contribute. If you have a new feature in mind, you can always open an issue on github describing it so you don't end up doing a lot of work that might not be eventually merged. Generally we are very open to contributions as long as they follow the below keypoints.
|
||||
|
||||
* Try to keep the API as minimal as possible. That means if a feature or fix can be implemented without affecting the existing API but requires more development time, then we will opt to sacrifice development time.
|
||||
* Solutions need to be POSIX compliant. The thread-pool is advertised as such so it makes sense that it actually is.
|
||||
* For coding style simply try to stick to the conventions you find in the existing codebase.
|
||||
* Tests: A new fix or feature should be covered by tests. If the existing tests are not sufficient, we expect an according test to follow with the pull request.
|
||||
* Documentation: for a new feature please add documentation. For an API change the documentation has to be thorough and super easy to understand.
|
||||
|
||||
If you wish to **get access as a collaborator** feel free to mention it in the issue https://github.com/Pithikos/C-Thread-Pool/issues/78
|
||||
@@ -1,571 +0,0 @@
|
||||
/* ********************************
|
||||
* Author: Johan Hanssen Seferidis
|
||||
* License: MIT
|
||||
* Description: Library providing a threading pool where you can add
|
||||
* work. For usage, check the thpool.h file or README.md
|
||||
*
|
||||
*//** @file thpool.h *//*
|
||||
*
|
||||
********************************/
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <AvailabilityMacros.h>
|
||||
#else
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
#ifndef _XOPEN_SOURCE
|
||||
#define _XOPEN_SOURCE 500
|
||||
#endif
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#if defined(__linux__)
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
#if defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
#include "thpool.h"
|
||||
|
||||
#ifdef THPOOL_DEBUG
|
||||
#define THPOOL_DEBUG 1
|
||||
#else
|
||||
#define THPOOL_DEBUG 0
|
||||
#endif
|
||||
|
||||
#if !defined(DISABLE_PRINT) || defined(THPOOL_DEBUG)
|
||||
#define err(str) fprintf(stderr, str)
|
||||
#else
|
||||
#define err(str)
|
||||
#endif
|
||||
|
||||
#ifndef THPOOL_THREAD_NAME
|
||||
#define THPOOL_THREAD_NAME thpool
|
||||
#endif
|
||||
|
||||
#define STRINGIFY(x) #x
|
||||
#define TOSTRING(x) STRINGIFY(x)
|
||||
|
||||
static volatile int threads_keepalive;
|
||||
static volatile int threads_on_hold;
|
||||
|
||||
|
||||
|
||||
/* ========================== STRUCTURES ============================ */
|
||||
|
||||
|
||||
/* Binary semaphore */
|
||||
typedef struct bsem {
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
int v;
|
||||
} bsem;
|
||||
|
||||
|
||||
/* Job */
|
||||
typedef struct job{
|
||||
struct job* prev; /* pointer to previous job */
|
||||
void (*function)(void* arg); /* function pointer */
|
||||
void* arg; /* function's argument */
|
||||
} job;
|
||||
|
||||
|
||||
/* Job queue */
|
||||
typedef struct jobqueue{
|
||||
pthread_mutex_t rwmutex; /* used for queue r/w access */
|
||||
job *front; /* pointer to front of queue */
|
||||
job *rear; /* pointer to rear of queue */
|
||||
bsem *has_jobs; /* flag as binary semaphore */
|
||||
int len; /* number of jobs in queue */
|
||||
} jobqueue;
|
||||
|
||||
|
||||
/* Thread */
|
||||
typedef struct thread{
|
||||
int id; /* friendly id */
|
||||
pthread_t pthread; /* pointer to actual thread */
|
||||
struct thpool_* thpool_p; /* access to thpool */
|
||||
} thread;
|
||||
|
||||
|
||||
/* Threadpool */
|
||||
typedef struct thpool_{
|
||||
thread** threads; /* pointer to threads */
|
||||
volatile int num_threads_alive; /* threads currently alive */
|
||||
volatile int num_threads_working; /* threads currently working */
|
||||
pthread_mutex_t thcount_lock; /* used for thread count etc */
|
||||
pthread_cond_t threads_all_idle; /* signal to thpool_wait */
|
||||
jobqueue jobqueue; /* job queue */
|
||||
} thpool_;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ========================== PROTOTYPES ============================ */
|
||||
|
||||
|
||||
static int thread_init(thpool_* thpool_p, struct thread** thread_p, int id);
|
||||
static void* thread_do(struct thread* thread_p);
|
||||
static void thread_hold(int sig_id);
|
||||
static void thread_destroy(struct thread* thread_p);
|
||||
|
||||
static int jobqueue_init(jobqueue* jobqueue_p);
|
||||
static void jobqueue_clear(jobqueue* jobqueue_p);
|
||||
static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob_p);
|
||||
static struct job* jobqueue_pull(jobqueue* jobqueue_p);
|
||||
static void jobqueue_destroy(jobqueue* jobqueue_p);
|
||||
|
||||
static void bsem_init(struct bsem *bsem_p, int value);
|
||||
static void bsem_reset(struct bsem *bsem_p);
|
||||
static void bsem_post(struct bsem *bsem_p);
|
||||
static void bsem_post_all(struct bsem *bsem_p);
|
||||
static void bsem_wait(struct bsem *bsem_p);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ========================== THREADPOOL ============================ */
|
||||
|
||||
|
||||
/* Initialise thread pool */
|
||||
struct thpool_* thpool_init(int num_threads){
|
||||
|
||||
threads_on_hold = 0;
|
||||
threads_keepalive = 1;
|
||||
|
||||
if (num_threads < 0){
|
||||
num_threads = 0;
|
||||
}
|
||||
|
||||
/* Make new thread pool */
|
||||
thpool_* thpool_p;
|
||||
thpool_p = (struct thpool_*)malloc(sizeof(struct thpool_));
|
||||
if (thpool_p == NULL){
|
||||
err("thpool_init(): Could not allocate memory for thread pool\n");
|
||||
return NULL;
|
||||
}
|
||||
thpool_p->num_threads_alive = 0;
|
||||
thpool_p->num_threads_working = 0;
|
||||
|
||||
/* Initialise the job queue */
|
||||
if (jobqueue_init(&thpool_p->jobqueue) == -1){
|
||||
err("thpool_init(): Could not allocate memory for job queue\n");
|
||||
free(thpool_p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Make threads in pool */
|
||||
thpool_p->threads = (struct thread**)malloc(num_threads * sizeof(struct thread *));
|
||||
if (thpool_p->threads == NULL){
|
||||
err("thpool_init(): Could not allocate memory for threads\n");
|
||||
jobqueue_destroy(&thpool_p->jobqueue);
|
||||
free(thpool_p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_init(&(thpool_p->thcount_lock), NULL);
|
||||
pthread_cond_init(&thpool_p->threads_all_idle, NULL);
|
||||
|
||||
/* Thread init */
|
||||
int n;
|
||||
for (n=0; n<num_threads; n++){
|
||||
thread_init(thpool_p, &thpool_p->threads[n], n);
|
||||
#if THPOOL_DEBUG
|
||||
printf("THPOOL_DEBUG: Created thread %d in pool \n", n);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Wait for threads to initialize */
|
||||
while (thpool_p->num_threads_alive != num_threads) {}
|
||||
|
||||
return thpool_p;
|
||||
}
|
||||
|
||||
|
||||
/* Add work to the thread pool */
|
||||
int thpool_add_work(thpool_* thpool_p, void (*function_p)(void*), void* arg_p){
|
||||
job* newjob;
|
||||
|
||||
newjob=(struct job*)malloc(sizeof(struct job));
|
||||
if (newjob==NULL){
|
||||
err("thpool_add_work(): Could not allocate memory for new job\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* add function and argument */
|
||||
newjob->function=function_p;
|
||||
newjob->arg=arg_p;
|
||||
|
||||
/* add job to queue */
|
||||
jobqueue_push(&thpool_p->jobqueue, newjob);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Wait until all jobs have finished */
|
||||
void thpool_wait(thpool_* thpool_p){
|
||||
pthread_mutex_lock(&thpool_p->thcount_lock);
|
||||
while (thpool_p->jobqueue.len || thpool_p->num_threads_working) {
|
||||
pthread_cond_wait(&thpool_p->threads_all_idle, &thpool_p->thcount_lock);
|
||||
}
|
||||
pthread_mutex_unlock(&thpool_p->thcount_lock);
|
||||
}
|
||||
|
||||
|
||||
/* Destroy the threadpool */
|
||||
void thpool_destroy(thpool_* thpool_p){
|
||||
/* No need to destroy if it's NULL */
|
||||
if (thpool_p == NULL) return ;
|
||||
|
||||
volatile int threads_total = thpool_p->num_threads_alive;
|
||||
|
||||
/* End each thread 's infinite loop */
|
||||
threads_keepalive = 0;
|
||||
|
||||
/* Give one second to kill idle threads */
|
||||
double TIMEOUT = 1.0;
|
||||
time_t start, end;
|
||||
double tpassed = 0.0;
|
||||
time (&start);
|
||||
while (tpassed < TIMEOUT && thpool_p->num_threads_alive){
|
||||
bsem_post_all(thpool_p->jobqueue.has_jobs);
|
||||
time (&end);
|
||||
tpassed = difftime(end,start);
|
||||
}
|
||||
|
||||
/* Poll remaining threads */
|
||||
while (thpool_p->num_threads_alive){
|
||||
bsem_post_all(thpool_p->jobqueue.has_jobs);
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/* Job queue cleanup */
|
||||
jobqueue_destroy(&thpool_p->jobqueue);
|
||||
/* Deallocs */
|
||||
int n;
|
||||
for (n=0; n < threads_total; n++){
|
||||
thread_destroy(thpool_p->threads[n]);
|
||||
}
|
||||
free(thpool_p->threads);
|
||||
free(thpool_p);
|
||||
}
|
||||
|
||||
|
||||
/* Pause all threads in threadpool */
|
||||
void thpool_pause(thpool_* thpool_p) {
|
||||
int n;
|
||||
for (n=0; n < thpool_p->num_threads_alive; n++){
|
||||
pthread_kill(thpool_p->threads[n]->pthread, SIGUSR1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Resume all threads in threadpool */
|
||||
void thpool_resume(thpool_* thpool_p) {
|
||||
// resuming a single threadpool hasn't been
|
||||
// implemented yet, meanwhile this suppresses
|
||||
// the warnings
|
||||
(void)thpool_p;
|
||||
|
||||
threads_on_hold = 0;
|
||||
}
|
||||
|
||||
|
||||
int thpool_num_threads_working(thpool_* thpool_p){
|
||||
return thpool_p->num_threads_working;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ============================ THREAD ============================== */
|
||||
|
||||
|
||||
/* Initialize a thread in the thread pool
|
||||
*
|
||||
* @param thread address to the pointer of the thread to be created
|
||||
* @param id id to be given to the thread
|
||||
* @return 0 on success, -1 otherwise.
|
||||
*/
|
||||
static int thread_init (thpool_* thpool_p, struct thread** thread_p, int id){
|
||||
|
||||
*thread_p = (struct thread*)malloc(sizeof(struct thread));
|
||||
if (*thread_p == NULL){
|
||||
err("thread_init(): Could not allocate memory for thread\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
(*thread_p)->thpool_p = thpool_p;
|
||||
(*thread_p)->id = id;
|
||||
|
||||
pthread_create(&(*thread_p)->pthread, NULL, (void * (*)(void *)) thread_do, (*thread_p));
|
||||
pthread_detach((*thread_p)->pthread);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Sets the calling thread on hold */
|
||||
static void thread_hold(int sig_id) {
|
||||
(void)sig_id;
|
||||
threads_on_hold = 1;
|
||||
while (threads_on_hold){
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* What each thread is doing
|
||||
*
|
||||
* In principle this is an endless loop. The only time this loop gets interrupted is once
|
||||
* thpool_destroy() is invoked or the program exits.
|
||||
*
|
||||
* @param thread thread that will run this function
|
||||
* @return nothing
|
||||
*/
|
||||
static void* thread_do(struct thread* thread_p){
|
||||
|
||||
/* Set thread name for profiling and debugging */
|
||||
char thread_name[16] = {0};
|
||||
|
||||
snprintf(thread_name, 16, TOSTRING(THPOOL_THREAD_NAME) "-%d", thread_p->id);
|
||||
|
||||
#if defined(__linux__)
|
||||
/* Use prctl instead to prevent using _GNU_SOURCE flag and implicit declaration */
|
||||
prctl(PR_SET_NAME, thread_name);
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
pthread_setname_np(thread_name);
|
||||
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
pthread_set_name_np(thread_p->pthread, thread_name);
|
||||
#else
|
||||
err("thread_do(): pthread_setname_np is not supported on this system");
|
||||
#endif
|
||||
|
||||
/* Assure all threads have been created before starting serving */
|
||||
thpool_* thpool_p = thread_p->thpool_p;
|
||||
|
||||
/* Register signal handler */
|
||||
struct sigaction act;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_ONSTACK;
|
||||
act.sa_handler = thread_hold;
|
||||
if (sigaction(SIGUSR1, &act, NULL) == -1) {
|
||||
err("thread_do(): cannot handle SIGUSR1");
|
||||
}
|
||||
|
||||
/* Mark thread as alive (initialized) */
|
||||
pthread_mutex_lock(&thpool_p->thcount_lock);
|
||||
thpool_p->num_threads_alive += 1;
|
||||
pthread_mutex_unlock(&thpool_p->thcount_lock);
|
||||
|
||||
while(threads_keepalive){
|
||||
|
||||
bsem_wait(thpool_p->jobqueue.has_jobs);
|
||||
|
||||
if (threads_keepalive){
|
||||
|
||||
pthread_mutex_lock(&thpool_p->thcount_lock);
|
||||
thpool_p->num_threads_working++;
|
||||
pthread_mutex_unlock(&thpool_p->thcount_lock);
|
||||
|
||||
/* Read job from queue and execute it */
|
||||
void (*func_buff)(void*);
|
||||
void* arg_buff;
|
||||
job* job_p = jobqueue_pull(&thpool_p->jobqueue);
|
||||
if (job_p) {
|
||||
func_buff = job_p->function;
|
||||
arg_buff = job_p->arg;
|
||||
func_buff(arg_buff);
|
||||
free(job_p);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&thpool_p->thcount_lock);
|
||||
thpool_p->num_threads_working--;
|
||||
if (!thpool_p->num_threads_working) {
|
||||
pthread_cond_signal(&thpool_p->threads_all_idle);
|
||||
}
|
||||
pthread_mutex_unlock(&thpool_p->thcount_lock);
|
||||
|
||||
}
|
||||
}
|
||||
pthread_mutex_lock(&thpool_p->thcount_lock);
|
||||
thpool_p->num_threads_alive --;
|
||||
pthread_mutex_unlock(&thpool_p->thcount_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* Frees a thread */
|
||||
static void thread_destroy (thread* thread_p){
|
||||
free(thread_p);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ============================ JOB QUEUE =========================== */
|
||||
|
||||
|
||||
/* Initialize queue */
|
||||
static int jobqueue_init(jobqueue* jobqueue_p){
|
||||
jobqueue_p->len = 0;
|
||||
jobqueue_p->front = NULL;
|
||||
jobqueue_p->rear = NULL;
|
||||
|
||||
jobqueue_p->has_jobs = (struct bsem*)malloc(sizeof(struct bsem));
|
||||
if (jobqueue_p->has_jobs == NULL){
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_init(&(jobqueue_p->rwmutex), NULL);
|
||||
bsem_init(jobqueue_p->has_jobs, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Clear the queue */
|
||||
static void jobqueue_clear(jobqueue* jobqueue_p){
|
||||
|
||||
while(jobqueue_p->len){
|
||||
free(jobqueue_pull(jobqueue_p));
|
||||
}
|
||||
|
||||
jobqueue_p->front = NULL;
|
||||
jobqueue_p->rear = NULL;
|
||||
bsem_reset(jobqueue_p->has_jobs);
|
||||
jobqueue_p->len = 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Add (allocated) job to queue
|
||||
*/
|
||||
static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob){
|
||||
|
||||
pthread_mutex_lock(&jobqueue_p->rwmutex);
|
||||
newjob->prev = NULL;
|
||||
|
||||
switch(jobqueue_p->len){
|
||||
|
||||
case 0: /* if no jobs in queue */
|
||||
jobqueue_p->front = newjob;
|
||||
jobqueue_p->rear = newjob;
|
||||
break;
|
||||
|
||||
default: /* if jobs in queue */
|
||||
jobqueue_p->rear->prev = newjob;
|
||||
jobqueue_p->rear = newjob;
|
||||
|
||||
}
|
||||
jobqueue_p->len++;
|
||||
|
||||
bsem_post(jobqueue_p->has_jobs);
|
||||
pthread_mutex_unlock(&jobqueue_p->rwmutex);
|
||||
}
|
||||
|
||||
|
||||
/* Get first job from queue(removes it from queue)
|
||||
* Notice: Caller MUST hold a mutex
|
||||
*/
|
||||
static struct job* jobqueue_pull(jobqueue* jobqueue_p){
|
||||
|
||||
pthread_mutex_lock(&jobqueue_p->rwmutex);
|
||||
job* job_p = jobqueue_p->front;
|
||||
|
||||
switch(jobqueue_p->len){
|
||||
|
||||
case 0: /* if no jobs in queue */
|
||||
break;
|
||||
|
||||
case 1: /* if one job in queue */
|
||||
jobqueue_p->front = NULL;
|
||||
jobqueue_p->rear = NULL;
|
||||
jobqueue_p->len = 0;
|
||||
break;
|
||||
|
||||
default: /* if >1 jobs in queue */
|
||||
jobqueue_p->front = job_p->prev;
|
||||
jobqueue_p->len--;
|
||||
/* more than one job in queue -> post it */
|
||||
bsem_post(jobqueue_p->has_jobs);
|
||||
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&jobqueue_p->rwmutex);
|
||||
return job_p;
|
||||
}
|
||||
|
||||
|
||||
/* Free all queue resources back to the system */
|
||||
static void jobqueue_destroy(jobqueue* jobqueue_p){
|
||||
jobqueue_clear(jobqueue_p);
|
||||
free(jobqueue_p->has_jobs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ======================== SYNCHRONISATION ========================= */
|
||||
|
||||
|
||||
/* Init semaphore to 1 or 0 */
|
||||
static void bsem_init(bsem *bsem_p, int value) {
|
||||
if (value < 0 || value > 1) {
|
||||
err("bsem_init(): Binary semaphore can take only values 1 or 0");
|
||||
exit(1);
|
||||
}
|
||||
pthread_mutex_init(&(bsem_p->mutex), NULL);
|
||||
pthread_cond_init(&(bsem_p->cond), NULL);
|
||||
bsem_p->v = value;
|
||||
}
|
||||
|
||||
|
||||
/* Reset semaphore to 0 */
|
||||
static void bsem_reset(bsem *bsem_p) {
|
||||
pthread_mutex_destroy(&(bsem_p->mutex));
|
||||
pthread_cond_destroy(&(bsem_p->cond));
|
||||
bsem_init(bsem_p, 0);
|
||||
}
|
||||
|
||||
|
||||
/* Post to at least one thread */
|
||||
static void bsem_post(bsem *bsem_p) {
|
||||
pthread_mutex_lock(&bsem_p->mutex);
|
||||
bsem_p->v = 1;
|
||||
pthread_cond_signal(&bsem_p->cond);
|
||||
pthread_mutex_unlock(&bsem_p->mutex);
|
||||
}
|
||||
|
||||
|
||||
/* Post to all threads */
|
||||
static void bsem_post_all(bsem *bsem_p) {
|
||||
pthread_mutex_lock(&bsem_p->mutex);
|
||||
bsem_p->v = 1;
|
||||
pthread_cond_broadcast(&bsem_p->cond);
|
||||
pthread_mutex_unlock(&bsem_p->mutex);
|
||||
}
|
||||
|
||||
|
||||
/* Wait on semaphore until semaphore has value 0 */
|
||||
static void bsem_wait(bsem* bsem_p) {
|
||||
pthread_mutex_lock(&bsem_p->mutex);
|
||||
while (bsem_p->v != 1) {
|
||||
pthread_cond_wait(&bsem_p->cond, &bsem_p->mutex);
|
||||
}
|
||||
bsem_p->v = 0;
|
||||
pthread_mutex_unlock(&bsem_p->mutex);
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/**********************************
|
||||
* @author Johan Hanssen Seferidis
|
||||
* License: MIT
|
||||
*
|
||||
**********************************/
|
||||
|
||||
#ifndef _THPOOL_
|
||||
#define _THPOOL_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* =================================== API ======================================= */
|
||||
|
||||
|
||||
typedef struct thpool_* threadpool;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize threadpool
|
||||
*
|
||||
* Initializes a threadpool. This function will not return until all
|
||||
* threads have initialized successfully.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ..
|
||||
* threadpool thpool; //First we declare a threadpool
|
||||
* thpool = thpool_init(4); //then we initialize it to 4 threads
|
||||
* ..
|
||||
*
|
||||
* @param num_threads number of threads to be created in the threadpool
|
||||
* @return threadpool created threadpool on success,
|
||||
* NULL on error
|
||||
*/
|
||||
threadpool thpool_init(int num_threads);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Add work to the job queue
|
||||
*
|
||||
* Takes an action and its argument and adds it to the threadpool's job queue.
|
||||
* If you want to add to work a function with more than one arguments then
|
||||
* a way to implement this is by passing a pointer to a structure.
|
||||
*
|
||||
* NOTICE: You have to cast both the function and argument to not get warnings.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* void print_num(int num){
|
||||
* printf("%d\n", num);
|
||||
* }
|
||||
*
|
||||
* int main() {
|
||||
* ..
|
||||
* int a = 10;
|
||||
* thpool_add_work(thpool, (void*)print_num, (void*)a);
|
||||
* ..
|
||||
* }
|
||||
*
|
||||
* @param threadpool threadpool to which the work will be added
|
||||
* @param function_p pointer to function to add as work
|
||||
* @param arg_p pointer to an argument
|
||||
* @return 0 on success, -1 otherwise.
|
||||
*/
|
||||
int thpool_add_work(threadpool, void (*function_p)(void*), void* arg_p);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Wait for all queued jobs to finish
|
||||
*
|
||||
* Will wait for all jobs - both queued and currently running to finish.
|
||||
* Once the queue is empty and all work has completed, the calling thread
|
||||
* (probably the main program) will continue.
|
||||
*
|
||||
* Smart polling is used in wait. The polling is initially 0 - meaning that
|
||||
* there is virtually no polling at all. If after 1 seconds the threads
|
||||
* haven't finished, the polling interval starts growing exponentially
|
||||
* until it reaches max_secs seconds. Then it jumps down to a maximum polling
|
||||
* interval assuming that heavy processing is being used in the threadpool.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ..
|
||||
* threadpool thpool = thpool_init(4);
|
||||
* ..
|
||||
* // Add a bunch of work
|
||||
* ..
|
||||
* thpool_wait(thpool);
|
||||
* puts("All added work has finished");
|
||||
* ..
|
||||
*
|
||||
* @param threadpool the threadpool to wait for
|
||||
* @return nothing
|
||||
*/
|
||||
void thpool_wait(threadpool);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Pauses all threads immediately
|
||||
*
|
||||
* The threads will be paused no matter if they are idle or working.
|
||||
* The threads return to their previous states once thpool_resume
|
||||
* is called.
|
||||
*
|
||||
* While the thread is being paused, new work can be added.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* threadpool thpool = thpool_init(4);
|
||||
* thpool_pause(thpool);
|
||||
* ..
|
||||
* // Add a bunch of work
|
||||
* ..
|
||||
* thpool_resume(thpool); // Let the threads start their magic
|
||||
*
|
||||
* @param threadpool the threadpool where the threads should be paused
|
||||
* @return nothing
|
||||
*/
|
||||
void thpool_pause(threadpool);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unpauses all threads if they are paused
|
||||
*
|
||||
* @example
|
||||
* ..
|
||||
* thpool_pause(thpool);
|
||||
* sleep(10); // Delay execution 10 seconds
|
||||
* thpool_resume(thpool);
|
||||
* ..
|
||||
*
|
||||
* @param threadpool the threadpool where the threads should be unpaused
|
||||
* @return nothing
|
||||
*/
|
||||
void thpool_resume(threadpool);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Destroy the threadpool
|
||||
*
|
||||
* This will wait for the currently active threads to finish and then 'kill'
|
||||
* the whole threadpool to free up memory.
|
||||
*
|
||||
* @example
|
||||
* int main() {
|
||||
* threadpool thpool1 = thpool_init(2);
|
||||
* threadpool thpool2 = thpool_init(2);
|
||||
* ..
|
||||
* thpool_destroy(thpool1);
|
||||
* ..
|
||||
* return 0;
|
||||
* }
|
||||
*
|
||||
* @param threadpool the threadpool to destroy
|
||||
* @return nothing
|
||||
*/
|
||||
void thpool_destroy(threadpool);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Show currently working threads
|
||||
*
|
||||
* Working threads are the threads that are performing work (not idle).
|
||||
*
|
||||
* @example
|
||||
* int main() {
|
||||
* threadpool thpool1 = thpool_init(2);
|
||||
* threadpool thpool2 = thpool_init(2);
|
||||
* ..
|
||||
* printf("Working threads: %d\n", thpool_num_threads_working(thpool1));
|
||||
* ..
|
||||
* return 0;
|
||||
* }
|
||||
*
|
||||
* @param threadpool the threadpool of interest
|
||||
* @return integer number of threads working
|
||||
*/
|
||||
int thpool_num_threads_working(threadpool);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
120
CONTRIBUTING.md
120
CONTRIBUTING.md
@@ -11,15 +11,18 @@ 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
|
||||
```
|
||||
|
||||
The image contains the usual development tools (Go, GDB, Valgrind, Neovim...).
|
||||
The image contains the usual development tools (Go, GDB, Valgrind, Neovim...) and uses the following php setting locations
|
||||
|
||||
If docker version is lower than 23.0, build is failed by dockerignore [pattern issue](https://github.com/moby/moby/pull/42676). Add directories to `.dockerignore`.
|
||||
- php.ini: `/etc/frankenphp/php.ini` A php.ini file with development presets is provided by default.
|
||||
- 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`.
|
||||
|
||||
```patch
|
||||
!testdata/*.php
|
||||
!testdata/*.txt
|
||||
+!caddy
|
||||
+!C-Thread-Pool
|
||||
+!internal
|
||||
```
|
||||
|
||||
@@ -30,7 +33,7 @@ If docker version is lower than 23.0, build is failed by dockerignore [pattern i
|
||||
## Running the test suite
|
||||
|
||||
```console
|
||||
go test -race -v ./...
|
||||
go test -tags watcher -race -v ./...
|
||||
```
|
||||
|
||||
## Caddy module
|
||||
@@ -39,7 +42,7 @@ Build Caddy with the FrankenPHP Caddy module:
|
||||
|
||||
```console
|
||||
cd caddy/frankenphp/
|
||||
go build
|
||||
go build -tags watcher,brotli,nobadger,nomysql,nopgx
|
||||
cd ../../
|
||||
```
|
||||
|
||||
@@ -50,10 +53,13 @@ cd testdata/
|
||||
../caddy/frankenphp/frankenphp run
|
||||
```
|
||||
|
||||
The server is listening on `127.0.0.1:8080`:
|
||||
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
|
||||
|
||||
```console
|
||||
curl -vk https://localhost/phpinfo.php
|
||||
curl -vk http://127.0.0.1/phpinfo.php
|
||||
```
|
||||
|
||||
## Minimal test server
|
||||
@@ -109,22 +115,22 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
|
||||
1. Download the debug version of the FrankenPHP binary from GitHub or create your custom static build including debug symbols:
|
||||
|
||||
```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 dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
|
||||
```
|
||||
```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. Replace your current version of `frankenphp` by the debug FrankenPHP executable
|
||||
3. Start FrankenPHP as usual (alternatively, you can directly start FrankenPHP with GDB: `gdb --args ./frankenphp run`)
|
||||
3. Start FrankenPHP as usual (alternatively, you can directly start FrankenPHP with GDB: `gdb --args frankenphp run`)
|
||||
4. Attach to the process with GDB:
|
||||
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
|
||||
5. If necessary, type `continue` in the GDB shell
|
||||
6. Make FrankenPHP crash
|
||||
@@ -136,64 +142,60 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
1. Open `.github/workflows/tests.yml`
|
||||
2. Enable PHP debug symbols
|
||||
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
|
||||
3. Enable `tmate` to connect to the container
|
||||
|
||||
```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
|
||||
```
|
||||
```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. Connect to the container
|
||||
5. Open `frankenphp.go`
|
||||
6. Enable `cgosymbolizer`
|
||||
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
|
||||
7. Download the module: `go get`
|
||||
8. In the container, you can use GDB and the like:
|
||||
|
||||
```console
|
||||
go test -c -ldflags=-w
|
||||
gdb --args ./frankenphp.test -test.run ^MyTest$
|
||||
```
|
||||
```console
|
||||
go test -tags watcher -c -ldflags=-w
|
||||
gdb --args frankenphp.dev.test -test.run ^MyTest$
|
||||
```
|
||||
|
||||
9. When the bug is fixed, revert all these changes
|
||||
|
||||
## Misc Dev Resources
|
||||
|
||||
* [PHP embedding in uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
* [PHP embedding in NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
* [PHP embedding in Go (go-php)](https://github.com/deuill/go-php)
|
||||
* [PHP embedding in Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
* [PHP embedding in C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
* [Extending and Embedding PHP by Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
* [What the heck is TSRMLS_CC, anyway?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
* [PHP embedding on Mac](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4)
|
||||
* [SDL bindings](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
- [PHP embedding in uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
- [PHP embedding in NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
- [PHP embedding in Go (go-php)](https://github.com/deuill/go-php)
|
||||
- [PHP embedding in Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
- [PHP embedding in C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
- [Extending and Embedding PHP by Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
- [What the heck is TSRMLS_CC, anyway?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
- [SDL bindings](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
|
||||
## Docker-Related Resources
|
||||
|
||||
* [Bake file definition](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
* [docker buildx build](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
- [Bake file definition](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
- [docker buildx build](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
|
||||
## Useful Command
|
||||
|
||||
@@ -210,8 +212,8 @@ follow these steps:
|
||||
1. Create a new directory named with the language's 2-character ISO code in this repository's `docs/` directory
|
||||
2. Copy all the `.md` files in the root of the `docs/` directory into the new directory (always use the English version as source for translation, as it's always up to date)
|
||||
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 translates strings starting with `> [!` (it's special markup for GitHub)
|
||||
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), 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
|
||||
|
||||
73
Dockerfile
73
Dockerfile
@@ -1,4 +1,7 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
#checkov:skip=CKV_DOCKER_2
|
||||
#checkov:skip=CKV_DOCKER_3
|
||||
#checkov:skip=CKV_DOCKER_7
|
||||
FROM php-base AS common
|
||||
|
||||
WORKDIR /app
|
||||
@@ -16,19 +19,24 @@ RUN set -eux; \
|
||||
/app/public \
|
||||
/config/caddy \
|
||||
/data/caddy \
|
||||
/etc/caddy; \
|
||||
/etc/caddy \
|
||||
/etc/frankenphp; \
|
||||
sed -i 's/php/frankenphp run/g' /usr/local/bin/docker-php-entrypoint; \
|
||||
echo '<?php phpinfo();' > /app/public/index.php
|
||||
|
||||
COPY --link caddy/frankenphp/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
|
||||
RUN ln /etc/caddy/Caddyfile /etc/frankenphp/Caddyfile && \
|
||||
curl -sSLf \
|
||||
-o /usr/local/bin/install-php-extensions \
|
||||
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \
|
||||
chmod +x /usr/local/bin/install-php-extensions
|
||||
|
||||
CMD ["--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
||||
CMD ["--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"]
|
||||
HEALTHCHECK CMD curl -f http://localhost:2019/metrics || exit 1
|
||||
|
||||
# See https://caddyserver.com/docs/conventions#file-locations for details
|
||||
ENV XDG_CONFIG_HOME /config
|
||||
ENV XDG_DATA_HOME /data
|
||||
ENV XDG_CONFIG_HOME=/config
|
||||
ENV XDG_DATA_HOME=/data
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
@@ -50,11 +58,14 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
COPY --from=golang-base /usr/local/go /usr/local/go
|
||||
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
ENV PATH=/usr/local/go/bin:$PATH
|
||||
ENV GOTOOLCHAIN=local
|
||||
|
||||
# This is required to link the FrankenPHP binary to the PHP binary
|
||||
RUN apt-get update && \
|
||||
apt-get -y --no-install-recommends install \
|
||||
cmake \
|
||||
git \
|
||||
libargon2-dev \
|
||||
libbrotli-dev \
|
||||
libcurl4-openssl-dev \
|
||||
@@ -68,31 +79,45 @@ RUN apt-get update && \
|
||||
&& \
|
||||
apt-get clean
|
||||
|
||||
# Install e-dant/watcher (necessary for file watching)
|
||||
WORKDIR /usr/local/src/watcher
|
||||
RUN curl -s https://api.github.com/repos/e-dant/watcher/releases/latest | \
|
||||
grep tarball_url | \
|
||||
awk '{ print $2 }' | \
|
||||
sed 's/,$//' | \
|
||||
sed 's/"//g' | \
|
||||
xargs curl -L | \
|
||||
tar xz --strip-components 1 && \
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build build && \
|
||||
cmake --install build && \
|
||||
ldconfig
|
||||
|
||||
WORKDIR /go/src/app
|
||||
|
||||
COPY --link go.mod go.sum ./
|
||||
RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get
|
||||
RUN go mod download
|
||||
|
||||
WORKDIR /go/src/app/caddy
|
||||
COPY --link caddy/go.mod caddy/go.sum ./
|
||||
RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get
|
||||
RUN go mod download
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY --link *.* ./
|
||||
COPY --link caddy caddy
|
||||
COPY --link C-Thread-Pool C-Thread-Pool
|
||||
COPY --link internal internal
|
||||
COPY --link testdata testdata
|
||||
COPY --link . ./
|
||||
|
||||
# todo: automate this?
|
||||
# see https://github.com/docker-library/php/blob/master/8.2/bookworm/zts/Dockerfile#L57-L59 for PHP values
|
||||
ENV CGO_LDFLAGS="-lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS" CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $PHP_CFLAGS" CGO_CPPFLAGS=$PHP_CPPFLAGS
|
||||
# See https://github.com/docker-library/php/blob/master/8.3/bookworm/zts/Dockerfile#L57-L59 for PHP values
|
||||
ENV CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $PHP_CFLAGS"
|
||||
ENV CGO_CPPFLAGS=$PHP_CPPFLAGS
|
||||
ENV CGO_LDFLAGS="-L/usr/local/lib -lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS"
|
||||
|
||||
RUN echo $CGO_LDFLAGS
|
||||
|
||||
WORKDIR /go/src/app/caddy/frankenphp
|
||||
RUN GOBIN=/usr/local/bin go install -ldflags "-w -s -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" && \
|
||||
RUN GOBIN=/usr/local/bin go install -tags 'nobadger,nomysql,nopgx' -ldflags "-w -s -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" -buildvcs=true && \
|
||||
setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
cp Caddyfile /etc/caddy/Caddyfile && \
|
||||
frankenphp version
|
||||
cp Caddyfile /etc/frankenphp/Caddyfile && \
|
||||
frankenphp version && \
|
||||
frankenphp build-info
|
||||
|
||||
WORKDIR /go/src/app
|
||||
|
||||
@@ -101,6 +126,14 @@ FROM common AS runner
|
||||
|
||||
ENV GODEBUG=cgocheck=0
|
||||
|
||||
# copy watcher shared library
|
||||
COPY --from=builder /usr/local/lib/libwatcher* /usr/local/lib/
|
||||
# fix for the file watcher on arm
|
||||
RUN apt-get install -y --no-install-recommends libstdc++6 && \
|
||||
apt-get clean && \
|
||||
ldconfig
|
||||
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
RUN setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
frankenphp version
|
||||
frankenphp version && \
|
||||
frankenphp build-info
|
||||
|
||||
105
README.md
105
README.md
@@ -4,22 +4,51 @@
|
||||
|
||||
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/), 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.
|
||||
|
||||
FrankenPHP can also be used as a standalone Go library to embed PHP in any app using `net/http`.
|
||||
|
||||
[**Learn more** on *frankenphp.dev*](https://frankenphp.dev) and in this slide deck:
|
||||
[**Learn more** on _frankenphp.dev_](https://frankenphp.dev) and in this slide deck:
|
||||
|
||||
<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="Slides" width="600"></a>
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Docker
|
||||
### Standalone Binary
|
||||
|
||||
We provide static FrankenPHP binaries for Linux and macOS
|
||||
containing [PHP 8.4](https://www.php.net/releases/8.4/en.php) and most popular PHP extensions.
|
||||
|
||||
On Windows, use [WSL](https://learn.microsoft.com/windows/wsl/) to run FrankenPHP.
|
||||
|
||||
[Download FrankenPHP](https://github.com/dunglas/frankenphp/releases) or copy this line into your
|
||||
terminal to automatically install the version appropriate for your platform:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public \
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
mv frankenphp /usr/local/bin/
|
||||
```
|
||||
|
||||
To serve the content of the current directory, run:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
You can also run command-line scripts with:
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Alternatively, [Docker images](https://frankenphp.dev/docs/docker/) are available:
|
||||
|
||||
```console
|
||||
docker run -v .:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
@@ -31,47 +60,51 @@ Go to `https://localhost`, and enjoy!
|
||||
> Do not attempt to use `https://127.0.0.1`. Use `https://localhost` and accept the self-signed certificate.
|
||||
> Use the [`SERVER_NAME` environment variable](docs/config.md#environment-variables) to change the domain to use.
|
||||
|
||||
### Standalone Binary
|
||||
### Homebrew
|
||||
|
||||
If you prefer not to use Docker, we provide standalone FrankenPHP binaries for Linux and macOS
|
||||
containing [PHP 8.3](https://www.php.net/releases/8.3/en.php) and most popular PHP extensions: [Download FrankenPHP](https://github.com/dunglas/frankenphp/releases)
|
||||
FrankenPHP is also available as a [Homebrew](https://brew.sh) package for macOS and Linux.
|
||||
|
||||
To install it:
|
||||
|
||||
```console
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
To serve the content of the current directory, run:
|
||||
|
||||
```console
|
||||
./frankenphp php-server
|
||||
```
|
||||
|
||||
You can also run command-line scripts with:
|
||||
|
||||
```console
|
||||
./frankenphp php-cli /path/to/your/script.php
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
* [The worker mode](https://frankenphp.dev/docs/worker/)
|
||||
* [Early Hints support (103 HTTP status code)](https://frankenphp.dev/docs/early-hints/)
|
||||
* [Real-time](https://frankenphp.dev/docs/mercure/)
|
||||
* [Configuration](https://frankenphp.dev/docs/config/)
|
||||
* [Docker images](https://frankenphp.dev/docs/docker/)
|
||||
* [Deploy in production](docs/production.md)
|
||||
* [Create **standalone**, self-executable PHP apps](https://frankenphp.dev/docs/embed/)
|
||||
* [Create static binaries](https://frankenphp.dev/docs/static/)
|
||||
* [Compile from sources](https://frankenphp.dev/docs/compile/)
|
||||
* [Laravel integration](https://frankenphp.dev/docs/laravel/)
|
||||
* [Known issues](https://frankenphp.dev/docs/known-issues/)
|
||||
* [Demo app (Symfony) and benchmarks](https://github.com/dunglas/frankenphp-demo)
|
||||
* [Go library documentation](https://pkg.go.dev/github.com/dunglas/frankenphp)
|
||||
* [Contributing and debugging](https://frankenphp.dev/docs/contributing/)
|
||||
- [Classic mode](https://frankenphp.dev/docs/classic/)
|
||||
- [Worker mode](https://frankenphp.dev/docs/worker/)
|
||||
- [Early Hints support (103 HTTP status code)](https://frankenphp.dev/docs/early-hints/)
|
||||
- [Real-time](https://frankenphp.dev/docs/mercure/)
|
||||
- [Efficiently Serving Large Static Files](https://frankenphp.dev/docs/x-sendfile/)
|
||||
- [Configuration](https://frankenphp.dev/docs/config/)
|
||||
- [Docker images](https://frankenphp.dev/docs/docker/)
|
||||
- [Deploy in production](https://frankenphp.dev/docs/production/)
|
||||
- [Performance optimization](https://frankenphp.dev/docs/performance/)
|
||||
- [Create **standalone**, self-executable PHP apps](https://frankenphp.dev/docs/embed/)
|
||||
- [Create static binaries](https://frankenphp.dev/docs/static/)
|
||||
- [Compile from sources](https://frankenphp.dev/docs/compile/)
|
||||
- [Monitoring FrankenPHP](https://frankenphp.dev/docs/metrics/)
|
||||
- [Laravel integration](https://frankenphp.dev/docs/laravel/)
|
||||
- [Known issues](https://frankenphp.dev/docs/known-issues/)
|
||||
- [Demo app (Symfony) and benchmarks](https://github.com/dunglas/frankenphp-demo)
|
||||
- [Go library documentation](https://pkg.go.dev/frankenphp.dev)
|
||||
- [Contributing and debugging](https://frankenphp.dev/docs/contributing/)
|
||||
|
||||
## Examples and Skeletons
|
||||
|
||||
* [Symfony](https://github.com/dunglas/symfony-docker)
|
||||
* [API Platform](https://api-platform.com/docs/distribution/)
|
||||
* [Laravel](https://frankenphp.dev/docs/laravel/)
|
||||
* [Sulu](https://sulu.io/blog/running-sulu-with-frankenphp)
|
||||
* [WordPress](https://github.com/dunglas/frankenphp-wordpress)
|
||||
* [Drupal](https://github.com/dunglas/frankenphp-drupal)
|
||||
* [Joomla](https://github.com/alexandreelise/frankenphp-joomla)
|
||||
* [TYPO3](https://github.com/ochorocho/franken-typo3)
|
||||
- [Symfony](https://github.com/dunglas/symfony-docker)
|
||||
- [API Platform](https://api-platform.com/docs/symfony)
|
||||
- [Laravel](https://frankenphp.dev/docs/laravel/)
|
||||
- [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)
|
||||
|
||||
18
SECURITY.md
Normal file
18
SECURITY.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the latest version is supported.
|
||||
Please ensure that you're always using the latest release.
|
||||
|
||||
Binaries and Docker images are rebuilt nightly using the latest versions of dependencies.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe you have discovered a security issue directly affecting FrankenPHP,
|
||||
please do **NOT** report it publicly.
|
||||
|
||||
Please write a detailed vulnerability report and send it [through GitHub](https://github.com/dunglas/frankenphp/security/advisories/new) or to [kevin+frankenphp-security@dunglas.dev](mailto:kevin+frankenphp-security@dunglas.dev?subject=Security%20issue%20affecting%20FrankenPHP).
|
||||
|
||||
Only vulnerabilities directly affecting FrankenPHP should be reported to this project.
|
||||
Flaws affecting components used by FrankenPHP (PHP, Caddy, Go...) or using FrankenPHP (Laravel Octane, PHP Runtime...) should be reported to the relevant projects.
|
||||
@@ -1,6 +1,11 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
#checkov:skip=CKV_DOCKER_2
|
||||
#checkov:skip=CKV_DOCKER_3
|
||||
#checkov:skip=CKV_DOCKER_7
|
||||
FROM php-base AS common
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache \
|
||||
@@ -13,19 +18,25 @@ RUN set -eux; \
|
||||
/app/public \
|
||||
/config/caddy \
|
||||
/data/caddy \
|
||||
/etc/caddy; \
|
||||
/etc/caddy \
|
||||
/etc/frankenphp; \
|
||||
sed -i 's/php/frankenphp run/g' /usr/local/bin/docker-php-entrypoint; \
|
||||
echo '<?php phpinfo();' > /app/public/index.php
|
||||
|
||||
COPY --link caddy/frankenphp/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
|
||||
|
||||
CMD ["--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
||||
RUN ln /etc/caddy/Caddyfile /etc/frankenphp/Caddyfile && \
|
||||
curl -sSLf \
|
||||
-o /usr/local/bin/install-php-extensions \
|
||||
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \
|
||||
chmod +x /usr/local/bin/install-php-extensions
|
||||
|
||||
CMD ["--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"]
|
||||
HEALTHCHECK CMD curl -f http://localhost:2019/metrics || exit 1
|
||||
|
||||
# See https://caddyserver.com/docs/conventions#file-locations for details
|
||||
ENV XDG_CONFIG_HOME /config
|
||||
ENV XDG_DATA_HOME /data
|
||||
ENV XDG_CONFIG_HOME=/config
|
||||
ENV XDG_DATA_HOME=/data
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
@@ -43,21 +54,30 @@ LABEL org.opencontainers.image.vendor="Kévin Dunglas"
|
||||
FROM common AS builder
|
||||
|
||||
ARG FRANKENPHP_VERSION='dev'
|
||||
ARG NO_COMPRESS=''
|
||||
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||
|
||||
COPY --link --from=golang-base /usr/local/go /usr/local/go
|
||||
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
ENV PATH=/usr/local/go/bin:$PATH
|
||||
ENV GOTOOLCHAIN=local
|
||||
|
||||
# hadolint ignore=SC2086
|
||||
RUN apk add --no-cache --virtual .build-deps \
|
||||
$PHPIZE_DEPS \
|
||||
argon2-dev \
|
||||
# Needed for the custom Go build
|
||||
bash \
|
||||
brotli-dev \
|
||||
coreutils \
|
||||
curl-dev \
|
||||
# Needed for the custom Go build
|
||||
git \
|
||||
gnu-libiconv-dev \
|
||||
libsodium-dev \
|
||||
# Needed for the file watcher \
|
||||
cmake \
|
||||
libstdc++ \
|
||||
libxml2-dev \
|
||||
linux-headers \
|
||||
oniguruma-dev \
|
||||
@@ -66,31 +86,42 @@ RUN apk add --no-cache --virtual .build-deps \
|
||||
sqlite-dev \
|
||||
upx
|
||||
|
||||
# Install e-dant/watcher (necessary for file watching)
|
||||
WORKDIR /usr/local/src/watcher
|
||||
RUN curl -s https://api.github.com/repos/e-dant/watcher/releases/latest | \
|
||||
grep tarball_url | \
|
||||
awk '{ print $2 }' | \
|
||||
sed 's/,$//' | \
|
||||
sed 's/"//g' | \
|
||||
xargs curl -L | \
|
||||
tar xz --strip-components 1 && \
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build build && \
|
||||
cmake --install build
|
||||
|
||||
WORKDIR /go/src/app
|
||||
|
||||
COPY --link go.mod go.sum ./
|
||||
RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get
|
||||
RUN go mod download
|
||||
|
||||
WORKDIR /go/src/app/caddy
|
||||
COPY caddy/go.mod caddy/go.sum ./
|
||||
RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get
|
||||
RUN go mod download
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY --link *.* ./
|
||||
COPY --link caddy caddy
|
||||
COPY --link C-Thread-Pool C-Thread-Pool
|
||||
COPY --link internal internal
|
||||
COPY --link testdata testdata
|
||||
COPY --link . ./
|
||||
|
||||
# todo: automate this?
|
||||
# see https://github.com/docker-library/php/blob/master/8.2/bookworm/zts/Dockerfile#L57-L59 for php values
|
||||
ENV CGO_LDFLAGS="-lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS" CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $PHP_CFLAGS" CGO_CPPFLAGS=$PHP_CPPFLAGS
|
||||
# See https://github.com/docker-library/php/blob/master/8.3/alpine3.20/zts/Dockerfile#L53-L55
|
||||
ENV CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $PHP_CFLAGS"
|
||||
ENV CGO_CPPFLAGS=$PHP_CPPFLAGS
|
||||
ENV CGO_LDFLAGS="-lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS"
|
||||
|
||||
WORKDIR /go/src/app/caddy/frankenphp
|
||||
RUN GOBIN=/usr/local/bin go install -ldflags "-w -s -extldflags '-Wl,-z,stack-size=0x80000' -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" && \
|
||||
RUN GOBIN=/usr/local/bin go install -tags 'nobadger,nomysql,nopgx' -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 && \
|
||||
setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
upx --best /usr/local/bin/frankenphp && \
|
||||
frankenphp version
|
||||
([ -z "${NO_COMPRESS}" ] && upx --best /usr/local/bin/frankenphp || true) && \
|
||||
frankenphp version && \
|
||||
frankenphp build-info
|
||||
|
||||
WORKDIR /go/src/app
|
||||
|
||||
@@ -99,6 +130,12 @@ FROM common AS runner
|
||||
|
||||
ENV GODEBUG=cgocheck=0
|
||||
|
||||
# copy watcher shared library (libgcc and libstdc++ are needed for the watcher)
|
||||
COPY --from=builder /usr/local/lib/libwatcher* /usr/local/lib/
|
||||
RUN apk add --no-cache libstdc++ && \
|
||||
ldconfig /usr/local/lib
|
||||
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
RUN setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
frankenphp version
|
||||
frankenphp version && \
|
||||
frankenphp build-info
|
||||
|
||||
51
backoff.go
Normal file
51
backoff.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package frankenphp
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type exponentialBackoff struct {
|
||||
backoff time.Duration
|
||||
failureCount int
|
||||
mu sync.RWMutex
|
||||
maxBackoff time.Duration
|
||||
minBackoff time.Duration
|
||||
maxConsecutiveFailures int
|
||||
}
|
||||
|
||||
// recordSuccess resets the backoff and failureCount
|
||||
func (e *exponentialBackoff) recordSuccess() {
|
||||
e.mu.Lock()
|
||||
e.failureCount = 0
|
||||
e.backoff = e.minBackoff
|
||||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
// recordFailure increments the failure count and increases the backoff, it returns true if maxConsecutiveFailures has been reached
|
||||
func (e *exponentialBackoff) recordFailure() bool {
|
||||
e.mu.Lock()
|
||||
e.failureCount += 1
|
||||
if e.backoff < e.minBackoff {
|
||||
e.backoff = e.minBackoff
|
||||
}
|
||||
|
||||
e.backoff = min(e.backoff*2, e.maxBackoff)
|
||||
|
||||
e.mu.Unlock()
|
||||
return e.failureCount >= e.maxConsecutiveFailures
|
||||
}
|
||||
|
||||
// wait sleeps for the backoff duration if failureCount is non-zero.
|
||||
// NOTE: this is not tested and should be kept 'obviously correct' (i.e., simple)
|
||||
func (e *exponentialBackoff) wait() {
|
||||
e.mu.RLock()
|
||||
if e.failureCount == 0 {
|
||||
e.mu.RUnlock()
|
||||
|
||||
return
|
||||
}
|
||||
e.mu.RUnlock()
|
||||
|
||||
time.Sleep(e.backoff)
|
||||
}
|
||||
41
backoff_test.go
Normal file
41
backoff_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package frankenphp
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestExponentialBackoff_Reset(t *testing.T) {
|
||||
e := &exponentialBackoff{
|
||||
maxBackoff: 5 * time.Second,
|
||||
minBackoff: 500 * time.Millisecond,
|
||||
maxConsecutiveFailures: 3,
|
||||
}
|
||||
|
||||
assert.False(t, e.recordFailure())
|
||||
assert.False(t, e.recordFailure())
|
||||
e.recordSuccess()
|
||||
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
assert.Equal(t, 0, e.failureCount, "expected failureCount to be reset to 0")
|
||||
assert.Equal(t, e.backoff, e.minBackoff, "expected backoff to be reset to minBackoff")
|
||||
}
|
||||
|
||||
func TestExponentialBackoff_Trigger(t *testing.T) {
|
||||
e := &exponentialBackoff{
|
||||
maxBackoff: 500 * 3 * time.Millisecond,
|
||||
minBackoff: 500 * time.Millisecond,
|
||||
maxConsecutiveFailures: 3,
|
||||
}
|
||||
|
||||
assert.False(t, e.recordFailure())
|
||||
assert.False(t, e.recordFailure())
|
||||
assert.True(t, e.recordFailure())
|
||||
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
assert.Equal(t, e.failureCount, e.maxConsecutiveFailures, "expected failureCount to be maxConsecutiveFailures")
|
||||
assert.Equal(t, e.backoff, e.maxBackoff, "expected backoff to be maxBackoff")
|
||||
}
|
||||
119
build-packages.sh
Executable file
119
build-packages.sh
Executable file
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o errexit
|
||||
set -x
|
||||
|
||||
# Ensure required tools are installed
|
||||
if ! command -v rpmbuild &>/dev/null; then
|
||||
echo "Error: rpm-build is required to create RPM packages."
|
||||
echo "Install it with: sudo dnf install rpm-build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v ruby &>/dev/null; then
|
||||
echo "Error: Ruby is required by FPM."
|
||||
echo "Install it with: sudo dnf install ruby"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v fpm &>/dev/null; then
|
||||
echo "Error: FPM (rubygem-fpm) is required to create RPM packages."
|
||||
echo "Install it with: sudo gem install fpm"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
arch="$(uname -m)"
|
||||
os="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
bin="frankenphp-${os}-${arch}"
|
||||
|
||||
if [ ! -f "dist/$bin" ]; then
|
||||
echo "Error: dist/$bin not found. Run './build-static.sh' first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version_output="$(dist/"$bin" version)"
|
||||
frankenphp_version=$(echo "$version_output" | grep -oP 'FrankenPHP\s+\K[^ ]+' || true)
|
||||
frankenphp_version=${frankenphp_version#v}
|
||||
|
||||
if [[ ! "${frankenphp_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Warning: frankenphp_version must be set to X.Y.Z (e.g. 1.5.1), got '${frankenphp_version}'"
|
||||
echo "Falling back to non-release version 0.0.0"
|
||||
frankenphp_version=0.0.0
|
||||
fi
|
||||
|
||||
group_preexists=0
|
||||
user_preexists=0
|
||||
|
||||
if getent group frankenphp >/dev/null; then
|
||||
group_preexists=1
|
||||
else
|
||||
sudo groupadd --system frankenphp
|
||||
fi
|
||||
|
||||
if getent passwd frankenphp >/dev/null; then
|
||||
user_preexists=1
|
||||
else
|
||||
sudo useradd --system \
|
||||
--gid frankenphp \
|
||||
--create-home \
|
||||
--home-dir /var/lib/frankenphp \
|
||||
--shell /usr/sbin/nologin \
|
||||
--comment "FrankenPHP web server" \
|
||||
frankenphp
|
||||
fi
|
||||
|
||||
mkdir -p package/empty
|
||||
mkdir -p package/etc
|
||||
[ -f ./dist/static-php-cli/source/php-src/php.ini-production ] && cp -f ./dist/static-php-cli/source/php-src/php.ini-production ./package/etc/php.ini
|
||||
|
||||
cd dist
|
||||
iteration=1
|
||||
glibc_version=$(ldd -v "$bin" | awk '/GLIBC_/ {gsub(/[()]/, "", $2); print $2}' | grep -v GLIBC_PRIVATE | sort -V | tail -n1)
|
||||
cxxabi_version=$(strings "$bin" | grep -oP 'CXXABI_\d+\.\d+(\.\d+)?' | sort -V | tail -n1)
|
||||
|
||||
fpm -s dir -t rpm -n frankenphp -v "${frankenphp_version}" \
|
||||
--config-files /etc/frankenphp/Caddyfile \
|
||||
--config-files /etc/frankenphp/php.ini \
|
||||
--depends "libc.so.6(${glibc_version})(64bit)" \
|
||||
--depends "libstdc++.so.6(${cxxabi_version})(64bit)" \
|
||||
--before-install ../package/rhel/preinstall.sh \
|
||||
--after-install ../package/rhel/postinstall.sh \
|
||||
--before-remove ../package/rhel/preuninstall.sh \
|
||||
--after-remove ../package/rhel/postuninstall.sh \
|
||||
--iteration "${iteration}" \
|
||||
--rpm-user frankenphp --rpm-group frankenphp \
|
||||
"${bin}=/usr/bin/frankenphp" \
|
||||
"../package/rhel/frankenphp.service=/usr/lib/systemd/system/frankenphp.service" \
|
||||
"../package/Caddyfile=/etc/frankenphp/Caddyfile" \
|
||||
"../package/content/=/usr/share/frankenphp" \
|
||||
"../package/etc/php.ini=/etc/frankenphp/php.ini" \
|
||||
"../package/empty/=/etc/frankenphp/php.d" \
|
||||
"../package/empty/=/usr/lib/frankenphp/modules" \
|
||||
"../package/empty/=/var/lib/frankenphp"
|
||||
|
||||
glibc_version=$(ldd -v "$bin" | awk '/GLIBC_/ {gsub(/[()]/, "", $2); print $2}' | grep -v GLIBC_PRIVATE | sed 's/GLIBC_//' | sort -V | tail -n1)
|
||||
cxxabi_version=$(strings "$bin" | grep -oP 'CXXABI_\d+\.\d+(\.\d+)?' | sed 's/CXXABI_//' | sort -V | tail -n1)
|
||||
|
||||
fpm -s dir -t deb -n frankenphp -v "${frankenphp_version}" \
|
||||
--config-files /etc/frankenphp/Caddyfile \
|
||||
--config-files /etc/frankenphp/php.ini \
|
||||
--depends "libc6 (>= ${glibc_version})" \
|
||||
--depends "libstdc++6 (>= ${cxxabi_version})" \
|
||||
--after-install ../package/debian/postinst.sh \
|
||||
--before-remove ../package/debian/prerm.sh \
|
||||
--after-remove ../package/debian/postrm.sh \
|
||||
--iteration "${iteration}" \
|
||||
--deb-user frankenphp --deb-group frankenphp \
|
||||
"${bin}=/usr/bin/frankenphp" \
|
||||
"../package/debian/frankenphp.service=/usr/lib/systemd/system/frankenphp.service" \
|
||||
"../package/Caddyfile=/etc/frankenphp/Caddyfile" \
|
||||
"../package/content/=/usr/share/frankenphp" \
|
||||
"../package/etc/php.ini=/etc/frankenphp/php.ini" \
|
||||
"../package/empty/=/etc/frankenphp/php.d" \
|
||||
"../package/empty/=/usr/lib/frankenphp/modules" \
|
||||
"../package/empty/=/var/lib/frankenphp"
|
||||
|
||||
[ "$user_preexists" -eq 0 ] && sudo userdel frankenphp
|
||||
[ "$group_preexists" -eq 0 ] && (sudo groupdel frankenphp || true)
|
||||
|
||||
cd ..
|
||||
468
build-static.sh
468
build-static.sh
@@ -1,253 +1,355 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
|
||||
set -o errexit
|
||||
set -x
|
||||
|
||||
if ! type "git" > /dev/null; then
|
||||
echo "The \"git\" command must be installed."
|
||||
exit 1
|
||||
if ! type "git" >/dev/null 2>&1; then
|
||||
echo "The \"git\" command must be installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
arch="$(uname -m)"
|
||||
os="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
# Supported variables:
|
||||
# - PHP_VERSION: PHP version to build (default: "8.4")
|
||||
# - PHP_EXTENSIONS: PHP extensions to build (default: ${defaultExtensions} set below)
|
||||
# - PHP_EXTENSION_LIBS: PHP extension libraries to build (default: ${defaultExtensionLibs} set below)
|
||||
# - FRANKENPHP_VERSION: FrankenPHP version (default: current Git commit)
|
||||
# - EMBED: Path to the PHP app to embed (default: none)
|
||||
# - DEBUG_SYMBOLS: Enable debug symbols if set to 1 (default: none)
|
||||
# - MIMALLOC: Use mimalloc as the allocator if set to 1 (default: none)
|
||||
# - XCADDY_ARGS: Additional arguments to pass to xcaddy
|
||||
# - RELEASE: [maintainer only] Create a GitHub release if set to 1 (default: none)
|
||||
|
||||
# - SPC_REL_TYPE: Release type to download (accept "source" and "binary", default: "source")
|
||||
# - SPC_OPT_BUILD_ARGS: Additional arguments to pass to spc build
|
||||
# - SPC_OPT_DOWNLOAD_ARGS: Additional arguments to pass to spc download
|
||||
# - SPC_LIBC: Set to glibc to build with GNU toolchain (default: musl)
|
||||
|
||||
# init spc command, if we use spc binary, just use it instead of fetching source
|
||||
if [ -z "${SPC_REL_TYPE}" ]; then
|
||||
SPC_REL_TYPE="source"
|
||||
fi
|
||||
# init spc libc
|
||||
if [ -z "${SPC_LIBC}" ]; then
|
||||
if [ "${os}" = "linux" ]; then
|
||||
SPC_LIBC="musl"
|
||||
fi
|
||||
fi
|
||||
# init spc build additional args
|
||||
if [ -z "${SPC_OPT_BUILD_ARGS}" ]; then
|
||||
SPC_OPT_BUILD_ARGS=""
|
||||
fi
|
||||
if [ "${SPC_LIBC}" = "musl" ] && [[ "${SPC_OPT_BUILD_ARGS}" != *"--disable-opcache-jit"* ]]; then
|
||||
SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --disable-opcache-jit"
|
||||
fi
|
||||
# init spc download additional args
|
||||
if [ -z "${SPC_OPT_DOWNLOAD_ARGS}" ]; then
|
||||
SPC_OPT_DOWNLOAD_ARGS="--ignore-cache-sources=php-src --retry 5"
|
||||
if [ "${SPC_LIBC}" = "musl" ]; then
|
||||
SPC_OPT_DOWNLOAD_ARGS="${SPC_OPT_DOWNLOAD_ARGS} --prefer-pre-built"
|
||||
fi
|
||||
fi
|
||||
# if we need debug symbols, disable strip
|
||||
if [ -n "${DEBUG_SYMBOLS}" ]; then
|
||||
SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --no-strip"
|
||||
fi
|
||||
# php version to build
|
||||
if [ -z "${PHP_VERSION}" ]; then
|
||||
get_latest_php_version() {
|
||||
input="$1"
|
||||
json=$(curl -s "https://www.php.net/releases/index.php?json&version=$input")
|
||||
latest=$(echo "$json" | jq -r '.version')
|
||||
|
||||
if [[ "$latest" == "$input"* ]]; then
|
||||
echo "$latest"
|
||||
else
|
||||
echo "$input"
|
||||
fi
|
||||
}
|
||||
|
||||
PHP_VERSION="$(get_latest_php_version "8.4")"
|
||||
export PHP_VERSION
|
||||
fi
|
||||
# default extension set
|
||||
defaultExtensions="apcu,bcmath,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,parallel,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,ssh2,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,xlswriter,xml,xmlreader,xmlwriter,zip,zlib,yaml,zstd"
|
||||
# if [ "${os}" != "linux" ] || [ "${SPC_LIBC}" = "glibc" ]; then
|
||||
# defaultExtensions="${defaultExtensions},ffi"
|
||||
# fi
|
||||
defaultExtensionLibs="bzip2,freetype,libavif,libjpeg,liblz4,libwebp,libzip,nghttp2"
|
||||
|
||||
md5binary="md5sum"
|
||||
if [ "${os}" = "darwin" ]; then
|
||||
os="mac"
|
||||
md5binary="md5 -q"
|
||||
os="mac"
|
||||
md5binary="md5 -q"
|
||||
fi
|
||||
|
||||
if [ "${os}" = "linux" ] && ! type "cmake" > /dev/null; then
|
||||
echo "The \"cmake\" command must be installed."
|
||||
exit 1
|
||||
if [ "${os}" = "linux" ] && ! type "cmake" >/dev/null 2>&1; then
|
||||
echo "The \"cmake\" command must be installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${PHP_EXTENSIONS}" ]; then
|
||||
if [ -n "${EMBED}" ] && [ -f "${EMBED}/composer.json" ]; then
|
||||
cd "${EMBED}"
|
||||
# Replace "libxml" by "xml" for compatibility with Static PHP CLI extension name
|
||||
PHP_EXTENSIONS="$(composer check-platform-reqs --no-dev 2>/dev/null | grep ^ext | sed -e 's/^ext-//' -e 's/ .*//' -e 's/^libxml$/xml/' | xargs | tr ' ' ',')"
|
||||
export PHP_EXTENSIONS
|
||||
cd -
|
||||
else
|
||||
export PHP_EXTENSIONS="apcu,bcmath,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,gd,iconv,igbinary,intl,ldap,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,readline,redis,session,simplexml,sockets,sodium,sqlite3,sysvsem,tokenizer,xml,xmlreader,xmlwriter,zip,zlib"
|
||||
fi
|
||||
fi
|
||||
if [ "${os}" = "linux" ] && { [[ "${arch}" =~ "aarch" ]] || [[ "${arch}" =~ "arm" ]]; }; then
|
||||
fpic="-fPIC"
|
||||
fpie="-fPIE"
|
||||
|
||||
if [ -z "${PHP_EXTENSION_LIBS}" ]; then
|
||||
export PHP_EXTENSION_LIBS="bzip2,freetype,libavif,libjpeg,liblz4,libwebp,libzip"
|
||||
fi
|
||||
|
||||
# The Brotli library must always be built as it is required by http://github.com/dunglas/caddy-cbrotli
|
||||
if ! echo "${PHP_EXTENSION_LIBS}" | grep -q "\bbrotli\b"; then
|
||||
export PHP_EXTENSION_LIBS="${PHP_EXTENSION_LIBS},brotli"
|
||||
fi
|
||||
|
||||
if [ -z "${PHP_VERSION}" ]; then
|
||||
export PHP_VERSION="8.3"
|
||||
if [ -z "${DEBUG_SYMBOLS}" ]; then
|
||||
export SPC_PHP_DEFAULT_OPTIMIZE_CFLAGS="-g -fstack-protector-strong -fPIC -fPIE -Os -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
|
||||
fi
|
||||
else
|
||||
fpic="-fpic"
|
||||
fpie="-fpie"
|
||||
fi
|
||||
|
||||
if [ -z "${FRANKENPHP_VERSION}" ]; then
|
||||
FRANKENPHP_VERSION="$(git rev-parse --verify HEAD)"
|
||||
export FRANKENPHP_VERSION
|
||||
FRANKENPHP_VERSION="$(git rev-parse --verify HEAD)"
|
||||
export FRANKENPHP_VERSION
|
||||
elif [ -d ".git/" ]; then
|
||||
CURRENT_REF="$(git rev-parse --abbrev-ref HEAD)"
|
||||
export CURRENT_REF
|
||||
CURRENT_REF="$(git rev-parse --abbrev-ref HEAD)"
|
||||
export CURRENT_REF
|
||||
|
||||
if echo "${FRANKENPHP_VERSION}" | grep -F -q "."; then
|
||||
# Tag
|
||||
if echo "${FRANKENPHP_VERSION}" | grep -F -q "."; then
|
||||
# Tag
|
||||
|
||||
# Trim "v" prefix if any
|
||||
FRANKENPHP_VERSION=${FRANKENPHP_VERSION#v}
|
||||
export FRANKENPHP_VERSION
|
||||
# Trim "v" prefix if any
|
||||
FRANKENPHP_VERSION=${FRANKENPHP_VERSION#v}
|
||||
export FRANKENPHP_VERSION
|
||||
|
||||
git checkout "v${FRANKENPHP_VERSION}"
|
||||
else
|
||||
git checkout "${FRANKENPHP_VERSION}"
|
||||
fi
|
||||
git checkout "v${FRANKENPHP_VERSION}"
|
||||
else
|
||||
git checkout "${FRANKENPHP_VERSION}"
|
||||
fi
|
||||
fi
|
||||
|
||||
bin="frankenphp-${os}-${arch}"
|
||||
|
||||
if [ -n "${CLEAN}" ]; then
|
||||
rm -Rf dist/
|
||||
go clean -cache
|
||||
rm -Rf dist/
|
||||
go clean -cache
|
||||
fi
|
||||
|
||||
mkdir -p dist/
|
||||
cd dist/
|
||||
|
||||
if type "brew" >/dev/null 2>&1; then
|
||||
if ! type "composer" >/dev/null; then
|
||||
packages="composer"
|
||||
fi
|
||||
if ! type "go" >/dev/null 2>&1; then
|
||||
packages="${packages} go"
|
||||
fi
|
||||
if [ -n "${RELEASE}" ] && ! type "gh" >/dev/null 2>&1; then
|
||||
packages="${packages} gh"
|
||||
fi
|
||||
|
||||
if [ -n "${packages}" ]; then
|
||||
# shellcheck disable=SC2086
|
||||
brew install --formula --quiet ${packages}
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${SPC_REL_TYPE}" = "binary" ]; then
|
||||
mkdir -p static-php-cli/
|
||||
cd static-php-cli/
|
||||
if [[ "${arch}" =~ "arm" ]]; then
|
||||
dl_arch="aarch64"
|
||||
else
|
||||
dl_arch="${arch}"
|
||||
fi
|
||||
curl -o spc -fsSL "https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-${dl_arch}"
|
||||
chmod +x spc
|
||||
spcCommand="./spc"
|
||||
elif [ -d "static-php-cli/src" ]; then
|
||||
cd static-php-cli/
|
||||
git pull
|
||||
composer install --no-dev -a --no-interaction
|
||||
spcCommand="./bin/spc"
|
||||
else
|
||||
git clone --depth 1 https://github.com/crazywhalecc/static-php-cli --branch main
|
||||
cd static-php-cli/
|
||||
composer install --no-dev -a --no-interaction
|
||||
spcCommand="./bin/spc"
|
||||
fi
|
||||
|
||||
# Extensions to build
|
||||
if [ -z "${PHP_EXTENSIONS}" ]; then
|
||||
# enable EMBED mode, first check if project has dumped extensions
|
||||
if [ -n "${EMBED}" ] && [ -f "${EMBED}/composer.json" ] && [ -f "${EMBED}/composer.lock" ] && [ -f "${EMBED}/vendor/installed.json" ]; then
|
||||
cd "${EMBED}"
|
||||
# read the extensions using spc dump-extensions
|
||||
PHP_EXTENSIONS=$(${spcCommand} dump-extensions "${EMBED}" --format=text --no-dev --no-ext-output="${defaultExtensions}")
|
||||
else
|
||||
PHP_EXTENSIONS="${defaultExtensions}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Additional libraries to build
|
||||
if [ -z "${PHP_EXTENSION_LIBS}" ]; then
|
||||
PHP_EXTENSION_LIBS="${defaultExtensionLibs}"
|
||||
fi
|
||||
|
||||
# The Brotli library must always be built as it is required by http://github.com/dunglas/caddy-cbrotli
|
||||
if ! echo "${PHP_EXTENSION_LIBS}" | grep -q "\bbrotli\b"; then
|
||||
PHP_EXTENSION_LIBS="${PHP_EXTENSION_LIBS},brotli"
|
||||
fi
|
||||
|
||||
# The mimalloc library must be built if MIMALLOC is true
|
||||
if [ -n "${MIMALLOC}" ]; then
|
||||
if ! echo "${PHP_EXTENSION_LIBS}" | grep -q "\bmimalloc\b"; then
|
||||
PHP_EXTENSION_LIBS="${PHP_EXTENSION_LIBS},mimalloc"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build libphp if necessary
|
||||
if [ -f "dist/static-php-cli/buildroot/lib/libphp.a" ]; then
|
||||
cd dist/static-php-cli
|
||||
cache_key="${PHP_VERSION}-${PHP_EXTENSIONS}-${PHP_EXTENSION_LIBS}"
|
||||
if [ -f ../cache_key ] && [ "$(cat ../cache_key)" = "${cache_key}" ] && [ -f "buildroot/lib/libphp.a" ]; then
|
||||
echo "Hit cache, skipping libphp build."
|
||||
else
|
||||
mkdir -p dist/
|
||||
cd dist/
|
||||
${spcCommand} doctor --auto-fix
|
||||
# shellcheck disable=SC2086
|
||||
${spcCommand} download --with-php="${PHP_VERSION}" --for-extensions="${PHP_EXTENSIONS}" --for-libs="${PHP_EXTENSION_LIBS}" ${SPC_OPT_DOWNLOAD_ARGS}
|
||||
# shellcheck disable=SC2086
|
||||
${spcCommand} build --enable-zts --build-embed ${SPC_OPT_BUILD_ARGS} "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}"
|
||||
|
||||
if [ -d "static-php-cli/" ]; then
|
||||
cd static-php-cli/
|
||||
git pull
|
||||
else
|
||||
git clone --depth 1 https://github.com/crazywhalecc/static-php-cli
|
||||
cd static-php-cli/
|
||||
fi
|
||||
|
||||
if type "brew" > /dev/null; then
|
||||
if ! type "composer" > /dev/null; then
|
||||
packages="composer"
|
||||
fi
|
||||
if ! type "go" > /dev/null; then
|
||||
packages="${packages} go"
|
||||
fi
|
||||
if [ -n "${RELEASE}" ] && ! type "gh" > /dev/null; then
|
||||
packages="${packages} gh"
|
||||
fi
|
||||
|
||||
if [ -n "${packages}" ]; then
|
||||
# shellcheck disable=SC2086
|
||||
brew install --formula --quiet ${packages}
|
||||
fi
|
||||
fi
|
||||
|
||||
composer install --no-dev -a
|
||||
|
||||
if [ "${os}" = "linux" ]; then
|
||||
extraOpts="--disable-opcache-jit"
|
||||
fi
|
||||
|
||||
if [ -n "${DEBUG_SYMBOLS}" ]; then
|
||||
extraOpts="${extraOpts} --no-strip"
|
||||
fi
|
||||
|
||||
./bin/spc doctor --auto-fix
|
||||
./bin/spc download --with-php="${PHP_VERSION}" --for-extensions="${PHP_EXTENSIONS}" --for-libs="${PHP_EXTENSION_LIBS}" --ignore-cache-sources=php-src
|
||||
# shellcheck disable=SC2086
|
||||
./bin/spc build --debug --enable-zts --build-embed ${extraOpts} "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}"
|
||||
echo -n "${cache_key}" >../cache_key
|
||||
fi
|
||||
|
||||
CGO_CFLAGS="-DFRANKENPHP_VERSION=${FRANKENPHP_VERSION} -I${PWD}/buildroot/include/ $(./buildroot/bin/php-config --includes | sed s#-I/#-I"${PWD}"/buildroot/#g)"
|
||||
if ! type "go" >/dev/null 2>&1; then
|
||||
echo "The \"go\" command must be installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
XCADDY_COMMAND="xcaddy"
|
||||
if ! type "$XCADDY_COMMAND" >/dev/null 2>&1; then
|
||||
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
|
||||
XCADDY_COMMAND="$(go env GOPATH)/bin/xcaddy"
|
||||
fi
|
||||
|
||||
curlGitHubHeaders=(--header "X-GitHub-Api-Version: 2022-11-28")
|
||||
if [ "${GITHUB_TOKEN}" ]; then
|
||||
curlGitHubHeaders+=(--header "Authorization: Bearer ${GITHUB_TOKEN}")
|
||||
fi
|
||||
|
||||
# Compile e-dant/watcher as a static library
|
||||
mkdir -p watcher
|
||||
cd watcher
|
||||
curl -f --retry 5 "${curlGitHubHeaders[@]}" https://api.github.com/repos/e-dant/watcher/releases/latest |
|
||||
grep tarball_url |
|
||||
awk '{ print $2 }' |
|
||||
sed 's/,$//' |
|
||||
sed 's/"//g' |
|
||||
xargs curl -fL --retry 5 "${curlGitHubHeaders[@]}" |
|
||||
tar xz --strip-components 1
|
||||
cd watcher-c
|
||||
if [ -z "${CC}" ]; then
|
||||
watcherCC=cc
|
||||
else
|
||||
watcherCC="${CC}"
|
||||
fi
|
||||
${watcherCC} -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra "${fpic}"
|
||||
ar rcs libwatcher-c.a libwatcher-c.o
|
||||
cp libwatcher-c.a ../../buildroot/lib/libwatcher-c.a
|
||||
mkdir -p ../../buildroot/include/wtr
|
||||
cp -R include/wtr/watcher-c.h ../../buildroot/include/wtr/watcher-c.h
|
||||
cd ../../
|
||||
|
||||
# See https://github.com/docker-library/php/blob/master/8.3/alpine3.20/zts/Dockerfile#L53-L55
|
||||
CGO_CFLAGS="-DFRANKENPHP_VERSION=${FRANKENPHP_VERSION} -I${PWD}/buildroot/include/ $(${spcCommand} spc-config "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}" --includes)"
|
||||
if [ -n "${DEBUG_SYMBOLS}" ]; then
|
||||
CGO_CFLAGS="-g ${CGO_CFLAGS}"
|
||||
CGO_CFLAGS="-g ${CGO_CFLAGS}"
|
||||
else
|
||||
CGO_CFLAGS="-fstack-protector-strong ${fpic} ${fpie} -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 ${CGO_CFLAGS}"
|
||||
fi
|
||||
export CGO_CFLAGS
|
||||
export CGO_CPPFLAGS="${CGO_CFLAGS}"
|
||||
|
||||
if [ "${os}" = "mac" ]; then
|
||||
export CGO_LDFLAGS="-framework CoreFoundation -framework SystemConfiguration"
|
||||
export CGO_LDFLAGS="-framework CoreFoundation -framework SystemConfiguration"
|
||||
elif [ "${os}" = "linux" ] && [ -z "${DEBUG_SYMBOLS}" ]; then
|
||||
CGO_LDFLAGS="-Wl,-O1 -pie"
|
||||
fi
|
||||
if [ "${os}" = "linux" ] && [ "${SPC_LIBC}" = "glibc" ]; then
|
||||
CGO_LDFLAGS="${CGO_LDFLAGS} -Wl,--allow-multiple-definition -Wl,--export-dynamic"
|
||||
fi
|
||||
|
||||
CGO_LDFLAGS="${CGO_LDFLAGS} ${PWD}/buildroot/lib/libbrotlicommon.a ${PWD}/buildroot/lib/libbrotlienc.a ${PWD}/buildroot/lib/libbrotlidec.a ${PWD}/buildroot/lib/libwatcher-c.a $(${spcCommand} spc-config "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}" --libs)"
|
||||
if [[ "$CGO_LDFLAGS" == *"${PWD}/buildroot/lib/mimalloc.o"* ]]; then
|
||||
CGO_LDFLAGS=${CGO_LDFLAGS//${PWD}\/buildroot\/lib\/mimalloc.o/}
|
||||
CGO_LDFLAGS="${PWD}/buildroot/lib/libmimalloc.a $CGO_LDFLAGS"
|
||||
fi
|
||||
if [ "${os}" = "linux" ] && [ "${SPC_LIBC}" = "glibc" ]; then
|
||||
CGO_LDFLAGS="${CGO_LDFLAGS//-lphp/-Wl,--whole-archive -lphp -Wl,--no-whole-archive}"
|
||||
# shellcheck disable=SC2046
|
||||
ar d "${PWD}/buildroot/lib/libphp.a" $(ar t "${PWD}/buildroot/lib/libphp.a" | grep '\.a$')
|
||||
fi
|
||||
|
||||
CGO_LDFLAGS="${CGO_LDFLAGS} ${PWD}/buildroot/lib/libbrotlicommon.a ${PWD}/buildroot/lib/libbrotlienc.a ${PWD}/buildroot/lib/libbrotlidec.a $(./buildroot/bin/php-config --ldflags || true) $(./buildroot/bin/php-config --libs || true)"
|
||||
export CGO_LDFLAGS
|
||||
|
||||
LIBPHP_VERSION="$(./buildroot/bin/php-config --version)"
|
||||
export LIBPHP_VERSION
|
||||
|
||||
cd ../
|
||||
|
||||
if [ "${os}" = "linux" ]; then
|
||||
if [ -n "${MIMALLOC}" ]; then
|
||||
# Replace musl's mallocng by mimalloc
|
||||
# The default musl allocator is slow, especially when used by multi-threaded apps,
|
||||
# and triggers weird bugs
|
||||
# Adapted from https://www.tweag.io/blog/2023-08-10-rust-static-link-with-mimalloc/
|
||||
|
||||
echo 'The USE_MIMALLOC environment variable is EXPERIMENTAL.'
|
||||
echo 'This option can be removed or its behavior modified at any time.'
|
||||
|
||||
if [ ! -f "mimalloc/out/libmimalloc.a" ]; then
|
||||
if [ -d "mimalloc" ]; then
|
||||
cd mimalloc/
|
||||
git reset --hard
|
||||
git clean -xdf
|
||||
git fetch --tags
|
||||
else
|
||||
git clone https://github.com/microsoft/mimalloc.git
|
||||
cd mimalloc/
|
||||
fi
|
||||
|
||||
git checkout "$(git describe --tags "$(git rev-list --tags --max-count=1 || true)" || true)"
|
||||
|
||||
curl -f -L --retry 5 https://raw.githubusercontent.com/tweag/rust-alpine-mimalloc/b26002b49d466a295ea8b50828cb7520a71a872a/mimalloc.diff -o mimalloc.diff
|
||||
patch -p1 < mimalloc.diff
|
||||
|
||||
mkdir -p out/
|
||||
cd out/
|
||||
if [ -n "${DEBUG_SYMBOLS}" ]; then
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DMI_BUILD_SHARED=OFF \
|
||||
-DMI_BUILD_OBJECT=OFF \
|
||||
-DMI_BUILD_TESTS=OFF \
|
||||
../
|
||||
else
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DMI_BUILD_SHARED=OFF \
|
||||
-DMI_BUILD_OBJECT=OFF \
|
||||
-DMI_BUILD_TESTS=OFF \
|
||||
../
|
||||
fi
|
||||
make -j"$(nproc || true)"
|
||||
|
||||
cd ../../
|
||||
fi
|
||||
|
||||
if [ -n "${DEBUG_SYMBOLS}" ]; then
|
||||
libmimalloc_path=mimalloc/out/libmimalloc-debug.a
|
||||
else
|
||||
libmimalloc_path=mimalloc/out/libmimalloc.a
|
||||
fi
|
||||
|
||||
# Patch musl library to use mimalloc
|
||||
for libc_path in "/usr/local/musl/lib/libc.a" "/usr/local/musl/$(uname -m)-linux-musl/lib/libc.a" "/usr/lib/libc.a"
|
||||
do
|
||||
if [ ! -f "${libc_path}" ] || [ -f "${libc_path}.unpatched" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
{
|
||||
echo "CREATE libc.a"
|
||||
echo "ADDLIB ${libc_path}"
|
||||
echo "DELETE aligned_alloc.lo calloc.lo donate.lo free.lo libc_calloc.lo lite_malloc.lo malloc.lo malloc_usable_size.lo memalign.lo posix_memalign.lo realloc.lo reallocarray.lo valloc.lo"
|
||||
echo "ADDLIB ${libmimalloc_path}"
|
||||
echo "SAVE"
|
||||
} | ar -M
|
||||
mv "${libc_path}" "${libc_path}.unpatched"
|
||||
mv libc.a "${libc_path}"
|
||||
done
|
||||
fi
|
||||
|
||||
# Increase the default stack size to prevents issues with code including many files such as Symfony containers
|
||||
extraExtldflags="-Wl,-z,stack-size=0x80000"
|
||||
fi
|
||||
|
||||
if [ -z "${DEBUG_SYMBOLS}" ]; then
|
||||
extraLdflags="-w -s"
|
||||
extraLdflags="-w -s"
|
||||
fi
|
||||
|
||||
cd ../
|
||||
|
||||
# Embed PHP app, if any
|
||||
if [ -n "${EMBED}" ] && [ -d "${EMBED}" ]; then
|
||||
tar -cf app.tar -C "${EMBED}" .
|
||||
${md5binary} app.tar > app_checksum.txt
|
||||
tar -cf app.tar -C "${EMBED}" .
|
||||
${md5binary} app.tar | awk '{printf $1}' >app_checksum.txt
|
||||
fi
|
||||
|
||||
if [ -z "${XCADDY_ARGS}" ]; then
|
||||
XCADDY_ARGS="--with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy"
|
||||
fi
|
||||
|
||||
XCADDY_DEBUG=0
|
||||
if [ -n "${DEBUG_SYMBOLS}" ]; then
|
||||
XCADDY_DEBUG=1
|
||||
fi
|
||||
|
||||
if [ "${SPC_LIBC}" = "musl" ]; then
|
||||
muslStackSizeFix="-Wl,-z,stack-size=0x80000"
|
||||
fi
|
||||
|
||||
cd caddy/frankenphp/
|
||||
go env
|
||||
go build -buildmode=pie -tags "cgo netgo osusergo static_build" -ldflags "-linkmode=external -extldflags '-static-pie ${extraExtldflags}' ${extraLdflags} -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ${FRANKENPHP_VERSION} PHP ${LIBPHP_VERSION} Caddy'" -o "../../dist/${bin}"
|
||||
cd ../..
|
||||
cd caddy/
|
||||
if [ -z "${SPC_LIBC}" ] || [ "${SPC_LIBC}" = "musl" ]; then
|
||||
xcaddyGoBuildFlags="-buildmode=pie -tags cgo,netgo,osusergo,static_build,nobadger,nomysql,nopgx -ldflags \"-linkmode=external -extldflags '-static-pie ${muslStackSizeFix}' ${extraLdflags} -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ${FRANKENPHP_VERSION} PHP ${LIBPHP_VERSION} Caddy'\""
|
||||
elif [ "${SPC_LIBC}" = "glibc" ]; then
|
||||
xcaddyGoBuildFlags="-buildmode=pie -tags cgo,netgo,osusergo,nobadger,nomysql,nopgx -ldflags \"-linkmode=external -extldflags '-pie' ${extraLdflags} -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ${FRANKENPHP_VERSION} PHP ${LIBPHP_VERSION} Caddy'\""
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS=${xcaddyGoBuildFlags} \
|
||||
XCADDY_DEBUG="${XCADDY_DEBUG}" \
|
||||
${XCADDY_COMMAND} build \
|
||||
--output "../dist/${bin}" \
|
||||
${XCADDY_ARGS} \
|
||||
--with frankenphp.dev=.. \
|
||||
--with frankenphp.dev/caddy=.
|
||||
cd ..
|
||||
|
||||
if [ -d "${EMBED}" ]; then
|
||||
truncate -s 0 app.tar
|
||||
truncate -s 0 app_checksum.txt
|
||||
truncate -s 0 app.tar
|
||||
truncate -s 0 app_checksum.txt
|
||||
fi
|
||||
|
||||
if type "upx" > /dev/null && [ -z "${DEBUG_SYMBOLS}" ] && [ -z "${NO_COMPRESS}" ]; then
|
||||
upx --best "dist/${bin}"
|
||||
if type "upx" >/dev/null 2>&1 && [ -z "${DEBUG_SYMBOLS}" ] && [ -z "${NO_COMPRESS}" ]; then
|
||||
upx --best "dist/${bin}"
|
||||
fi
|
||||
|
||||
"dist/${bin}" version
|
||||
"dist/${bin}" build-info
|
||||
|
||||
if [ -n "${RELEASE}" ]; then
|
||||
gh release upload "v${FRANKENPHP_VERSION}" "dist/${bin}" --repo dunglas/frankenphp --clobber
|
||||
gh release upload "v${FRANKENPHP_VERSION}" "dist/${bin}" --repo dunglas/frankenphp --clobber
|
||||
fi
|
||||
|
||||
if [ -n "${CURRENT_REF}" ]; then
|
||||
git checkout "${CURRENT_REF}"
|
||||
git checkout "${CURRENT_REF}"
|
||||
fi
|
||||
|
||||
65
caddy/admin.go
Normal file
65
caddy/admin.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"frankenphp.dev"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type FrankenPHPAdmin struct{}
|
||||
|
||||
// if the id starts with "admin.api" the module will register AdminRoutes via module.Routes()
|
||||
func (FrankenPHPAdmin) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "admin.api.frankenphp",
|
||||
New: func() caddy.Module { return new(FrankenPHPAdmin) },
|
||||
}
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: These routes are not yet stable and may change in the future.
|
||||
func (admin FrankenPHPAdmin) Routes() []caddy.AdminRoute {
|
||||
return []caddy.AdminRoute{
|
||||
{
|
||||
Pattern: "/frankenphp/workers/restart",
|
||||
Handler: caddy.AdminHandlerFunc(admin.restartWorkers),
|
||||
},
|
||||
{
|
||||
Pattern: "/frankenphp/threads",
|
||||
Handler: caddy.AdminHandlerFunc(admin.threads),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (admin *FrankenPHPAdmin) restartWorkers(w http.ResponseWriter, r *http.Request) error {
|
||||
if r.Method != http.MethodPost {
|
||||
return admin.error(http.StatusMethodNotAllowed, fmt.Errorf("method not allowed"))
|
||||
}
|
||||
|
||||
frankenphp.RestartWorkers()
|
||||
caddy.Log().Info("workers restarted from admin api")
|
||||
admin.success(w, "workers restarted successfully\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (admin *FrankenPHPAdmin) threads(w http.ResponseWriter, _ *http.Request) error {
|
||||
debugState := frankenphp.DebugState()
|
||||
prettyJson, err := json.MarshalIndent(debugState, "", " ")
|
||||
if err != nil {
|
||||
return admin.error(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return admin.success(w, string(prettyJson))
|
||||
}
|
||||
|
||||
func (admin *FrankenPHPAdmin) success(w http.ResponseWriter, message string) error {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := w.Write([]byte(message))
|
||||
return err
|
||||
}
|
||||
|
||||
func (admin *FrankenPHPAdmin) error(statusCode int, err error) error {
|
||||
return caddy.APIError{HTTPStatus: statusCode, Err: err}
|
||||
}
|
||||
293
caddy/admin_test.go
Normal file
293
caddy/admin_test.go
Normal file
@@ -0,0 +1,293 @@
|
||||
package caddy_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"frankenphp.dev"
|
||||
"frankenphp.dev/internal/fastabs"
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRestartWorkerViaAdminApi(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
|
||||
frankenphp {
|
||||
worker ../testdata/worker-with-counter.php 1
|
||||
}
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
route {
|
||||
root ../testdata
|
||||
rewrite worker-with-counter.php
|
||||
php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/", http.StatusOK, "requests:1")
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/", http.StatusOK, "requests:2")
|
||||
|
||||
assertAdminResponse(t, tester, "POST", "workers/restart", http.StatusOK, "workers restarted successfully\n")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/", http.StatusOK, "requests:1")
|
||||
}
|
||||
|
||||
func TestShowTheCorrectThreadDebugStatus(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
|
||||
frankenphp {
|
||||
num_threads 3
|
||||
max_threads 6
|
||||
worker ../testdata/worker-with-counter.php 1
|
||||
worker ../testdata/index.php 1
|
||||
}
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
route {
|
||||
root ../testdata
|
||||
rewrite worker-with-counter.php
|
||||
php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
debugState := getDebugState(t, tester)
|
||||
|
||||
// assert that the correct threads are present in the thread info
|
||||
assert.Equal(t, debugState.ThreadDebugStates[0].State, "ready")
|
||||
assert.Contains(t, debugState.ThreadDebugStates[1].Name, "worker-with-counter.php")
|
||||
assert.Contains(t, debugState.ThreadDebugStates[2].Name, "index.php")
|
||||
assert.Equal(t, debugState.ReservedThreadCount, 3)
|
||||
assert.Len(t, debugState.ThreadDebugStates, 3)
|
||||
}
|
||||
|
||||
func TestAutoScaleWorkerThreads(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
maxTries := 10
|
||||
requestsPerTry := 200
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
|
||||
frankenphp {
|
||||
max_threads 10
|
||||
num_threads 2
|
||||
worker ../testdata/sleep.php 1
|
||||
}
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
route {
|
||||
root ../testdata
|
||||
rewrite sleep.php
|
||||
php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// spam an endpoint that simulates IO
|
||||
endpoint := "http://localhost:" + testPort + "/?sleep=2&work=1000"
|
||||
amountOfThreads := len(getDebugState(t, tester).ThreadDebugStates)
|
||||
|
||||
// try to spawn the additional threads by spamming the server
|
||||
for tries := 0; tries < maxTries; tries++ {
|
||||
wg.Add(requestsPerTry)
|
||||
for i := 0; i < requestsPerTry; i++ {
|
||||
go func() {
|
||||
tester.AssertGetResponse(endpoint, http.StatusOK, "slept for 2 ms and worked for 1000 iterations")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
amountOfThreads = len(getDebugState(t, tester).ThreadDebugStates)
|
||||
if amountOfThreads > 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// assert that there are now more threads than before
|
||||
assert.NotEqual(t, amountOfThreads, 2)
|
||||
}
|
||||
|
||||
// Note this test requires at least 2x40MB available memory for the process
|
||||
func TestAutoScaleRegularThreadsOnAutomaticThreadLimit(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
maxTries := 10
|
||||
requestsPerTry := 200
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
|
||||
frankenphp {
|
||||
max_threads auto
|
||||
num_threads 1
|
||||
php_ini memory_limit 40M # a reasonable limit for the test
|
||||
}
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
route {
|
||||
root ../testdata
|
||||
php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// spam an endpoint that simulates IO
|
||||
endpoint := "http://localhost:" + testPort + "/sleep.php?sleep=2&work=1000"
|
||||
amountOfThreads := len(getDebugState(t, tester).ThreadDebugStates)
|
||||
|
||||
// try to spawn the additional threads by spamming the server
|
||||
for tries := 0; tries < maxTries; tries++ {
|
||||
wg.Add(requestsPerTry)
|
||||
for i := 0; i < requestsPerTry; i++ {
|
||||
go func() {
|
||||
tester.AssertGetResponse(endpoint, http.StatusOK, "slept for 2 ms and worked for 1000 iterations")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
amountOfThreads = len(getDebugState(t, tester).ThreadDebugStates)
|
||||
if amountOfThreads > 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// assert that there are now more threads present
|
||||
assert.NotEqual(t, amountOfThreads, 1)
|
||||
}
|
||||
|
||||
func assertAdminResponse(t *testing.T, tester *caddytest.Tester, method string, path string, expectedStatus int, expectedBody string) {
|
||||
adminUrl := "http://localhost:2999/frankenphp/"
|
||||
r, err := http.NewRequest(method, adminUrl+path, nil)
|
||||
assert.NoError(t, err)
|
||||
if expectedBody == "" {
|
||||
_ = tester.AssertResponseCode(r, expectedStatus)
|
||||
return
|
||||
}
|
||||
_, _ = tester.AssertResponse(r, expectedStatus, expectedBody)
|
||||
}
|
||||
|
||||
func getAdminResponseBody(t *testing.T, tester *caddytest.Tester, method string, path string) string {
|
||||
adminUrl := "http://localhost:2999/frankenphp/"
|
||||
r, err := http.NewRequest(method, adminUrl+path, nil)
|
||||
assert.NoError(t, err)
|
||||
resp := tester.AssertResponseCode(r, http.StatusOK)
|
||||
defer resp.Body.Close()
|
||||
bytes, err := io.ReadAll(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func getDebugState(t *testing.T, tester *caddytest.Tester) frankenphp.FrankenPHPDebugState {
|
||||
threadStates := getAdminResponseBody(t, tester, "GET", "threads")
|
||||
|
||||
var debugStates frankenphp.FrankenPHPDebugState
|
||||
err := json.Unmarshal([]byte(threadStates), &debugStates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return debugStates
|
||||
}
|
||||
|
||||
func TestAddModuleWorkerViaAdminApi(t *testing.T) {
|
||||
// Initialize a server with admin API enabled
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
route {
|
||||
root ../testdata
|
||||
php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// Get initial debug state to check number of workers
|
||||
initialDebugState := getDebugState(t, tester)
|
||||
initialWorkerCount := 0
|
||||
for _, thread := range initialDebugState.ThreadDebugStates {
|
||||
if thread.Name != "" && thread.Name != "ready" {
|
||||
initialWorkerCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Create a Caddyfile configuration with a module worker
|
||||
workerConfig := `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port ` + testPort + `
|
||||
}
|
||||
|
||||
localhost:` + testPort + ` {
|
||||
route {
|
||||
root ../testdata
|
||||
php {
|
||||
worker ../testdata/worker-with-counter.php 1
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Send the configuration to the admin API
|
||||
adminUrl := "http://localhost:2999/load"
|
||||
r, err := http.NewRequest("POST", adminUrl, bytes.NewBufferString(workerConfig))
|
||||
assert.NoError(t, err)
|
||||
r.Header.Set("Content-Type", "text/caddyfile")
|
||||
resp := tester.AssertResponseCode(r, http.StatusOK)
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Get the updated debug state to check if the worker was added
|
||||
updatedDebugState := getDebugState(t, tester)
|
||||
updatedWorkerCount := 0
|
||||
workerFound := false
|
||||
filename, _ := fastabs.FastAbs("../testdata/worker-with-counter.php")
|
||||
for _, thread := range updatedDebugState.ThreadDebugStates {
|
||||
if thread.Name != "" && thread.Name != "ready" {
|
||||
updatedWorkerCount++
|
||||
if thread.Name == "Worker PHP Thread - "+filename {
|
||||
workerFound = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that the worker was added
|
||||
assert.Greater(t, updatedWorkerCount, initialWorkerCount, "Worker count should have increased")
|
||||
assert.True(t, workerFound, fmt.Sprintf("Worker with name %q should be found", "Worker PHP Thread - "+filename))
|
||||
|
||||
// Make a request to the worker to verify it's working
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/worker-with-counter.php", http.StatusOK, "requests:1")
|
||||
}
|
||||
5
caddy/br-skip.go
Normal file
5
caddy/br-skip.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build nobrotli
|
||||
|
||||
package caddy
|
||||
|
||||
var brotli = false
|
||||
5
caddy/br.go
Normal file
5
caddy/br.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build !nobrotli
|
||||
|
||||
package caddy
|
||||
|
||||
var brotli = true
|
||||
622
caddy/caddy.go
622
caddy/caddy.go
@@ -6,9 +6,15 @@ package caddy
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"frankenphp.dev/internal/fastabs"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
@@ -17,170 +23,348 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"go.uber.org/zap"
|
||||
"frankenphp.dev"
|
||||
)
|
||||
|
||||
const defaultDocumentRoot = "public"
|
||||
const (
|
||||
defaultDocumentRoot = "public"
|
||||
defaultWatchPattern = "./**/*.{php,yaml,yml,twig,env}"
|
||||
)
|
||||
|
||||
var iniError = errors.New("'php_ini' must be in the format: php_ini \"<key>\" \"<value>\"")
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(FrankenPHPApp{})
|
||||
caddy.RegisterModule(FrankenPHPModule{})
|
||||
caddy.RegisterModule(FrankenPHPAdmin{})
|
||||
|
||||
httpcaddyfile.RegisterGlobalOption("frankenphp", parseGlobalOption)
|
||||
|
||||
httpcaddyfile.RegisterHandlerDirective("php", parseCaddyfile)
|
||||
httpcaddyfile.RegisterDirectiveOrder("php", "before", "file_server")
|
||||
|
||||
httpcaddyfile.RegisterDirective("php_server", parsePhpServer)
|
||||
}
|
||||
|
||||
type mainPHPinterpreterKeyType int
|
||||
|
||||
var mainPHPInterpreterKey mainPHPinterpreterKeyType
|
||||
|
||||
var phpInterpreter = caddy.NewUsagePool()
|
||||
|
||||
type phpInterpreterDestructor struct{}
|
||||
|
||||
func (phpInterpreterDestructor) Destruct() error {
|
||||
frankenphp.Shutdown()
|
||||
|
||||
return nil
|
||||
httpcaddyfile.RegisterDirectiveOrder("php_server", "before", "file_server")
|
||||
}
|
||||
|
||||
type workerConfig struct {
|
||||
// Name for the worker. Default: the filename for FrankenPHPApp workers, always prefixed with "m#" for FrankenPHPModule workers.
|
||||
Name string `json:"name,omitempty"`
|
||||
// FileName sets the path to the worker script.
|
||||
FileName string `json:"file_name,omitempty"`
|
||||
// Num sets the number of workers to start.
|
||||
Num int `json:"num,omitempty"`
|
||||
// Env sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables.
|
||||
Env map[string]string `json:"env,omitempty"`
|
||||
// Directories to watch for file changes
|
||||
Watch []string `json:"watch,omitempty"`
|
||||
}
|
||||
|
||||
type FrankenPHPApp struct {
|
||||
// NumThreads sets the number of PHP threads to start. Default: 2x the number of available CPUs.
|
||||
NumThreads int `json:"num_threads,omitempty"`
|
||||
// MaxThreads limits how many threads can be started at runtime. Default 2x NumThreads
|
||||
MaxThreads int `json:"max_threads,omitempty"`
|
||||
// Workers configures the worker scripts to start.
|
||||
Workers []workerConfig `json:"workers,omitempty"`
|
||||
// Overwrites the default php ini configuration
|
||||
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"`
|
||||
|
||||
metrics frankenphp.Metrics
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (a FrankenPHPApp) CaddyModule() caddy.ModuleInfo {
|
||||
func (f FrankenPHPApp) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "frankenphp",
|
||||
New: func() caddy.Module { return &a },
|
||||
New: func() caddy.Module { return &f },
|
||||
}
|
||||
}
|
||||
|
||||
// Provision sets up the module.
|
||||
func (f *FrankenPHPApp) Provision(ctx caddy.Context) error {
|
||||
f.logger = ctx.Slogger()
|
||||
|
||||
if httpApp, err := ctx.AppIfConfigured("http"); err == nil {
|
||||
if httpApp.(*caddyhttp.App).Metrics != nil {
|
||||
f.metrics = frankenphp.NewPrometheusMetrics(ctx.GetMetricsRegistry())
|
||||
}
|
||||
} else {
|
||||
// if the http module is not configured (this should never happen) then collect the metrics by default
|
||||
f.metrics = frankenphp.NewPrometheusMetrics(ctx.GetMetricsRegistry())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FrankenPHPApp) generateUniqueModuleWorkerName(filepath string) string {
|
||||
var i uint
|
||||
filepath, _ = fastabs.FastAbs(filepath)
|
||||
name := "m#" + filepath
|
||||
|
||||
retry:
|
||||
for _, wc := range f.Workers {
|
||||
if wc.Name == name {
|
||||
name = fmt.Sprintf("m#%s_%d", filepath, i)
|
||||
i++
|
||||
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func (f *FrankenPHPApp) addModuleWorkers(workers ...workerConfig) ([]workerConfig, error) {
|
||||
for i := range workers {
|
||||
w := &workers[i]
|
||||
if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(w.FileName) {
|
||||
w.FileName = filepath.Join(frankenphp.EmbeddedAppPath, w.FileName)
|
||||
}
|
||||
if w.Name == "" {
|
||||
w.Name = f.generateUniqueModuleWorkerName(w.FileName)
|
||||
} else if !strings.HasPrefix(w.Name, "m#") {
|
||||
w.Name = "m#" + w.Name
|
||||
}
|
||||
f.Workers = append(f.Workers, *w)
|
||||
}
|
||||
return workers, nil
|
||||
}
|
||||
|
||||
func (f *FrankenPHPApp) Start() error {
|
||||
repl := caddy.NewReplacer()
|
||||
logger := caddy.Log()
|
||||
|
||||
opts := []frankenphp.Option{frankenphp.WithNumThreads(f.NumThreads), frankenphp.WithLogger(logger)}
|
||||
for _, w := range f.Workers {
|
||||
opts = append(opts, frankenphp.WithWorkers(repl.ReplaceKnown(w.FileName, ""), w.Num, w.Env))
|
||||
opts := []frankenphp.Option{
|
||||
frankenphp.WithNumThreads(f.NumThreads),
|
||||
frankenphp.WithMaxThreads(f.MaxThreads),
|
||||
frankenphp.WithLogger(f.logger),
|
||||
frankenphp.WithMetrics(f.metrics),
|
||||
frankenphp.WithPhpIni(f.PhpIni),
|
||||
frankenphp.WithMaxWaitTime(f.MaxWaitTime),
|
||||
}
|
||||
for _, w := range append(f.Workers) {
|
||||
opts = append(opts, frankenphp.WithWorkers(w.Name, repl.ReplaceKnown(w.FileName, ""), w.Num, w.Env, w.Watch))
|
||||
}
|
||||
|
||||
_, loaded, err := phpInterpreter.LoadOrNew(mainPHPInterpreterKey, func() (caddy.Destructor, error) {
|
||||
if err := frankenphp.Init(opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return phpInterpreterDestructor{}, nil
|
||||
})
|
||||
if err != nil {
|
||||
frankenphp.Shutdown()
|
||||
if err := frankenphp.Init(opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if loaded {
|
||||
frankenphp.Shutdown()
|
||||
if err := frankenphp.Init(opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*FrankenPHPApp) Stop() error {
|
||||
caddy.Log().Info("FrankenPHP stopped 🐘")
|
||||
func (f *FrankenPHPApp) Stop() error {
|
||||
f.logger.Info("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()
|
||||
}
|
||||
|
||||
// reset the configuration so it doesn't bleed into later tests
|
||||
f.Workers = nil
|
||||
f.NumThreads = 0
|
||||
f.MaxWaitTime = 0
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
|
||||
wc := workerConfig{}
|
||||
if d.NextArg() {
|
||||
wc.FileName = d.Val()
|
||||
}
|
||||
|
||||
if d.NextArg() {
|
||||
if d.Val() == "watch" {
|
||||
wc.Watch = append(wc.Watch, defaultWatchPattern)
|
||||
} else {
|
||||
v, err := strconv.ParseUint(d.Val(), 10, 32)
|
||||
if err != nil {
|
||||
return wc, err
|
||||
}
|
||||
|
||||
wc.Num = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
if d.NextArg() {
|
||||
return wc, errors.New(`FrankenPHP: too many "worker" arguments: ` + d.Val())
|
||||
}
|
||||
|
||||
for d.NextBlock(1) {
|
||||
v := d.Val()
|
||||
switch v {
|
||||
case "name":
|
||||
if !d.NextArg() {
|
||||
return wc, d.ArgErr()
|
||||
}
|
||||
wc.Name = d.Val()
|
||||
case "file":
|
||||
if !d.NextArg() {
|
||||
return wc, d.ArgErr()
|
||||
}
|
||||
wc.FileName = d.Val()
|
||||
case "num":
|
||||
if !d.NextArg() {
|
||||
return wc, d.ArgErr()
|
||||
}
|
||||
|
||||
v, err := strconv.ParseUint(d.Val(), 10, 32)
|
||||
if err != nil {
|
||||
return wc, err
|
||||
}
|
||||
|
||||
wc.Num = int(v)
|
||||
case "env":
|
||||
args := d.RemainingArgs()
|
||||
if len(args) != 2 {
|
||||
return wc, d.ArgErr()
|
||||
}
|
||||
if wc.Env == nil {
|
||||
wc.Env = make(map[string]string)
|
||||
}
|
||||
wc.Env[args[0]] = args[1]
|
||||
case "watch":
|
||||
if !d.NextArg() {
|
||||
// the default if the watch directory is left empty:
|
||||
wc.Watch = append(wc.Watch, defaultWatchPattern)
|
||||
} else {
|
||||
wc.Watch = append(wc.Watch, d.Val())
|
||||
}
|
||||
default:
|
||||
allowedDirectives := "name, file, num, env, watch"
|
||||
return wc, wrongSubDirectiveError("worker", allowedDirectives, v)
|
||||
}
|
||||
}
|
||||
|
||||
if wc.FileName == "" {
|
||||
return wc, errors.New(`the "file" argument must be specified`)
|
||||
}
|
||||
|
||||
if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(wc.FileName) {
|
||||
wc.FileName = filepath.Join(frankenphp.EmbeddedAppPath, wc.FileName)
|
||||
}
|
||||
|
||||
return wc, nil
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||
func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
for d.NextBlock(0) {
|
||||
// when adding a new directive, also update the allowedDirectives error message
|
||||
switch d.Val() {
|
||||
case "num_threads":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
v, err := strconv.Atoi(d.Val())
|
||||
v, err := strconv.ParseUint(d.Val(), 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.NumThreads = v
|
||||
|
||||
case "worker":
|
||||
wc := workerConfig{}
|
||||
if d.NextArg() {
|
||||
wc.FileName = d.Val()
|
||||
f.NumThreads = int(v)
|
||||
case "max_threads":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
if d.NextArg() {
|
||||
v, err := strconv.Atoi(d.Val())
|
||||
if d.Val() == "auto" {
|
||||
f.MaxThreads = -1
|
||||
continue
|
||||
}
|
||||
|
||||
v, err := strconv.ParseUint(d.Val(), 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.MaxThreads = int(v)
|
||||
case "max_wait_time":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
v, err := time.ParseDuration(d.Val())
|
||||
if err != nil {
|
||||
return errors.New("max_wait_time must be a valid duration (example: 10s)")
|
||||
}
|
||||
|
||||
f.MaxWaitTime = v
|
||||
case "php_ini":
|
||||
parseIniLine := func(d *caddyfile.Dispenser) error {
|
||||
key := d.Val()
|
||||
if !d.NextArg() {
|
||||
return iniError
|
||||
}
|
||||
if f.PhpIni == nil {
|
||||
f.PhpIni = make(map[string]string)
|
||||
}
|
||||
f.PhpIni[key] = d.Val()
|
||||
if d.NextArg() {
|
||||
return iniError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
isBlock := false
|
||||
for d.NextBlock(1) {
|
||||
isBlock = true
|
||||
err := parseIniLine(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wc.Num = v
|
||||
}
|
||||
|
||||
for d.NextBlock(1) {
|
||||
v := d.Val()
|
||||
switch v {
|
||||
case "file":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
wc.FileName = d.Val()
|
||||
case "num":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
v, err := strconv.Atoi(d.Val())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wc.Num = v
|
||||
case "env":
|
||||
args := d.RemainingArgs()
|
||||
if len(args) != 2 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
if wc.Env == nil {
|
||||
wc.Env = make(map[string]string)
|
||||
}
|
||||
wc.Env[args[0]] = args[1]
|
||||
if !isBlock {
|
||||
if !d.NextArg() {
|
||||
return iniError
|
||||
}
|
||||
|
||||
if wc.FileName == "" {
|
||||
return errors.New(`The "file" argument must be specified`)
|
||||
err := parseIniLine(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(wc.FileName) {
|
||||
wc.FileName = filepath.Join(frankenphp.EmbeddedAppPath, wc.FileName)
|
||||
case "worker":
|
||||
wc, err := parseWorkerConfig(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(wc.FileName) {
|
||||
wc.FileName = filepath.Join(frankenphp.EmbeddedAppPath, wc.FileName)
|
||||
}
|
||||
if strings.HasPrefix(wc.Name, "m#") {
|
||||
return fmt.Errorf(`global worker names must not start with "m#": %q`, wc.Name)
|
||||
}
|
||||
// check for duplicate workers
|
||||
for _, existingWorker := range f.Workers {
|
||||
if existingWorker.FileName == wc.FileName {
|
||||
return fmt.Errorf("global workers must not have duplicate filenames: %q", wc.FileName)
|
||||
}
|
||||
}
|
||||
|
||||
f.Workers = append(f.Workers, wc)
|
||||
default:
|
||||
allowedDirectives := "num_threads, max_threads, php_ini, worker, max_wait_time"
|
||||
return wrongSubDirectiveError("frankenphp", allowedDirectives, d.Val())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if f.MaxThreads > 0 && f.NumThreads > 0 && f.MaxThreads < f.NumThreads {
|
||||
return errors.New(`"max_threads"" must be greater than or equal to "num_threads"`)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -205,8 +389,14 @@ type FrankenPHPModule struct {
|
||||
// ResolveRootSymlink enables resolving the `root` directory to its actual value by evaluating a symbolic link, if one exists.
|
||||
ResolveRootSymlink *bool `json:"resolve_root_symlink,omitempty"`
|
||||
// Env sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables.
|
||||
Env frankenphp.PreparedEnv `json:"env,omitempty"`
|
||||
logger *zap.Logger
|
||||
Env map[string]string `json:"env,omitempty"`
|
||||
// Workers configures the worker scripts to start.
|
||||
Workers []workerConfig `json:"workers,omitempty"`
|
||||
|
||||
resolvedDocumentRoot string
|
||||
preparedEnv frankenphp.PreparedEnv
|
||||
preparedEnvNeedsReplacement bool
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
@@ -219,7 +409,24 @@ func (FrankenPHPModule) CaddyModule() caddy.ModuleInfo {
|
||||
|
||||
// Provision sets up the module.
|
||||
func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
|
||||
f.logger = ctx.Logger(f)
|
||||
f.logger = ctx.Slogger()
|
||||
app, err := ctx.App("frankenphp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fapp, ok := app.(*FrankenPHPApp)
|
||||
if !ok {
|
||||
return fmt.Errorf(`expected ctx.App("frankenphp") to return *FrankenPHPApp, got %T`, app)
|
||||
}
|
||||
if fapp == nil {
|
||||
return fmt.Errorf(`expected ctx.App("frankenphp") to return *FrankenPHPApp, got nil`)
|
||||
}
|
||||
|
||||
workers, err := fapp.addModuleWorkers(f.Workers...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Workers = workers
|
||||
|
||||
if f.Root == "" {
|
||||
if frankenphp.EmbeddedAppPath == "" {
|
||||
@@ -244,39 +451,98 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
|
||||
f.ResolveRootSymlink = &rrs
|
||||
}
|
||||
|
||||
if !needReplacement(f.Root) {
|
||||
root, err := fastabs.FastAbs(f.Root)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to make the root path absolute: %w", err)
|
||||
}
|
||||
f.resolvedDocumentRoot = root
|
||||
|
||||
if *f.ResolveRootSymlink {
|
||||
root, err := filepath.EvalSymlinks(root)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to resolve root symlink: %w", err)
|
||||
}
|
||||
|
||||
f.resolvedDocumentRoot = root
|
||||
}
|
||||
}
|
||||
|
||||
if f.preparedEnv == nil {
|
||||
f.preparedEnv = frankenphp.PrepareEnv(f.Env)
|
||||
|
||||
for _, e := range f.preparedEnv {
|
||||
if needReplacement(e) {
|
||||
f.preparedEnvNeedsReplacement = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// needReplacement checks if a string contains placeholders.
|
||||
func needReplacement(s string) bool {
|
||||
return strings.ContainsAny(s, "{}")
|
||||
}
|
||||
|
||||
// ServeHTTP implements caddyhttp.MiddlewareHandler.
|
||||
// TODO: Expose TLS versions as env vars, as Apache's mod_ssl: https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go#L298
|
||||
func (f FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ caddyhttp.Handler) error {
|
||||
func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ caddyhttp.Handler) error {
|
||||
origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
|
||||
documentRoot := repl.ReplaceKnown(f.Root, "")
|
||||
var documentRootOption frankenphp.RequestOption
|
||||
var documentRoot string
|
||||
if f.resolvedDocumentRoot == "" {
|
||||
documentRoot = repl.ReplaceKnown(f.Root, "")
|
||||
if documentRoot == "" && frankenphp.EmbeddedAppPath != "" {
|
||||
documentRoot = frankenphp.EmbeddedAppPath
|
||||
}
|
||||
documentRootOption = frankenphp.WithRequestDocumentRoot(documentRoot, *f.ResolveRootSymlink)
|
||||
} else {
|
||||
documentRoot = f.resolvedDocumentRoot
|
||||
documentRootOption = frankenphp.WithRequestResolvedDocumentRoot(documentRoot)
|
||||
}
|
||||
|
||||
env := make(map[string]string, len(f.Env)+1)
|
||||
env["REQUEST_URI\x00"] = origReq.URL.RequestURI()
|
||||
for k, v := range f.Env {
|
||||
env[k] = repl.ReplaceKnown(v, "")
|
||||
env := f.preparedEnv
|
||||
if f.preparedEnvNeedsReplacement {
|
||||
env = make(frankenphp.PreparedEnv, len(f.Env))
|
||||
for k, v := range f.preparedEnv {
|
||||
env[k] = repl.ReplaceKnown(v, "")
|
||||
}
|
||||
}
|
||||
|
||||
fullScriptPath, _ := fastabs.FastAbs(documentRoot + "/" + r.URL.Path)
|
||||
|
||||
workerName := ""
|
||||
for _, w := range f.Workers {
|
||||
if p, _ := fastabs.FastAbs(w.FileName); p == fullScriptPath {
|
||||
workerName = w.Name
|
||||
}
|
||||
}
|
||||
|
||||
fr, err := frankenphp.NewRequestWithContext(
|
||||
r,
|
||||
frankenphp.WithRequestDocumentRoot(documentRoot, *f.ResolveRootSymlink),
|
||||
documentRootOption,
|
||||
frankenphp.WithRequestSplitPath(f.SplitPath),
|
||||
frankenphp.WithRequestPreparedEnv(env),
|
||||
frankenphp.WithOriginalRequest(&origReq),
|
||||
frankenphp.WithWorkerName(workerName),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
if err = frankenphp.ServeHTTP(w, fr); err != nil {
|
||||
return caddyhttp.Error(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return frankenphp.ServeHTTP(w, fr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||
func (f *FrankenPHPModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
// First pass: Parse all directives except "worker"
|
||||
for d.Next() {
|
||||
for d.NextBlock(0) {
|
||||
switch d.Val() {
|
||||
@@ -298,24 +564,75 @@ func (f *FrankenPHPModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return d.ArgErr()
|
||||
}
|
||||
if f.Env == nil {
|
||||
f.Env = make(frankenphp.PreparedEnv)
|
||||
f.Env = make(map[string]string)
|
||||
f.preparedEnv = make(frankenphp.PreparedEnv)
|
||||
}
|
||||
f.Env[args[0]+"\x00"] = args[1]
|
||||
f.Env[args[0]] = args[1]
|
||||
f.preparedEnv[args[0]+"\x00"] = args[1]
|
||||
|
||||
case "resolve_root_symlink":
|
||||
if !d.NextArg() {
|
||||
continue
|
||||
}
|
||||
v, err := strconv.ParseBool(d.Val())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.NextArg() {
|
||||
if v, err := strconv.ParseBool(d.Val()); err == nil {
|
||||
f.ResolveRootSymlink = &v
|
||||
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
}
|
||||
|
||||
return d.ArgErr()
|
||||
}
|
||||
rrs := true
|
||||
f.ResolveRootSymlink = &rrs
|
||||
f.ResolveRootSymlink = &v
|
||||
|
||||
case "worker":
|
||||
for d.NextBlock(1) {
|
||||
}
|
||||
for d.NextArg() {
|
||||
}
|
||||
// Skip "worker" blocks in the first pass
|
||||
continue
|
||||
|
||||
default:
|
||||
allowedDirectives := "root, split, env, resolve_root_symlink, worker"
|
||||
return wrongSubDirectiveError("php or php_server", allowedDirectives, d.Val())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: Parse only "worker" blocks
|
||||
d.Reset()
|
||||
for d.Next() {
|
||||
for d.NextBlock(0) {
|
||||
if d.Val() == "worker" {
|
||||
wc, err := parseWorkerConfig(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Inherit environment variables from the parent php_server directive
|
||||
if !filepath.IsAbs(wc.FileName) && f.Root != "" {
|
||||
wc.FileName = filepath.Join(f.Root, wc.FileName)
|
||||
}
|
||||
|
||||
if f.Env != nil {
|
||||
if wc.Env == nil {
|
||||
wc.Env = make(map[string]string)
|
||||
}
|
||||
for k, v := range f.Env {
|
||||
// Only set if not already defined in the worker
|
||||
if _, exists := wc.Env[k]; !exists {
|
||||
wc.Env[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a worker with this filename already exists in this module
|
||||
for _, existingWorker := range f.Workers {
|
||||
if existingWorker.FileName == wc.FileName {
|
||||
return fmt.Errorf(`workers in a single "php_server" block must not have duplicate filenames: %q`, wc.FileName)
|
||||
}
|
||||
}
|
||||
|
||||
f.Workers = append(f.Workers, wc)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,7 +642,7 @@ func (f *FrankenPHPModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
|
||||
// parseCaddyfile unmarshals tokens from h into a new Middleware.
|
||||
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
m := FrankenPHPModule{}
|
||||
m := &FrankenPHPModule{}
|
||||
err := m.UnmarshalCaddyfile(h.Dispenser)
|
||||
|
||||
return m, err
|
||||
@@ -377,7 +694,7 @@ func parsePhpServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
|
||||
indexFile := "index.php"
|
||||
|
||||
// set up for explicitly overriding try_files
|
||||
tryFiles := []string{}
|
||||
var tryFiles []string
|
||||
|
||||
// if the user specified a matcher token, use that
|
||||
// matcher in a route that wraps both of our routes;
|
||||
@@ -475,37 +792,65 @@ func parsePhpServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
|
||||
|
||||
// if the index is turned off, we skip the redirect and try_files
|
||||
if indexFile != "off" {
|
||||
// route to redirect to canonical path if index PHP file
|
||||
redirMatcherSet := caddy.ModuleMap{
|
||||
"file": h.JSON(fileserver.MatchFile{
|
||||
TryFiles: []string{"{http.request.uri.path}/" + indexFile},
|
||||
}),
|
||||
"not": h.JSON(caddyhttp.MatchNot{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{
|
||||
{
|
||||
"path": h.JSON(caddyhttp.MatchPath{"*/"}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
redirHandler := caddyhttp.StaticResponse{
|
||||
StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
|
||||
Headers: http.Header{"Location": []string{"{http.request.orig_uri.path}/"}},
|
||||
}
|
||||
redirRoute := caddyhttp.Route{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet},
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)},
|
||||
}
|
||||
dirRedir := false
|
||||
dirIndex := "{http.request.uri.path}/" + indexFile
|
||||
tryPolicy := "first_exist_fallback"
|
||||
|
||||
// if tryFiles wasn't overridden, use a reasonable default
|
||||
if len(tryFiles) == 0 {
|
||||
tryFiles = []string{"{http.request.uri.path}", "{http.request.uri.path}/" + indexFile, indexFile}
|
||||
if disableFsrv {
|
||||
tryFiles = []string{dirIndex, indexFile}
|
||||
} else {
|
||||
tryFiles = []string{"{http.request.uri.path}", dirIndex, indexFile}
|
||||
}
|
||||
|
||||
dirRedir = true
|
||||
} else {
|
||||
if !strings.HasSuffix(tryFiles[len(tryFiles)-1], ".php") {
|
||||
// use first_exist strategy if the last file is not a PHP file
|
||||
tryPolicy = ""
|
||||
}
|
||||
|
||||
for _, tf := range tryFiles {
|
||||
if tf == dirIndex {
|
||||
dirRedir = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// route to redirect to canonical path if index PHP file
|
||||
if dirRedir {
|
||||
redirMatcherSet := caddy.ModuleMap{
|
||||
"file": h.JSON(fileserver.MatchFile{
|
||||
TryFiles: []string{dirIndex},
|
||||
}),
|
||||
"not": h.JSON(caddyhttp.MatchNot{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{
|
||||
{
|
||||
"path": h.JSON(caddyhttp.MatchPath{"*/"}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
redirHandler := caddyhttp.StaticResponse{
|
||||
StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
|
||||
Headers: http.Header{"Location": []string{"{http.request.orig_uri.path}/"}},
|
||||
}
|
||||
redirRoute := caddyhttp.Route{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet},
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)},
|
||||
}
|
||||
|
||||
routes = append(routes, redirRoute)
|
||||
}
|
||||
|
||||
// route to rewrite to PHP index file
|
||||
rewriteMatcherSet := caddy.ModuleMap{
|
||||
"file": h.JSON(fileserver.MatchFile{
|
||||
TryFiles: tryFiles,
|
||||
TryPolicy: tryPolicy,
|
||||
SplitPath: extensions,
|
||||
}),
|
||||
}
|
||||
@@ -517,12 +862,12 @@ func parsePhpServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)},
|
||||
}
|
||||
|
||||
routes = append(routes, redirRoute, rewriteRoute)
|
||||
routes = append(routes, rewriteRoute)
|
||||
}
|
||||
|
||||
// route to actually pass requests to PHP files;
|
||||
// match only requests that are for PHP files
|
||||
pathList := []string{}
|
||||
var pathList []string
|
||||
for _, ext := range extensions {
|
||||
pathList = append(pathList, "*"+ext)
|
||||
}
|
||||
@@ -534,6 +879,7 @@ func parsePhpServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
|
||||
// using the php directive syntax
|
||||
dispenser.Next() // consume the directive name
|
||||
err = phpsrv.UnmarshalCaddyfile(dispenser)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -585,9 +931,15 @@ func parsePhpServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
// return a nice error message
|
||||
func wrongSubDirectiveError(module string, allowedDriectives string, wrongValue string) error {
|
||||
return fmt.Errorf("unknown '%s' subdirective: '%s' (allowed directives are: %s)", module, wrongValue, allowedDriectives)
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.App = (*FrankenPHPApp)(nil)
|
||||
_ caddy.Provisioner = (*FrankenPHPApp)(nil)
|
||||
_ caddy.Provisioner = (*FrankenPHPModule)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*FrankenPHPModule)(nil)
|
||||
_ caddyfile.Unmarshaler = (*FrankenPHPModule)(nil)
|
||||
|
||||
1193
caddy/caddy_test.go
1193
caddy/caddy_test.go
File diff suppressed because it is too large
Load Diff
222
caddy/config_test.go
Normal file
222
caddy/config_test.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestModuleWorkerDuplicateFilenamesFail(t *testing.T) {
|
||||
// Create a test configuration with duplicate worker filenames
|
||||
configWithDuplicateFilenames := `
|
||||
{
|
||||
php {
|
||||
worker {
|
||||
file worker-with-env.php
|
||||
num 1
|
||||
}
|
||||
worker {
|
||||
file worker-with-env.php
|
||||
num 2
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the configuration
|
||||
d := caddyfile.NewTestDispenser(configWithDuplicateFilenames)
|
||||
module := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the configuration
|
||||
err := module.UnmarshalCaddyfile(d)
|
||||
|
||||
// Verify that an error was returned
|
||||
require.Error(t, err, "Expected an error when two workers in the same module have the same filename")
|
||||
require.Contains(t, err.Error(), "must not have duplicate filenames", "Error message should mention duplicate filenames")
|
||||
}
|
||||
|
||||
func TestModuleWorkersWithDifferentFilenames(t *testing.T) {
|
||||
// Create a test configuration with different worker filenames
|
||||
configWithDifferentFilenames := `
|
||||
{
|
||||
php {
|
||||
worker ../testdata/worker-with-env.php
|
||||
worker ../testdata/worker-with-counter.php
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the configuration
|
||||
d := caddyfile.NewTestDispenser(configWithDifferentFilenames)
|
||||
module := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the configuration
|
||||
err := module.UnmarshalCaddyfile(d)
|
||||
|
||||
// Verify that no error was returned
|
||||
require.NoError(t, err, "Expected no error when two workers in the same module have different filenames")
|
||||
|
||||
// Verify that both workers were added to the module
|
||||
require.Len(t, module.Workers, 2, "Expected two workers to be added to the module")
|
||||
require.Equal(t, "../testdata/worker-with-env.php", module.Workers[0].FileName, "First worker should have the correct filename")
|
||||
require.Equal(t, "../testdata/worker-with-counter.php", module.Workers[1].FileName, "Second worker should have the correct filename")
|
||||
}
|
||||
|
||||
func TestModuleWorkersDifferentNamesSucceed(t *testing.T) {
|
||||
// Create a test configuration with a worker name
|
||||
configWithWorkerName1 := `
|
||||
{
|
||||
php_server {
|
||||
worker {
|
||||
name test-worker-1
|
||||
file ../testdata/worker-with-env.php
|
||||
num 1
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the first configuration
|
||||
d1 := caddyfile.NewTestDispenser(configWithWorkerName1)
|
||||
app := &FrankenPHPApp{}
|
||||
module1 := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the first configuration
|
||||
err := module1.UnmarshalCaddyfile(d1)
|
||||
require.NoError(t, err, "First module should be configured without errors")
|
||||
|
||||
// Create a second test configuration with a different worker name
|
||||
configWithWorkerName2 := `
|
||||
{
|
||||
php_server {
|
||||
worker {
|
||||
name test-worker-2
|
||||
file ../testdata/worker-with-env.php
|
||||
num 1
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the second configuration
|
||||
d2 := caddyfile.NewTestDispenser(configWithWorkerName2)
|
||||
module2 := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the second configuration
|
||||
err = module2.UnmarshalCaddyfile(d2)
|
||||
|
||||
// Verify that no error was returned
|
||||
require.NoError(t, err, "Expected no error when two workers have different names")
|
||||
|
||||
_, err = app.addModuleWorkers(module1.Workers...)
|
||||
require.NoError(t, err, "Expected no error when adding the first module workers")
|
||||
_, err = app.addModuleWorkers(module2.Workers...)
|
||||
require.NoError(t, err, "Expected no error when adding the second module workers")
|
||||
|
||||
// Verify that both workers were added
|
||||
require.Len(t, app.Workers, 2, "Expected two workers in the app")
|
||||
require.Equal(t, "m#test-worker-1", app.Workers[0].Name, "First worker should have the correct name")
|
||||
require.Equal(t, "m#test-worker-2", app.Workers[1].Name, "Second worker should have the correct name")
|
||||
}
|
||||
|
||||
func TestModuleWorkerWithEnvironmentVariables(t *testing.T) {
|
||||
// Create a test configuration with environment variables
|
||||
configWithEnv := `
|
||||
{
|
||||
php {
|
||||
worker {
|
||||
file ../testdata/worker-with-env.php
|
||||
num 1
|
||||
env APP_ENV production
|
||||
env DEBUG true
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the configuration
|
||||
d := caddyfile.NewTestDispenser(configWithEnv)
|
||||
module := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the configuration
|
||||
err := module.UnmarshalCaddyfile(d)
|
||||
|
||||
// Verify that no error was returned
|
||||
require.NoError(t, err, "Expected no error when configuring a worker with environment variables")
|
||||
|
||||
// Verify that the worker was added to the module
|
||||
require.Len(t, module.Workers, 1, "Expected one worker to be added to the module")
|
||||
require.Equal(t, "../testdata/worker-with-env.php", module.Workers[0].FileName, "Worker should have the correct filename")
|
||||
|
||||
// Verify that the environment variables were set correctly
|
||||
require.Len(t, module.Workers[0].Env, 2, "Expected two environment variables")
|
||||
require.Equal(t, "production", module.Workers[0].Env["APP_ENV"], "APP_ENV should be set to production")
|
||||
require.Equal(t, "true", module.Workers[0].Env["DEBUG"], "DEBUG should be set to true")
|
||||
}
|
||||
|
||||
func TestModuleWorkerWithWatchConfiguration(t *testing.T) {
|
||||
// Create a test configuration with watch directories
|
||||
configWithWatch := `
|
||||
{
|
||||
php {
|
||||
worker {
|
||||
file ../testdata/worker-with-env.php
|
||||
num 1
|
||||
watch
|
||||
watch ./src/**/*.php
|
||||
watch ./config/**/*.yaml
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the configuration
|
||||
d := caddyfile.NewTestDispenser(configWithWatch)
|
||||
module := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the configuration
|
||||
err := module.UnmarshalCaddyfile(d)
|
||||
|
||||
// Verify that no error was returned
|
||||
require.NoError(t, err, "Expected no error when configuring a worker with watch directories")
|
||||
|
||||
// Verify that the worker was added to the module
|
||||
require.Len(t, module.Workers, 1, "Expected one worker to be added to the module")
|
||||
require.Equal(t, "../testdata/worker-with-env.php", module.Workers[0].FileName, "Worker should have the correct filename")
|
||||
|
||||
// Verify that the watch directories were set correctly
|
||||
require.Len(t, module.Workers[0].Watch, 3, "Expected three watch patterns")
|
||||
require.Equal(t, "./**/*.{php,yaml,yml,twig,env}", module.Workers[0].Watch[0], "First watch pattern should be the default")
|
||||
require.Equal(t, "./src/**/*.php", module.Workers[0].Watch[1], "Second watch pattern should match the configuration")
|
||||
require.Equal(t, "./config/**/*.yaml", module.Workers[0].Watch[2], "Third watch pattern should match the configuration")
|
||||
}
|
||||
|
||||
func TestModuleWorkerWithCustomName(t *testing.T) {
|
||||
// Create a test configuration with a custom worker name
|
||||
configWithCustomName := `
|
||||
{
|
||||
php {
|
||||
worker {
|
||||
file ../testdata/worker-with-env.php
|
||||
num 1
|
||||
name custom-worker-name
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the configuration
|
||||
d := caddyfile.NewTestDispenser(configWithCustomName)
|
||||
module := &FrankenPHPModule{}
|
||||
app := &FrankenPHPApp{}
|
||||
|
||||
// Unmarshal the configuration
|
||||
err := module.UnmarshalCaddyfile(d)
|
||||
|
||||
// Verify that no error was returned
|
||||
require.NoError(t, err, "Expected no error when configuring a worker with a custom name")
|
||||
|
||||
// Verify that the worker was added to the module
|
||||
require.Len(t, module.Workers, 1, "Expected one worker to be added to the module")
|
||||
require.Equal(t, "../testdata/worker-with-env.php", module.Workers[0].FileName, "Worker should have the correct filename")
|
||||
|
||||
// Verify that the worker was added to app.Workers with the m# prefix
|
||||
module.Workers, err = app.addModuleWorkers(module.Workers...)
|
||||
require.NoError(t, err, "Expected no error when adding the worker to the app")
|
||||
require.Equal(t, "m#custom-worker-name", module.Workers[0].Name, "Worker should have the custom name, prefixed with m#")
|
||||
require.Equal(t, "m#custom-worker-name", app.Workers[0].Name, "Worker should have the custom name, prefixed with m#")
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
# The Caddyfile is an easy way to configure FrankenPHP and the Caddy web server.
|
||||
#
|
||||
# https://frankenphp.dev/docs/config
|
||||
# https://caddyserver.com/docs/caddyfile
|
||||
{
|
||||
skip_install_trust
|
||||
|
||||
{$CADDY_GLOBAL_OPTIONS}
|
||||
|
||||
frankenphp {
|
||||
{$FRANKENPHP_CONFIG}
|
||||
#worker /path/to/your/worker.php
|
||||
}
|
||||
}
|
||||
|
||||
# https://caddyserver.com/docs/caddyfile/directives#sorting-algorithm
|
||||
order mercure after encode
|
||||
order vulcain after reverse_proxy
|
||||
order php_server before file_server
|
||||
order php before file_server
|
||||
|
||||
{$CADDY_EXTRA_CONFIG}
|
||||
|
||||
@@ -19,16 +19,13 @@
|
||||
# # Redact the authorization query parameter that can be set by Mercure
|
||||
# format filter {
|
||||
# request>uri query {
|
||||
# wrap console
|
||||
# fields {
|
||||
# uri query {
|
||||
# replace authorization REDACTED
|
||||
# }
|
||||
# replace authorization REDACTED
|
||||
# }
|
||||
# }
|
||||
#}
|
||||
|
||||
root public/
|
||||
root * public/
|
||||
encode zstd br gzip
|
||||
|
||||
# Uncomment the following lines to enable Mercure and Vulcain modules
|
||||
#mercure {
|
||||
@@ -50,5 +47,13 @@
|
||||
{$CADDY_SERVER_EXTRA_DIRECTIVES}
|
||||
|
||||
php_server {
|
||||
php_server
|
||||
#worker /path/to/your/worker.php
|
||||
}
|
||||
}
|
||||
|
||||
# As an alternative to editing the above site block, you can add your own site
|
||||
# block files in the Caddyfile.d directory, and they will be included as long
|
||||
# as they use the .caddyfile extension.
|
||||
|
||||
import Caddyfile.d/*.caddyfile
|
||||
import Caddyfile.d/*.caddyfile
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
"go.uber.org/automaxprocs/maxprocs"
|
||||
"go.uber.org/zap"
|
||||
|
||||
// plug in Caddy modules here.
|
||||
_ "github.com/caddyserver/caddy/v2/modules/standard"
|
||||
_ "github.com/dunglas/caddy-cbrotli"
|
||||
_ "github.com/dunglas/frankenphp/caddy"
|
||||
_ "frankenphp.dev/caddy"
|
||||
_ "github.com/dunglas/mercure/caddy"
|
||||
_ "github.com/dunglas/vulcain/caddy"
|
||||
)
|
||||
|
||||
func main() {
|
||||
undo, err := maxprocs.Set()
|
||||
defer undo()
|
||||
if err != nil {
|
||||
caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err))
|
||||
}
|
||||
|
||||
caddycmd.Main()
|
||||
}
|
||||
|
||||
279
caddy/go.mod
279
caddy/go.mod
@@ -1,199 +1,208 @@
|
||||
module github.com/dunglas/frankenphp/caddy
|
||||
module frankenphp.dev/caddy
|
||||
|
||||
go 1.21
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.22.0
|
||||
|
||||
replace github.com/dunglas/frankenphp => ../
|
||||
replace frankenphp.dev => ../
|
||||
|
||||
retract v1.0.0-rc.1 // Human error
|
||||
|
||||
require (
|
||||
github.com/caddyserver/caddy/v2 v2.7.6
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/caddyserver/caddy/v2 v2.10.0
|
||||
github.com/caddyserver/certmagic v0.23.0
|
||||
github.com/dunglas/caddy-cbrotli v1.0.0
|
||||
github.com/dunglas/frankenphp v1.1.4
|
||||
github.com/dunglas/mercure/caddy v0.15.11
|
||||
github.com/dunglas/vulcain/caddy v1.0.2
|
||||
github.com/spf13/cobra v1.8.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
go.uber.org/zap v1.27.0
|
||||
frankenphp.dev v1.7.0
|
||||
github.com/dunglas/mercure/caddy v0.19.2
|
||||
github.com/dunglas/vulcain/caddy v1.2.0
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect
|
||||
require github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca // indirect
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/KimMachineGun/automemlimit v0.7.2 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.9.1 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.12.0 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
|
||||
github.com/MicahParks/jwkset v0.9.6 // indirect
|
||||
github.com/MicahParks/keyfunc/v3 v3.4.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.9.4 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.18.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/ccoveille/go-safecast v1.6.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgraph-io/badger v1.6.2 // indirect
|
||||
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/dgraph-io/ristretto v0.2.0 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dunglas/httpsfv v1.0.2 // indirect
|
||||
github.com/dunglas/mercure v0.15.11 // indirect
|
||||
github.com/dunglas/vulcain v1.0.2 // indirect
|
||||
github.com/dunglas/httpsfv v1.1.0 // indirect
|
||||
github.com/dunglas/mercure v0.19.2 // indirect
|
||||
github.com/dunglas/vulcain v1.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
|
||||
github.com/gammazero/deque v0.2.1 // indirect
|
||||
github.com/getkin/kin-openapi v0.123.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.10 // indirect
|
||||
github.com/go-kit/kit v0.13.0 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
||||
github.com/gammazero/deque v1.0.0 // indirect
|
||||
github.com/getkin/kin-openapi v0.132.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.2 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang/glog v1.2.1 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/brotli/go/cbrotli v0.0.0-20240116120200-adbc354d23af // indirect
|
||||
github.com/google/cel-go v0.15.1 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.7 // indirect
|
||||
github.com/google/go-tpm v0.9.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/brotli/go/cbrotli v0.0.0-20241111155135-4303850b01d6 // indirect
|
||||
github.com/google/cel-go v0.25.0 // indirect
|
||||
github.com/google/certificate-transparency-go v1.3.1 // indirect
|
||||
github.com/google/go-tpm v0.9.5 // indirect
|
||||
github.com/google/go-tspi v0.3.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f // indirect
|
||||
github.com/google/pprof v0.0.0-20250602020802-c6617b811d0e // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/invopop/yaml v0.2.0 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.14.3 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||
github.com/jackc/pgtype v1.14.2 // indirect
|
||||
github.com/jackc/pgx/v4 v4.18.3 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kevburnsjr/skipfilter v0.0.1 // indirect
|
||||
github.com/klauspost/compress v1.17.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/libdns/libdns v0.2.1 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/libdns/libdns v1.1.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||
github.com/mastercactapus/proxyprotocol v0.0.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/maypok86/otter v1.2.1 // indirect
|
||||
github.com/maypok86/otter v1.2.4 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mholt/acmez v1.2.0 // indirect
|
||||
github.com/micromdm/scep/v2 v2.1.0 // indirect
|
||||
github.com/miekg/dns v1.1.58 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.2 // indirect
|
||||
github.com/miekg/dns v1.1.66 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v1.19.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.52.2 // indirect
|
||||
github.com/prometheus/procfs v0.13.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/quic-go v0.41.0 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
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.64.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.52.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/slackhq/nebula v1.8.2 // indirect
|
||||
github.com/smallstep/certificates v0.25.0 // indirect
|
||||
github.com/smallstep/nosql v0.6.0 // indirect
|
||||
github.com/slackhq/nebula v1.9.5 // indirect
|
||||
github.com/smallstep/certificates v0.28.3 // indirect
|
||||
github.com/smallstep/cli-utils v0.12.1 // indirect
|
||||
github.com/smallstep/linkedca v0.23.0 // indirect
|
||||
github.com/smallstep/nosql v0.7.0 // indirect
|
||||
github.com/smallstep/pkcs7 v0.2.1 // indirect
|
||||
github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492 // indirect
|
||||
github.com/smallstep/truststore v0.13.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/spf13/viper v1.20.1 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 // indirect
|
||||
github.com/tidwall/gjson v1.17.1 // indirect
|
||||
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/unrolled/secure v1.14.0 // indirect
|
||||
github.com/urfave/cli v1.22.14 // indirect
|
||||
github.com/unrolled/secure v1.17.0 // indirect
|
||||
github.com/urfave/cli v1.22.16 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
github.com/yuin/goldmark v1.7.0 // indirect
|
||||
github.com/yuin/goldmark v1.7.12 // indirect
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.45.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.20.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.20.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.20.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.20.0 // indirect
|
||||
go.opentelemetry.io/otel v1.23.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.23.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.23.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||
go.step.sm/cli-utils v0.8.0 // indirect
|
||||
go.step.sm/crypto v0.36.0 // indirect
|
||||
go.step.sm/linkedca v0.20.1 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.35.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.35.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
|
||||
go.step.sm/crypto v0.66.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/mock v0.5.2 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240228224816-df926f6c8641 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 // indirect
|
||||
google.golang.org/grpc v1.62.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20250531095911-4f9f0ca9fcfb // indirect
|
||||
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
|
||||
google.golang.org/grpc v1.72.2 // indirect
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.1 // indirect
|
||||
)
|
||||
|
||||
985
caddy/go.sum
985
caddy/go.sum
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"frankenphp.dev"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -37,7 +37,13 @@ func cmdPHPCLI(fs caddycmd.Flags) (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
status := frankenphp.ExecuteScriptCLI(args[0], args)
|
||||
var status int
|
||||
if len(args) >= 2 && args[0] == "-r" {
|
||||
status = frankenphp.ExecutePHPCode(args[1])
|
||||
} else {
|
||||
status = frankenphp.ExecuteScriptCLI(args[0], args)
|
||||
}
|
||||
|
||||
os.Exit(status)
|
||||
|
||||
return status, nil
|
||||
|
||||
@@ -3,9 +3,9 @@ package caddy
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -20,16 +20,14 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"frankenphp.dev"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddycmd.RegisterCommand(caddycmd.Command{
|
||||
Name: "php-server",
|
||||
Usage: "[--domain <example.com>] [--root <path>] [--listen <addr>] [--worker /path/to/worker.php<,nb-workers>] [--access-log] [--debug] [--no-compress] [--mercure]",
|
||||
Usage: "[--domain=<example.com>] [--root=<path>] [--listen=<addr>] [--worker=/path/to/worker.php<,nb-workers>] [--watch[=<glob-pattern>]]... [--access-log] [--debug] [--no-compress] [--mercure]",
|
||||
Short: "Spins up a production-ready PHP server",
|
||||
Long: `
|
||||
A simple but production-ready PHP server. Useful for quick deployments,
|
||||
@@ -48,10 +46,14 @@ For more advanced use cases, see https://github.com/dunglas/frankenphp/blob/main
|
||||
cmd.Flags().StringP("root", "r", "", "The path to the root of the site")
|
||||
cmd.Flags().StringP("listen", "l", "", "The address to which to bind the listener")
|
||||
cmd.Flags().StringArrayP("worker", "w", []string{}, "Worker script")
|
||||
cmd.Flags().StringArray("watch", []string{}, "Glob pattern of directories and files to watch for changes")
|
||||
cmd.Flags().BoolP("access-log", "a", false, "Enable the access log")
|
||||
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
|
||||
cmd.Flags().BoolP("mercure", "m", false, "Enable the built-in Mercure.rocks hub")
|
||||
cmd.Flags().BoolP("no-compress", "", false, "Disable Zstandard, Brotli and Gzip compression")
|
||||
cmd.Flags().Bool("no-compress", false, "Disable Zstandard, Brotli and Gzip compression")
|
||||
|
||||
cmd.Flags().Lookup("watch").NoOptDefVal = defaultWatchPattern
|
||||
|
||||
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdPHPServer)
|
||||
},
|
||||
})
|
||||
@@ -73,27 +75,35 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
watch, err := fs.GetStringArray("watch")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if frankenphp.EmbeddedAppPath != "" {
|
||||
if err := os.Chdir(frankenphp.EmbeddedAppPath); err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
}
|
||||
|
||||
var workersOption []workerConfig
|
||||
if len(workers) != 0 {
|
||||
workersOption = make([]workerConfig, 0, len(workers))
|
||||
for _, worker := range workers {
|
||||
parts := strings.SplitN(worker, ",", 2)
|
||||
if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(parts[0]) {
|
||||
parts[0] = filepath.Join(frankenphp.EmbeddedAppPath, parts[0])
|
||||
}
|
||||
|
||||
var num int
|
||||
var num uint64
|
||||
if len(parts) > 1 {
|
||||
num, _ = strconv.Atoi(parts[1])
|
||||
num, _ = strconv.ParseUint(parts[1], 10, 32)
|
||||
}
|
||||
|
||||
workersOption = append(workersOption, workerConfig{FileName: parts[0], Num: num})
|
||||
workersOption = append(workersOption, workerConfig{FileName: parts[0], Num: int(num)})
|
||||
}
|
||||
workersOption[0].Watch = watch
|
||||
}
|
||||
|
||||
if frankenphp.EmbeddedAppPath != "" {
|
||||
if _, err := os.Stat(filepath.Join(frankenphp.EmbeddedAppPath, "php.ini")); err == nil {
|
||||
if _, err := os.Stat("php.ini"); err == nil {
|
||||
iniScanDir := os.Getenv("PHP_INI_SCAN_DIR")
|
||||
|
||||
if err := os.Setenv("PHP_INI_SCAN_DIR", iniScanDir+":"+frankenphp.EmbeddedAppPath); err != nil {
|
||||
@@ -101,8 +111,8 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(frankenphp.EmbeddedAppPath, "Caddyfile")); err == nil {
|
||||
config, _, err := caddycmd.LoadConfig(filepath.Join(frankenphp.EmbeddedAppPath, "Caddyfile"), "")
|
||||
if _, err := os.Stat("Caddyfile"); err == nil {
|
||||
config, _, err := caddycmd.LoadConfig("Caddyfile", "caddyfile")
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
@@ -115,14 +125,12 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) {
|
||||
}
|
||||
|
||||
if root == "" {
|
||||
root = filepath.Join(frankenphp.EmbeddedAppPath, defaultDocumentRoot)
|
||||
} else if filepath.IsLocal(root) {
|
||||
root = filepath.Join(frankenphp.EmbeddedAppPath, root)
|
||||
root = defaultDocumentRoot
|
||||
}
|
||||
}
|
||||
|
||||
const indexFile = "index.php"
|
||||
extensions := []string{"php"}
|
||||
extensions := []string{".php"}
|
||||
tryFiles := []string{"{http.request.uri.path}", "{http.request.uri.path}/" + indexFile, indexFile}
|
||||
|
||||
rrs := true
|
||||
@@ -173,7 +181,7 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) {
|
||||
|
||||
// route to actually pass requests to PHP files;
|
||||
// match only requests that are for PHP files
|
||||
pathList := []string{}
|
||||
var pathList []string
|
||||
for _, ext := range extensions {
|
||||
pathList = append(pathList, "*"+ext)
|
||||
}
|
||||
@@ -213,15 +221,30 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
var (
|
||||
encodings caddy.ModuleMap
|
||||
prefer []string
|
||||
)
|
||||
if brotli {
|
||||
encodings = caddy.ModuleMap{
|
||||
"zstd": caddyconfig.JSON(zstd.New(), nil),
|
||||
"br": caddyconfig.JSON(br.New(), nil),
|
||||
"gzip": caddyconfig.JSON(gzip.New(), nil),
|
||||
}
|
||||
prefer = []string{"zstd", "br", "gzip"}
|
||||
} else {
|
||||
encodings = caddy.ModuleMap{
|
||||
"zstd": caddyconfig.JSON(zstd.New(), nil),
|
||||
"gzip": caddyconfig.JSON(gzip.New(), nil),
|
||||
}
|
||||
prefer = []string{"zstd", "gzip"}
|
||||
}
|
||||
|
||||
encodeRoute := caddyhttp.Route{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{},
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(encode.Encode{
|
||||
EncodingsRaw: caddy.ModuleMap{
|
||||
"zstd": caddyconfig.JSON(zstd.New(), nil),
|
||||
"br": caddyconfig.JSON(br.New(), nil),
|
||||
"gzip": caddyconfig.JSON(gzip.New(), nil),
|
||||
},
|
||||
Prefer: []string{"zstd", "br", "gzip"},
|
||||
EncodingsRaw: encodings,
|
||||
Prefer: prefer,
|
||||
}, "handler", "encode", nil)},
|
||||
}
|
||||
|
||||
@@ -295,12 +318,12 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) {
|
||||
Servers: map[string]*caddyhttp.Server{"php": server},
|
||||
}
|
||||
|
||||
var false bool
|
||||
var f bool
|
||||
cfg := &caddy.Config{
|
||||
Admin: &caddy.AdminConfig{
|
||||
Disabled: true,
|
||||
Config: &caddy.ConfigSettings{
|
||||
Persist: &false,
|
||||
Persist: &f,
|
||||
},
|
||||
},
|
||||
AppsRaw: caddy.ModuleMap{
|
||||
@@ -313,7 +336,7 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) {
|
||||
cfg.Logging = &caddy.Logging{
|
||||
Logs: map[string]*caddy.CustomLog{
|
||||
"default": {
|
||||
BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()},
|
||||
BaseLog: caddy.BaseLog{Level: slog.LevelDebug.String()},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
38
caddy/watcher_test.go
Normal file
38
caddy/watcher_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build !nowatcher
|
||||
|
||||
package caddy_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestWorkerWithInactiveWatcher(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
|
||||
frankenphp {
|
||||
worker {
|
||||
file ../testdata/worker-with-counter.php
|
||||
num 1
|
||||
watch ./**/*.php
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
root ../testdata
|
||||
rewrite worker-with-counter.php
|
||||
php
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:"+testPort, http.StatusOK, "requests:1")
|
||||
tester.AssertGetResponse("http://localhost:"+testPort, http.StatusOK, "requests:2")
|
||||
}
|
||||
262
cgi.go
262
cgi.go
@@ -1,84 +1,63 @@
|
||||
package frankenphp
|
||||
|
||||
// #cgo nocallback frankenphp_register_bulk
|
||||
// #cgo nocallback frankenphp_register_variables_from_request_info
|
||||
// #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 noescape frankenphp_register_variable_safe
|
||||
// #cgo noescape frankenphp_register_single
|
||||
// #include <php_variables.h>
|
||||
// #include "frankenphp.h"
|
||||
import "C"
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"frankenphp.dev/internal/phpheaders"
|
||||
)
|
||||
|
||||
type serverKey int
|
||||
|
||||
const (
|
||||
contentLength serverKey = iota
|
||||
documentRoot
|
||||
documentUri
|
||||
gatewayInterface
|
||||
httpHost
|
||||
https
|
||||
pathInfo
|
||||
phpSelf
|
||||
remoteAddr
|
||||
remoteHost
|
||||
remotePort
|
||||
requestScheme
|
||||
scriptFilename
|
||||
scriptName
|
||||
serverName
|
||||
serverPort
|
||||
serverProtocol
|
||||
serverSoftware
|
||||
sslProtocol
|
||||
)
|
||||
|
||||
var knownServerKeys = map[string]struct{}{
|
||||
"CONTENT_LENGTH\x00": {},
|
||||
"DOCUMENT_ROOT\x00": {},
|
||||
"DOCUMENT_URI\x00": {},
|
||||
"GATEWAY_INTERFACE\x00": {},
|
||||
"HTTP_HOST\x00": {},
|
||||
"HTTPS\x00": {},
|
||||
"PATH_INFO\x00": {},
|
||||
"PHP_SELF\x00": {},
|
||||
"REMOTE_ADDR\x00": {},
|
||||
"REMOTE_HOST\x00": {},
|
||||
"REMOTE_PORT\x00": {},
|
||||
"REQUEST_SCHEME\x00": {},
|
||||
"SCRIPT_FILENAME\x00": {},
|
||||
"SCRIPT_NAME\x00": {},
|
||||
"SERVER_NAME\x00": {},
|
||||
"SERVER_PORT\x00": {},
|
||||
"SERVER_PROTOCOL\x00": {},
|
||||
"SERVER_SOFTWARE\x00": {},
|
||||
"SSL_PROTOCOL\x00": {},
|
||||
}
|
||||
|
||||
func setKnownServerVariable(p *runtime.Pinner, cArr *[27]C.go_string, serverKey serverKey, val string) {
|
||||
if val == "" {
|
||||
return
|
||||
}
|
||||
|
||||
valData := unsafe.StringData(val)
|
||||
p.Pin(valData)
|
||||
cArr[serverKey].len = C.size_t(len(val))
|
||||
cArr[serverKey].data = (*C.char)(unsafe.Pointer(valData))
|
||||
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",
|
||||
"AUTH_TYPE",
|
||||
"REMOTE_IDENT",
|
||||
"CONTENT_TYPE",
|
||||
"PATH_TRANSLATED",
|
||||
"QUERY_STRING",
|
||||
"REMOTE_USER",
|
||||
"REQUEST_METHOD",
|
||||
"REQUEST_URI",
|
||||
}
|
||||
|
||||
// computeKnownVariables returns a set of CGI environment variables for the request.
|
||||
//
|
||||
// TODO: handle this case https://github.com/caddyserver/caddy/issues/3718
|
||||
// Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
|
||||
func computeKnownVariables(request *http.Request, p *runtime.Pinner) (cArr [27]C.go_string) {
|
||||
fc, fcOK := FromContext(request.Context())
|
||||
if !fcOK {
|
||||
panic("not a FrankenPHP request")
|
||||
}
|
||||
|
||||
func addKnownVariablesToServer(thread *phpThread, 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 {
|
||||
@@ -92,55 +71,26 @@ func computeKnownVariables(request *http.Request, p *runtime.Pinner) (cArr [27]C
|
||||
ip = strings.Replace(ip, "[", "", 1)
|
||||
ip = strings.Replace(ip, "]", "", 1)
|
||||
|
||||
ra, raOK := fc.env["REMOTE_ADDR\x00"]
|
||||
if raOK {
|
||||
setKnownServerVariable(p, &cArr, remoteAddr, ra)
|
||||
} else {
|
||||
setKnownServerVariable(p, &cArr, remoteAddr, ip)
|
||||
}
|
||||
|
||||
if rh, ok := fc.env["REMOTE_HOST\x00"]; ok {
|
||||
setKnownServerVariable(p, &cArr, remoteHost, rh) // For speed, remote host lookups disabled
|
||||
} else {
|
||||
if raOK {
|
||||
setKnownServerVariable(p, &cArr, remoteHost, ip)
|
||||
} else {
|
||||
cArr[remoteHost] = cArr[remoteAddr]
|
||||
}
|
||||
}
|
||||
|
||||
setKnownServerVariable(p, &cArr, remotePort, port)
|
||||
setKnownServerVariable(p, &cArr, documentRoot, fc.documentRoot)
|
||||
setKnownServerVariable(p, &cArr, pathInfo, fc.pathInfo)
|
||||
setKnownServerVariable(p, &cArr, phpSelf, request.URL.Path)
|
||||
setKnownServerVariable(p, &cArr, documentUri, fc.docURI)
|
||||
setKnownServerVariable(p, &cArr, scriptFilename, fc.scriptFilename)
|
||||
setKnownServerVariable(p, &cArr, scriptName, fc.scriptName)
|
||||
|
||||
var https string
|
||||
var sslProtocol string
|
||||
var rs string
|
||||
if request.TLS == nil {
|
||||
rs = "http"
|
||||
https = ""
|
||||
sslProtocol = ""
|
||||
} else {
|
||||
rs = "https"
|
||||
|
||||
if h, ok := fc.env["HTTPS\x00"]; ok {
|
||||
setKnownServerVariable(p, &cArr, https, h)
|
||||
} else {
|
||||
setKnownServerVariable(p, &cArr, https, "on")
|
||||
}
|
||||
https = "on"
|
||||
|
||||
// and pass the protocol details in a manner compatible with apache's mod_ssl
|
||||
// (which is why these have a SSL_ prefix and not TLS_).
|
||||
if pr, ok := fc.env["SSL_PROTOCOL\x00"]; ok {
|
||||
setKnownServerVariable(p, &cArr, sslProtocol, pr)
|
||||
// (which is why these have an SSL_ prefix and not TLS_).
|
||||
if v, ok := tlsProtocolStrings[request.TLS.Version]; ok {
|
||||
sslProtocol = v
|
||||
} else {
|
||||
if v, ok := tlsProtocolStrings[request.TLS.Version]; ok {
|
||||
setKnownServerVariable(p, &cArr, sslProtocol, v)
|
||||
}
|
||||
sslProtocol = ""
|
||||
}
|
||||
}
|
||||
|
||||
setKnownServerVariable(p, &cArr, requestScheme, rs)
|
||||
reqHost, reqPort, _ := net.SplitHostPort(request.Host)
|
||||
|
||||
if reqHost == "" {
|
||||
@@ -161,23 +111,96 @@ func computeKnownVariables(request *http.Request, p *runtime.Pinner) (cArr [27]C
|
||||
}
|
||||
}
|
||||
|
||||
setKnownServerVariable(p, &cArr, serverName, reqHost)
|
||||
if reqPort != "" {
|
||||
setKnownServerVariable(p, &cArr, serverPort, reqPort)
|
||||
serverPort := reqPort
|
||||
contentLength := request.Header.Get("Content-Length")
|
||||
|
||||
var requestURI string
|
||||
if fc.originalRequest != nil {
|
||||
requestURI = fc.originalRequest.URL.RequestURI()
|
||||
} else {
|
||||
requestURI = request.URL.RequestURI()
|
||||
}
|
||||
|
||||
// Variables defined in CGI 1.1 spec
|
||||
// Some variables are unused but cleared explicitly to prevent
|
||||
// the parent environment from interfering.
|
||||
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"], 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),
|
||||
)
|
||||
|
||||
// These values can not be override
|
||||
setKnownServerVariable(p, &cArr, contentLength, request.Header.Get("Content-Length"))
|
||||
setKnownServerVariable(p, &cArr, gatewayInterface, "CGI/1.1")
|
||||
setKnownServerVariable(p, &cArr, serverProtocol, request.Proto)
|
||||
setKnownServerVariable(p, &cArr, serverSoftware, "FrankenPHP")
|
||||
setKnownServerVariable(p, &cArr, httpHost, request.Host) // added here, since not always part of headers
|
||||
// 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"],
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
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))}
|
||||
}
|
||||
|
||||
func addHeadersToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
for field, val := range fc.request.Header {
|
||||
if k := mainThread.commonHeaders[field]; k != nil {
|
||||
v := strings.Join(val, ", ")
|
||||
C.frankenphp_register_single(k, toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
|
||||
continue
|
||||
}
|
||||
|
||||
// if the header name could not be cached, it needs to be registered safely
|
||||
// this is more inefficient but allows additional sanitizing by PHP
|
||||
k := phpheaders.GetUnCommonHeader(field)
|
||||
v := strings.Join(val, ", ")
|
||||
C.frankenphp_register_variable_safe(toUnsafeChar(k), toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
|
||||
}
|
||||
}
|
||||
|
||||
func addPreparedEnvToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
for k, v := range fc.env {
|
||||
C.frankenphp_register_variable_safe(toUnsafeChar(k), toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
|
||||
}
|
||||
fc.env = nil
|
||||
}
|
||||
|
||||
//export go_register_variables
|
||||
func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
|
||||
thread := phpThreads[threadIndex]
|
||||
fc := thread.getRequestContext()
|
||||
|
||||
addKnownVariablesToServer(thread, fc, trackVarsArray)
|
||||
addHeadersToServer(fc, trackVarsArray)
|
||||
|
||||
// The Prepared Environment is registered last and can overwrite any previous values
|
||||
addPreparedEnvToServer(fc, trackVarsArray)
|
||||
}
|
||||
|
||||
// splitPos returns the index where path should
|
||||
@@ -185,7 +208,7 @@ func computeKnownVariables(request *http.Request, p *runtime.Pinner) (cArr [27]C
|
||||
//
|
||||
// Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
|
||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
func splitPos(fc *FrankenPHPContext, path string) int {
|
||||
func splitPos(fc *frankenPHPContext, path string) int {
|
||||
if len(fc.splitPath) == 0 {
|
||||
return 0
|
||||
}
|
||||
@@ -208,8 +231,6 @@ var tlsProtocolStrings = map[uint16]string{
|
||||
tls.VersionTLS13: "TLSv1.3",
|
||||
}
|
||||
|
||||
var headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
|
||||
|
||||
// SanitizedPathJoin performs filepath.Join(root, reqPath) that
|
||||
// is safe against directory traversal attacks. It uses logic
|
||||
// similar to that in the Go standard library, specifically
|
||||
@@ -228,7 +249,7 @@ func sanitizedPathJoin(root, reqPath string) string {
|
||||
path := filepath.Join(root, filepath.Clean("/"+reqPath))
|
||||
|
||||
// filepath.Join also cleans the path, and cleaning strips
|
||||
// the trailing slash, so we need to re-add it afterwards.
|
||||
// the trailing slash, so we need to re-add it afterward.
|
||||
// if the length is 1, then it's a path to the root,
|
||||
// and that should return ".", so we don't append the separator.
|
||||
if strings.HasSuffix(reqPath, "/") && len(reqPath) > 1 {
|
||||
@@ -239,3 +260,8 @@ func sanitizedPathJoin(root, reqPath string) string {
|
||||
}
|
||||
|
||||
const separator = string(filepath.Separator)
|
||||
|
||||
func toUnsafeChar(s string) *C.char {
|
||||
sData := unsafe.StringData(s)
|
||||
return (*C.char)(unsafe.Pointer(sData))
|
||||
}
|
||||
|
||||
166
context.go
Normal file
166
context.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package frankenphp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// frankenPHPContext provides contextual information about the Request to handle.
|
||||
type frankenPHPContext struct {
|
||||
documentRoot string
|
||||
splitPath []string
|
||||
env PreparedEnv
|
||||
logger *slog.Logger
|
||||
request *http.Request
|
||||
originalRequest *http.Request
|
||||
|
||||
docURI string
|
||||
pathInfo string
|
||||
scriptName string
|
||||
scriptFilename string
|
||||
workerName string
|
||||
|
||||
// Whether the request is already closed by us
|
||||
isDone bool
|
||||
|
||||
responseWriter http.ResponseWriter
|
||||
|
||||
done chan interface{}
|
||||
startedAt time.Time
|
||||
}
|
||||
|
||||
// fromContext extracts the frankenPHPContext from a context.
|
||||
func fromContext(ctx context.Context) (fctx *frankenPHPContext, ok bool) {
|
||||
fctx, ok = ctx.Value(contextKey).(*frankenPHPContext)
|
||||
return
|
||||
}
|
||||
|
||||
// NewRequestWithContext creates a new FrankenPHP request context.
|
||||
func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Request, error) {
|
||||
fc := &frankenPHPContext{
|
||||
done: make(chan interface{}),
|
||||
startedAt: time.Now(),
|
||||
request: r,
|
||||
}
|
||||
for _, o := range opts {
|
||||
if err := o(fc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if fc.logger == nil {
|
||||
fc.logger = logger
|
||||
}
|
||||
|
||||
if fc.documentRoot == "" {
|
||||
if EmbeddedAppPath != "" {
|
||||
fc.documentRoot = EmbeddedAppPath
|
||||
} else {
|
||||
var err error
|
||||
if fc.documentRoot, err = os.Getwd(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fc.splitPath == nil {
|
||||
fc.splitPath = []string{".php"}
|
||||
}
|
||||
|
||||
if fc.env == nil {
|
||||
fc.env = make(map[string]string)
|
||||
}
|
||||
|
||||
if splitPos := splitPos(fc, r.URL.Path); splitPos > -1 {
|
||||
fc.docURI = r.URL.Path[:splitPos]
|
||||
fc.pathInfo = r.URL.Path[splitPos:]
|
||||
|
||||
// Strip PATH_INFO from SCRIPT_NAME
|
||||
fc.scriptName = strings.TrimSuffix(r.URL.Path, fc.pathInfo)
|
||||
|
||||
// Ensure the SCRIPT_NAME has a leading slash for compliance with RFC3875
|
||||
// Info: https://tools.ietf.org/html/rfc3875#section-4.1.13
|
||||
if fc.scriptName != "" && !strings.HasPrefix(fc.scriptName, "/") {
|
||||
fc.scriptName = "/" + fc.scriptName
|
||||
}
|
||||
}
|
||||
|
||||
// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
|
||||
fc.scriptFilename = sanitizedPathJoin(fc.documentRoot, fc.scriptName)
|
||||
c := context.WithValue(r.Context(), contextKey, fc)
|
||||
|
||||
return r.WithContext(c), nil
|
||||
}
|
||||
|
||||
func newDummyContext(requestPath string, opts ...RequestOption) (*frankenPHPContext, error) {
|
||||
r, err := http.NewRequest(http.MethodGet, requestPath, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fr, err := NewRequestWithContext(r, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fc, _ := fromContext(fr.Context())
|
||||
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
// closeContext sends the response to the client
|
||||
func (fc *frankenPHPContext) closeContext() {
|
||||
if fc.isDone {
|
||||
return
|
||||
}
|
||||
|
||||
close(fc.done)
|
||||
fc.isDone = true
|
||||
}
|
||||
|
||||
// validate checks if the request should be outright rejected
|
||||
func (fc *frankenPHPContext) validate() bool {
|
||||
if !strings.Contains(fc.request.URL.Path, "\x00") {
|
||||
return true
|
||||
}
|
||||
|
||||
fc.rejectBadRequest("Invalid request path")
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (fc *frankenPHPContext) clientHasClosed() bool {
|
||||
select {
|
||||
case <-fc.request.Context().Done():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// reject sends a response with the given status code and message
|
||||
func (fc *frankenPHPContext) reject(statusCode int, message string) {
|
||||
if fc.isDone {
|
||||
return
|
||||
}
|
||||
|
||||
rw := fc.responseWriter
|
||||
if rw != nil {
|
||||
rw.WriteHeader(statusCode)
|
||||
_, _ = rw.Write([]byte(message))
|
||||
|
||||
if f, ok := rw.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
fc.closeContext()
|
||||
}
|
||||
|
||||
func (fc *frankenPHPContext) rejectBadRequest(message string) {
|
||||
fc.reject(http.StatusBadRequest, message)
|
||||
}
|
||||
46
debugstate.go
Normal file
46
debugstate.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package frankenphp
|
||||
|
||||
// EXPERIMENTAL: ThreadDebugState prints the state of a single PHP thread - debugging purposes only
|
||||
type ThreadDebugState struct {
|
||||
Index int
|
||||
Name string
|
||||
State string
|
||||
IsWaiting bool
|
||||
IsBusy bool
|
||||
WaitingSinceMilliseconds int64
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: FrankenPHPDebugState prints the state of all PHP threads - debugging purposes only
|
||||
type FrankenPHPDebugState struct {
|
||||
ThreadDebugStates []ThreadDebugState
|
||||
ReservedThreadCount int
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: DebugState prints the state of all PHP threads - debugging purposes only
|
||||
func DebugState() FrankenPHPDebugState {
|
||||
fullState := FrankenPHPDebugState{
|
||||
ThreadDebugStates: make([]ThreadDebugState, 0, len(phpThreads)),
|
||||
ReservedThreadCount: 0,
|
||||
}
|
||||
for _, thread := range phpThreads {
|
||||
if thread.state.is(stateReserved) {
|
||||
fullState.ReservedThreadCount++
|
||||
continue
|
||||
}
|
||||
fullState.ThreadDebugStates = append(fullState.ThreadDebugStates, threadDebugState(thread))
|
||||
}
|
||||
|
||||
return fullState
|
||||
}
|
||||
|
||||
// threadDebugState creates a small jsonable status message for debugging purposes
|
||||
func threadDebugState(thread *phpThread) ThreadDebugState {
|
||||
return ThreadDebugState{
|
||||
Index: thread.threadIndex,
|
||||
Name: thread.name(),
|
||||
State: thread.state.name(),
|
||||
IsWaiting: thread.state.isInWaitingState(),
|
||||
IsBusy: !thread.state.isInWaitingState(),
|
||||
WaitingSinceMilliseconds: thread.state.waitTime(),
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM golang:1.22-alpine
|
||||
#checkov:skip=CKV_DOCKER_2
|
||||
#checkov:skip=CKV_DOCKER_3
|
||||
FROM golang:1.24-alpine
|
||||
|
||||
ENV GOTOOLCHAIN=local
|
||||
ENV CFLAGS="-ggdb3"
|
||||
ENV PHPIZE_DEPS \
|
||||
ENV PHPIZE_DEPS="\
|
||||
autoconf \
|
||||
dpkg-dev \
|
||||
file \
|
||||
@@ -11,7 +14,9 @@ ENV PHPIZE_DEPS \
|
||||
libc-dev \
|
||||
make \
|
||||
pkgconfig \
|
||||
re2c
|
||||
re2c"
|
||||
|
||||
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||
|
||||
RUN apk add --no-cache \
|
||||
$PHPIZE_DEPS \
|
||||
@@ -27,9 +32,13 @@ RUN apk add --no-cache \
|
||||
zlib-dev \
|
||||
bison \
|
||||
nss-tools \
|
||||
# file watcher
|
||||
libstdc++ \
|
||||
linux-headers \
|
||||
# Dev tools \
|
||||
git \
|
||||
clang \
|
||||
cmake \
|
||||
llvm \
|
||||
gdb \
|
||||
valgrind \
|
||||
@@ -39,23 +48,33 @@ RUN apk add --no-cache \
|
||||
echo 'set auto-load safe-path /' > /root/.gdbinit
|
||||
|
||||
WORKDIR /usr/local/src/php
|
||||
RUN git clone --branch=PHP-8.3 https://github.com/php/php-src.git . && \
|
||||
RUN git clone --branch=PHP-8.4 https://github.com/php/php-src.git . && \
|
||||
# --enable-embed is only necessary to generate libphp.so, we don't use this SAPI directly
|
||||
./buildconf --force && \
|
||||
./configure \
|
||||
EXTENSION_DIR=/usr/lib/frankenphp/modules ./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--enable-zend-max-execution-timers \
|
||||
--with-config-file-path=/etc/frankenphp/php.ini \
|
||||
--with-config-file-scan-dir=/etc/frankenphp/php.d \
|
||||
--enable-debug && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
ldconfig /etc/ld.so.conf.d && \
|
||||
cp php.ini-development /usr/local/lib/php.ini && \
|
||||
echo "zend_extension=opcache.so" >> /usr/local/lib/php.ini && \
|
||||
echo "opcache.enable=1" >> /usr/local/lib/php.ini && \
|
||||
mkdir -p /etc/frankenphp/php.d && \
|
||||
cp php.ini-development /etc/frankenphp/php.ini && \
|
||||
echo "zend_extension=opcache.so" >> /etc/frankenphp/php.ini && \
|
||||
echo "opcache.enable=1" >> /etc/frankenphp/php.ini && \
|
||||
php --version
|
||||
|
||||
# Install e-dant/watcher (necessary for file watching)
|
||||
WORKDIR /usr/local/src/watcher
|
||||
RUN git clone https://github.com/e-dant/watcher . && \
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build build/ && \
|
||||
cmake --install build
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY . .
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM golang:1.22
|
||||
#checkov:skip=CKV_DOCKER_2
|
||||
#checkov:skip=CKV_DOCKER_3
|
||||
FROM golang:1.24
|
||||
|
||||
ENV GOTOOLCHAIN=local
|
||||
ENV CFLAGS="-ggdb3"
|
||||
ENV PHPIZE_DEPS \
|
||||
ENV PHPIZE_DEPS="\
|
||||
autoconf \
|
||||
dpkg-dev \
|
||||
file \
|
||||
@@ -11,7 +14,9 @@ ENV PHPIZE_DEPS \
|
||||
libc-dev \
|
||||
make \
|
||||
pkg-config \
|
||||
re2c
|
||||
re2c"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# hadolint ignore=DL3009
|
||||
RUN apt-get update && \
|
||||
@@ -32,6 +37,7 @@ RUN apt-get update && \
|
||||
# Dev tools \
|
||||
git \
|
||||
clang \
|
||||
cmake \
|
||||
llvm \
|
||||
gdb \
|
||||
valgrind \
|
||||
@@ -41,31 +47,43 @@ RUN apt-get update && \
|
||||
echo 'set auto-load safe-path /' > /root/.gdbinit && \
|
||||
echo '* soft core unlimited' >> /etc/security/limits.conf \
|
||||
&& \
|
||||
apt-get clean
|
||||
apt-get clean
|
||||
|
||||
WORKDIR /usr/local/src/php
|
||||
RUN git clone --branch=PHP-8.3 https://github.com/php/php-src.git . && \
|
||||
RUN git clone --branch=PHP-8.4 https://github.com/php/php-src.git . && \
|
||||
# --enable-embed is only necessary to generate libphp.so, we don't use this SAPI directly
|
||||
./buildconf --force && \
|
||||
./configure \
|
||||
EXTENSION_DIR=/usr/lib/frankenphp/modules ./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--enable-zend-max-execution-timers \
|
||||
--with-config-file-path=/etc/frankenphp/php.ini \
|
||||
--with-config-file-scan-dir=/etc/frankenphp/php.d \
|
||||
--enable-debug && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
ldconfig && \
|
||||
cp php.ini-development /usr/local/lib/php.ini && \
|
||||
echo "zend_extension=opcache.so" >> /usr/local/lib/php.ini && \
|
||||
echo "opcache.enable=1" >> /usr/local/lib/php.ini && \
|
||||
mkdir -p /etc/frankenphp/php.d && \
|
||||
cp php.ini-development /etc/frankenphp/php.ini && \
|
||||
echo "zend_extension=opcache.so" >> /etc/frankenphp/php.ini && \
|
||||
echo "opcache.enable=1" >> /etc/frankenphp/php.ini && \
|
||||
php --version
|
||||
|
||||
# Install e-dant/watcher (necessary for file watching)
|
||||
WORKDIR /usr/local/src/watcher
|
||||
RUN git clone https://github.com/e-dant/watcher . && \
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build build/ && \
|
||||
cmake --install build && \
|
||||
cp build/libwatcher-c.so /usr/local/lib/libwatcher-c.so && \
|
||||
ldconfig
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY . .
|
||||
|
||||
WORKDIR /go/src/app/caddy/frankenphp
|
||||
RUN go build
|
||||
RUN go build -buildvcs=false -tags 'nobadger,nomysql,nopgx'
|
||||
|
||||
WORKDIR /go/src/app
|
||||
CMD [ "zsh" ]
|
||||
|
||||
@@ -7,17 +7,17 @@ variable "VERSION" {
|
||||
}
|
||||
|
||||
variable "PHP_VERSION" {
|
||||
default = "8.2,8.3"
|
||||
default = "8.2,8.3,8.4"
|
||||
}
|
||||
|
||||
variable "GO_VERSION" {
|
||||
default = "1.22"
|
||||
default = "1.24"
|
||||
}
|
||||
|
||||
variable "SHA" {}
|
||||
|
||||
variable "LATEST" {
|
||||
default = false
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "CACHE" {
|
||||
@@ -25,20 +25,20 @@ variable "CACHE" {
|
||||
}
|
||||
|
||||
variable DEFAULT_PHP_VERSION {
|
||||
default = "8.3"
|
||||
default = "8.4"
|
||||
}
|
||||
|
||||
function "tag" {
|
||||
params = [version, os, php-version, tgt]
|
||||
result = [
|
||||
version != "" ? format("%s:%s%s-php%s-%s", IMAGE_NAME, version, tgt == "builder" ? "-builder" : "", php-version, os) : "",
|
||||
php-version == DEFAULT_PHP_VERSION && os == "bookworm" && version != "" ? format("%s:%s%s", IMAGE_NAME, version, tgt == "builder" ? "-builder" : "") : "",
|
||||
php-version == DEFAULT_PHP_VERSION && version != "" ? format("%s:%s%s-%s", IMAGE_NAME, version, tgt == "builder" ? "-builder" : "", os) : "",
|
||||
php-version == DEFAULT_PHP_VERSION && version == "latest" ? format("%s:%s%s", IMAGE_NAME, os, tgt == "builder" ? "-builder" : "") : "",
|
||||
os == "bookworm" && version != "" ? format("%s:%s%s-php%s", IMAGE_NAME, version, tgt == "builder" ? "-builder" : "", php-version) : "",
|
||||
version == "" ? "" : "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}-php${php-version}-${os}", "latest-")}",
|
||||
php-version == DEFAULT_PHP_VERSION && os == "bookworm" && version != "" ? "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}", "latest-")}" : "",
|
||||
php-version == DEFAULT_PHP_VERSION && version != "" ? "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}-${os}", "latest-")}" : "",
|
||||
os == "bookworm" && version != "" ? "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}-php${php-version}", "latest-")}" : "",
|
||||
]
|
||||
}
|
||||
|
||||
# cleanTag ensures that the tag is a valid Docker tag
|
||||
# cleanTag ensures that the tag is a valid Docker tag
|
||||
# see https://github.com/distribution/distribution/blob/v2.8.2/reference/regexp.go#L37
|
||||
function "clean_tag" {
|
||||
@@ -60,7 +60,7 @@ function "_semver" {
|
||||
|
||||
function "__semver" {
|
||||
params = [v]
|
||||
result = v == {} ? [clean_tag(VERSION)] : v.prerelease == null ? ["latest", v.major, "${v.major}.${v.minor}", "${v.major}.${v.minor}.${v.patch}"] : ["${v.major}.${v.minor}.${v.patch}-${v.prerelease}"]
|
||||
result = v == {} ? [clean_tag(VERSION)] : v.prerelease == null ? [v.major, "${v.major}.${v.minor}", "${v.major}.${v.minor}.${v.patch}"] : ["${v.major}.${v.minor}.${v.patch}-${v.prerelease}"]
|
||||
}
|
||||
|
||||
function "php_version" {
|
||||
@@ -91,7 +91,8 @@ target "default" {
|
||||
platforms = os == "alpine" ? [
|
||||
"linux/amd64",
|
||||
"linux/386",
|
||||
"linux/arm/v6",
|
||||
# FIXME: armv6 doesn't build in GitHub actions because we use a custom Go build
|
||||
#"linux/arm/v6",
|
||||
"linux/arm/v7",
|
||||
"linux/arm64",
|
||||
] : [
|
||||
@@ -103,8 +104,8 @@ target "default" {
|
||||
tags = distinct(flatten(
|
||||
[for pv in php_version(php-version) : flatten([
|
||||
LATEST ? tag("latest", os, pv, tgt) : [],
|
||||
tag(SHA == "" ? "" : "sha-${substr(SHA, 0, 7)}", os, pv, tgt),
|
||||
[for v in semver(VERSION) : tag(v, os, pv, tgt)]
|
||||
tag(SHA == "" || VERSION != "dev" ? "" : "sha-${substr(SHA, 0, 7)}", os, pv, tgt),
|
||||
VERSION == "dev" ? [] : [for v in semver(VERSION) : tag(v, os, pv, tgt)]
|
||||
])
|
||||
]))
|
||||
labels = {
|
||||
@@ -117,20 +118,20 @@ target "default" {
|
||||
}
|
||||
}
|
||||
|
||||
target "static-builder" {
|
||||
target "static-builder-musl" {
|
||||
contexts = {
|
||||
golang-base = "docker-image://golang:${GO_VERSION}-alpine"
|
||||
}
|
||||
dockerfile = "static-builder.Dockerfile"
|
||||
dockerfile = "static-builder-musl.Dockerfile"
|
||||
context = "./"
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
]
|
||||
tags = distinct(flatten([
|
||||
LATEST ? "${IMAGE_NAME}:static-builder" : "",
|
||||
SHA == "" ? "" : "${IMAGE_NAME}:static-builder-sha-${substr(SHA, 0, 7)}",
|
||||
[for v in semver(VERSION) : v == "latest" ? "${IMAGE_NAME}:static-builder": "${IMAGE_NAME}:static-builder-${v}"]
|
||||
LATEST ? "${IMAGE_NAME}:static-builder-musl" : "",
|
||||
SHA == "" || VERSION != "dev" ? "" : "${IMAGE_NAME}:static-builder-musl-sha-${substr(SHA, 0, 7)}",
|
||||
VERSION == "dev" ? [] : [for v in semver(VERSION) : "${IMAGE_NAME}:static-builder-musl-${v}"]
|
||||
]))
|
||||
labels = {
|
||||
"org.opencontainers.image.created" = "${timestamp()}"
|
||||
@@ -142,3 +143,27 @@ target "static-builder" {
|
||||
}
|
||||
secret = ["id=github-token,env=GITHUB_TOKEN"]
|
||||
}
|
||||
|
||||
target "static-builder-gnu" {
|
||||
dockerfile = "static-builder-gnu.Dockerfile"
|
||||
context = "./"
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64"
|
||||
]
|
||||
tags = distinct(flatten([
|
||||
LATEST ? "${IMAGE_NAME}:static-builder-gnu" : "",
|
||||
SHA == "" || VERSION != "dev" ? "" : "${IMAGE_NAME}:static-builder-gnu-sha-${substr(SHA, 0, 7)}",
|
||||
VERSION == "dev" ? [] : [for v in semver(VERSION) : "${IMAGE_NAME}:static-builder-gnu-${v}"]
|
||||
]))
|
||||
labels = {
|
||||
"org.opencontainers.image.created" = "${timestamp()}"
|
||||
"org.opencontainers.image.version" = VERSION
|
||||
"org.opencontainers.image.revision" = SHA
|
||||
}
|
||||
args = {
|
||||
FRANKENPHP_VERSION = VERSION
|
||||
GO_VERSION = GO_VERSION
|
||||
}
|
||||
secret = ["id=github-token,env=GITHUB_TOKEN"]
|
||||
}
|
||||
|
||||
11
docs/classic.md
Normal file
11
docs/classic.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Using Classic Mode
|
||||
|
||||
Without any additional configuration, FrankenPHP operates in classic mode. In this mode, FrankenPHP functions like a traditional PHP server, directly serving PHP files. This makes it a seamless drop-in replacement for PHP-FPM or Apache with mod_php.
|
||||
|
||||
Similar to Caddy, FrankenPHP accepts an unlimited number of connections and uses a [fixed number of threads](config.md#caddyfile-config) to serve them. The number of accepted and queued connections is limited only by the available system resources.
|
||||
The PHP thread pool operates with a fixed number of threads initialized at startup, comparable to the static mode of PHP-FPM. It's also possible to let threads [scale automatically at runtime](performance.md#max_threads), similar to the dynamic mode of PHP-FPM.
|
||||
|
||||
Queued connections will wait indefinitely until a PHP thread is available to serve them. To prevent that, you can use the `max_wait_time` [configuration](config.md#caddyfile-config) to limit how long a request may wait for a free PHP thread before being rejected.
|
||||
Additionally, you can set a reasonable [write timeout in Caddy](https://caddyserver.com/docs/caddyfile/options#timeouts).
|
||||
|
||||
Each Caddy instance will only spin up one FrankenPHP thread pool, which will be shared across all `php_server` blocks.
|
||||
@@ -11,15 +11,18 @@ 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
|
||||
```
|
||||
|
||||
该镜像包含常用的开发工具(Go、GDB、Valgrind、Neovim等)。
|
||||
该镜像包含常用的开发工具(Go、GDB、Valgrind、Neovim等)并使用以下 php 设置位置
|
||||
|
||||
如果 docker 版本低于 23.0,则会因为 dockerignore [pattern issue](https://github.com/moby/moby/pull/42676) 而导致构建失败。将目录添加到 `.dockerignore`。
|
||||
- php.ini: `/etc/frankenphp/php.ini` 默认提供了一个带有开发预设的 php.ini 文件。
|
||||
- 附加配置文件: `/etc/frankenphp/php.d/*.ini`
|
||||
- php 扩展: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
如果您的 docker 版本低于 23.0,则会因为 dockerignore [pattern issue](https://github.com/moby/moby/pull/42676) 而导致构建失败。将目录添加到 `.dockerignore`。
|
||||
|
||||
```patch
|
||||
!testdata/*.php
|
||||
!testdata/*.txt
|
||||
+!caddy
|
||||
+!C-Thread-Pool
|
||||
+!internal
|
||||
```
|
||||
|
||||
@@ -30,7 +33,7 @@ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 -
|
||||
## 运行测试套件
|
||||
|
||||
```console
|
||||
go test -race -v ./...
|
||||
go test -tags watcher -race -v ./...
|
||||
```
|
||||
|
||||
## Caddy 模块
|
||||
@@ -115,11 +118,11 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
--set static-builder.args.DEBUG_SYMBOLS=1 \
|
||||
--set "static-builder.platform=linux/amd64" \
|
||||
static-builder
|
||||
docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
|
||||
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
|
||||
```
|
||||
|
||||
2. 将当前版本的 `frankenphp` 替换为 debug FrankenPHP 可执行文件
|
||||
3. 照常启动 FrankenPHP(或者,你可以直接使用 GDB 启动 FrankenPHP: `gdb --args ./frankenphp run`)
|
||||
3. 照常启动 FrankenPHP(或者,你可以直接使用 GDB 启动 FrankenPHP: `gdb --args frankenphp run`)
|
||||
4. 使用 GDB 附加到进程:
|
||||
|
||||
```console
|
||||
@@ -172,28 +175,28 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
8. 在容器中,可以使用 GDB 和以下:
|
||||
|
||||
```console
|
||||
go test -c -ldflags=-w
|
||||
gdb --args ./frankenphp.test -test.run ^MyTest$
|
||||
go test -tags watcher -c -ldflags=-w
|
||||
gdb --args frankenphp.dev.test -test.run ^MyTest$
|
||||
```
|
||||
|
||||
9. 当错误修复后,恢复所有这些更改
|
||||
|
||||
## 其他开发资源
|
||||
|
||||
* [PHP 嵌入 uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
* [PHP 嵌入 NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
* [PHP 嵌入 Go (go-php)](https://github.com/deuill/go-php)
|
||||
* [PHP 嵌入 Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
* [PHP 嵌入 C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
* [扩展和嵌入 PHP 作者:Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
* [TSRMLS_CC到底是什么?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
* [Mac 上的 PHP 嵌入](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4)
|
||||
* [SDL 绑定](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
- [PHP 嵌入 uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
- [PHP 嵌入 NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
- [PHP 嵌入 Go (go-php)](https://github.com/deuill/go-php)
|
||||
- [PHP 嵌入 Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
- [PHP 嵌入 C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
- [扩展和嵌入 PHP 作者:Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
- [TSRMLS_CC到底是什么?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
- [Mac 上的 PHP 嵌入](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4)
|
||||
- [SDL 绑定](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
|
||||
## Docker 相关资源
|
||||
|
||||
* [Bake 文件定义](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
* [docker buildx 构建](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
- [Bake 文件定义](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
- [docker buildx 构建](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
|
||||
## 有用的命令
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# FrankenPHP: 适用于 PHP 的现代应用服务器
|
||||
|
||||
<h1 align="center"><a href="https://frankenphp.dev"><img src="frankenphp.png" alt="FrankenPHP" width="600"></a></h1>
|
||||
<h1 align="center"><a href="https://frankenphp.dev"><img src="../../frankenphp.png" alt="FrankenPHP" width="600"></a></h1>
|
||||
|
||||
FrankenPHP 是建立在 [Caddy](https://caddyserver.com/) Web 服务器之上的现代 PHP 应用程序服务器。
|
||||
|
||||
@@ -34,7 +34,7 @@ docker run -v $PWD:/app/public \
|
||||
### 独立二进制
|
||||
|
||||
如果您不想使用 Docker,我们为 Linux 和 macOS 提供独立的 FrankenPHP 二进制文件
|
||||
,其中包含 [PHP 8.3](https://www.php.net/releases/8.3/en.php) 和最流行的 PHP 扩展:[下载 FrankenPHP](https://github.com/dunglas/frankenphp/releases)。
|
||||
,其中包含 [PHP 8.4](https://www.php.net/releases/8.4/en.php) 和最流行的 PHP 扩展:[下载 FrankenPHP](https://github.com/dunglas/frankenphp/releases)。
|
||||
|
||||
若要启动当前目录的内容,请运行:
|
||||
|
||||
@@ -71,7 +71,8 @@ docker run -v $PWD:/app/public \
|
||||
* [API Platform](https://api-platform.com/docs/distribution/)
|
||||
* [Laravel](laravel.md)
|
||||
* [Sulu](https://sulu.io/blog/running-sulu-with-frankenphp)
|
||||
* [WordPress](https://github.com/dunglas/frankenphp-wordpress)
|
||||
* [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)
|
||||
|
||||
@@ -16,7 +16,9 @@ tar xf php-*
|
||||
cd php-*/
|
||||
```
|
||||
|
||||
然后,为您的平台配置 PHP:
|
||||
然后,为您的平台配置 PHP.
|
||||
|
||||
这些参数是必需的,但你也可以添加其他编译参数(例如额外的扩展)。
|
||||
|
||||
### Linux
|
||||
|
||||
@@ -28,13 +30,6 @@ cd php-*/
|
||||
--enable-zend-max-execution-timers
|
||||
```
|
||||
|
||||
最后,编译并安装 PHP:
|
||||
|
||||
```console
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
```
|
||||
|
||||
### Mac
|
||||
|
||||
使用 [Homebrew](https://brew.sh/) 包管理器安装 `libiconv`、`bison`、`re2c` 和 `pkg-config`:
|
||||
@@ -57,12 +52,12 @@ echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
|
||||
--with-iconv=/opt/homebrew/opt/libiconv/
|
||||
```
|
||||
|
||||
这些参数是必需的,但你也可以添加其他编译参数(例如额外的扩展)。
|
||||
## 编译并安装 PHP
|
||||
|
||||
最后,编译并安装 PHP:
|
||||
|
||||
```console
|
||||
make -j$(sysctl -n hw.logicalcpu)
|
||||
make -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
sudo make install
|
||||
```
|
||||
|
||||
@@ -71,7 +66,7 @@ sudo make install
|
||||
您现在可以使用 Go 库并编译我们的 Caddy 构建:
|
||||
|
||||
```console
|
||||
curl -L https://github.com/dunglas/frankenphp/archive/refs/heads/main.tar.gz | tar x
|
||||
curl -L https://github.com/dunglas/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
|
||||
```
|
||||
@@ -85,7 +80,8 @@ CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags '-w -s'" \
|
||||
xcaddy build \
|
||||
--output frankenphp \
|
||||
--with github.com/dunglas/frankenphp/caddy \
|
||||
--with frankenphp.dev/caddy \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Add extra Caddy modules here
|
||||
|
||||
@@ -1,37 +1,49 @@
|
||||
# 配置
|
||||
# 配置
|
||||
|
||||
FrankenPHP,Caddy 以及 Mercure 和 Vulcain 模块可以使用 [Caddy 支持的格式](https://caddyserver.com/docs/getting-started#your-first-config) 进行配置。
|
||||
|
||||
在 Docker 镜像中,`Caddyfile` 位于 `/etc/caddy/Caddyfile`。
|
||||
在[Docker 映像](docker.md) 中,`Caddyfile` 位于 `/etc/frankenphp/Caddyfile`。
|
||||
静态二进制文件会在启动时所在的目录中查找 `Caddyfile`。
|
||||
PHP 本身可以[使用 `php.ini` 文件](https://www.php.net/manual/zh/configuration.file.php)进行配置。
|
||||
PHP 解释器将在以下位置查找:
|
||||
|
||||
您也可以像往常一样使用 `php.ini` 配置 PHP。
|
||||
Docker:
|
||||
|
||||
在 Docker 镜像中,默认不存在 `php.ini`,您可以手动创建它或从官方模板中复制。
|
||||
- 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 项目提供的官方模板:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# 开发:
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
|
||||
# 生产:
|
||||
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
|
||||
```
|
||||
|
||||
FrankenPHP 安装 (.rpm 或 .deb):
|
||||
|
||||
- php.ini: `/etc/frankenphp/php.ini` 默认情况下提供带有生产预设的 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 源代码](https://github.com/php/php-src/)中提供的`php.ini-production`或`php.ini-development`中的一个。
|
||||
|
||||
## Caddyfile 配置
|
||||
|
||||
要注册 FrankenPHP 执行器,必须设置 `frankenphp` [全局选项](https://caddyserver.com/docs/caddyfile/concepts#global-options),然后可以在站点块中使用 `php_server` 或 `php` [HTTP 指令](https://caddyserver.com/docs/caddyfile/concepts#directives) 来为您的 PHP 应用程序提供服务。
|
||||
可以在站点块中使用 `php_server` 或 `php` [HTTP 指令](https://caddyserver.com/docs/caddyfile/concepts#directives) 来为您的 PHP 应用程序提供服务。
|
||||
|
||||
最小示例:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
# 启用 FrankenPHP
|
||||
frankenphp
|
||||
# 配置何时必须执行指令
|
||||
order php_server before file_server
|
||||
}
|
||||
|
||||
localhost {
|
||||
# 启用压缩(可选)
|
||||
encode zstd br gzip
|
||||
@@ -40,7 +52,8 @@ localhost {
|
||||
}
|
||||
```
|
||||
|
||||
或者,可以在全局选项下指定要创建的线程数和要从服务器启动的 [worker 脚本](worker.md)。
|
||||
您也可以使用全局选项显式配置 FrankenPHP:
|
||||
`frankenphp` [全局选项](https://caddyserver.com/docs/caddyfile/concepts#global-options) 可用于配置 FrankenPHP。
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
@@ -72,21 +85,18 @@ localhost {
|
||||
如果在同一服务器上运行多个应用,还可以定义多个 worker:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker /path/to/app/public/index.php <num>
|
||||
worker /path/to/other/public/index.php <num>
|
||||
app.example.com {
|
||||
php_server {
|
||||
root /path/to/app/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
app.example.com {
|
||||
root * /path/to/app/public
|
||||
php_server
|
||||
}
|
||||
|
||||
other.example.com {
|
||||
root * /path/to/other/public
|
||||
php_server
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
# ...
|
||||
```
|
||||
@@ -122,9 +132,18 @@ route {
|
||||
```caddyfile
|
||||
php_server [<matcher>] {
|
||||
root <directory> # 设置站点的根目录。默认值:`root` 指令。
|
||||
split_path <delim...> # 设置用于将 URI 拆分为两部分的子字符串。第一个匹配的子字符串将用于从路径中拆分“路径信息”。第一个部分以匹配的子字符串为后缀,并将假定为实际资源(CGI 脚本)名称。第二部分将设置为PATH_INFO,供脚本使用。默认值:`.php`
|
||||
split_path <delim...> # 设置用于将 URI 拆分为两部分的子字符串。第一个匹配的子字符串将用于从路径中拆分"路径信息"。第一个部分以匹配的子字符串为后缀,并将假定为实际资源(CGI 脚本)名称。第二部分将设置为PATH_INFO,供脚本使用。默认值:`.php`
|
||||
resolve_root_symlink false # 禁用将 `root` 目录在符号链接时将其解析为实际值(默认启用)。
|
||||
env <key> <value> # 设置额外的环境变量,可以设置多个环境变量。
|
||||
file_server off # 禁用内置的 file_server 指令。
|
||||
worker { # 创建特定于此服务器的 worker。可以为多个 worker 多次指定。
|
||||
file <path> # 设置 worker 脚本的路径,可以相对于 php_server 根目录
|
||||
num <num> # 设置要启动的 PHP 线程数,默认为可用 CPU 数的 2 倍
|
||||
name <name> # 为 worker 设置名称,用于日志和指标。默认值:worker 文件的绝对路径。在 php_server 块中定义时始终以 m# 开头。
|
||||
watch <path> # 设置要监视文件更改的路径。可以为多个路径多次指定。
|
||||
env <key> <value> # 将额外的环境变量设置为给定值。可以为多个环境变量多次指定。此 worker 的环境变量也从 php_server 父级继承,但可以在此处覆盖。
|
||||
}
|
||||
worker <other_file> <num> # 也可以像在全局 frankenphp 块中一样使用简短形式。
|
||||
}
|
||||
```
|
||||
|
||||
@@ -132,9 +151,9 @@ php_server [<matcher>] {
|
||||
|
||||
以下环境变量可用于在 `Caddyfile` 中注入 Caddy 指令,而无需对其进行修改:
|
||||
|
||||
* `SERVER_NAME`: 更改 [要监听的地址](https://caddyserver.com/docs/caddyfile/concepts#addresses),提供的主机名也将用于生成的 TLS 证书
|
||||
* `CADDY_GLOBAL_OPTIONS`: 注入 [全局选项](https://caddyserver.com/docs/caddyfile/options)
|
||||
* `FRANKENPHP_CONFIG`: 在 `frankenphp` 指令下注入配置
|
||||
- `SERVER_NAME`: 更改 [要监听的地址](https://caddyserver.com/docs/caddyfile/concepts#addresses),提供的主机名也将用于生成的 TLS 证书
|
||||
- `CADDY_GLOBAL_OPTIONS`: 注入 [全局选项](https://caddyserver.com/docs/caddyfile/options)
|
||||
- `FRANKENPHP_CONFIG`: 在 `frankenphp` 指令下注入配置
|
||||
|
||||
## PHP 配置
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 构建自定义 Docker 镜像
|
||||
|
||||
[FrankenPHP Docker 镜像](https://hub.docker.com/r/dunglas/frankenphp) 基于 [官方 PHP 镜像](https://hub.docker.com/_/php/)。
|
||||
Alpine Linux 和 Debian 衍生版适用于常见的处理器架构,且支持 PHP 8.2 和 PHP 8.3。[查看 Tags](https://hub.docker.com/r/dunglas/frankenphp/tags)。
|
||||
Alpine Linux 和 Debian 衍生版适用于常见的处理器架构,支持 PHP 8.2、8.3 和 8.4。。[查看 Tags](https://hub.docker.com/r/dunglas/frankenphp/tags)。
|
||||
|
||||
## 如何使用镜像
|
||||
|
||||
@@ -30,11 +30,11 @@ FROM dunglas/frankenphp
|
||||
|
||||
# 在此处添加其他扩展:
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
```
|
||||
|
||||
## 如何安装更多 Caddy 模块
|
||||
@@ -44,21 +44,26 @@ FrankenPHP 建立在 Caddy 之上,所有 [Caddy 模块](https://caddyserver.co
|
||||
安装自定义 Caddy 模块的最简单方法是使用 [xcaddy](https://github.com/caddyserver/xcaddy):
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp:latest-builder AS builder
|
||||
FROM dunglas/frankenphp:builder AS builder
|
||||
|
||||
# 在构建器镜像中复制 xcaddy
|
||||
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
|
||||
|
||||
# 必须启用 CGO 才能构建 FrankenPHP
|
||||
ENV CGO_ENABLED=1 XCADDY_SETCAP=1 XCADDY_GO_BUILD_FLAGS="-ldflags '-w -s'"
|
||||
RUN xcaddy build \
|
||||
--output /usr/local/bin/frankenphp \
|
||||
--with github.com/dunglas/frankenphp=./ \
|
||||
--with github.com/dunglas/frankenphp/caddy=./caddy/ \
|
||||
# Mercure 和 Vulcain 包含在官方版本中,如果不需要你可以删除它们
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# 在此处添加额外的 Caddy 模块
|
||||
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 frankenphp.dev=./ \
|
||||
--with frankenphp.dev/caddy=./caddy/ \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
# Mercure 和 Vulcain 包含在官方版本中,如果不需要你可以删除它们
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# 在此处添加额外的 Caddy 模块
|
||||
|
||||
FROM dunglas/frankenphp AS runner
|
||||
|
||||
@@ -136,15 +141,15 @@ FrankenPHP 可以在 Docker 中以非 root 用户身份运行。
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=www-data
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# 在基于 alpine 的发行版使用 "adduser -D ${USER}"
|
||||
useradd -D ${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;
|
||||
# 在基于 alpine 的发行版使用 "adduser -D ${USER}"
|
||||
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;
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
@@ -1,41 +1,11 @@
|
||||
# 已知问题
|
||||
|
||||
## Fibers
|
||||
|
||||
在 [Fibers](https://www.php.net/manual/en/language.fibers.php) 中调用 PHP 的函数和代码等语言结构,这些结构内部再调用 [cgo](https://go.dev/blog/cgo) 会导致崩溃。
|
||||
|
||||
这个问题 [正在由 Go 项目处理](https://github.com/golang/go/issues/62130)。
|
||||
|
||||
一种解决方案是不要使用从 Fibers 内部委托给 Go 的构造(如 `echo`)和函数(如 `header()`)。
|
||||
|
||||
下面的代码可能会崩溃,因为它在 Fiber 中使用了 `echo`:
|
||||
|
||||
```php
|
||||
$fiber = new Fiber(function() {
|
||||
echo 'In the Fiber'.PHP_EOL;
|
||||
echo 'Still inside'.PHP_EOL;
|
||||
});
|
||||
$fiber->start();
|
||||
```
|
||||
|
||||
相反,请从 Fiber 返回值并在外部使用它:
|
||||
|
||||
```php
|
||||
$fiber = new Fiber(function() {
|
||||
Fiber::suspend('In the Fiber'.PHP_EOL));
|
||||
Fiber::suspend('Still inside'.PHP_EOL));
|
||||
});
|
||||
echo $fiber->start();
|
||||
echo $fiber->resume();
|
||||
$fiber->resume();
|
||||
```
|
||||
|
||||
## 不支持的 PHP 扩展
|
||||
|
||||
已知以下扩展与 FrankenPHP 不兼容:
|
||||
|
||||
| 名称 | 原因 | 替代方案 |
|
||||
| ----------------------------------------------------------- | --------------- |----------------------------------------------------------------------------------------------------------------------|
|
||||
| 名称 | 原因 | 替代方案 |
|
||||
|-------------------------------------------------------------|-------|----------------------------------------------------------------------------------------------------------------------|
|
||||
| [imap](https://www.php.net/manual/en/imap.installation.php) | 非线程安全 | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
|
||||
|
||||
## get_browser
|
||||
|
||||
@@ -22,13 +22,12 @@ docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
order php_server before file_server
|
||||
}
|
||||
|
||||
# 服务器的域名
|
||||
localhost {
|
||||
# 将 webroot 设置为 public/ 目录
|
||||
root * public/
|
||||
root public/
|
||||
# 启用压缩(可选)
|
||||
encode zstd br gzip
|
||||
# 执行当前目录中的 PHP 文件并提供资产
|
||||
@@ -36,7 +35,7 @@ docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
|
||||
}
|
||||
```
|
||||
|
||||
3. 从 Laravel 项目的根目录启动 FrankenPHP:`./frankenphp run`
|
||||
3. 从 Laravel 项目的根目录启动 FrankenPHP:`frankenphp run`
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
@@ -52,13 +51,13 @@ composer require laravel/octane
|
||||
php artisan octane:install --server=frankenphp
|
||||
```
|
||||
|
||||
Octane 服务可以通过 `octane:start` Artisan 命令启动。
|
||||
Octane 服务可以通过 `octane:frankenphp` Artisan 命令启动。
|
||||
|
||||
```console
|
||||
php artisan octane:start
|
||||
php artisan octane:frankenphp
|
||||
```
|
||||
|
||||
`octane:start` 命令可以采用以下选项:
|
||||
`octane:frankenphp` 命令可以采用以下选项:
|
||||
|
||||
* `--host`: 服务器应绑定到的 IP 地址(默认值: `127.0.0.1`)
|
||||
* `--port`: 服务器应可用的端口(默认值: `8000`)
|
||||
|
||||
@@ -5,7 +5,7 @@ Mercure 允许将事件实时推送到所有连接的设备:它们将立即收
|
||||
|
||||
无需 JS 库或 SDK!
|
||||
|
||||

|
||||

|
||||
|
||||
要启用 Mercure Hub,请按照 [Mercure 网站](https://mercure.rocks/docs/hub/config) 中的说明更新 `Caddyfile`。
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ FrankenPHP 还支持 [将 PHP 应用程序嵌入到静态二进制文件中](emb
|
||||
|
||||
```console
|
||||
docker buildx bake --load static-builder
|
||||
docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder
|
||||
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
|
||||
```
|
||||
|
||||
生成的静态二进制文件名为 `frankenphp`,可在当前目录中找到。
|
||||
@@ -43,6 +43,24 @@ docker buildx bake \
|
||||
static-builder
|
||||
```
|
||||
|
||||
### 额外的 Caddy 模块
|
||||
|
||||
要向 [xcaddy](https://github.com/caddyserver/xcaddy) 添加额外的 Caddy 模块或传递其他参数,请使用 `XCADDY_ARGS` Docker 参数:
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.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
|
||||
```
|
||||
|
||||
在本例中,我们为 Caddy 添加了 [Souin](https://souin.io) HTTP 缓存模块,以及 [cbrotli](https://github.com/dunglas/caddy-cbrotli)、[Mercure](https://mercure.rocks) 和 [Vulcain](https://vulcain.rocks) 模块。
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> 如果 `XCADDY_ARGS` 为空或未设置,则默认包含 cbrotli、Mercure 和 Vulcain 模块。
|
||||
> 如果自定义了 `XCADDY_ARGS` 的值,则必须显式地包含它们。
|
||||
|
||||
参见:[自定义构建](#自定义构建)
|
||||
|
||||
### GitHub Token
|
||||
@@ -75,6 +93,7 @@ cd frankenphp
|
||||
* `PHP_VERSION`: 要使用的 PHP 版本
|
||||
* `PHP_EXTENSIONS`: 要构建的 PHP 扩展([支持的扩展列表](https://static-php.dev/zh/guide/extensions.html))
|
||||
* `PHP_EXTENSION_LIBS`: 要构建的额外库,为扩展添加额外的功能
|
||||
* `XCADDY_ARGS`:传递给 [xcaddy](https://github.com/caddyserver/xcaddy) 的参数,例如用于添加额外的 Caddy 模块
|
||||
* `EMBED`: 要嵌入二进制文件的 PHP 应用程序的路径
|
||||
* `CLEAN`: 设置后,libphp 及其所有依赖项都是重新构建的(不使用缓存)
|
||||
* `DEBUG_SYMBOLS`: 设置后,调试符号将被保留在二进制文件内
|
||||
|
||||
@@ -22,7 +22,7 @@ docker run \
|
||||
使用 `php-server` 命令的 `--worker` 选项, 执行命令使当前目录的内容使用 worker:
|
||||
|
||||
```console
|
||||
./frankenphp php-server --worker /path/to/your/worker/script.php
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php
|
||||
```
|
||||
|
||||
## Symfony Runtime
|
||||
@@ -68,11 +68,11 @@ $myApp->boot();
|
||||
|
||||
// 循环外的处理程序以获得更好的性能(减少工作量)
|
||||
$handler = static function () use ($myApp) {
|
||||
// 收到请求时调用
|
||||
// 超全局变量 php://input
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
// 收到请求时调用
|
||||
// 超全局变量 php://input
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
};
|
||||
for($nbRequests = 0, $running = true; isset($_SERVER['MAX_REQUESTS']) && ($nbRequests < ((int)$_SERVER['MAX_REQUESTS'])) && $running; ++$nbRequests) {
|
||||
for ($nbRequests = 0, $running = true; isset($_SERVER['MAX_REQUESTS']) && ($nbRequests < ((int)$_SERVER['MAX_REQUESTS'])) && $running; ++$nbRequests) {
|
||||
$running = \frankenphp_handle_request($handler);
|
||||
|
||||
// 发送 HTTP 响应后执行某些操作
|
||||
|
||||
@@ -1,24 +1,42 @@
|
||||
# Compile From Sources
|
||||
|
||||
This document explain how to create a FrankenPHP build that will load PHP as a dynamic library.
|
||||
This document explains how to create a FrankenPHP binary that will load PHP as a dynamic library.
|
||||
This is the recommended method.
|
||||
|
||||
Alternatively, [creating static builds](static.md) is also possible.
|
||||
Alternatively, [fully and mostly static builds](static.md) can also be created.
|
||||
|
||||
## Install PHP
|
||||
|
||||
FrankenPHP is compatible with the PHP 8.2 and superior.
|
||||
FrankenPHP is compatible with PHP 8.2 and superior.
|
||||
|
||||
First, [get the sources of PHP](https://www.php.net/downloads.php) and extract them:
|
||||
### With Homebrew (Linux and Mac)
|
||||
|
||||
The easiest way to install a version of libphp compatible with FrankenPHP is to use the ZTS packages provided by [Homebrew PHP](https://github.com/shivammathur/homebrew-php).
|
||||
|
||||
First, if not already done, install [Homebrew](https://brew.sh).
|
||||
|
||||
Then, install the ZTS variant of PHP, Brotli (optional, for compression support) and watcher (optional, for file change detection):
|
||||
|
||||
```console
|
||||
brew install shivammathur/php/php-zts brotli watcher
|
||||
brew link --overwrite --force shivammathur/php/php-zts
|
||||
```
|
||||
|
||||
### By Compiling PHP
|
||||
|
||||
Alternatively, you can compile PHP from sources with the options needed by FrankenPHP by following these steps.
|
||||
~~
|
||||
~~First, [get the PHP sources](https://www.php.net/downloads.php) and extract them:
|
||||
|
||||
```console
|
||||
tar xf php-*
|
||||
cd php-*/
|
||||
```
|
||||
|
||||
Then, configure PHP for your platform:
|
||||
Then, run the `configure` script with the options needed for your platform.
|
||||
The following `./configure` flags are mandatory, but you can add others, for example, to compile extensions or additional features.
|
||||
|
||||
### Linux
|
||||
#### Linux
|
||||
|
||||
```console
|
||||
./configure \
|
||||
@@ -28,20 +46,12 @@ Then, configure PHP for your platform:
|
||||
--enable-zend-max-execution-timers
|
||||
```
|
||||
|
||||
Finally, compile and install PHP:
|
||||
#### Mac
|
||||
|
||||
Use the [Homebrew](https://brew.sh/) package manager to install the required and optional dependencies:
|
||||
|
||||
```console
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
```
|
||||
|
||||
### Mac
|
||||
|
||||
Use the [Homebrew](https://brew.sh/) package manager to install
|
||||
`libiconv`, `bison`, `re2c` and `pkg-config`:
|
||||
|
||||
```console
|
||||
brew install libiconv bison re2c pkg-config
|
||||
brew install libiconv bison brotli re2c pkg-config watcher
|
||||
echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
|
||||
```
|
||||
|
||||
@@ -49,48 +59,51 @@ Then run the configure script:
|
||||
|
||||
```console
|
||||
./configure \
|
||||
--enable-embed=static \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--disable-opcache-jit \
|
||||
--enable-static \
|
||||
--enable-shared=no \
|
||||
--with-iconv=/opt/homebrew/opt/libiconv/
|
||||
```
|
||||
|
||||
These flags are required, but you can add other flags (e.g. extra extensions)
|
||||
if needed.
|
||||
#### Compile PHP
|
||||
|
||||
Finally, compile and install PHP:
|
||||
|
||||
```console
|
||||
make -j$(sysctl -n hw.logicalcpu)
|
||||
make -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## Install Optional Dependencies
|
||||
|
||||
Some FrankenPHP features depend on optional system dependencies that must be installed.
|
||||
Alternatively, these features can be disabled by passing build tags to the Go compiler.
|
||||
|
||||
| Feature | Dependency | Build tag to disable it |
|
||||
|--------------------------------|-----------------------------------------------------------------------|-------------------------|
|
||||
| Brotli compression | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| Restart workers on file change | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
|
||||
## Compile the Go App
|
||||
|
||||
You can now use the Go library and compile our Caddy build:
|
||||
|
||||
```console
|
||||
curl -L https://github.com/dunglas/frankenphp/archive/refs/heads/main.tar.gz | tar x
|
||||
cd frankenphp-main/caddy/frankenphp
|
||||
CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build
|
||||
```
|
||||
You can now build the final binary.
|
||||
|
||||
### Using xcaddy
|
||||
|
||||
Alternatively, use [xcaddy](https://github.com/caddyserver/xcaddy) to compile FrankenPHP with [custom Caddy modules](https://caddyserver.com/docs/modules/):
|
||||
The recommended way is to use [xcaddy](https://github.com/caddyserver/xcaddy) to compile FrankenPHP.
|
||||
`xcaddy` also allows to easily add [custom Caddy modules](https://caddyserver.com/docs/modules/) and FrankenPHP extensions:
|
||||
|
||||
```console
|
||||
CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags '-w -s'" \
|
||||
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 frankenphp.dev/caddy \
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Add extra Caddy modules here
|
||||
# Add extra Caddy modules and FrankenPHP extensions here
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
@@ -101,4 +114,14 @@ xcaddy build \
|
||||
>
|
||||
> To do so, change the `XCADDY_GO_BUILD_FLAGS` environment variable to something like
|
||||
> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'`
|
||||
> (change the value of the stack size according to your app needs).
|
||||
> (change the stack size value according to your app needs).
|
||||
|
||||
### Without xcaddy
|
||||
|
||||
Alternatively, it's possible to compile FrankenPHP without `xcaddy` by using the `go` command directly:
|
||||
|
||||
```console
|
||||
curl -L https://github.com/dunglas/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
|
||||
```
|
||||
|
||||
181
docs/config.md
181
docs/config.md
@@ -2,36 +2,51 @@
|
||||
|
||||
FrankenPHP, Caddy as well as the Mercure and Vulcain modules can be configured using [the formats supported by Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
|
||||
In the Docker image, the `Caddyfile` is located at `/etc/caddy/Caddyfile`.
|
||||
In [the Docker images](docker.md), the `Caddyfile` is located at `/etc/frankenphp/Caddyfile`.
|
||||
The static binary will also look for the `Caddyfile` in the directory where the `frankenphp run` command is executed.
|
||||
You can specify a custom path with the `-c` or `--config` option.
|
||||
|
||||
You can also configure PHP using `php.ini` as usual.
|
||||
PHP itself can be configured [using a `php.ini` file](https://www.php.net/manual/en/configuration.file.php).
|
||||
|
||||
In the Docker image, the `php.ini` file is not present, you can create it manually or copy an official template:
|
||||
Depending on your installation method, the PHP interpreter will look for configuration files in locations described above.
|
||||
|
||||
## Docker
|
||||
|
||||
- `php.ini`: `/usr/local/etc/php/php.ini` (no `php.ini` is provided by default)
|
||||
- additional configuration files: `/usr/local/etc/php/conf.d/*.ini`
|
||||
- PHP extensions: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
- You should copy an official template provided by the PHP project:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# Developement:
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
|
||||
# Or production:
|
||||
# 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 and Debian packages
|
||||
|
||||
- `php.ini`: `/etc/frankenphp/php.ini` (a `php.ini` file with production presets is provided by default)
|
||||
- additional configuration files: `/etc/frankenphp/php.d/*.ini`
|
||||
- PHP extensions: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
## Static binary
|
||||
|
||||
- `php.ini`: The directory in which `frankenphp run` or `frankenphp php-server` is executed, then `/etc/frankenphp/php.ini`
|
||||
- additional configuration files: `/etc/frankenphp/php.d/*.ini`
|
||||
- PHP extensions: cannot be loaded, bundle them in the binary itself
|
||||
- copy one of `php.ini-production` or `php.ini-development` provided [in the PHP sources](https://github.com/php/php-src/).
|
||||
|
||||
## Caddyfile Config
|
||||
|
||||
To register the FrankenPHP executor, the `frankenphp` [global option](https://caddyserver.com/docs/caddyfile/concepts#global-options) must be set, then the `php_server` or the `php` [HTTP directives](https://caddyserver.com/docs/caddyfile/concepts#directives) may be used within the site blocks to serve your PHP app.
|
||||
The `php_server` or the `php` [HTTP directives](https://caddyserver.com/docs/caddyfile/concepts#directives) may be used within the site blocks to serve your PHP app.
|
||||
|
||||
Minimal example:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
# Enable FrankenPHP
|
||||
frankenphp
|
||||
# Configure when the directive must be executed
|
||||
order php_server before file_server
|
||||
}
|
||||
|
||||
localhost {
|
||||
# Enable compression (optional)
|
||||
encode zstd br gzip
|
||||
@@ -40,16 +55,22 @@ localhost {
|
||||
}
|
||||
```
|
||||
|
||||
Optionally, the number of threads to create and [worker scripts](worker.md) to start with the server can be specified under the global option.
|
||||
You can also explicitly configure FrankenPHP using the global option:
|
||||
The `frankenphp` [global option](https://caddyserver.com/docs/caddyfile/concepts#global-options) can be used to configure FrankenPHP.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
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.
|
||||
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.
|
||||
num <num> # Sets the number of PHP threads to start, defaults to 2x the number of available CPUs.
|
||||
env <key> <value> # Sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables.
|
||||
watch <path> # Sets the path to watch for file changes. Can be specified more than once for multiple paths.
|
||||
name <name> # Sets the name of the worker, used in logs and metrics. Default: absolute path of worker file
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,28 +93,27 @@ Alternatively, you may use the one-line short form of the `worker` option:
|
||||
You can also define multiple workers if you serve multiple apps on the same server:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker /path/to/app/public/index.php <num>
|
||||
worker /path/to/other/public/index.php <num>
|
||||
app.example.com {
|
||||
php_server {
|
||||
root /path/to/app/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
app.example.com {
|
||||
root * /path/to/app/public
|
||||
php_server
|
||||
}
|
||||
|
||||
other.example.com {
|
||||
root * /path/to/other/public
|
||||
php_server
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Using the `php_server` directive is generally what you need,
|
||||
but if you need full control, you can use the lower level `php` directive:
|
||||
but if you need full control, you can use the lower-level `php` directive.
|
||||
The `php` directive passes all input to PHP, instead of first checking whether
|
||||
it's a PHP file or not. Read more about it in the [performance page](performance.md).
|
||||
|
||||
Using the `php_server` directive is equivalent to this configuration:
|
||||
|
||||
@@ -126,16 +146,98 @@ php_server [<matcher>] {
|
||||
split_path <delim...> # Sets the substrings for splitting the URI into two parts. The first matching substring will be used to split the "path info" from the path. The first piece is suffixed with the matching substring and will be assumed as the actual resource (CGI script) name. The second piece will be set to PATH_INFO for the script to use. Default: `.php`
|
||||
resolve_root_symlink false # Disables resolving the `root` directory to its actual value by evaluating a symbolic link, if one exists (enabled by default).
|
||||
env <key> <value> # Sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables.
|
||||
file_server off # Disables the built-in file_server directive.
|
||||
worker { # Creates a worker specific to this server. Can be specified more than once for multiple workers.
|
||||
file <path> # Sets the path to the worker script, can be relative to the php_server root
|
||||
num <num> # Sets the number of PHP threads to start, defaults to 2x the number of available
|
||||
name <name> # Sets the name for the worker, used in logs and metrics. Default: absolute path of worker file. Always starts with m# when defined in a php_server block.
|
||||
watch <path> # Sets the path to watch for file changes. Can be specified more than once for multiple paths.
|
||||
env <key> <value> # Sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables. Environment variables for this worker are also inherited from the php_server parent, but can be overwritten here.
|
||||
}
|
||||
worker <other_file> <num> # Can also use the short form like in the global frankenphp block.
|
||||
}
|
||||
```
|
||||
|
||||
### Watching for File Changes
|
||||
|
||||
Since workers only boot your application once and keep it in memory, any changes
|
||||
to your PHP files will not be reflected immediately.
|
||||
|
||||
Workers can instead be restarted on file changes via the `watch` directive.
|
||||
This is useful for development environments.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the `watch` directory is not specified, it will fall back to `./**/*.{php,yaml,yml,twig,env}`,
|
||||
which watches all `.php`, `.yaml`, `.yml`, `.twig` and `.env` files in the directory and subdirectories
|
||||
where the FrankenPHP process was started. You can instead also specify one or more directories via a
|
||||
[shell filename pattern](https://pkg.go.dev/path/filepath#Match):
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch /path/to/app # watches all files in all subdirectories of /path/to/app
|
||||
watch /path/to/app/*.php # watches files ending in .php in /path/to/app
|
||||
watch /path/to/app/**/*.php # watches PHP files in /path/to/app and subdirectories
|
||||
watch /path/to/app/**/*.{php,twig} # watches PHP and Twig files in /path/to/app and subdirectories
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- The `**` pattern signifies recursive watching
|
||||
- Directories can also be relative (to where the FrankenPHP process is started from)
|
||||
- If you have multiple workers defined, all of them will be restarted when a file changes
|
||||
- Be wary about watching files that are created at runtime (like logs) since they might cause unwanted worker restarts.
|
||||
|
||||
The file watcher is based on [e-dant/watcher](https://github.com/e-dant/watcher).
|
||||
|
||||
### Full Duplex (HTTP/1)
|
||||
|
||||
When using HTTP/1.x, it may be desirable to enable full-duplex mode to allow writing a response before the entire body
|
||||
has been read. (for example: WebSocket, Server-Sent Events, etc.)
|
||||
|
||||
This is an opt-in configuration that needs to be added to the global options in the `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Enabling this option may cause old HTTP/1.x clients that don't support full-duplex to deadlock.
|
||||
> This can also be configured using the `CADDY_GLOBAL_OPTIONS` environment config:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
You can find more information about this setting in the [Caddy documentation](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The following environment variables can be used to inject Caddy directives in the `Caddyfile` without modifying it:
|
||||
|
||||
* `SERVER_NAME`: change [the addresses on which to listen](https://caddyserver.com/docs/caddyfile/concepts#addresses), the provided hostnames will also be used for the generated TLS certificate
|
||||
* `CADDY_GLOBAL_OPTIONS`: inject [global options](https://caddyserver.com/docs/caddyfile/options)
|
||||
* `FRANKENPHP_CONFIG`: inject config under the `frankenphp` directive
|
||||
- `SERVER_NAME`: change [the addresses on which to listen](https://caddyserver.com/docs/caddyfile/concepts#addresses), the provided hostnames will also be used for the generated TLS certificate
|
||||
- `CADDY_GLOBAL_OPTIONS`: inject [global options](https://caddyserver.com/docs/caddyfile/options)
|
||||
- `FRANKENPHP_CONFIG`: inject config under the `frankenphp` directive
|
||||
|
||||
As for FPM and CLI SAPIs, environment variables are exposed by default in the `$_SERVER` superglobal.
|
||||
|
||||
@@ -147,6 +249,23 @@ To load [additional PHP configuration files](https://www.php.net/manual/en/confi
|
||||
the `PHP_INI_SCAN_DIR` environment variable can be used.
|
||||
When set, PHP will load all the file with the `.ini` extension present in the given directories.
|
||||
|
||||
You can also change the PHP configuration using the `php_ini` directive in the `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
php_ini memory_limit 256M
|
||||
|
||||
# or
|
||||
|
||||
php_ini {
|
||||
memory_limit 256M
|
||||
max_execution_time 15
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Enable the Debug Mode
|
||||
|
||||
When using the Docker image, set the `CADDY_GLOBAL_OPTIONS` environment variable to `debug` to enable the debug mode:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 73 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 87 KiB |
@@ -2,7 +2,14 @@
|
||||
|
||||
[FrankenPHP Docker images](https://hub.docker.com/r/dunglas/frankenphp) are based on [official PHP images](https://hub.docker.com/_/php/). Debian and Alpine Linux variants are provided for popular architectures. Debian variants are recommended.
|
||||
|
||||
Variants for PHP 8.2 and PHP 8.3 are provided. [Browse tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
Variants for PHP 8.2, 8.3 and 8.4 are provided.
|
||||
|
||||
The tags follow this pattern: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
|
||||
- `<frankenphp-version>` and `<php-version>` are version numbers of FrankenPHP and PHP respectively, ranging from major (e.g. `1`), minor (e.g. `1.2`) to patch versions (e.g. `1.2.3`).
|
||||
- `<os>` is either `bookworm` (for Debian Bookworm) or `alpine` (for the latest stable version of Alpine).
|
||||
|
||||
[Browse tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
## How to Use The Images
|
||||
|
||||
@@ -45,22 +52,26 @@ FrankenPHP is built on top of Caddy, and all [Caddy modules](https://caddyserver
|
||||
The easiest way to install custom Caddy modules is to use [xcaddy](https://github.com/caddyserver/xcaddy):
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp:latest-builder AS builder
|
||||
FROM dunglas/frankenphp:builder AS builder
|
||||
|
||||
# Copy xcaddy in the builder image
|
||||
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
|
||||
|
||||
# CGO must be enabled to build FrankenPHP
|
||||
ENV CGO_ENABLED=1 XCADDY_SETCAP=1 XCADDY_GO_BUILD_FLAGS="-ldflags '-w -s'"
|
||||
RUN xcaddy build \
|
||||
--output /usr/local/bin/frankenphp \
|
||||
--with github.com/dunglas/frankenphp=./ \
|
||||
--with github.com/dunglas/frankenphp/caddy=./caddy/ \
|
||||
# Mercure and Vulcain are included in the official build, but feel free to remove them
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Add extra Caddy modules here
|
||||
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 frankenphp.dev=./ \
|
||||
--with frankenphp.dev/caddy=./caddy/ \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
# Mercure and Vulcain are included in the official build, but feel free to remove them
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Add extra Caddy modules here
|
||||
|
||||
FROM dunglas/frankenphp AS runner
|
||||
|
||||
@@ -96,7 +107,7 @@ To develop easily with FrankenPHP, mount the directory from your host containing
|
||||
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app
|
||||
```
|
||||
|
||||
> ![TIP]
|
||||
> [!TIP]
|
||||
>
|
||||
> The `--tty` option allows to have nice human-readable logs instead of JSON logs.
|
||||
|
||||
@@ -138,25 +149,52 @@ Here is a sample `Dockerfile` doing this:
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=www-data
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Use "adduser -D ${USER}" for alpine based distros
|
||||
useradd -D ${USER}; \
|
||||
useradd ${USER}; \
|
||||
# Add additional capability to bind to port 80 and 443
|
||||
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
|
||||
# Give write access to /data/caddy and /config/caddy
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy;
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
### Running With No Capabilities
|
||||
|
||||
Even when running rootless, FrankenPHP needs the `CAP_NET_BIND_SERVICE` capability to bind the
|
||||
web server on privileged ports (80 and 443).
|
||||
|
||||
If you expose FrankenPHP on a non-privileged port (1024 and above), it's possible to run
|
||||
the webserver as a non-root user, and without the need for any capability:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Use "adduser -D ${USER}" for alpine based distros
|
||||
useradd ${USER}; \
|
||||
# Remove default capability
|
||||
setcap -r /usr/local/bin/frankenphp; \
|
||||
# Give write access to /data/caddy and /config/caddy
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
Next, set the `SERVER_NAME` environment variable to use an unprivileged port.
|
||||
Example: `:8000`
|
||||
|
||||
## Updates
|
||||
|
||||
The Docker images are built:
|
||||
|
||||
* when a new release is tagged
|
||||
* daily at 4 am UTC, if new versions of the official PHP images are available
|
||||
- when a new release is tagged
|
||||
- daily at 4 am UTC, if new versions of the official PHP images are available
|
||||
|
||||
## Development Versions
|
||||
|
||||
|
||||
@@ -2,20 +2,22 @@
|
||||
|
||||
FrankenPHP has the ability to embed the source code and assets of PHP applications in a static, self-contained binary.
|
||||
|
||||
Thanks to this feature, PHP applications can be distributed as standalone binaries that include the application itself, the PHP interpreter and Caddy, a production-level web server.
|
||||
Thanks to this feature, PHP applications can be distributed as standalone binaries that include the application itself, the PHP interpreter, and Caddy, a production-level web server.
|
||||
|
||||
Learn more about this feature [in the presentation made by Kévin at SymfonyCon 2023](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/).
|
||||
|
||||
For embedding Laravel applications, [read this specific documentation entry](laravel.md#laravel-apps-as-standalone-binaries).
|
||||
|
||||
## Preparing Your App
|
||||
|
||||
Before creating the self-contained binary be sure that your app is ready for embedding.
|
||||
|
||||
For instance you likely want to:
|
||||
For instance, you likely want to:
|
||||
|
||||
* Install the production dependencies of the app
|
||||
* Dump the autoloader
|
||||
* Enable the production mode of your application (if any)
|
||||
* Strip uneeded files such as `.git` or tests to reduce the size of your final binary
|
||||
- Install the production dependencies of the app
|
||||
- Dump the autoloader
|
||||
- Enable the production mode of your application (if any)
|
||||
- Strip unneeded files such as `.git` or tests to reduce the size of your final binary
|
||||
|
||||
For instance, for a Symfony app, you can use the following commands:
|
||||
|
||||
@@ -29,7 +31,8 @@ cd $TMPDIR/my-prepared-app
|
||||
echo APP_ENV=prod > .env.local
|
||||
echo APP_DEBUG=0 >> .env.local
|
||||
|
||||
# Remove the tests
|
||||
# Remove the tests and other unneeded files to save space
|
||||
# Alternatively, add these files with the export-ignore attribute in your .gitattributes file
|
||||
rm -Rf tests/
|
||||
|
||||
# Install the dependencies
|
||||
@@ -39,40 +42,45 @@ composer install --ignore-platform-reqs --no-dev -a
|
||||
composer dump-env prod
|
||||
```
|
||||
|
||||
### Customizing the Configuration
|
||||
|
||||
To customize [the configuration](config.md), you can put a `Caddyfile` as well as a `php.ini` file
|
||||
in the main directory of the app to be embedded (`$TMPDIR/my-prepared-app` in the previous example).
|
||||
|
||||
## Creating a Linux Binary
|
||||
|
||||
The easiest way to create a Linux binary is to use the Docker-based builder we provide.
|
||||
|
||||
1. Create a file named `static-build.Dockerfile` in the repository of your prepared app:
|
||||
1. Create a file named `static-build.Dockerfile` in the repository of your app:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# Copy your app
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
# Copy your app
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Build the static binary, be sure to select only the PHP extensions you want
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
# Build the static binary
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Some `.dockerignore` files (e.g. default [Symfony Docker `.dockerignore`](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))
|
||||
> will ignore the `vendor/` directory and `.env` files. Be sure to adjust or remove the `.dockerignore` file before the build.
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Some `.dockerignore` files (e.g. default [Symfony Docker `.dockerignore`](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))
|
||||
> will ignore the `vendor/` directory and `.env` files. Be sure to adjust or remove the `.dockerignore` file before the build.
|
||||
|
||||
2. Build:
|
||||
|
||||
```console
|
||||
docker build -t static-app -f static-build.Dockerfile .
|
||||
```
|
||||
```console
|
||||
docker build -t static-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Extract the binary:
|
||||
|
||||
```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
|
||||
```
|
||||
```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
|
||||
```
|
||||
|
||||
The resulting binary is the file named `my-app` in the current directory.
|
||||
|
||||
@@ -104,7 +112,7 @@ If your app contains a [worker script](worker.md), start the worker with somethi
|
||||
./my-app php-server --worker public/index.php
|
||||
```
|
||||
|
||||
To enable HTTPS (a Let's Encrypt certificate is automatically created), HTTP/2 and HTTP/3, specify the domain name to use:
|
||||
To enable HTTPS (a Let's Encrypt certificate is automatically created), HTTP/2, and HTTP/3, specify the domain name to use:
|
||||
|
||||
```console
|
||||
./my-app php-server --domain localhost
|
||||
|
||||
@@ -11,15 +11,18 @@ 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
|
||||
```
|
||||
|
||||
L'image contient les outils de développement habituels (Go, GDB, Valgrind, Neovim...).
|
||||
L'image contient les outils de développement habituels (Go, GDB, Valgrind, Neovim...) et utilise les emplacements de configuration PHP suivants
|
||||
|
||||
Si la version de Docker est inférieure à 23.0, la construction échoue à cause d'un [problème de pattern](https://github.com/moby/moby/pull/42676) dans `.dockerignore`. Ajoutez les répertoires à `.dockerignore`.
|
||||
- php.ini: `/etc/frankenphp/php.ini` Un fichier php.ini avec des préréglages de développement est fourni par défaut.
|
||||
- fichiers de configuration supplémentaires: `/etc/frankenphp/php.d/*.ini`
|
||||
- extensions php: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
Si votre version de Docker est inférieure à 23.0, la construction échouera à cause d'un [problème de pattern](https://github.com/moby/moby/pull/42676) dans `.dockerignore`. Ajoutez les répertoires à `.dockerignore`.
|
||||
|
||||
```patch
|
||||
!testdata/*.php
|
||||
!testdata/*.txt
|
||||
+!caddy
|
||||
+!C-Thread-Pool
|
||||
+!internal
|
||||
```
|
||||
|
||||
@@ -30,7 +33,7 @@ Si la version de Docker est inférieure à 23.0, la construction échoue à caus
|
||||
## Exécution de la suite de tests
|
||||
|
||||
```console
|
||||
go test -race -v ./...
|
||||
go test -tags watcher -race -v ./...
|
||||
```
|
||||
|
||||
## Module Caddy
|
||||
@@ -39,7 +42,7 @@ Construire Caddy avec le module FrankenPHP :
|
||||
|
||||
```console
|
||||
cd caddy/frankenphp/
|
||||
go build
|
||||
go build -tags watcher,brotli,nobadger,nomysql,nopgx
|
||||
cd ../../
|
||||
```
|
||||
|
||||
@@ -50,10 +53,14 @@ cd testdata/
|
||||
../caddy/frankenphp/frankenphp run
|
||||
```
|
||||
|
||||
Le serveur est configuré pour écouter à l'adresse `127.0.0.1:8080`:
|
||||
Le serveur est configuré pour écouter à l'adresse `127.0.0.1:80`:
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Si vous utilisez Docker, vous devrez soit lier le port 80 du conteneur, soit exécuter depuis l'intérieur du conteneur.
|
||||
|
||||
```console
|
||||
curl -vk https://localhost/phpinfo.php
|
||||
curl -vk http://127.0.0.1/phpinfo.php
|
||||
```
|
||||
|
||||
## Serveur de test minimal
|
||||
@@ -105,69 +112,92 @@ Construire à partir de zéro les images FrankenPHP pour arm64 & amd64 et les po
|
||||
docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
```
|
||||
|
||||
## Déboguer les erreurs de segmentation avec les builds statiques
|
||||
|
||||
1. Téléchargez la version de débogage du binaire FrankenPHP depuis GitHub ou créez votre propre build statique incluant des symboles de débogage :
|
||||
|
||||
```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. Remplacez votre version actuelle de `frankenphp` par l'exécutable de débogage de FrankenPHP.
|
||||
3. Démarrez FrankenPHP comme d'habitude (alternativement, vous pouvez directement démarrer FrankenPHP avec GDB : `gdb --args frankenphp run`).
|
||||
4. Attachez-vous au processus avec GDB :
|
||||
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
|
||||
5. Si nécessaire, tapez `continue` dans le shell GDB
|
||||
6. Faites planter FrankenPHP.
|
||||
7. Tapez `bt` dans le shell GDB
|
||||
8. Copiez la sortie
|
||||
|
||||
## Déboguer les erreurs de segmentation dans GitHub Actions
|
||||
|
||||
1. Ouvrir `.github/workflows/tests.yml`
|
||||
2. Activer les symboles de débogage de la bibliothèque PHP
|
||||
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
|
||||
3. Activer `tmate` pour se connecter au conteneur
|
||||
|
||||
```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
|
||||
```
|
||||
```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. Se connecter au conteneur
|
||||
5. Ouvrir `frankenphp.go`
|
||||
6. Activer `cgosymbolizer`
|
||||
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
|
||||
7. Télécharger le module : `go get`
|
||||
8. Dans le conteneur, vous pouvez utiliser GDB et similaires :
|
||||
|
||||
```console
|
||||
go test -c -ldflags=-w
|
||||
gdb --args ./frankenphp.test -test.run ^MyTest$
|
||||
```
|
||||
```console
|
||||
go test -tags watcher -c -ldflags=-w
|
||||
gdb --args frankenphp.dev.test -test.run ^MyTest$
|
||||
```
|
||||
|
||||
9. Quand le bug est corrigé, annulez tous les changements
|
||||
9. Quand le bug est corrigé, annulez tous les changements.
|
||||
|
||||
## Ressources Diverses pour le Développement
|
||||
|
||||
* [Intégration de PHP dans uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
* [Intégration de PHP dans NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
* [Intégration de PHP dans Go (go-php)](https://github.com/deuill/go-php)
|
||||
* [Intégration de PHP dans Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
* [Intégration de PHP dans C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
* [Extending and Embedding PHP par Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
* [Qu'est-ce que TSRMLS_CC, au juste ?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
* [Intégration de PHP sur Mac](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4)
|
||||
* [Bindings SDL](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
- [Intégration de PHP dans uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
- [Intégration de PHP dans NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
- [Intégration de PHP dans Go (go-php)](https://github.com/deuill/go-php)
|
||||
- [Intégration de PHP dans Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
- [Intégration de PHP dans C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
- [Extending and Embedding PHP par Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
- [Qu'est-ce que TSRMLS_CC, au juste ?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
- [Intégration de PHP sur Mac](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4)
|
||||
- [Bindings SDL](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
|
||||
## Ressources Liées à Docker
|
||||
|
||||
* [Définition du fichier Bake](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
* [docker buildx build](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
- [Définition du fichier Bake](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
- [docker buildx build](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
|
||||
## Commande utile
|
||||
|
||||
@@ -175,3 +205,16 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
apk add strace util-linux gdb
|
||||
strace -e 'trace=!futex,epoll_ctl,epoll_pwait,tgkill,rt_sigreturn' -p 1
|
||||
```
|
||||
|
||||
## Traduire la documentation
|
||||
|
||||
Pour traduire la documentation et le site dans une nouvelle langue, procédez comme suit :
|
||||
|
||||
1. Créez un nouveau répertoire nommé avec le code ISO à 2 caractères de la langue dans le répertoire `docs/` de ce dépôt
|
||||
2. Copiez tous les fichiers `.md` à la racine du répertoire `docs/` dans le nouveau répertoire (utilisez toujours la version anglaise comme source de traduction, car elle est toujours à jour).
|
||||
3. Copiez les fichiers `README.md` et `CONTRIBUTING.md` du répertoire racine vers le nouveau répertoire.
|
||||
4. Traduisez le contenu des fichiers, mais ne changez pas les noms de fichiers, ne traduisez pas non plus les chaînes commençant par `> [!` (c'est un balisage spécial pour GitHub).
|
||||
5. Créez une Pull Request avec les traductions
|
||||
6. Dans le [référentiel du site](https://github.com/dunglas/frankenphp-website/tree/main), copiez et traduisez les fichiers de traduction dans les répertoires `content/`, `data/` et `i18n/`.
|
||||
7. Traduire les valeurs dans le fichier YAML créé.
|
||||
8. Ouvrir une Pull Request sur le dépôt du site.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
FrankenPHP est un serveur d'applications moderne pour PHP construit à partir du serveur web [Caddy](https://caddyserver.com/).
|
||||
|
||||
FrankenPHP donne des super-pouvoirs à vos applications PHP grâce à ses fonctionnalités à la pointe : [*Early Hints*](early-hints.md), [mode worker](worker.md), [fonctionnalités en temps réel](mercure.md), HTTPS automatique, prise en charge de HTTP/2 et HTTP/3...
|
||||
FrankenPHP donne des super-pouvoirs à vos applications PHP grâce à ses fonctionnalités à la pointe : [_Early Hints_](early-hints.md), [mode worker](worker.md), [fonctionnalités en temps réel](mercure.md), HTTPS automatique, prise en charge de HTTP/2 et HTTP/3...
|
||||
|
||||
FrankenPHP fonctionne avec n'importe quelle application PHP et rend vos projets Laravel et Symfony plus rapides que jamais grâce à leurs intégrations officielles avec le mode worker.
|
||||
|
||||
@@ -16,10 +16,38 @@ Découvrez plus de détails sur ce serveur d’application dans le replay de cet
|
||||
|
||||
## Pour Commencer
|
||||
|
||||
### Docker
|
||||
### Binaire autonome
|
||||
|
||||
Si vous préférez ne pas utiliser Docker, nous fournissons des binaires autonomes de FrankenPHP pour Linux et macOS
|
||||
contenant [PHP 8.4](https://www.php.net/releases/8.4/fr.php) et la plupart des extensions PHP populaires.
|
||||
|
||||
Sous Windows, utilisez [WSL](https://learn.microsoft.com/windows/wsl/) pour exécuter FrankenPHP.
|
||||
|
||||
[Téléchargez FrankenPHP](https://github.com/dunglas/frankenphp/releases) ou copiez cette ligne dans votre terminal pour installer automatiquement la version appropriée à votre plateforme :
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public \
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
mv frankenphp /usr/local/bin/
|
||||
```
|
||||
|
||||
Pour servir le contenu du répertoire courant, exécutez :
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Vous pouvez également exécuter des scripts en ligne de commande avec :
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Des [images Docker](https://frankenphp.dev/docs/fr/docker/) sont également disponibles :
|
||||
|
||||
```console
|
||||
docker run -v .:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
@@ -29,49 +57,53 @@ Rendez-vous sur `https://localhost`, c'est parti !
|
||||
> [!TIP]
|
||||
>
|
||||
> Ne tentez pas d'utiliser `https://127.0.0.1`. Utilisez `https://localhost` et acceptez le certificat auto-signé.
|
||||
> Utilisez [la variable d'environnement `SERVER_NAME`](config.md#environment-variables) pour changer le domaine à utiliser.
|
||||
> Utilisez [la variable d'environnement `SERVER_NAME`](config.md#variables-denvironnement) pour changer le domaine à utiliser.
|
||||
|
||||
### Binaire autonome
|
||||
### Homebrew
|
||||
|
||||
Si vous préférez ne pas utiliser Docker, nous fournissons des binaires autonomes de FrankenPHP pour Linux et macOS
|
||||
contenant [PHP 8.3](https://www.php.net/releases/8.3/fr.php) et la plupart des extensions PHP populaires : [Télécharger FrankenPHP](https://github.com/dunglas/frankenphp/releases)
|
||||
FrankenPHP est également disponible sous forme de paquet [Homebrew](https://brew.sh) pour macOS et Linux.
|
||||
|
||||
Pour l'installer :
|
||||
|
||||
```console
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
Pour servir le contenu du répertoire courant, exécutez :
|
||||
|
||||
```console
|
||||
./frankenphp php-server
|
||||
```
|
||||
|
||||
Vous pouvez également exécuter des scripts en ligne de commande avec :
|
||||
|
||||
```console
|
||||
./frankenphp php-cli /path/to/your/script.php
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
* [Le mode worker](worker.md)
|
||||
* [Le support des Early Hints (code de statut HTTP 103)](early-hints.md)
|
||||
* [Temps réel](mercure.md)
|
||||
* [Configuration](config.md)
|
||||
* [Images Docker](docker.md)
|
||||
* [Déploiement en production](production.md)
|
||||
* [Créer des applications PHP **standalone**, auto-exécutables](embed.md)
|
||||
* [Créer un build statique](static.md)
|
||||
* [Compiler depuis les sources](compile.md)
|
||||
* [Intégration Laravel](laravel.md)
|
||||
* [Problèmes connus](known-issues.md)
|
||||
* [Application de démo (Symfony) et benchmarks](https://github.com/dunglas/frankenphp-demo)
|
||||
* [Documentation de la bibliothèque Go](https://pkg.go.dev/github.com/dunglas/frankenphp)
|
||||
* [Contribuer et débugger](CONTRIBUTING.md)
|
||||
- [Le mode classique](classic.md)
|
||||
- [Le mode worker](worker.md)
|
||||
- [Le support des Early Hints (code de statut HTTP 103)](early-hints.md)
|
||||
- [Temps réel](mercure.md)
|
||||
- [Servir efficacement les fichiers statiques volumineux](x-sendfile.md)
|
||||
- [Configuration](config.md)
|
||||
- [Images Docker](docker.md)
|
||||
- [Déploiement en production](production.md)
|
||||
- [Optimisation des performances](performance.md)
|
||||
- [Créer des applications PHP **standalone**, auto-exécutables](embed.md)
|
||||
- [Créer un build statique](static.md)
|
||||
- [Compiler depuis les sources](compile.md)
|
||||
- [Surveillance de FrankenPHP](metrics.md)
|
||||
- [Intégration Laravel](laravel.md)
|
||||
- [Problèmes connus](known-issues.md)
|
||||
- [Application de démo (Symfony) et benchmarks](https://github.com/dunglas/frankenphp-demo)
|
||||
- [Documentation de la bibliothèque Go](https://pkg.go.dev/github.com/dunglas/frankenphp)
|
||||
- [Contribuer et débugger](CONTRIBUTING.md)
|
||||
|
||||
## Exemples et squelettes
|
||||
|
||||
* [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/dunglas/frankenphp-wordpress)
|
||||
* [Drupal](https://github.com/dunglas/frankenphp-drupal)
|
||||
* [Joomla](https://github.com/alexandreelise/frankenphp-joomla)
|
||||
* [TYPO3](https://github.com/ochorocho/franken-typo3)
|
||||
- [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/fr/classic.md
Normal file
11
docs/fr/classic.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Utilisation du mode classique
|
||||
|
||||
Sans aucune configuration additionnelle, FrankenPHP fonctionne en mode classique. Dans ce mode, FrankenPHP fonctionne comme un serveur PHP traditionnel, en servant directement les fichiers PHP. Cela en fait un remplaçant parfait à PHP-FPM ou Apache avec mod_php.
|
||||
|
||||
Comme Caddy, FrankenPHP accepte un nombre illimité de connexions et utilise un [nombre fixe de threads](config.md#configuration-du-caddyfile) pour les servir. Le nombre de connexions acceptées et en attente n'est limité que par les ressources système disponibles.
|
||||
Le pool de threads PHP fonctionne avec un nombre fixe de threads initialisés au démarrage, comparable au mode statique de PHP-FPM. Il est également possible de laisser les threads [s'adapter automatiquement à l'exécution](performance.md#max_threads), comme dans le mode dynamique de PHP-FPM.
|
||||
|
||||
Les connexions en file d'attente attendront indéfiniment jusqu'à ce qu'un thread PHP soit disponible pour les servir. Pour éviter cela, vous pouvez utiliser la [configuration](config.md#configuration-du-caddyfile) `max_wait_time` pour limiter la durée pendant laquelle une requête peut attendre un thread PHP libre avant d'être rejetée.
|
||||
En outre, vous pouvez définir un [délai d'écriture dans Caddy](https://caddyserver.com/docs/caddyfile/options#timeouts) raisonnable.
|
||||
|
||||
Chaque instance de Caddy n'utilisera qu'un seul pool de threads FrankenPHP, qui sera partagé par tous les blocs `php_server`.
|
||||
@@ -1,13 +1,31 @@
|
||||
# Compiler depuis les sources
|
||||
|
||||
Ce document explique comment créer un build FrankenPHP qui chargera PHP comme une bibliothèque dynamique. C'est la méthode recommandée.
|
||||
Ce document explique comment créer un build FrankenPHP qui chargera PHP en tant que bibliothèque dynamique.
|
||||
C'est la méthode recommandée.
|
||||
|
||||
En alternative, il est aussi possible de [créer des builds statiques](static.md).
|
||||
Alternativement, il est aussi possible de [créer des builds statiques](static.md).
|
||||
|
||||
## Installer PHP
|
||||
|
||||
FrankenPHP est compatible avec PHP 8.2 et versions ultérieures.
|
||||
|
||||
### Avec Homebrew (Linux et Mac)
|
||||
|
||||
La manière la plus simple d'installer une version de libphp compatible avec FrankenPHP est d'utiliser les paquets ZTS fournis par [Homebrew PHP](https://github.com/shivammathur/homebrew-php).
|
||||
|
||||
Tout d'abord, si ce n'est déjà fait, installez [Homebrew](https://brew.sh).
|
||||
|
||||
Ensuite, installez la variante ZTS de PHP, Brotli (facultatif, pour la prise en charge de la compression) et watcher (facultatif, pour la détection des modifications de fichiers) :
|
||||
|
||||
```console
|
||||
brew install shivammathur/php/php-zts brotli watcher
|
||||
brew link --overwrite --force shivammathur/php/php-zts
|
||||
```
|
||||
|
||||
### En compilant PHP
|
||||
|
||||
Vous pouvez également compiler PHP à partir des sources avec les options requises par FrankenPHP en suivant ces étapes.
|
||||
|
||||
Tout d'abord, [téléchargez les sources de PHP](https://www.php.net/downloads.php) et extrayez-les :
|
||||
|
||||
```console
|
||||
@@ -15,7 +33,9 @@ tar xf php-*
|
||||
cd php-*/
|
||||
```
|
||||
|
||||
Ensuite, configurez PHP pour votre système d'exploitation :
|
||||
Ensuite, configurez PHP pour votre système d'exploitation.
|
||||
|
||||
Les options de configuration suivantes sont nécessaires pour la compilation, mais vous pouvez également inclure d'autres options selon vos besoins, par exemple pour ajouter des extensions et fonctionnalités supplémentaires.
|
||||
|
||||
### Linux
|
||||
|
||||
@@ -27,19 +47,12 @@ Ensuite, configurez PHP pour votre système d'exploitation :
|
||||
--enable-zend-max-execution-timers
|
||||
```
|
||||
|
||||
Finalement, compilez et installez PHP :
|
||||
|
||||
```console
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
```
|
||||
|
||||
### Mac
|
||||
|
||||
Utilisez le gestionnaire de paquets [Homebrew](https://brew.sh/) pour installer `libiconv`, `bison`, `re2c` et `pkg-config` :
|
||||
Utilisez le gestionnaire de paquets [Homebrew](https://brew.sh/) pour installer les dépendances obligatoires et optionnelles :
|
||||
|
||||
```console
|
||||
brew install libiconv bison re2c pkg-config
|
||||
brew install libiconv bison brotli re2c pkg-config watcher
|
||||
echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
|
||||
```
|
||||
|
||||
@@ -47,47 +60,51 @@ Puis exécutez le script de configuration :
|
||||
|
||||
```console
|
||||
./configure \
|
||||
--enable-embed=static \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--disable-opcache-jit \
|
||||
--enable-static \
|
||||
--enable-shared=no \
|
||||
--with-iconv=/opt/homebrew/opt/libiconv/
|
||||
```
|
||||
|
||||
Les options de configuration spécifiées sont nécessaires pour la compilation, mais vous pouvez également inclure d'autres options selon vos besoins, par exemple pour ajouter des extensions supplémentaires.
|
||||
### Compilez PHP
|
||||
|
||||
Finalement, compilez et installez PHP :
|
||||
|
||||
```console
|
||||
make -j$(sysctl -n hw.logicalcpu)
|
||||
make -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## Installez les dépendances optionnelles
|
||||
|
||||
Certaines fonctionnalités de FrankenPHP nécessitent des dépendances optionnelles qui doivent être installées.
|
||||
Ces fonctionnalités peuvent également être désactivées en passant des tags de compilation au compilateur Go.
|
||||
|
||||
| Fonctionnalité | Dépendance | Tag de compilation pour la désactiver |
|
||||
|---------------------------------------------------------|-----------------------------------------------------------------------|---------------------------------------|
|
||||
| Compression Brotli | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| Redémarrage des workers en cas de changement de fichier | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
|
||||
## Compiler l'application Go
|
||||
|
||||
Vous pouvez maintenant utiliser la bibliothèque Go et compiler notre build de Caddy :
|
||||
|
||||
```console
|
||||
curl -L https://github.com/dunglas/frankenphp/archive/refs/heads/main.tar.gz | tar x
|
||||
cd frankenphp-main/caddy/frankenphp
|
||||
CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build
|
||||
```
|
||||
|
||||
### Utiliser xcaddy
|
||||
|
||||
Vous pouvez utiliser à la place [xcaddy](https://github.com/caddyserver/xcaddy) pour compiler FrankenPHP avec [des modules Caddy additionnels](https://caddyserver.com/docs/modules/):
|
||||
La méthode recommandée consiste à utiliser [xcaddy](https://github.com/caddyserver/xcaddy) pour compiler FrankenPHP.
|
||||
`xcaddy` permet également d'ajouter facilement des [modules Caddy personnalisés](https://caddyserver.com/docs/modules/) et des extensions FrankenPHP :
|
||||
|
||||
```console
|
||||
CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags '-w -s'" \
|
||||
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 frankenphp.dev/caddy \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Add extra Caddy modules here
|
||||
# Ajoutez les modules Caddy supplémentaires et les extensions FrankenPHP ici
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
@@ -99,3 +116,13 @@ xcaddy build \
|
||||
> Pour ce faire, modifiez la variable d'environnement `XCADDY_GO_BUILD_FLAGS` en quelque chose comme
|
||||
> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'`
|
||||
> (modifiez la valeur de la taille de la pile selon les besoins de votre application).
|
||||
|
||||
### Sans xcaddy
|
||||
|
||||
Il est également possible de compiler FrankenPHP sans `xcaddy` en utilisant directement la commande `go` :
|
||||
|
||||
```console
|
||||
curl -L https://github.com/dunglas/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
|
||||
```
|
||||
|
||||
@@ -1,57 +1,77 @@
|
||||
# Configuration
|
||||
# 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).
|
||||
|
||||
Dans l'image Docker, le chemin du `Caddyfile` est `/etc/caddy/Caddyfile`.
|
||||
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é.
|
||||
|
||||
Vous pouvez également configurer PHP en utilisant `php.ini` comme d'habitude.
|
||||
PHP lui-même peut être configuré [en utilisant un fichier `php.ini`](https://www.php.net/manual/fr/configuration.file.php).
|
||||
|
||||
Dans l'image Docker, le fichier `php.ini` n'est pas présent, vous pouvez le créer manuellement ou copier un template officiel :
|
||||
L'interpréteur PHP cherchera dans les emplacements suivants :
|
||||
|
||||
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>/`
|
||||
- Vous devriez copier un modèle officiel fourni par le projet PHP :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# Développement :
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
|
||||
# Ou production :
|
||||
# Production :
|
||||
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
|
||||
|
||||
# Ou développement :
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
```
|
||||
|
||||
Installation de FrankenPHP (.rpm ou .deb) :
|
||||
|
||||
- php.ini : `/etc/frankenphp/php.ini` Un fichier php.ini avec des préréglages de production est fourni par défaut.
|
||||
- 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
|
||||
- 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
|
||||
|
||||
Pour enregistrer l'exécutable de FrankenPHP, l'[option globale](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp` doit être définie, puis les [directives HTTP](https://caddyserver.com/docs/caddyfile/concepts#directives) `php_server` ou `php` peuvent être utilisées dans les blocs de site pour servir votre application PHP.
|
||||
Les [directives HTTP](https://caddyserver.com/docs/caddyfile/concepts#directives) `php_server` ou `php` peuvent être utilisées dans les blocs de site pour servir votre application PHP.
|
||||
|
||||
Exemple minimal :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
# Activer FrankenPHP
|
||||
frankenphp
|
||||
# Configurer l'ordre d'exécution de la directive
|
||||
order php_server before file_server
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
En option, le nombre de threads à créer et les [workers](worker.md) à démarrer avec le serveur peuvent être spécifiés sous l'option globale.
|
||||
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.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Définit le nombre de threads PHP à démarrer. Par défaut : 2x le nombre de CPUs disponibles.
|
||||
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.
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
@@ -61,9 +81,9 @@ Vous pouvez également utiliser la forme courte de l'option worker en une seule
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker <file> <num>
|
||||
}
|
||||
frankenphp {
|
||||
worker <file> <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
@@ -72,21 +92,18 @@ Vous pouvez également utiliser la forme courte de l'option worker en une seule
|
||||
Vous pouvez aussi définir plusieurs workers si vous servez plusieurs applications sur le même serveur :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker /path/to/app/public/index.php <num>
|
||||
worker /path/to/other/public/index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
app.example.com {
|
||||
root * /path/to/app/public
|
||||
php_server
|
||||
php_server {
|
||||
root /path/to/app/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
other.example.com {
|
||||
root * /path/to/other/public
|
||||
php_server
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
@@ -94,27 +111,29 @@ other.example.com {
|
||||
|
||||
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.
|
||||
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).
|
||||
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
@@ -122,20 +141,102 @@ 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.
|
||||
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.
|
||||
}
|
||||
```
|
||||
|
||||
### Surveillance des modifications de fichier
|
||||
|
||||
Vu que les workers ne démarrent votre application qu'une seule fois et la gardent en mémoire, toute modification
|
||||
apportée à vos fichiers PHP ne sera pas répercutée immédiatement.
|
||||
|
||||
Les workers peuvent être redémarrés en cas de changement de fichier via la directive `watch`.
|
||||
Ceci est utile pour les environnements de développement.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
[motif de nom de fichier shell](https://pkg.go.dev/path/filepath#Match) :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch /path/to/app # surveille tous les fichiers dans tous les sous-répertoires de /path/to/app
|
||||
watch /path/to/app/*.php # surveille les fichiers se terminant par .php dans /path/to/app
|
||||
watch /path/to/app/**/*.php # surveille les fichiers PHP dans /path/to/app et les sous-répertoires
|
||||
watch /path/to/app/**/*.{php,twig} # surveille les fichiers PHP et Twig dans /path/to/app et les sous-répertoires
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Le motif `**` signifie une surveillance récursive.
|
||||
- Les répertoires peuvent également être relatifs (depuis l'endroit où le processus FrankenPHP est démarré).
|
||||
- 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).
|
||||
|
||||
### 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 : WebSocket, événements envoyés par le serveur, etc.)
|
||||
|
||||
Il s'agit d'une configuration optionnelle qui doit être ajoutée aux options globales dans le fichier `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).
|
||||
|
||||
## Variables d'environnement
|
||||
|
||||
Les variables d'environnement suivantes peuvent être utilisées pour insérer des directives Caddy dans le `Caddyfile` sans le modifier :
|
||||
|
||||
* `SERVER_NAME` : change [les adresses sur lesquelles écouter](https://caddyserver.com/docs/caddyfile/concepts#addresses), les noms d'hôte fournis seront également utilisés pour le certificat TLS généré
|
||||
* `CADDY_GLOBAL_OPTIONS` : injecte [des options globales](https://caddyserver.com/docs/caddyfile/options)
|
||||
* `FRANKENPHP_CONFIG` : insère la configuration sous la directive `frankenphp`
|
||||
- `SERVER_NAME` : change [les adresses sur lesquelles écouter](https://caddyserver.com/docs/caddyfile/concepts#addresses), les noms d'hôte fournis seront également utilisés pour le certificat TLS généré
|
||||
- `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`.
|
||||
|
||||
@@ -143,9 +244,27 @@ La valeur `S` de [la directive `variables_order` de PHP](https://www.php.net/man
|
||||
|
||||
## Configuration PHP
|
||||
|
||||
Pour charger [des fichiers de configuration PHP supplémentaires](https://www.php.net/manual/fr/configuration.file.php#configuration.file.scan), la variable d'environnement `PHP_INI_SCAN_DIR` peut être utilisée.
|
||||
Pour charger [des fichiers de configuration PHP supplémentaires](https://www.php.net/manual/fr/configuration.file.php#configuration.file.scan),
|
||||
la variable d'environnement `PHP_INI_SCAN_DIR` peut être utilisée.
|
||||
Lorsqu'elle est définie, PHP chargera tous les fichiers avec l'extension `.ini` présents dans les répertoires donnés.
|
||||
|
||||
Vous pouvez également modifier la configuration de PHP en utilisant la directive `php_ini` dans le fichier `Caddyfile` :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
php_ini memory_limit 256M
|
||||
|
||||
# or
|
||||
|
||||
php_ini {
|
||||
memory_limit 256M
|
||||
max_execution_time 15
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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 :
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
|
||||
Les images Docker de [FrankenPHP](https://hub.docker.com/r/dunglas/frankenphp) sont basées sur les [images PHP officielles](https://hub.docker.com/_/php/). Des variantes Debian et Alpine Linux sont fournies pour les architectures populaires. Les variantes Debian sont recommandées.
|
||||
|
||||
Des variantes pour PHP 8.2 et PHP 8.3 sont disponibles. [Parcourir les tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
Des variantes pour PHP 8.2, 8.3 et 8.4 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>`
|
||||
|
||||
- `<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 `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).
|
||||
|
||||
## Comment utiliser les images
|
||||
|
||||
@@ -31,35 +38,40 @@ FROM dunglas/frankenphp
|
||||
|
||||
# ajoutez des extensions supplémentaires ici :
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
```
|
||||
|
||||
## Comment installer plus de modules Caddy
|
||||
|
||||
FrankenPHP est construit sur Caddy, et tous les [modules Caddy](https://caddyserver.com/docs/modules/) peuvent être utilisés avec FrankenPHP.
|
||||
|
||||
La manière la plus simple d'installer des modules Caddy personnalisés est d'utiliser [xcaddy](https://github.com/caddyserver/xcaddy):
|
||||
La manière la plus simple d'installer des modules Caddy personnalisés est d'utiliser [xcaddy](https://github.com/caddyserver/xcaddy) :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp:latest-builder AS builder
|
||||
FROM dunglas/frankenphp:builder AS builder
|
||||
|
||||
# Copier xcaddy dans l'image du constructeur
|
||||
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
|
||||
|
||||
# CGO doit être activé pour construire FrankenPHP
|
||||
ENV CGO_ENABLED=1 XCADDY_SETCAP=1 XCADDY_GO_BUILD_FLAGS="-ldflags '-w -s'"
|
||||
RUN xcaddy build \
|
||||
--output /usr/local/bin/frankenphp \
|
||||
--with github.com/dunglas/frankenphp=./ \
|
||||
--with github.com/dunglas/frankenphp/caddy=./caddy/ \
|
||||
# Mercure et Vulcain sont inclus dans la construction officielle, mais n'hésitez pas à les retirer
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Ajoutez des modules Caddy supplémentaires ici
|
||||
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 frankenphp.dev=./ \
|
||||
--with frankenphp.dev/caddy=./caddy/ \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
# Mercure et Vulcain sont inclus dans la construction officielle, mais n'hésitez pas à les retirer
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Ajoutez des modules Caddy supplémentaires ici
|
||||
|
||||
FROM dunglas/frankenphp AS runner
|
||||
|
||||
@@ -73,7 +85,7 @@ L'image builder fournie par FrankenPHP contient une version compilée de `libphp
|
||||
> [!TIP]
|
||||
>
|
||||
> Si vous utilisez Alpine Linux et Symfony,
|
||||
> vous devrez peut-être [augmenter la taille de pile par défaut](compile.md#using-xcaddy).
|
||||
> vous devrez peut-être [augmenter la taille de pile par défaut](compile.md#utiliser-xcaddy).
|
||||
|
||||
## Activer le mode Worker par défaut
|
||||
|
||||
@@ -95,7 +107,7 @@ Pour développer facilement avec FrankenPHP, montez le répertoire de l'hôte co
|
||||
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app
|
||||
```
|
||||
|
||||
> ![TIP]
|
||||
> [!TIP]
|
||||
>
|
||||
> L'option --tty permet d'avoir des logs lisibles par un humain au lieu de logs JSON.
|
||||
|
||||
@@ -137,25 +149,51 @@ Voici un exemple de `Dockerfile` le permettant :
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=www-data
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Utilisez "adduser -D ${USER}" pour les distributions basées sur Alpine
|
||||
useradd -D ${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;
|
||||
# Utilisez "adduser -D ${USER}" pour les distributions basées sur Alpine
|
||||
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
|
||||
|
||||
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).
|
||||
|
||||
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é.
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Utiliser "adduser -D ${USER}" pour les distros basées sur Alpine
|
||||
useradd ${USER}; \
|
||||
# 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
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
Ensuite, définissez la variable d'environnement `SERVER_NAME` pour utiliser un port non privilégié.
|
||||
Exemple `:8000`
|
||||
|
||||
## Mises à jour
|
||||
|
||||
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
|
||||
- lorsqu'une nouvelle version est taguée
|
||||
- tous les jours à 4h UTC, si de nouvelles versions des images officielles PHP sont disponibles
|
||||
|
||||
## Versions de développement
|
||||
|
||||
|
||||
@@ -6,16 +6,18 @@ Grâce à cette fonctionnalité, les applications PHP peuvent être distribuées
|
||||
|
||||
Pour en savoir plus sur cette fonctionnalité, consultez [la présentation faite par Kévin à la SymfonyCon 2023](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/).
|
||||
|
||||
Pour embarquer des applications Laravel, [lisez ce point spécifique de la documentation](laravel.md#les-applications-laravel-en-tant-que-binaires-autonomes).
|
||||
|
||||
## Préparer votre application
|
||||
|
||||
Avant de créer le binaire autonome, assurez-vous que votre application est prête à être intégrée.
|
||||
|
||||
Vous devrez probablement :
|
||||
|
||||
* Installer les dépendances de production de l'application
|
||||
* Dumper l'autoloader
|
||||
* Activer le mode production de votre application (si disponible)
|
||||
* Supprimer les fichiers inutiles tels que `.git` ou les tests pour réduire la taille de votre binaire final
|
||||
- Installer les dépendances de production de l'application
|
||||
- Dumper l'autoloader
|
||||
- Activer le mode production de votre application (si disponible)
|
||||
- Supprimer les fichiers inutiles tels que `.git` ou les tests pour réduire la taille de votre binaire final
|
||||
|
||||
Par exemple, pour une application Symfony, lancez les commandes suivantes :
|
||||
|
||||
@@ -29,7 +31,8 @@ cd $TMPDIR/my-prepared-app
|
||||
echo APP_ENV=prod > .env.local
|
||||
echo APP_DEBUG=0 >> .env.local
|
||||
|
||||
# Supprimer les tests
|
||||
# Supprimer les tests et autres fichiers inutiles pour économiser de l'espace
|
||||
# Alternativement, ajoutez ces fichiers avec l'attribut export-ignore dans votre fichier .gitattributes
|
||||
rm -Rf tests/
|
||||
|
||||
# Installer les dépendances
|
||||
@@ -39,42 +42,47 @@ composer install --ignore-platform-reqs --no-dev -a
|
||||
composer dump-env prod
|
||||
```
|
||||
|
||||
### Personnaliser la configuration
|
||||
|
||||
Pour personnaliser [la configuration](config.md),
|
||||
vous pouvez mettre un fichier `Caddyfile` ainsi qu'un fichier `php.ini`
|
||||
dans le répertoire principal de l'application à intégrer
|
||||
(`$TMPDIR/my-prepared-app` dans l'exemple précédent).
|
||||
|
||||
## Créer un binaire Linux
|
||||
|
||||
La manière la plus simple de créer un binaire Linux est d'utiliser le builder basé sur Docker que nous fournissons.
|
||||
|
||||
1. Créez un fichier nommé `static-build.Dockerfile` dans le répertoire de votre application préparée :
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# Copy your app
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
# Copy your app
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Build the static binary, be sure to select only the PHP extensions you want
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ \
|
||||
PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \
|
||||
./build-static.sh
|
||||
```
|
||||
# Build the static binary, be sure to select only the PHP extensions you want
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Certains fichiers `.dockerignore` (par exemple celui fourni par défaut par [Symfony Docker](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))
|
||||
> empêchent la copie du dossier `vendor/` et des fichiers `.env`. Assurez-vous d'ajuster ou de supprimer le fichier `.dockerignore` avant le build.
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Certains fichiers `.dockerignore` (par exemple celui fourni par défaut par [Symfony Docker](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))
|
||||
> empêchent la copie du dossier `vendor/` et des fichiers `.env`. Assurez-vous d'ajuster ou de supprimer le fichier `.dockerignore` avant le build.
|
||||
|
||||
2. Construisez:
|
||||
|
||||
```console
|
||||
docker build -t static-app -f static-build.Dockerfile .
|
||||
```
|
||||
```console
|
||||
docker build -t static-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Extrayez le binaire :
|
||||
|
||||
```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
|
||||
```
|
||||
```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
|
||||
```
|
||||
|
||||
Le binaire généré sera nommé `my-app` dans le répertoire courant.
|
||||
|
||||
@@ -85,9 +93,7 @@ Si vous ne souhaitez pas utiliser Docker, ou souhaitez construire un binaire mac
|
||||
```console
|
||||
git clone https://github.com/dunglas/frankenphp
|
||||
cd frankenphp
|
||||
EMBED=/path/to/your/app \
|
||||
PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \
|
||||
./build-static.sh
|
||||
EMBED=/path/to/your/app ./build-static.sh
|
||||
```
|
||||
|
||||
Le binaire obtenu est le fichier nommé `frankenphp-<os>-<arch>` dans le répertoire `dist/`.
|
||||
@@ -120,6 +126,19 @@ Vous pouvez également exécuter les scripts CLI PHP incorporés dans votre bina
|
||||
./my-app php-cli bin/console
|
||||
```
|
||||
|
||||
## Extensions PHP
|
||||
|
||||
Par défaut, le script construira les extensions requises par le fichier `composer.json` de votre projet, s'il y en a.
|
||||
Si le fichier `composer.json` n'existe pas, les extensions par défaut sont construites, comme documenté dans [Créer un binaire statique](static.md).
|
||||
|
||||
Pour personnaliser les extensions, utilisez la variable d'environnement `PHP_EXTENSIONS`.
|
||||
|
||||
```console
|
||||
EMBED=/path/to/your/app \
|
||||
PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \
|
||||
./build-static.sh
|
||||
```
|
||||
|
||||
## Personnaliser la compilation
|
||||
|
||||
[Consultez la documentation sur la compilation statique](static.md) pour voir comment personnaliser le binaire (extensions, version PHP...).
|
||||
|
||||
@@ -1,50 +1,21 @@
|
||||
# Problèmes Connus
|
||||
|
||||
## Fibres
|
||||
|
||||
Appeller de fonctions et mots clefs PHP qui eux-mêmes appellent [cgo](https://go.dev/blog/cgo) dans des [Fibres](https://www.php.net/manual/fr/language.fibers.php) est connu pour provoquer des plantages.
|
||||
|
||||
Ce problème est [en cours de correction par le projet Go](https://github.com/golang/go/issues/62130).
|
||||
|
||||
En attendant, une solution consiste à ne pas utiliser de mots clefs (comme `echo`) et de fonctions (comme `header()`) qui délèguent à Go depuis l'intérieur de fibres.
|
||||
|
||||
Ce code risque de planter car il utilise `echo` dans une fibre :
|
||||
|
||||
```php
|
||||
$fiber = new Fiber(function() {
|
||||
echo 'Dans la fibre'.PHP_EOL;
|
||||
echo 'Toujours dedans'.PHP_EOL;
|
||||
});
|
||||
$fiber->start();
|
||||
```
|
||||
|
||||
A la place, retournez la valeur de la Fibre et utilisez-la à l'extérieur :
|
||||
|
||||
```php
|
||||
$fiber = new Fiber(function() {
|
||||
Fiber::suspend('Dans la fibre'.PHP_EOL));
|
||||
Fiber::suspend('Toujours dedans'.PHP_EOL));
|
||||
});
|
||||
echo $fiber->start();
|
||||
echo $fiber->resume();
|
||||
$fiber->resume();
|
||||
```
|
||||
|
||||
## Extensions PHP non prises en charge
|
||||
|
||||
Les extensions suivantes sont connues pour ne pas être compatibles avec FrankenPHP :
|
||||
|
||||
| Nom | Raison | Alternatives |
|
||||
| ---------------------------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/en/imap.installation.php) | Non 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/)| Non thread-safe | - |
|
||||
| Nom | Raison | Alternatives |
|
||||
| ----------------------------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/en/imap.installation.php) | Non 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/) | Non thread-safe | - |
|
||||
|
||||
## Extensions PHP boguées
|
||||
|
||||
Les extensions suivantes ont des bugs connus ou des comportements inattendus lorsqu'elles sont utilisées avec FrankenPHP :
|
||||
|
||||
Nom | Problème
|
||||
[ext-openssl](https://www.php.net/manual/fr/book.openssl.php) | Lors de l'utilisation d'une version statique de FrankenPHP (construite avec la libc musl), l'extension OpenSSL peut planter sous de fortes charges. Une solution consiste à utiliser une version liée dynamiquement (comme celle utilisée dans les images Docker). Ce bogue est [suivi par PHP](https://github.com/php/php-src/issues/13648).
|
||||
| Nom | Problème |
|
||||
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ext-openssl](https://www.php.net/manual/fr/book.openssl.php) | Lors de l'utilisation d'une version statique de FrankenPHP (construite avec la libc musl), l'extension OpenSSL peut planter sous de fortes charges. Une solution consiste à utiliser une version liée dynamiquement (comme celle utilisée dans les images Docker). Ce bogue est [suivi par PHP](https://github.com/php/php-src/issues/13648). |
|
||||
|
||||
## get_browser
|
||||
|
||||
@@ -57,9 +28,9 @@ Le binaire autonome et les images docker basées sur Alpine (`dunglas/frankenphp
|
||||
## Utilisation de `https://127.0.0.1` avec Docker
|
||||
|
||||
Par défaut, FrankenPHP génère un certificat TLS pour `localhost`.
|
||||
C'est l'option est la plus simple et est recommandée pour le développement local.
|
||||
C'est l'option la plus simple et recommandée pour le développement local.
|
||||
|
||||
Si vous voulez vraiment utiliser `127.0.0.1` comme hôte, il est possible de configure FrankenPHP pour générer un certificat pour cela en définissant le nom du serveur à `127.0.0.1`.
|
||||
Si vous voulez vraiment utiliser `127.0.0.1` comme hôte, il est possible de configurer FrankenPHP pour générer un certificat pour cela en définissant le nom du serveur à `127.0.0.1`.
|
||||
|
||||
Malheureusement, cela ne suffit pas lors de l'utilisation de Docker à cause de [son système de gestion des réseaux](https://docs.docker.com/network/).
|
||||
Vous obtiendrez une erreur TLS similaire à `curl: (35) LibreSSL/3.3.6: error:1404B438:SSL routines:ST_CONNECT:tlsv1 alert internal error`.
|
||||
@@ -76,7 +47,7 @@ docker run \
|
||||
|
||||
Le pilote de réseau "hôte" n'est pas pris en charge sur Mac et Windows. Sur ces plateformes, vous devrez deviner l'adresse IP du conteneur et l'inclure dans les noms de serveur.
|
||||
|
||||
Exécutez la commande `docker network inspect bridge` et inpectez la clef `Containers` pour identifier la dernière adresse IP attribuée sous la clef `IPv4Address`, puis incrémentez-la de un. Si aucun conteneur n'est en cours d'exécution, la première adresse IP attribuée est généralement `172.17.0.2`.
|
||||
Exécutez la commande `docker network inspect bridge` et inpectez la clef `Containers` pour identifier la dernière adresse IP attribuée sous la clef `IPv4Address`, puis incrémentez-la d'un. Si aucun conteneur n'est en cours d'exécution, la première adresse IP attribuée est généralement `172.17.0.2`.
|
||||
|
||||
Ensuite, incluez ceci dans la variable d'environnement `SERVER_NAME` :
|
||||
|
||||
@@ -88,7 +59,7 @@ docker run \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
> ![CAUTION]
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Assurez-vous de remplacer `172.17.0.3` par l'IP qui sera attribuée à votre conteneur.
|
||||
|
||||
@@ -104,3 +75,69 @@ docker run \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
## Scripts Composer Faisant Références à `@php`
|
||||
|
||||
Les [scripts Composer](https://getcomposer.org/doc/articles/scripts.md) peuvent vouloir exécuter un binaire PHP pour certaines tâches, par exemple dans [un projet Laravel](laravel.md) pour exécuter `@php artisan package:discover --ansi`. Cela [echoue actuellement](https://github.com/dunglas/frankenphp/issues/483#issuecomment-1899890915) pour deux raisons :
|
||||
|
||||
- Composer ne sait pas comment appeler le binaire FrankenPHP ;
|
||||
- Composer peut ajouter des paramètres PHP en utilisant le paramètre `-d` dans la commande, ce que FrankenPHP ne supporte pas encore.
|
||||
|
||||
Comme solution de contournement, nous pouvons créer un script shell dans `/usr/local/bin/php` qui supprime les paramètres non supportés et appelle ensuite 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[@]}
|
||||
```
|
||||
|
||||
Ensuite, mettez la variable d'environnement `PHP_BINARY` au chemin de notre script `php` et lancez Composer :
|
||||
|
||||
```console
|
||||
export PHP_BINARY=/usr/local/bin/php
|
||||
composer install
|
||||
```
|
||||
|
||||
## Résolution des problèmes TLS/SSL avec les binaires statiques
|
||||
|
||||
Lorsque vous utilisez les binaires statiques, vous pouvez rencontrer les erreurs suivantes liées à TLS, par exemple lors de l'envoi de courriels utilisant STARTTLS :
|
||||
|
||||
```text
|
||||
Unable to connect with STARTTLS: stream_socket_enable_crypto(): SSL operation failed with code 5. OpenSSL Error messages:
|
||||
error:80000002:system library::No such file or directory
|
||||
error:80000002:system library::No such file or directory
|
||||
error:80000002:system library::No such file or directory
|
||||
error:0A000086:SSL routines::certificate verify failed
|
||||
```
|
||||
|
||||
Comme le binaire statique ne contient pas de certificats TLS, vous devez indiquer à OpenSSL l'installation de vos certificats CA locaux.
|
||||
|
||||
Inspectez la sortie de [`openssl_get_cert_locations()`](https://www.php.net/manual/en/function.openssl-get-cert-locations.php),
|
||||
pour trouver l'endroit où les certificats CA doivent être installés et stockez-les à cet endroit.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> Les contextes Web et CLI peuvent avoir des paramètres différents.
|
||||
> Assurez-vous d'exécuter `openssl_get_cert_locations()` dans le bon contexte.
|
||||
|
||||
[Les certificats CA extraits de Mozilla peuvent être téléchargés sur le site curl](https://curl.se/docs/caextract.html).
|
||||
|
||||
Alternativement, de nombreuses distributions, y compris Debian, Ubuntu, et Alpine fournissent des paquets nommés `ca-certificates` qui contiennent ces certificats.
|
||||
|
||||
Il est également possible d'utiliser `SSL_CERT_FILE` et `SSL_CERT_DIR` pour indiquer à OpenSSL où chercher les certificats CA :
|
||||
|
||||
```console
|
||||
# Définir les variables d'environnement des certificats TLS
|
||||
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
export SSL_CERT_DIR=/etc/ssl/certs
|
||||
```
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
## Docker
|
||||
|
||||
Déployer une application web [Laravel](https://laravel.com) avec FrankenPHP est très facile.
|
||||
Il suffit de monter le projet dans le répertoire `/app` de l'image Docker officielle.
|
||||
Déployer une application web [Laravel](https://laravel.com) avec FrankenPHP est très facile. Il suffit de monter le projet dans le répertoire `/app` de l'image Docker officielle.
|
||||
|
||||
Exécutez cette commande depuis le répertoire principal de votre application Laravel :
|
||||
|
||||
@@ -17,27 +16,28 @@ Et profitez !
|
||||
|
||||
Vous pouvez également exécuter vos projets Laravel avec FrankenPHP depuis votre machine locale :
|
||||
|
||||
1. [Téléchargez le binaire correspondant à votre système](https://github.com/dunglas/frankenphp/releases)
|
||||
1. [Téléchargez le binaire correspondant à votre système](README.md#binaire-autonome)
|
||||
2. Ajoutez la configuration suivante dans un fichier nommé `Caddyfile` placé dans le répertoire racine de votre projet Laravel :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
order php_server before file_server
|
||||
}
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
|
||||
# Le nom de domaine de votre serveur
|
||||
localhost {
|
||||
# Définir le répertoire racine sur le dossier public/
|
||||
root * public/
|
||||
# Autoriser la compression (optionnel)
|
||||
encode zstd br gzip
|
||||
# Exécuter les scripts PHP du dossier public/ et servir les assets
|
||||
php_server
|
||||
}
|
||||
```
|
||||
# Le nom de domaine de votre serveur
|
||||
localhost {
|
||||
# Définir le répertoire racine sur le dossier public/
|
||||
root public/
|
||||
# Autoriser la compression (optionnel)
|
||||
encode zstd br gzip
|
||||
# Exécuter les scripts PHP du dossier public/ et servir les assets
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Démarrez FrankenPHP depuis le répertoire racine de votre projet Laravel : `./frankenphp run`
|
||||
3. Démarrez FrankenPHP depuis le répertoire racine de votre projet Laravel : `frankenphp run`
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
@@ -53,24 +53,132 @@ Après avoir installé Octane, vous pouvez exécuter la commande Artisan `octane
|
||||
php artisan octane:install --server=frankenphp
|
||||
```
|
||||
|
||||
Le serveur Octane peut être démarré via la commande Artisan `octane:start`.
|
||||
Le serveur Octane peut être démarré via la commande Artisan `octane:frankenphp`.
|
||||
|
||||
```console
|
||||
php artisan octane:start
|
||||
php artisan octane:frankenphp
|
||||
```
|
||||
|
||||
La commande `octane:start` peut prendre les options suivantes :
|
||||
La commande `octane:frankenphp` peut prendre les options suivantes :
|
||||
|
||||
* `--host` : L'adresse IP à laquelle le serveur doit se lier (par défaut : `127.0.0.1`)
|
||||
* `--port` : Le port sur lequel le serveur doit être disponible (par défaut : `8000`)
|
||||
* `--admin-port` : Le port sur lequel le serveur administratif doit être disponible (par défaut : `2019`)
|
||||
* `--workers` : Le nombre de workers qui doivent être disponibles pour traiter les requêtes (par défaut : `auto`)
|
||||
* `--max-requests` : Le nombre de requêtes à traiter avant de recharger le serveur (par défaut : `500`)
|
||||
* `--caddyfile` : Le chemin vers le fichier `Caddyfile` de FrankenPHP
|
||||
* `--https` : Activer HTTPS, HTTP/2, et HTTP/3, et générer automatiquement et renouveler les certificats
|
||||
* `--http-redirect` : Activer la redirection HTTP vers HTTPS (uniquement activé si --https est passé)
|
||||
* `--watch` : Recharger automatiquement le serveur lorsque l'application est modifiée
|
||||
* `--poll` : Utiliser le sondage du système de fichiers pendant la surveillance pour surveiller les fichiers sur un réseau
|
||||
* `--log-level` : Enregistrer les messages au niveau de journalisation spécifié ou au-dessus
|
||||
- `--host` : L'adresse IP à laquelle le serveur doit se lier (par défaut : `127.0.0.1`)
|
||||
- `--port` : Le port sur lequel le serveur doit être disponible (par défaut : `8000`)
|
||||
- `--admin-port` : Le port sur lequel le serveur administratif doit être disponible (par défaut : `2019`)
|
||||
- `--workers` : Le nombre de workers qui doivent être disponibles pour traiter les requêtes (par défaut : `auto`)
|
||||
- `--max-requests` : Le nombre de requêtes à traiter avant de recharger le serveur (par défaut : `500`)
|
||||
- `--caddyfile` : Le chemin vers le fichier `Caddyfile` de FrankenPHP
|
||||
- `--https` : Activer HTTPS, HTTP/2, et HTTP/3, et générer automatiquement et renouveler les certificats
|
||||
- `--http-redirect` : Activer la redirection HTTP vers HTTPS (uniquement activé si --https est passé)
|
||||
- `--watch` : Recharger automatiquement le serveur lorsque l'application est modifiée
|
||||
- `--poll` : Utiliser le sondage du système de fichiers pendant la surveillance pour surveiller les fichiers sur un réseau
|
||||
- `--log-level` : Enregistrer les messages au niveau de journalisation spécifié ou au-dessus, en utilisant le logger natif de Caddy
|
||||
|
||||
> [!TIP]
|
||||
> Pour obtenir des logs structurés en JSON logs (utile quand vous utilisez des solutions d'analyse de logs), passez explicitement l'option `--log-level`.
|
||||
|
||||
En savoir plus sur Laravel Octane [dans sa documentation officielle](https://laravel.com/docs/octane).
|
||||
|
||||
## Les Applications Laravel En Tant Que Binaires Autonomes
|
||||
|
||||
En utilisant la [fonctionnalité d'intégration d'applications de FrankenPHP](embed.md), il est possible de distribuer
|
||||
les applications Laravel sous forme de binaires autonomes.
|
||||
|
||||
Suivez ces étapes pour empaqueter votre application Laravel en tant que binaire autonome pour Linux :
|
||||
|
||||
1. Créez un fichier nommé `static-build.Dockerfile` dans le dépôt de votre application :
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# Copiez votre application
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Supprimez les tests et autres fichiers inutiles pour gagner de la place
|
||||
# Alternativement, ajoutez ces fichiers à un fichier .dockerignore
|
||||
RUN rm -Rf tests/
|
||||
|
||||
# Copiez le fichier .env
|
||||
RUN cp .env.example .env
|
||||
# Modifier APP_ENV et APP_DEBUG pour qu'ils soient prêts pour la production
|
||||
RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env
|
||||
|
||||
# Apportez d'autres modifications à votre fichier .env si nécessaire
|
||||
|
||||
# Installez les dépendances
|
||||
RUN composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# Construire le binaire statique
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Certains fichiers `.dockerignore` ignoreront le répertoire `vendor/`
|
||||
> et les fichiers `.env`. Assurez-vous d'ajuster ou de supprimer le fichier `.dockerignore` avant la construction.
|
||||
|
||||
2. Build:
|
||||
|
||||
```console
|
||||
docker build -t static-laravel-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Extraire le binaire
|
||||
|
||||
```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. Remplir les caches :
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan optimize
|
||||
```
|
||||
|
||||
5. Exécutez les migrations de base de données (s'il y en a) :
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan migrate
|
||||
```
|
||||
|
||||
6. Générer la clé secrète de l'application :
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan key:generate
|
||||
```
|
||||
|
||||
7. Démarrez le serveur:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Votre application est maintenant prête !
|
||||
|
||||
Pour en savoir plus sur les options disponibles et sur la construction de binaires pour d'autres systèmes d'exploitation,
|
||||
consultez la documentation [Applications PHP en tant que binaires autonomes](embed.md).
|
||||
|
||||
### Changer le chemin de stockage
|
||||
|
||||
Par défaut, Laravel stocke les fichiers téléchargés, les caches, les logs, etc. dans le répertoire `storage/` de l'application.
|
||||
Ceci n'est pas adapté aux applications embarquées, car chaque nouvelle version sera extraite dans un répertoire temporaire différent.
|
||||
|
||||
Définissez la variable d'environnement `LARAVEL_STORAGE_PATH` (par exemple, dans votre fichier `.env`) ou appelez la méthode `Illuminate\Foundation\Application::useStoragePath()` pour utiliser un répertoire en dehors du répertoire temporaire.
|
||||
|
||||
### Exécuter Octane avec des binaires autonomes
|
||||
|
||||
Il est même possible d'empaqueter les applications Laravel Octane en tant que binaires autonomes !
|
||||
|
||||
Pour ce faire, [installez Octane correctement](#laravel-octane) et suivez les étapes décrites dans [la section précédente](#les-applications-laravel-en-tant-que-binaires-autonomes).
|
||||
|
||||
Ensuite, pour démarrer FrankenPHP en mode worker via Octane, exécutez :
|
||||
|
||||
```console
|
||||
PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Pour que la commande fonctionne, le binaire autonome **doit** être nommé `frankenphp`
|
||||
> car Octane a besoin d'un programme nommé `frankenphp` disponible dans le chemin
|
||||
|
||||
@@ -5,7 +5,7 @@ Mercure permet de pousser des événements en temps réel vers tous les appareil
|
||||
|
||||
Aucune bibliothèque JS ou SDK requis !
|
||||
|
||||

|
||||

|
||||
|
||||
Pour activer le hub Mercure, mettez à jour le `Caddyfile` comme décrit [sur le site de Mercure](https://mercure.rocks/docs/hub/config).
|
||||
|
||||
|
||||
17
docs/fr/metrics.md
Normal file
17
docs/fr/metrics.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Métriques
|
||||
|
||||
Lorsque les [métriques Caddy](https://caddyserver.com/docs/metrics) sont activées, FrankenPHP expose les métriques suivantes :
|
||||
|
||||
- `frankenphp_total_threads` : Le nombre total de threads PHP.
|
||||
- `frankenphp_busy_threads` : Le nombre de threads PHP en cours de traitement d'une requête (les workers en cours d'exécution consomment toujours un thread).
|
||||
- `frankenphp_queue_depth` : Le nombre de requêtes régulières en file d'attente
|
||||
- `frankenphp_total_workers{worker=« [nom_du_worker] »}` : Le nombre total de workers.
|
||||
- `frankenphp_busy_workers{worker=« [nom_du_worker] »}` : Le nombre de workers qui traitent actuellement une requête.
|
||||
- `frankenphp_worker_request_time{worker=« [nom_du_worker] »}` : Le temps passé à traiter les requêtes par tous les workers.
|
||||
- `frankenphp_worker_request_count{worker=« [nom_du_worker] »}` : Le nombre de requêtes traitées par tous les workers.
|
||||
- `frankenphp_ready_workers{worker=« [nom_du_worker] »}` : Le nombre de workers qui ont appelé `frankenphp_handle_request` au moins une fois.
|
||||
- `frankenphp_worker_crashes{worker=« [nom_du_worker] »}` : Le nombre de fois où un worker s'est arrêté de manière inattendue.
|
||||
- `frankenphp_worker_restarts{worker=« [nom_du_worker] »}` : Le nombre de fois où un worker a été délibérément redémarré.
|
||||
- `frankenphp_worker_queue_depth{worker=« [nom_du_worker] »}` : Le nombre de requêtes en file d'attente.
|
||||
|
||||
Pour les métriques de worker, le placeholder `[nom_du_worker]` est remplacé par le nom du worker dans le Caddyfile, sinon le chemin absolu du fichier du worker sera utilisé.
|
||||
157
docs/fr/performance.md
Normal file
157
docs/fr/performance.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Performance
|
||||
|
||||
Par défaut, FrankenPHP essaie d'offrir un bon compromis entre performance et facilité d'utilisation.
|
||||
Cependant, il est possible d'améliorer considérablement les performances en utilisant une configuration appropriée.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
Pour trouver les bonnes valeurs, il est souhaitable 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`.
|
||||
|
||||
### `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.
|
||||
`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.
|
||||
|
||||
## Mode worker
|
||||
|
||||
Activer [le mode worker](worker.md) améliore considérablement les performances,
|
||||
mais votre application doit être adaptée pour être compatible avec ce mode :
|
||||
vous devez créer un script worker et vous assurer que l'application n'a pas de fuite de mémoire.
|
||||
|
||||
## 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).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Cela peut être réalisé en utilisant les images Docker Debian (par défaut) et [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).
|
||||
|
||||
## Configuration du runtime Go
|
||||
|
||||
FrankenPHP est écrit en Go.
|
||||
|
||||
En général, le runtime Go ne nécessite pas de configuration particulière, mais dans certaines circonstances,
|
||||
une configuration spécifique améliore les performances.
|
||||
|
||||
Vous voudrez probablement mettre la variable d'environnement `GODEBUG` à `cgocheck=0` (la valeur par défaut dans les images Docker de FrankenPHP).
|
||||
|
||||
Si vous exécutez FrankenPHP dans des conteneurs (Docker, Kubernetes, LXC...) et que vous limitez la mémoire disponible pour les conteneurs,
|
||||
mettez la variable d'environnement `GOMEMLIMIT` à la quantité de mémoire disponible.
|
||||
|
||||
Pour plus de détails, [la page de documentation Go dédiée à ce sujet](https://pkg.go.dev/runtime#hdr-Environment_Variables) est à lire absolument pour tirer le meilleur parti du runtime.
|
||||
|
||||
## `file_server`
|
||||
|
||||
Par défaut, la directive `php_server` met automatiquement en place un serveur de fichiers
|
||||
pour servir les fichiers statiques (assets) stockés dans le répertoire racine.
|
||||
|
||||
Cette fonctionnalité est pratique, mais a un coût.
|
||||
Pour la désactiver, utilisez la configuration suivante :
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
file_server off
|
||||
}
|
||||
```
|
||||
|
||||
## `try_files`
|
||||
|
||||
En plus des fichiers statiques et des fichiers PHP, `php_server` essaiera aussi de servir les fichiers d'index
|
||||
et d'index de répertoire de votre application (`/path/` -> `/path/index.php`). Si vous n'avez pas besoin des index de répertoires,
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
Cela permet de réduire considérablement le nombre d'opérations inutiles sur les fichiers.
|
||||
|
||||
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.
|
||||
Un exemple de [configuration](config.md#configuration-du-caddyfile) qui sert des fichiers statiques derrière un dossier `/assets` pourrait ressembler à ceci :
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
@assets {
|
||||
path /assets/*
|
||||
}
|
||||
|
||||
# tout ce qui se trouve derrière /assets est géré par le serveur de fichiers
|
||||
file_server @assets {
|
||||
root /root/to/your/app
|
||||
}
|
||||
|
||||
# tout ce qui n'est pas dans /assets est géré par votre index ou votre fichier PHP worker
|
||||
rewrite index.php
|
||||
php {
|
||||
root /root/to/your/app # l'ajout explicite de la racine ici permet une meilleure mise en cache
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## _Placeholders_
|
||||
|
||||
Vous pouvez utiliser des [_placeholders_](https://caddyserver.com/docs/conventions#placeholders) dans les directives `root` et `env`.
|
||||
Cependant, cela empêche la mise en cache de ces valeurs et a un coût important en termes de performances.
|
||||
|
||||
Si possible, évitez les _placeholders_ dans ces directives.
|
||||
|
||||
## `resolve_root_symlink`
|
||||
|
||||
Par défaut, si le _document root_ est un lien symbolique, il est automatiquement résolu par FrankenPHP (c'est nécessaire pour le bon fonctionnement de PHP).
|
||||
Si la racine du document n'est pas un lien symbolique, vous pouvez désactiver cette fonctionnalité.
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
resolve_root_symlink false
|
||||
}
|
||||
```
|
||||
|
||||
Cela améliorera les performances si la directive `root` contient des [_placeholders_](https://caddyserver.com/docs/conventions#placeholders).
|
||||
Le gain sera négligeable dans les autres cas.
|
||||
|
||||
## Journaux
|
||||
|
||||
La journalisation est évidemment très utile, mais, par définition, elle nécessite des opérations d'_I/O_ et des allocations de mémoire,
|
||||
ce qui réduit considérablement les performances.
|
||||
Assurez-vous de [définir le niveau de journalisation](https://caddyserver.com/docs/caddyfile/options#log) correctement,
|
||||
et de ne journaliser que ce qui est nécessaire.
|
||||
|
||||
## Performances de PHP
|
||||
|
||||
FrankenPHP utilise l'interpréteur PHP officiel.
|
||||
Toutes les optimisations de performances habituelles liées à PHP s'appliquent à FrankenPHP.
|
||||
|
||||
En particulier :
|
||||
|
||||
- vérifiez que [OPcache](https://www.php.net/manual/en/book.opcache.php) est installé, activé et correctement configuré
|
||||
- activez [les optimisations de l'autoloader de Composer](https://getcomposer.org/doc/articles/autoloader-optimization.md)
|
||||
- assurez-vous que le cache `realpath` est suffisamment grand pour les besoins de votre application
|
||||
- utilisez le [pré-chargement](https://www.php.net/manual/en/opcache.preloading.php)
|
||||
|
||||
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).
|
||||
@@ -4,20 +4,49 @@ Au lieu d'utiliser une installation locale de la bibliothèque PHP, il est possi
|
||||
|
||||
Avec cette méthode, un binaire portable unique contiendra l'interpréteur PHP, le serveur web Caddy et FrankenPHP !
|
||||
|
||||
Les exécutables natifs entièrement statiques ne nécessitent aucune dépendance et peuvent même être exécutés sur une [image Docker `scratch`](https://docs.docker.com/build/building/base-images/#create-a-minimal-base-image-using-scratch).
|
||||
Cependant, ils ne peuvent pas charger les extensions dynamiques de PHP (comme Xdebug) et ont quelques limitations parce qu'ils utilisent la librairie musl.
|
||||
|
||||
La plupart des binaires statiques ne nécessitent que la `glibc` et peuvent charger des extensions dynamiques.
|
||||
|
||||
Lorsque c'est possible, nous recommandons d'utiliser des binaires statiques basés sur la glibc.
|
||||
|
||||
FrankenPHP permet également [d'embarquer l'application PHP dans le binaire statique](embed.md).
|
||||
|
||||
## Linux
|
||||
|
||||
Nous fournissons une image Docker pour créer un binaire statique pour Linux :
|
||||
Nous fournissons des images Docker pour créer des binaires statiques pour Linux :
|
||||
|
||||
### Build entièrement statique, basé sur musl
|
||||
|
||||
Pour un binaire entièrement statique qui fonctionne sur n'importe quelle distribution Linux sans dépendances,
|
||||
mais qui ne prend pas en charge le chargement dynamique des extensions :
|
||||
|
||||
```console
|
||||
docker buildx bake --load static-builder
|
||||
docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder
|
||||
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
|
||||
```
|
||||
|
||||
Le binaire statique résultant est nommé `frankenphp`, et il est disponible dans le répertoire courant.
|
||||
Pour améliorer les performances dans les scénarios fortement concurrents, envisagez d'utiliser l'allocateur [mimalloc](https://github.com/microsoft/mimalloc).
|
||||
|
||||
Si vous souhaitez construire le binaire statique sans Docker, regardez les instructions pour macOS, qui fonctionnent également pour Linux.
|
||||
```console
|
||||
docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl
|
||||
```
|
||||
|
||||
### Construction principalement statique (avec prise en charge des extensions dynamiques), basé sur la glibc
|
||||
|
||||
Pour un binaire qui supporte le chargement dynamique des extensions PHP tout en ayant les extensions sélectionnées compilées statiquement :
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Ce binaire supporte toutes les versions 2.17 et supérieures de la glibc mais ne fonctionne pas sur les systèmes basés sur musl (comme Alpine Linux).
|
||||
|
||||
Le binaire résultant, principalement statique (à l'exception de `glibc`), est nommé `frankenphp` et est disponible dans le répertoire courant.
|
||||
|
||||
Si vous voulez construire le binaire statique sans Docker, jetez un coup d'œil aux instructions pour macOS, qui fonctionnent aussi pour Linux.
|
||||
|
||||
### Extensions personnalisées
|
||||
|
||||
@@ -28,7 +57,7 @@ Pour réduire la taille du binaire et diminuer la surface d'attaque, vous pouvez
|
||||
Par exemple, exécutez la commande suivante pour ne construire que l'extension `opcache` :
|
||||
|
||||
```console
|
||||
docker buildx bake --load --set static-builder.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder
|
||||
docker buildx bake --load --set static-builder-musl.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder-musl
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -37,11 +66,29 @@ Pour ajouter des bibliothèques permettant des fonctionnalités supplémentaires
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.args.PHP_EXTENSIONS=gd \
|
||||
--set static-builder.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \
|
||||
static-builder
|
||||
--set static-builder-musl.args.PHP_EXTENSIONS=gd \
|
||||
--set static-builder-musl.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \
|
||||
static-builder-musl
|
||||
```
|
||||
|
||||
### Modules supplémentaires de Caddy
|
||||
|
||||
Pour ajouter des modules Caddy supplémentaires ou passer d'autres arguments à [xcaddy](https://github.com/caddyserver/xcaddy), utilisez l'argument 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
|
||||
```
|
||||
|
||||
Dans cet exemple, nous ajoutons le module de cache HTTP [Souin](https://souin.io) pour Caddy ainsi que les modules [cbrotli](https://github.com/dunglas/caddy-cbrotli), [Mercure](https://mercure.rocks) et [Vulcain](https://vulcain.rocks).
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Les modules cbrotli, Mercure et Vulcain sont inclus par défaut si `XCADDY_ARGS` est vide ou n'est pas défini.
|
||||
> Si vous personnalisez la valeur de `XCADDY_ARGS`, vous devez les inclure explicitement si vous voulez qu'ils soient inclus.
|
||||
|
||||
Voir aussi comment [personnaliser la construction](#personnalisation-de-la-construction)
|
||||
|
||||
### Jeton GitHub
|
||||
@@ -49,7 +96,7 @@ Voir aussi comment [personnaliser la construction](#personnalisation-de-la-const
|
||||
Si vous atteignez la limite de taux d'appels de l'API GitHub, définissez un jeton d'accès personnel GitHub dans une variable d'environnement nommée `GITHUB_TOKEN` :
|
||||
|
||||
```console
|
||||
GITHUB_TOKEN="xxx" docker --load buildx bake static-builder
|
||||
GITHUB_TOKEN="xxx" docker --load buildx bake static-builder-musl
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -69,13 +116,46 @@ Note : ce script fonctionne également sur Linux (et probablement sur d'autres U
|
||||
|
||||
Les variables d'environnement suivantes peuvent être transmises à `docker build` et au script `build-static.sh` pour personnaliser la construction statique :
|
||||
|
||||
* `FRANKENPHP_VERSION` : la version de FrankenPHP à utiliser
|
||||
* `PHP_VERSION` : la version de PHP à utiliser
|
||||
* `PHP_EXTENSIONS` : les extensions PHP à construire ([liste des extensions prises en charge](https://static-php.dev/en/guide/extensions.html))
|
||||
* `PHP_EXTENSION_LIBS` : bibliothèques supplémentaires à construire qui ajoutent des fonctionnalités aux extensions
|
||||
* `EMBED` : chemin de l'application PHP à intégrer dans le binaire
|
||||
* `CLEAN` : lorsque défini, `libphp` et toutes ses dépendances sont construites à partir de zéro (pas de cache)
|
||||
* `DEBUG_SYMBOLS` : lorsque défini, les symboles de débogage ne seront pas supprimés et seront ajoutés dans le binaire
|
||||
* `NO_COMPRESS`: ne pas compresser le binaire avec UPX
|
||||
* `MIMALLOC`: (expérimental, Linux seulement) remplace l'allocateur mallocng de musl par [mimalloc](https://github.com/microsoft/mimalloc) pour des performances améliorées
|
||||
* `RELEASE` : (uniquement pour les mainteneurs) lorsque défini, le binaire résultant sera uploadé sur GitHub
|
||||
- `FRANKENPHP_VERSION` : la version de FrankenPHP à utiliser
|
||||
- `PHP_VERSION` : la version de PHP à utiliser
|
||||
- `PHP_EXTENSIONS` : les extensions PHP à construire ([liste des extensions prises en charge](https://static-php.dev/en/guide/extensions.html))
|
||||
- `PHP_EXTENSION_LIBS` : bibliothèques supplémentaires à construire qui ajoutent des fonctionnalités aux extensions
|
||||
- `XCADDY_ARGS` : arguments à passer à [xcaddy](https://github.com/caddyserver/xcaddy), par exemple pour ajouter des modules Caddy supplémentaires
|
||||
- `EMBED` : chemin de l'application PHP à intégrer dans le binaire
|
||||
- `CLEAN` : lorsque défini, `libphp` et toutes ses dépendances sont construites à partir de zéro (pas de cache)
|
||||
- `DEBUG_SYMBOLS` : lorsque défini, les symboles de débogage ne seront pas supprimés et seront ajoutés dans le binaire
|
||||
- `NO_COMPRESS`: ne pas compresser le binaire avec UPX
|
||||
- `MIMALLOC`: (expérimental, Linux seulement) remplace l'allocateur mallocng de musl par [mimalloc](https://github.com/microsoft/mimalloc) pour des performances améliorées
|
||||
- `RELEASE` : (uniquement pour les mainteneurs) lorsque défini, le binaire résultant sera uploadé sur GitHub
|
||||
|
||||
## Extensions
|
||||
|
||||
Avec la glibc ou les binaires basés sur macOS, vous pouvez charger des extensions PHP dynamiquement. Cependant, ces extensions devront être compilées avec le support ZTS.
|
||||
Comme la plupart des gestionnaires de paquets ne proposent pas de versions ZTS de leurs extensions, vous devrez les compiler vous-même.
|
||||
|
||||
Pour cela, vous pouvez construire et exécuter le conteneur Docker `static-builder-gnu`, vous y connecter à distance et compiler les extensions avec `./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config`.
|
||||
|
||||
Exemple d'étapes pour [l'extension 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
|
||||
```
|
||||
|
||||
Cela aura créé `frankenphp` et `xdebug-zts.so` dans le répertoire courant.
|
||||
Si vous déplacez `xdebug-zts.so` dans votre répertoire d'extension, ajoutez `zend_extension=xdebug-zts.so` à votre php.ini
|
||||
et lancez FrankenPHP, il chargera Xdebug.
|
||||
|
||||
@@ -22,12 +22,19 @@ docker run \
|
||||
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
|
||||
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.
|
||||
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`.
|
||||
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"
|
||||
```
|
||||
|
||||
## Runtime Symfony
|
||||
|
||||
Le mode worker de FrankenPHP est pris en charge par le [Composant Runtime de Symfony](https://symfony.com/doc/current/components/runtime.html).
|
||||
@@ -71,19 +78,22 @@ $myApp->boot();
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// Faire quelque chose après l'envoi de la réponse HTTP
|
||||
$myApp->terminate();
|
||||
|
||||
// Exécuter le ramasse-miettes pour réduire les chances qu'il soit déclenché au milieu de la génération d'une page
|
||||
gc_collect_cycles();
|
||||
|
||||
if (!$keepRunning) break;
|
||||
}
|
||||
|
||||
// Nettoyage
|
||||
@@ -117,3 +127,48 @@ Comme PHP n'a pas été initialement conçu pour des processus de longue durée,
|
||||
Une solution pour utiliser ce type de code en mode worker est de redémarrer le script worker après avoir traité un certain nombre de requêtes :
|
||||
|
||||
Le code du worker précédent permet de configurer un nombre maximal de requêtes à traiter en définissant une variable d'environnement nommée `MAX_REQUESTS`.
|
||||
|
||||
### 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),
|
||||
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
|
||||
à 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
|
||||
|
||||
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).
|
||||
|
||||
## Comportement des superglobales
|
||||
|
||||
[Les superglobales PHP](https://www.php.net/manual/fr/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET`...)
|
||||
se comportent comme suit :
|
||||
|
||||
- avant le premier appel à `frankenphp_handle_request()`, les superglobales contiennent des valeurs liées au script worker lui-même
|
||||
- pendant et après l'appel à `frankenphp_handle_request()`, les superglobales contiennent des valeurs générées à partir de la requête HTTP traitée, chaque appel à `frankenphp_handle_request()` change les valeurs des superglobales
|
||||
|
||||
Pour accéder aux superglobales du script worker à l'intérieur de la fonction de rappel, vous devez les copier et importer la copie dans le scope de la fonction :
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Copier la superglobale $_SERVER du worker avant le premier appel à frankenphp_handle_request()
|
||||
$workerServer = $_SERVER;
|
||||
|
||||
$handler = static function () use ($workerServer) {
|
||||
var_dump($_SERVER); // $_SERVER lié à la requête
|
||||
var_dump($workerServer); // $_SERVER du script worker
|
||||
};
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
71
docs/fr/x-sendfile.md
Normal file
71
docs/fr/x-sendfile.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Servir efficacement les gros fichiers statiques (`X-Sendfile`/`X-Accel-Redirect`)
|
||||
|
||||
Habituellement, les fichiers statiques peuvent être servis directement par le serveur web,
|
||||
mais parfois, il est nécessaire d'exécuter du code PHP avant de les envoyer :
|
||||
contrôle d'accès, statistiques, en-têtes HTTP personnalisés...
|
||||
|
||||
Malheureusement, utiliser PHP pour servir de gros fichiers statiques est inefficace comparé à
|
||||
à l'utilisation directe du serveur web (surcharge mémoire, diminution des performances...).
|
||||
|
||||
FrankenPHP permet de déléguer l'envoi des fichiers statiques au serveur web
|
||||
**après** avoir exécuté du code PHP personnalisé.
|
||||
|
||||
Pour ce faire, votre application PHP n'a qu'à définir un en-tête HTTP personnalisé
|
||||
contenant le chemin du fichier à servir. FrankenPHP se chargera du reste.
|
||||
|
||||
Cette fonctionnalité est connue sous le nom de **`X-Sendfile`** pour Apache, et **`X-Accel-Redirect`** pour NGINX.
|
||||
|
||||
Dans les exemples suivants, nous supposons que le "document root" du projet est le répertoire `public/`
|
||||
et que nous voulons utiliser PHP pour servir des fichiers stockés en dehors du dossier `public/`,
|
||||
depuis un répertoire nommé `private-files/`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Tout d'abord, ajoutez la configuration suivante à votre `Caddyfile` pour activer cette fonctionnalité :
|
||||
|
||||
```patch
|
||||
root public/
|
||||
# ...
|
||||
|
||||
+ # Needed for Symfony, Laravel and other projects using the Symfony HttpFoundation component
|
||||
+ 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
|
||||
+
|
||||
+ # Remove the X-Accel-Redirect header set by PHP for increased security
|
||||
+ header -X-Accel-Redirect
|
||||
+
|
||||
+ file_server
|
||||
+ }
|
||||
+ }
|
||||
|
||||
php_server
|
||||
```
|
||||
|
||||
## PHP simple
|
||||
|
||||
Définissez le chemin relatif du fichier (à partir de `private-files/`) comme valeur de l'en-tête `X-Accel-Redirect` :
|
||||
|
||||
```php
|
||||
header('X-Accel-Redirect: file.txt') ;
|
||||
```
|
||||
|
||||
## Projets utilisant le composant Symfony HttpFoundation (Symfony, Laravel, Drupal...)
|
||||
|
||||
Symfony HttpFoundation [supporte nativement cette fonctionnalité](https://symfony.com/doc/current/components/http_foundation.html#serving-files).
|
||||
Il va automatiquement déterminer la bonne valeur pour l'en-tête `X-Accel-Redirect` et l'ajoutera à la réponse.
|
||||
|
||||
```php
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
BinaryFileResponse::trustXSendfileTypeHeader();
|
||||
$response = new BinaryFileResponse(__DIR__.'/../private-files/file.txt');
|
||||
|
||||
// ...
|
||||
```
|
||||
@@ -8,8 +8,8 @@ every approved pull request or on your own fork once setup.
|
||||
In the repository settings, under secrets, add the following secrets:
|
||||
|
||||
- `REGISTRY_LOGIN_SERVER`: The docker registry to use (e.g. `docker.io`).
|
||||
- `REGISTRY_USERNAME`: The username to use to login to the registry (e.g. `dunglas`).
|
||||
- `REGISTRY_PASSWORD`: The password to use to login to the registry (e.g. an access key).
|
||||
- `REGISTRY_USERNAME`: The username to use to log in to the registry (e.g. `dunglas`).
|
||||
- `REGISTRY_PASSWORD`: The password to use to log in to the registry (e.g. an access key).
|
||||
- `IMAGE_NAME`: The name of the image (e.g. `dunglas/frankenphp`).
|
||||
|
||||
## Building and Pushing the Image
|
||||
|
||||
@@ -1,50 +1,20 @@
|
||||
# Known Issues
|
||||
|
||||
## Fibers
|
||||
|
||||
Calling PHP functions and language constructs that themselves call [cgo](https://go.dev/blog/cgo) in [Fibers](https://www.php.net/manual/en/language.fibers.php) is known to cause crashes.
|
||||
|
||||
This issue [is being worked on by the Go project](https://github.com/golang/go/issues/62130).
|
||||
|
||||
In the meantime, one solution is not to use constructs (like `echo`) and functions (like `header()`) that delegate to Go from inside Fibers.
|
||||
|
||||
This code will likely crash because it uses `echo` in the Fiber:
|
||||
|
||||
```php
|
||||
$fiber = new Fiber(function() {
|
||||
echo 'In the Fiber'.PHP_EOL;
|
||||
echo 'Still inside'.PHP_EOL;
|
||||
});
|
||||
$fiber->start();
|
||||
```
|
||||
|
||||
Instead, return the value from the Fiber and use it outside:
|
||||
|
||||
```php
|
||||
$fiber = new Fiber(function() {
|
||||
Fiber::suspend('In the Fiber'.PHP_EOL));
|
||||
Fiber::suspend('Still inside'.PHP_EOL));
|
||||
});
|
||||
echo $fiber->start();
|
||||
echo $fiber->resume();
|
||||
$fiber->resume();
|
||||
```
|
||||
|
||||
## Unsupported PHP Extensions
|
||||
|
||||
The following extensions are known not to be compatible with FrankenPHP:
|
||||
|
||||
| Name | Reason | Alternatives |
|
||||
| ---------------------------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/en/imap.installation.php) | Not 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/)| Not thread-safe | - |
|
||||
| Name | Reason | Alternatives |
|
||||
| ----------------------------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/en/imap.installation.php) | Not 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/) | Not thread-safe | - |
|
||||
|
||||
## Buggy PHP Extensions
|
||||
|
||||
The following extensions have known bugs and unexpected behaviors when used with FrankenPHP:
|
||||
|
||||
| Name | Problem |
|
||||
|---------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | When using a static build of FrankenPHP (built with the musl libc), the OpenSSL extension may crash under heavy loads. A workaround is to use a dynamically linked build (like the one used in Docker images). This bug is [being tracked by PHP](https://github.com/php/php-src/issues/13648). |
|
||||
|
||||
## get_browser
|
||||
@@ -110,8 +80,8 @@ docker run \
|
||||
|
||||
[Composer scripts](https://getcomposer.org/doc/articles/scripts.md) may want to execute a PHP binary for some tasks, e.g. in [a Laravel project](laravel.md) to run `@php artisan package:discover --ansi`. This [currently fails](https://github.com/dunglas/frankenphp/issues/483#issuecomment-1899890915) for two reasons:
|
||||
|
||||
* Composer does not know how to call the FrankenPHP binary;
|
||||
* Composer may add PHP settings using the `-d` flag in the command, which FrankenPHP does not yet support.
|
||||
- Composer does not know how to call the FrankenPHP binary;
|
||||
- Composer may add PHP settings using the `-d` flag in the command, which FrankenPHP does not yet support.
|
||||
|
||||
As a workaround, we can create a shell script in `/usr/local/bin/php` which strips the unsupported parameters and then calls FrankenPHP:
|
||||
|
||||
@@ -131,9 +101,43 @@ done
|
||||
/usr/local/bin/frankenphp php-cli ${args[@]}
|
||||
```
|
||||
|
||||
Then set the environment variable `PHP_BINARY` to the path of our php script and composer should pass:
|
||||
Then set the environment variable `PHP_BINARY` to the path of our `php` script and run Composer:
|
||||
|
||||
```bash
|
||||
```console
|
||||
export PHP_BINARY=/usr/local/bin/php
|
||||
composer install
|
||||
```
|
||||
|
||||
## Troubleshooting TLS/SSL Issues with Static Binaries
|
||||
|
||||
When using the static binaries, you may encounter the following TLS-related errors, for instance when sending emails using STARTTLS:
|
||||
|
||||
```text
|
||||
Unable to connect with STARTTLS: stream_socket_enable_crypto(): SSL operation failed with code 5. OpenSSL Error messages:
|
||||
error:80000002:system library::No such file or directory
|
||||
error:80000002:system library::No such file or directory
|
||||
error:80000002:system library::No such file or directory
|
||||
error:0A000086:SSL routines::certificate verify failed
|
||||
```
|
||||
|
||||
As the static binary doesn't bundle TLS certificates, you need to point OpenSSL to your local CA certificates installation.
|
||||
|
||||
Inspect the output of [`openssl_get_cert_locations()`](https://www.php.net/manual/en/function.openssl-get-cert-locations.php),
|
||||
to find where CA certificates must be installed and store them at this location.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> Web and CLI contexts may have different settings.
|
||||
> Be sure to run `openssl_get_cert_locations()` in the proper context.
|
||||
|
||||
[CA certificates extracted from Mozilla can be downloaded on the curl site](https://curl.se/docs/caextract.html).
|
||||
|
||||
Alternatively, many distributions, including Debian, Ubuntu, and Alpine provide packages named `ca-certificates` that contain these certificates.
|
||||
|
||||
It's also possible to use the `SSL_CERT_FILE` and `SSL_CERT_DIR` to hint OpenSSL where to look for CA certificates:
|
||||
|
||||
```console
|
||||
# Set TLS certificates environment variables
|
||||
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
export SSL_CERT_DIR=/etc/ssl/certs
|
||||
```
|
||||
|
||||
171
docs/laravel.md
171
docs/laravel.md
@@ -16,27 +16,28 @@ And enjoy!
|
||||
|
||||
Alternatively, you can run your Laravel projects with FrankenPHP from your local machine:
|
||||
|
||||
1. [Download the binary corresponding to your system](https://github.com/dunglas/frankenphp/releases)
|
||||
1. [Download the binary corresponding to your system](../#standalone-binary)
|
||||
2. Add the following configuration to a file named `Caddyfile` in the root directory of your Laravel project:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
order php_server before file_server
|
||||
}
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
|
||||
# The domain name of your server
|
||||
localhost {
|
||||
# Set the webroot to the public/ directory
|
||||
root * public/
|
||||
# Enable compression (optional)
|
||||
encode zstd br gzip
|
||||
# Execute PHP files from the public/ directory and serve assets
|
||||
php_server
|
||||
}
|
||||
```
|
||||
# The domain name of your server
|
||||
localhost {
|
||||
# Set the webroot to the public/ directory
|
||||
root public/
|
||||
# Enable compression (optional)
|
||||
encode zstd br gzip
|
||||
# Execute PHP files from the public/ directory and serve assets
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Start FrankenPHP from the root directory of your Laravel project: `./frankenphp run`
|
||||
3. Start FrankenPHP from the root directory of your Laravel project: `frankenphp run`
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
@@ -52,24 +53,132 @@ After installing Octane, you may execute the `octane:install` Artisan command, w
|
||||
php artisan octane:install --server=frankenphp
|
||||
```
|
||||
|
||||
The Octane server can be started via the `octane:start` Artisan command.
|
||||
The Octane server can be started via the `octane:frankenphp` Artisan command.
|
||||
|
||||
```console
|
||||
php artisan octane:start
|
||||
php artisan octane:frankenphp
|
||||
```
|
||||
|
||||
The `octane:start` command can take the following options:
|
||||
The `octane:frankenphp` command can take the following options:
|
||||
|
||||
* `--host`: The IP address the server should bind to (default: `127.0.0.1`)
|
||||
* `--port`: The port the server should be available on (default: `8000`)
|
||||
* `--admin-port`: The port the admin server should be available on (default: `2019`)
|
||||
* `--workers`: The number of workers that should be available to handle requests (default: `auto`)
|
||||
* `--max-requests`: The number of requests to process before reloading the server (default: `500`)
|
||||
* `--caddyfile`: The path to the FrankenPHP `Caddyfile` file
|
||||
* `--https`: Enable HTTPS, HTTP/2, and HTTP/3, and automatically generate and renew certificates
|
||||
* `--http-redirect`: Enable HTTP to HTTPS redirection (only enabled if --https is passed)
|
||||
* `--watch`: Automatically reload the server when the application is modified
|
||||
* `--poll`: Use file system polling while watching in order to watch files over a network
|
||||
* `--log-level`: Log messages at or above the specified log level
|
||||
- `--host`: The IP address the server should bind to (default: `127.0.0.1`)
|
||||
- `--port`: The port the server should be available on (default: `8000`)
|
||||
- `--admin-port`: The port the admin server should be available on (default: `2019`)
|
||||
- `--workers`: The number of workers that should be available to handle requests (default: `auto`)
|
||||
- `--max-requests`: The number of requests to process before reloading the server (default: `500`)
|
||||
- `--caddyfile`: The path to the FrankenPHP `Caddyfile` file (default: [stubbed `Caddyfile` in Laravel Octane](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile))
|
||||
- `--https`: Enable HTTPS, HTTP/2, and HTTP/3, and automatically generate and renew certificates
|
||||
- `--http-redirect`: Enable HTTP to HTTPS redirection (only enabled if --https is passed)
|
||||
- `--watch`: Automatically reload the server when the application is modified
|
||||
- `--poll`: Use file system polling while watching in order to watch files over a network
|
||||
- `--log-level`: Log messages at or above the specified log level, using the native Caddy logger
|
||||
|
||||
> [!TIP]
|
||||
> To get structured JSON logs (useful when using log analytics solutions), explicitly the pass `--log-level` option.
|
||||
|
||||
Learn more about [Laravel Octane in its official documentation](https://laravel.com/docs/octane).
|
||||
|
||||
## Laravel Apps As Standalone Binaries
|
||||
|
||||
Using [FrankenPHP's application embedding feature](embed.md), it's possible to distribute Laravel
|
||||
apps as standalone binaries.
|
||||
|
||||
Follow these steps to package your Laravel app as a standalone binary for Linux:
|
||||
|
||||
1. Create a file named `static-build.Dockerfile` in the repository of your app:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# Copy your app
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Remove the tests and other unneeded files to save space
|
||||
# Alternatively, add these files to a .dockerignore file
|
||||
RUN rm -Rf tests/
|
||||
|
||||
# Copy .env file
|
||||
RUN cp .env.example .env
|
||||
# Change APP_ENV and APP_DEBUG to be production ready
|
||||
RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env
|
||||
|
||||
# Make other changes to your .env file if needed
|
||||
|
||||
# Install the dependencies
|
||||
RUN composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# Build the static binary
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Some `.dockerignore` files
|
||||
> will ignore the `vendor/` directory and `.env` files. Be sure to adjust or remove the `.dockerignore` file before the build.
|
||||
|
||||
2. Build:
|
||||
|
||||
```console
|
||||
docker build -t static-laravel-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Extract the binary:
|
||||
|
||||
```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. Populate caches:
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan optimize
|
||||
```
|
||||
|
||||
5. Run database migrations (if any):
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan migrate
|
||||
```
|
||||
|
||||
6. Generate app's secret key:
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan key:generate
|
||||
```
|
||||
|
||||
7. Start the server:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Your app is now ready!
|
||||
|
||||
Learn more about the options available and how to build binaries for other OSes in the [applications embedding](embed.md)
|
||||
documentation.
|
||||
|
||||
### Changing The Storage Path
|
||||
|
||||
By default, Laravel stores uploaded files, caches, logs, etc. in the application's `storage/` directory.
|
||||
This is not suitable for embedded applications, as each new version will be extracted into a different temporary directory.
|
||||
|
||||
Set the `LARAVEL_STORAGE_PATH` environment variable (for example, in your `.env` file) or call the `Illuminate\Foundation\Application::useStoragePath()` method to use a directory outside the temporary directory.
|
||||
|
||||
### Running Octane With Standalone Binaries
|
||||
|
||||
It's even possible to package Laravel Octane apps as standalone binaries!
|
||||
|
||||
To do so, [install Octane properly](#laravel-octane) and follow the steps described in [the previous section](#laravel-apps-as-standalone-binaries).
|
||||
|
||||
Then, to start FrankenPHP in worker mode through Octane, run:
|
||||
|
||||
```console
|
||||
PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> For the command to work, the standalone binary **must** be named `frankenphp`
|
||||
> because Octane needs a program named `frankenphp` available in the path.
|
||||
|
||||
BIN
docs/mercure-hub.png
Normal file
BIN
docs/mercure-hub.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1009 KiB |
@@ -1,12 +1,15 @@
|
||||
# Real-time
|
||||
|
||||
FrankenPHP comes with a built-in [Mercure](https://mercure.rocks) hub!
|
||||
Mercure allows to push events in real-time to all the connected devices: they will receive a JavaScript event instantly.
|
||||
Mercure allows you to push real-time events to all the connected devices: they will receive a JavaScript event instantly.
|
||||
|
||||
No JS library or SDK required!
|
||||
No JS library or SDK is required!
|
||||
|
||||

|
||||

|
||||
|
||||
To enable the Mercure hub, update the `Caddyfile` as described [on Mercure's site](https://mercure.rocks/docs/hub/config).
|
||||
|
||||
To push Mercure updates from your code, we recommend the [Symfony Mercure Component](https://symfony.com/components/Mercure) (you don't need the Symfony full stack framework to use it).
|
||||
The path of the Mercure hub is `/.well-known/mercure`.
|
||||
When running FrankenPHP inside Docker, the full send URL would look like `http://php/.well-known/mercure` (with `php` being the container's name running FrankenPHP).
|
||||
|
||||
To push Mercure updates from your code, we recommend the [Symfony Mercure Component](https://symfony.com/components/Mercure) (you don't need the Symfony full-stack framework to use it).
|
||||
|
||||
17
docs/metrics.md
Normal file
17
docs/metrics.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Metrics
|
||||
|
||||
When [Caddy metrics](https://caddyserver.com/docs/metrics) are enabled, FrankenPHP exposes the following metrics:
|
||||
|
||||
- `frankenphp_total_threads`: The total number of PHP threads.
|
||||
- `frankenphp_busy_threads`: The number of PHP threads currently processing a request (running workers always consume a thread).
|
||||
- `frankenphp_queue_depth`: The number of regular queued requests
|
||||
- `frankenphp_total_workers{worker="[worker_name]"}`: The total number of workers.
|
||||
- `frankenphp_busy_workers{worker="[worker_name]"}`: The number of workers currently processing a request.
|
||||
- `frankenphp_worker_request_time{worker="[worker_name]"}`: The time spent processing requests by all workers.
|
||||
- `frankenphp_worker_request_count{worker="[worker_name]"}`: The number of requests processed by all workers.
|
||||
- `frankenphp_ready_workers{worker="[worker_name]"}`: The number of workers that have called `frankenphp_handle_request` at least once.
|
||||
- `frankenphp_worker_crashes{worker="[worker_name]"}`: The number of times a worker has unexpectedly terminated.
|
||||
- `frankenphp_worker_restarts{worker="[worker_name]"}`: The number of times a worker has been deliberately restarted.
|
||||
- `frankenphp_worker_queue_depth{worker="[worker_name]"}`: The number of queued requests.
|
||||
|
||||
For worker metrics, the `[worker_name]` placeholder is replaced by the worker name in the Caddyfile, otherwise absolute path of worker file will be used.
|
||||
157
docs/performance.md
Normal file
157
docs/performance.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Performance
|
||||
|
||||
By default, FrankenPHP tries to offer a good compromise between performance and ease of use.
|
||||
However, it is possible to substantially improve performance using an appropriate configuration.
|
||||
|
||||
## Number of Threads and Workers
|
||||
|
||||
By default, FrankenPHP starts 2 times more threads and workers (in worker mode) than the available numbers of CPU.
|
||||
|
||||
The appropriate values depend heavily on how your application is written, what it does and your hardware.
|
||||
We strongly recommend changing these values. For best system stability, it is recommended to have `num_threads` x `memory_limit` < `available_memory`.
|
||||
|
||||
To find the right values, it's best to run load tests simulating real traffic.
|
||||
[k6](https://k6.io) and [Gatling](https://gatling.io) are good tools for this.
|
||||
|
||||
To configure the number of threads, use the `num_threads` option of the `php_server` and `php` directives.
|
||||
To change the number of workers, use the `num` option of the `worker` section of the `frankenphp` directive.
|
||||
|
||||
### `max_threads`
|
||||
|
||||
While it's always better to know exactly what your traffic will look like, real-life applications tend to be more
|
||||
unpredictable. The `max_threads` [configuration](config.md#caddyfile-config) allows FrankenPHP to automatically spawn additional threads at runtime up to the specified limit.
|
||||
`max_threads` can help you figure out how many threads you need to handle your traffic and can make the server more resilient to latency spikes.
|
||||
If set to `auto`, the limit will be estimated based on the `memory_limit` in your `php.ini`. If not able to do so,
|
||||
`auto` will instead default to 2x `num_threads`. Keep in mind that `auto` might strongly underestimate the number of threads needed.
|
||||
`max_threads` is similar to PHP FPM's [pm.max_children](https://www.php.net/manual/en/install.fpm.configuration.php#pm.max-children). The main difference is that FrankenPHP uses threads instead of
|
||||
processes and automatically delegates them across different worker scripts and 'classic mode' as needed.
|
||||
|
||||
## Worker Mode
|
||||
|
||||
Enabling [the worker mode](worker.md) dramatically improves performance,
|
||||
but your app must be adapted to be compatible with this mode:
|
||||
you need to create a worker script and to be sure that the app is not leaking memory.
|
||||
|
||||
## Don't Use musl
|
||||
|
||||
The Alpine Linux variant of the official Docker images and the default binaries we provide are using [the musl libc](https://musl.libc.org).
|
||||
|
||||
PHP is known to be [slower](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381) when using this alternative C library instead of the traditional GNU library,
|
||||
especially when compiled in ZTS mode (thread-safe), which is required for FrankenPHP. The difference can be significant in a heavily threaded environment.
|
||||
|
||||
Also, [some bugs only happen when using musl](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl).
|
||||
|
||||
In production environments, we recommend using FrankenPHP linked against glibc.
|
||||
|
||||
This can be achieved by using the Debian Docker images (the default), downloading the -gnu suffix binary from our [Releases](https://github.com/dunglas/frankenphp/releases), or by [compiling FrankenPHP from sources](compile.md).
|
||||
|
||||
Alternatively, we provide static musl binaries compiled with [the mimalloc allocator](https://github.com/microsoft/mimalloc), which alleviates the problems in threaded scenarios.
|
||||
|
||||
## Go Runtime Configuration
|
||||
|
||||
FrankenPHP is written in Go.
|
||||
|
||||
In general, the Go runtime doesn't require any special configuration, but in certain circumstances,
|
||||
specific configuration improves performance.
|
||||
|
||||
You likely want to set the `GODEBUG` environment variable to `cgocheck=0` (the default in the FrankenPHP Docker images).
|
||||
|
||||
If you run FrankenPHP in containers (Docker, Kubernetes, LXC...) and limit the memory available for the containers,
|
||||
set the `GOMEMLIMIT` environment variable to the available amount of memory.
|
||||
|
||||
For more details, [the Go documentation page dedicated to this subject](https://pkg.go.dev/runtime#hdr-Environment_Variables) is a must-read to get the most out of the runtime.
|
||||
|
||||
## `file_server`
|
||||
|
||||
By default, the `php_server` directive automatically sets up a file server to
|
||||
serve static files (assets) stored in the root directory.
|
||||
|
||||
This feature is convenient, but comes with a cost.
|
||||
To disable it, use the following config:
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
file_server off
|
||||
}
|
||||
```
|
||||
|
||||
## `try_files`
|
||||
|
||||
Besides static files and PHP files, `php_server` will also try to serve your application's index
|
||||
and directory index files (`/path/` -> `/path/index.php`). If you don't need directory indices,
|
||||
you can disable them by explicitly defining `try_files` like this:
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
root /root/to/your/app # explicitly adding the root here allows for better caching
|
||||
}
|
||||
```
|
||||
|
||||
This can significantly reduce the number of unnecessary file operations.
|
||||
|
||||
An alternate approach with 0 unnecessary file system operations would be to instead use the `php` directive and split
|
||||
files from PHP by path. This approach works well if your entire application is served by one entry file.
|
||||
An example [configuration](config.md#caddyfile-config) that serves static files behind an `/assets` folder could look like this:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
@assets {
|
||||
path /assets/*
|
||||
}
|
||||
|
||||
# everything behind /assets is handled by the file server
|
||||
file_server @assets {
|
||||
root /root/to/your/app
|
||||
}
|
||||
|
||||
# everything that is not in /assets is handled by your index or worker PHP file
|
||||
rewrite index.php
|
||||
php {
|
||||
root /root/to/your/app # explicitly adding the root here allows for better caching
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Placeholders
|
||||
|
||||
You can use [placeholders](https://caddyserver.com/docs/conventions#placeholders) in the `root` and `env` directives.
|
||||
However, this prevents caching these values, and comes with a significant performance cost.
|
||||
|
||||
If possible, avoid placeholders in these directives.
|
||||
|
||||
## `resolve_root_symlink`
|
||||
|
||||
By default, if the document root is a symbolic link, it is automatically resolved by FrankenPHP (this is necessary for PHP to work properly).
|
||||
If the document root is not a symlink, you can disable this feature.
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
resolve_root_symlink false
|
||||
}
|
||||
```
|
||||
|
||||
This will improve performance if the `root` directive contains [placeholders](https://caddyserver.com/docs/conventions#placeholders).
|
||||
The gain will be negligible in other cases.
|
||||
|
||||
## Logs
|
||||
|
||||
Logging is obviously very useful, but, by definition,
|
||||
it requires I/O operations and memory allocations, which considerably reduces performance.
|
||||
Make sure you [set the logging level](https://caddyserver.com/docs/caddyfile/options#log) correctly,
|
||||
and only log what's necessary.
|
||||
|
||||
## PHP Performance
|
||||
|
||||
FrankenPHP uses the official PHP interpreter.
|
||||
All usual PHP-related performance optimizations apply with FrankenPHP.
|
||||
|
||||
In particular:
|
||||
|
||||
- check that [OPcache](https://www.php.net/manual/en/book.opcache.php) is installed, enabled and properly configured
|
||||
- enable [Composer autoloader optimizations](https://getcomposer.org/doc/articles/autoloader-optimization.md)
|
||||
- ensure that the `realpath` cache is big enough for the needs of your application
|
||||
- use [preloading](https://www.php.net/manual/en/opcache.preloading.php)
|
||||
|
||||
For more details, read [the dedicated Symfony documentation entry](https://symfony.com/doc/current/performance.html)
|
||||
(most tips are useful even if you don't use Symfony).
|
||||
@@ -31,7 +31,7 @@ Refer to "[Building Custom Docker Image](docker.md)" for more details and option
|
||||
and to learn how to customize the configuration, install PHP extensions and Caddy modules.
|
||||
|
||||
If your project uses Composer,
|
||||
be sure to include it in the Docker image and to install your depedencies.
|
||||
be sure to include it in the Docker image and to install your dependencies.
|
||||
|
||||
Then, add a `compose.yaml` file:
|
||||
|
||||
@@ -124,10 +124,10 @@ git clone git@github.com:<username>/<project-name>.git
|
||||
Go into the directory containing your project (`<project-name>`), and start the app in production mode:
|
||||
|
||||
```console
|
||||
docker compose up -d --wait
|
||||
docker compose up --wait
|
||||
```
|
||||
|
||||
Your server is up and running, and a HTTPS certificate has been automatically generated for you.
|
||||
Your server is up and running, and an HTTPS certificate has been automatically generated for you.
|
||||
Go to `https://your-domain-name.example.com` and enjoy!
|
||||
|
||||
> [!CAUTION]
|
||||
|
||||
218
docs/ru/CONTRIBUTING.md
Normal file
218
docs/ru/CONTRIBUTING.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# Участие в проекте
|
||||
|
||||
## Компиляция PHP
|
||||
|
||||
### С помощью Docker (Linux)
|
||||
|
||||
Создайте образ Docker для разработки:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Образ содержит стандартные инструменты для разработки (Go, GDB, Valgrind, Neovim и др.) и использует следующие пути для настроек PHP
|
||||
|
||||
- php.ini: `/etc/frankenphp/php.ini` По умолчанию предоставляется файл php.ini с настройками для разработки.
|
||||
- дополнительные файлы конфигурации: `/etc/frankenphp/php.d/*.ini`
|
||||
- расширения php: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
Если ваша версия Docker ниже 23.0, сборка может завершиться ошибкой из-за [проблемы с шаблонами dockerignore](https://github.com/moby/moby/pull/42676). Добавьте в `.dockerignore` следующие директории:
|
||||
|
||||
```patch
|
||||
!testdata/*.php
|
||||
!testdata/*.txt
|
||||
+!caddy
|
||||
+!internal
|
||||
```
|
||||
|
||||
### Без Docker (Linux и macOS)
|
||||
|
||||
[Следуйте инструкциям по компиляции из исходников](https://frankenphp.dev/docs/compile/) и укажите флаг конфигурации `--debug`.
|
||||
|
||||
## Запуск тестов
|
||||
|
||||
```console
|
||||
go test -tags watcher -race -v ./...
|
||||
```
|
||||
|
||||
## Модуль Caddy
|
||||
|
||||
Соберите Caddy с модулем FrankenPHP:
|
||||
|
||||
```console
|
||||
cd caddy/frankenphp/
|
||||
go build -tags watcher,brotli,nobadger,nomysql,nopgx
|
||||
cd ../../
|
||||
```
|
||||
|
||||
Запустите Caddy с модулем FrankenPHP:
|
||||
|
||||
```console
|
||||
cd testdata/
|
||||
../caddy/frankenphp/frankenphp run
|
||||
```
|
||||
|
||||
Сервер будет доступен по адресу `127.0.0.1:8080`:
|
||||
|
||||
```console
|
||||
curl -vk https://localhost/phpinfo.php
|
||||
```
|
||||
|
||||
## Минимальный тестовый сервер
|
||||
|
||||
Соберите минимальный тестовый сервер:
|
||||
|
||||
```console
|
||||
cd internal/testserver/
|
||||
go build
|
||||
cd ../../
|
||||
```
|
||||
|
||||
Запустите тестовый сервер:
|
||||
|
||||
```console
|
||||
cd testdata/
|
||||
../internal/testserver/testserver
|
||||
```
|
||||
|
||||
Сервер будет доступен по адресу `127.0.0.1:8080`:
|
||||
|
||||
```console
|
||||
curl -v http://127.0.0.1:8080/phpinfo.php
|
||||
```
|
||||
|
||||
## Локальная сборка Docker-образов
|
||||
|
||||
Выведите план bake:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --print
|
||||
```
|
||||
|
||||
Соберите образы FrankenPHP для amd64 локально:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/amd64"
|
||||
```
|
||||
|
||||
Соберите образы FrankenPHP для arm64 локально:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/arm64"
|
||||
```
|
||||
|
||||
Соберите образы FrankenPHP с нуля для arm64 и amd64 и отправьте их в Docker Hub:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
```
|
||||
|
||||
## Отладка ошибок сегментации с использованием статических сборок
|
||||
|
||||
1. Скачайте отладочную версию бинарного файла FrankenPHP с GitHub или создайте собственную статическую сборку с включённым отладочным режимом:
|
||||
|
||||
```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. Замените текущую версию `frankenphp` на бинарный файл с включенным отладочным режимом.
|
||||
3. Запустите FrankenPHP как обычно (или сразу запустите FrankenPHP с GDB: `gdb --args frankenphp run`).
|
||||
4. Подключитесь к процессу через GDB:
|
||||
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
|
||||
5. При необходимости введите `continue` в консоли GDB.
|
||||
6. Вызовите сбой FrankenPHP.
|
||||
7. Введите `bt` в консоли GDB.
|
||||
8. Скопируйте вывод.
|
||||
|
||||
## Отладка ошибок сегментации в GitHub Actions
|
||||
|
||||
1. Откройте файл `.github/workflows/tests.yml`.
|
||||
2. Включите режим отладки PHP:
|
||||
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
|
||||
3. Настройте `tmate` для удалённого подключения к контейнеру:
|
||||
|
||||
```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. Подключитесь к контейнеру.
|
||||
5. Откройте файл `frankenphp.go`.
|
||||
6. Включите `cgosymbolizer`:
|
||||
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
|
||||
7. Загрузите модуль: `go get`.
|
||||
8. В контейнере используйте GDB и другие инструменты:
|
||||
|
||||
```console
|
||||
go test -tags watcher -c -ldflags=-w
|
||||
gdb --args frankenphp.dev.test -test.run ^MyTest$
|
||||
```
|
||||
|
||||
9. После исправления ошибки откатите все внесенные изменения.
|
||||
|
||||
## Дополнительные ресурсы для разработки
|
||||
|
||||
- [Встраивание PHP в uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
- [Встраивание PHP в NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
- [Встраивание PHP в Go (go-php)](https://github.com/deuill/go-php)
|
||||
- [Встраивание PHP в Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
- [Встраивание PHP в C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
- [Книга "Extending and Embedding PHP" Сары Големан](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
- [Статья: Что такое TSRMLS_CC?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
- [SDL bindings](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
|
||||
## Docker-ресурсы
|
||||
|
||||
- [Определение файлов bake](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
- [Документация по команде `docker buildx build`](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
|
||||
## Полезные команды
|
||||
|
||||
```console
|
||||
apk add strace util-linux gdb
|
||||
strace -e 'trace=!futex,epoll_ctl,epoll_pwait,tgkill,rt_sigreturn' -p 1
|
||||
```
|
||||
|
||||
## Перевод документации
|
||||
|
||||
Чтобы перевести документацию и сайт на новый язык, выполните следующие шаги:
|
||||
|
||||
1. Создайте новую директорию с 2-буквенным ISO-кодом языка в папке `docs/`.
|
||||
2. Скопируйте все `.md` файлы из корня папки `docs/` в новую директорию (используйте английскую версию как основу для перевода).
|
||||
3. Скопируйте файлы `README.md` и `CONTRIBUTING.md` из корневой директории в новую папку.
|
||||
4. Переведите содержимое файлов, но не изменяйте имена файлов. Не переводите строки, начинающиеся с `> [!`, это специальная разметка GitHub.
|
||||
5. Создайте Pull Request с переводом.
|
||||
6. В [репозитории сайта](https://github.com/dunglas/frankenphp-website/tree/main) скопируйте и переведите файлы в папках `content/`, `data/` и `i18n/`.
|
||||
7. Переведите значения в созданных YAML-файлах.
|
||||
8. Откройте Pull Request в репозитории сайта.
|
||||
86
docs/ru/README.md
Normal file
86
docs/ru/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# FrankenPHP: Современный сервер приложений для PHP
|
||||
|
||||
<h1 align="center"><a href="https://frankenphp.dev"><img src="../../frankenphp.png" alt="FrankenPHP" width="600"></a></h1>
|
||||
|
||||
**FrankenPHP** — это современный сервер приложений для PHP, построенный на базе веб-сервера [Caddy](https://caddyserver.com/).
|
||||
|
||||
FrankenPHP добавляет новые возможности вашим PHP-приложениям благодаря следующим функциям: [_Early Hints_](https://frankenphp.dev/docs/early-hints/), [Worker режим](https://frankenphp.dev/docs/worker/), [Real-time режим](https://frankenphp.dev/docs/mercure/), автоматическая поддержка HTTPS, HTTP/2 и HTTP/3.
|
||||
|
||||
FrankenPHP совместим с любыми PHP-приложениями и значительно ускоряет ваши проекты на Laravel и Symfony благодаря их официальной поддержке в worker режиме.
|
||||
|
||||
FrankenPHP также может использоваться как автономная Go-библиотека для встраивания PHP в любое приложение с использованием `net/http`.
|
||||
|
||||
[**Узнайте больше** на сайте _frankenphp.dev_](https://frankenphp.dev) или из этой презентации:
|
||||
|
||||
<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="Slides" width="600"></a>
|
||||
|
||||
## Начало работы
|
||||
|
||||
### Docker
|
||||
|
||||
```console
|
||||
docker run -v .:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
Перейдите по адресу `https://localhost` и наслаждайтесь!
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Не используйте `https://127.0.0.1`. Используйте `https://localhost` и настройте самоподписанный сертификат.
|
||||
> Чтобы изменить используемый домен, настройте переменную окружения [`SERVER_NAME`](config.md#переменные-окружения).
|
||||
|
||||
### Автономный бинарный файл
|
||||
|
||||
Если вы предпочитаете не использовать Docker, мы предоставляем автономный бинарный файл FrankenPHP для Linux и macOS, включающий [PHP 8.4](https://www.php.net/releases/8.4/en.php) и большинство популярных PHP-расширений.
|
||||
|
||||
Для Windows используйте [WSL](https://learn.microsoft.com/windows/wsl/) для запуска FrankenPHP.
|
||||
|
||||
[Скачать FrankenPHP](https://github.com/dunglas/frankenphp/releases) или выполните следующую команду для автоматической установки подходящей версии:
|
||||
|
||||
```console
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
mv frankenphp /usr/local/bin/
|
||||
```
|
||||
|
||||
Для запуска содержимого текущей директории выполните:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Вы также можете запускать CLI-скрипты:
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
```
|
||||
|
||||
## Документация
|
||||
|
||||
- [Worker режим](https://frankenphp.dev/docs/worker/)
|
||||
- [Поддержка Early Hints (103 HTTP статус код)](https://frankenphp.dev/docs/early-hints/)
|
||||
- [Real-time режим](https://frankenphp.dev/docs/mercure/)
|
||||
- [Конфигурация](https://frankenphp.dev/docs/config/)
|
||||
- [Docker-образы](https://frankenphp.dev/docs/docker/)
|
||||
- [Деплой в продакшен](https://frankenphp.dev/docs/production/)
|
||||
- [Оптимизация производительности](https://frankenphp.dev/docs/performance/)
|
||||
- [Создание автономного PHP-приложений](https://frankenphp.dev/docs/embed/)
|
||||
- [Создание статических бинарных файлов](https://frankenphp.dev/docs/static/)
|
||||
- [Компиляция из исходников](https://frankenphp.dev/docs/compile/)
|
||||
- [Интеграция с Laravel](https://frankenphp.dev/docs/laravel/)
|
||||
- [Известные проблемы](https://frankenphp.dev/docs/known-issues/)
|
||||
- [Демо-приложение (Symfony) и бенчмарки](https://github.com/dunglas/frankenphp-demo)
|
||||
- [Документация Go-библиотеки](https://pkg.go.dev/github.com/dunglas/frankenphp)
|
||||
- [Участие в проекте и отладка](https://frankenphp.dev/docs/contributing/)
|
||||
|
||||
## Примеры и шаблоны
|
||||
|
||||
- [Symfony](https://github.com/dunglas/symfony-docker)
|
||||
- [API Platform](https://api-platform.com/docs/symfony)
|
||||
- [Laravel](https://frankenphp.dev/docs/laravel/)
|
||||
- [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)
|
||||
110
docs/ru/compile.md
Normal file
110
docs/ru/compile.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Компиляция из исходников
|
||||
|
||||
Этот документ объясняет, как создать бинарный файл FrankenPHP, который будет загружать PHP как динамическую библиотеку.
|
||||
Это рекомендуемый способ.
|
||||
|
||||
Альтернативно можно создать [статическую сборку](static.md).
|
||||
|
||||
## Установка PHP
|
||||
|
||||
FrankenPHP совместим с PHP версии 8.2 и выше.
|
||||
|
||||
Сначала [загрузите исходники PHP](https://www.php.net/downloads.php) и распакуйте их:
|
||||
|
||||
```console
|
||||
tar xf php-*
|
||||
cd php-*/
|
||||
```
|
||||
|
||||
Далее выполните скрипт `configure` с параметрами, необходимыми для вашей платформы.
|
||||
Следующие флаги `./configure` обязательны, но вы можете добавить и другие, например, для компиляции расширений или дополнительных функций.
|
||||
|
||||
### Linux
|
||||
|
||||
```console
|
||||
./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--enable-zend-max-execution-timers
|
||||
```
|
||||
|
||||
### Mac
|
||||
|
||||
Используйте пакетный менеджер [Homebrew](https://brew.sh/) для установки
|
||||
`libiconv`, `bison`, `re2c` и `pkg-config`:
|
||||
|
||||
```console
|
||||
brew install libiconv bison brotli re2c pkg-config
|
||||
echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
|
||||
```
|
||||
|
||||
Затем выполните скрипт configure:
|
||||
|
||||
```console
|
||||
./configure \
|
||||
--enable-embed=static \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--disable-opcache-jit \
|
||||
--enable-static \
|
||||
--enable-shared=no \
|
||||
--with-iconv=/opt/homebrew/opt/libiconv/
|
||||
```
|
||||
|
||||
## Компиляция PHP
|
||||
|
||||
Наконец, скомпилируйте и установите PHP:
|
||||
|
||||
```console
|
||||
make -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## Установка дополнительных зависимостей
|
||||
|
||||
Некоторые функции FrankenPHP зависят от опциональных системных зависимостей.
|
||||
Альтернативно, эти функции можно отключить, передав соответствующие теги сборки компилятору Go.
|
||||
|
||||
| Функция | Зависимость | Тег сборки для отключения |
|
||||
| ----------------------------------------------- | --------------------------------------------------------------------- | ------------------------- |
|
||||
| Сжатие Brotli | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| Перезапуск worker-скриптов при изменении файлов | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
|
||||
## Компиляция Go-приложения
|
||||
|
||||
Теперь можно собрать итоговый бинарный файл:
|
||||
|
||||
```console
|
||||
curl -L https://github.com/dunglas/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
|
||||
```
|
||||
|
||||
### Использование xcaddy
|
||||
|
||||
Альтернативно, используйте [xcaddy](https://github.com/caddyserver/xcaddy) для компиляции FrankenPHP с [пользовательскими модулями Caddy](https://caddyserver.com/docs/modules/):
|
||||
|
||||
```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 frankenphp.dev/caddy \
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Добавьте дополнительные модули Caddy здесь
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Если вы используете musl libc (по умолчанию в Alpine Linux) и Symfony,
|
||||
> возможно, потребуется увеличить размер стека.
|
||||
> В противном случае вы можете столкнуться с ошибками вроде
|
||||
> `PHP Fatal error: Maximum call stack size of 83360 bytes reached during compilation. Try splitting expression`.
|
||||
>
|
||||
> Для этого измените значение переменной окружения `XCADDY_GO_BUILD_FLAGS`, например:
|
||||
> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'`
|
||||
> (измените значение размера стека в зависимости от требований вашего приложения).
|
||||
247
docs/ru/config.md
Normal file
247
docs/ru/config.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# Конфигурация
|
||||
|
||||
FrankenPHP, Caddy, а также модули Mercure и Vulcain могут быть настроены с использованием [конфигурационных форматов, поддерживаемых Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
|
||||
В [Docker-образах](docker.md) файл `Caddyfile` находится по пути `/etc/frankenphp/Caddyfile`.
|
||||
Статический бинарный файл будет искать `Caddyfile` в директории запуска.
|
||||
|
||||
PHP можно настроить [с помощью файла `php.ini`](https://www.php.net/manual/en/configuration.file.php).
|
||||
|
||||
PHP-интерпретатор будет искать в следующих местах:
|
||||
|
||||
Docker:
|
||||
|
||||
- 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:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# Для production:
|
||||
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
|
||||
|
||||
# Или для development:
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
```
|
||||
|
||||
Установка FrankenPHP (.rpm или .deb):
|
||||
|
||||
- php.ini: `/etc/frankenphp/php.ini` По умолчанию предоставляется файл 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.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-приложения.
|
||||
|
||||
Минимальный пример:
|
||||
|
||||
```caddyfile
|
||||
localhost {
|
||||
# Включить сжатие (опционально)
|
||||
encode zstd br gzip
|
||||
# Выполнять PHP-файлы в текущей директории и обслуживать ресурсы
|
||||
php_server
|
||||
}
|
||||
```
|
||||
|
||||
Вы также можете явно настроить FrankenPHP с помощью глобальной опции:
|
||||
[Глобальная опция](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp` может быть использована для настройки FrankenPHP.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Указывает количество потоков PHP. По умолчанию: 2x от числа доступных CPU.
|
||||
worker {
|
||||
file <path> # Указывает путь к worker-скрипту.
|
||||
num <num> # Указывает количество потоков PHP. По умолчанию: 2x от числа доступных CPU.
|
||||
env <key> <value> # Устанавливает дополнительную переменную окружения. Можно указать несколько раз для разных переменных.
|
||||
watch <path> # Указывает путь для отслеживания изменений файлов.Можно указать несколько раз для разных путей.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
В качестве альтернативы можно использовать однострочную краткую форму для опции `worker`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker <file> <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Вы также можете определить несколько workers, если обслуживаете несколько приложений на одном сервере:
|
||||
|
||||
```caddyfile
|
||||
app.example.com {
|
||||
php_server {
|
||||
root /path/to/app/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
other.example.com {
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Использование директивы `php_server` — это то, что нужно в большинстве случаев. Однако если требуется полный контроль, вы можете использовать более низкоуровневую директиву `php`:
|
||||
|
||||
Использование директивы `php_server` эквивалентно следующей конфигурации:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
# Добавить слэш в конец запросов к директориям
|
||||
@canonicalPath {
|
||||
file {path}/index.php
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
# Если запрошенный файл не существует, попытаться использовать файлы 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
|
||||
}
|
||||
```
|
||||
|
||||
Директивы `php_server` и `php` имеют следующие опции:
|
||||
|
||||
```caddyfile
|
||||
php_server [<matcher>] {
|
||||
root <directory> # Указывает корневую директорию сайта. По умолчанию: директива `root`.
|
||||
split_path <delim...> # Устанавливает подстроки для разделения URI на две части. Первая часть будет использована как имя ресурса (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.
|
||||
name <name> # Устанавливает имя для worker, используемое в логах и метриках. По умолчанию: абсолютный путь к файлу worker. Всегда начинается с m# при определении в блоке php_server.
|
||||
watch <path> # Указывает путь для отслеживания изменений файлов. Можно указать несколько раз для разных путей.
|
||||
env <key> <value> # Устанавливает дополнительную переменную окружения. Можно указать несколько раз для разных переменных. Переменные окружения для этого worker также наследуются от родительского php_server, но могут быть переопределены здесь.
|
||||
}
|
||||
worker <other_file> <num> # Также можно использовать краткую форму как в глобальном блоке frankenphp.
|
||||
}
|
||||
```
|
||||
|
||||
### Отслеживание изменений файлов
|
||||
|
||||
Поскольку workers запускают ваше приложение только один раз и держат его в памяти, изменения в PHP-файлах не будут применяться сразу.
|
||||
|
||||
Для разработки можно настроить перезапуск workers при изменении файлов с помощью директивы `watch`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Если директория для `watch` не указана, по умолчанию будет использоваться путь `./**/*.{php,yaml,yml,twig,env}`,
|
||||
который отслеживает все файлы с расширениями `.php`, `.yaml`, `.yml`, `.twig` и `.env` в директории, где был запущен процесс FrankenPHP, и во всех её поддиректориях. Вы также можете указать одну или несколько директорий с использованием [шаблона имён файлов](https://pkg.go.dev/path/filepath#Match):
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch /path/to/app # отслеживает все файлы во всех поддиректориях /path/to/app
|
||||
watch /path/to/app/*.php # отслеживает файлы с расширением .php в /path/to/app
|
||||
watch /path/to/app/**/*.php # отслеживает PHP-файлы в /path/to/app и поддиректориях
|
||||
watch /path/to/app/**/*.{php,twig} # отслеживает PHP и Twig-файлы в /path/to/app и поддиректориях
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Шаблон `**` указывает на рекурсивное отслеживание.
|
||||
- Директории могут быть указаны относительно директории запуска FrankenPHP.
|
||||
- Если у вас определено несколько workers, все они будут перезапущены при изменении файлов.
|
||||
- Избегайте отслеживания файлов, создаваемых во время выполнения (например, логов), так как это может вызвать нежелательные перезапуски.
|
||||
|
||||
Механизм отслеживания файлов основан на [e-dant/watcher](https://github.com/e-dant/watcher).
|
||||
|
||||
### Полный дуплекс (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` без его изменения:
|
||||
|
||||
- `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`, чтобы включить режим отладки:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public \
|
||||
-e CADDY_GLOBAL_OPTIONS=debug \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
201
docs/ru/docker.md
Normal file
201
docs/ru/docker.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# Создание кастомных Docker-образов
|
||||
|
||||
[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.
|
||||
|
||||
Теги следуют следующему шаблону: `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).
|
||||
|
||||
[Просмотреть доступные теги](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
## Как использовать образы
|
||||
|
||||
Создайте `Dockerfile` в вашем проекте:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
COPY . /app/public
|
||||
```
|
||||
|
||||
Затем выполните следующие команды для сборки и запуска Docker-образа:
|
||||
|
||||
```console
|
||||
docker build -t my-php-app .
|
||||
docker run -it --rm --name my-running-app my-php-app
|
||||
```
|
||||
|
||||
## Как установить дополнительные PHP-расширения
|
||||
|
||||
Скрипт [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) включён в базовый образ. Установка дополнительных PHP-расширений осуществляется просто:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# Добавьте дополнительные расширения здесь:
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
```
|
||||
|
||||
## Как установить дополнительные модули Caddy
|
||||
|
||||
FrankenPHP построен на базе Caddy, и все [модули Caddy](https://caddyserver.com/docs/modules/) можно использовать с FrankenPHP.
|
||||
|
||||
Самый простой способ установить пользовательские модули Caddy — использовать [xcaddy](https://github.com/caddyserver/xcaddy):
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp:builder AS builder
|
||||
|
||||
# Копируем xcaddy в образ сборки
|
||||
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
|
||||
|
||||
# Для сборки FrankenPHP необходимо включить CGO
|
||||
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 frankenphp.dev=./ \
|
||||
--with frankenphp.dev/caddy=./caddy/ \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
# Mercure и Vulcain включены в официальный билд, но вы можете их удалить
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Добавьте дополнительные модули Caddy здесь
|
||||
|
||||
FROM dunglas/frankenphp AS runner
|
||||
|
||||
# Заменяем официальный бинарный файл на пользовательский с добавленными модулями
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
```
|
||||
|
||||
Образ `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).
|
||||
|
||||
## Активировать worker режим по умолчанию
|
||||
|
||||
Установите переменную окружения `FRANKENPHP_CONFIG`, чтобы запускать FrankenPHP с Worker-скриптом:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# ...
|
||||
|
||||
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
|
||||
```
|
||||
|
||||
## Использование тома в разработке
|
||||
|
||||
Для удобной разработки с FrankenPHP смонтируйте директорию с исходным кодом приложения на хосте как том в Docker-контейнере:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Опция `--tty` позволяет видеть удобочитаемые логи вместо JSON-формата.
|
||||
|
||||
С использованием Docker Compose:
|
||||
|
||||
```yaml
|
||||
# compose.yaml
|
||||
|
||||
services:
|
||||
php:
|
||||
image: dunglas/frankenphp
|
||||
# раскомментируйте следующую строку, если хотите использовать собственный Dockerfile
|
||||
#build: .
|
||||
# раскомментируйте следующую строку, если вы запускаете это в продакшн среде
|
||||
# restart: always
|
||||
ports:
|
||||
- "80:80" # HTTP
|
||||
- "443:443" # HTTPS
|
||||
- "443:443/udp" # HTTP/3
|
||||
volumes:
|
||||
- ./:/app/public
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
# закомментируйте следующую строку в продакшн среде, она позволяет получать удобочитаемые логи в режиме разработки
|
||||
tty: true
|
||||
|
||||
# Томы, необходимые для сертификатов и конфигурации Caddy
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
```
|
||||
|
||||
## Запуск под обычным пользователем
|
||||
|
||||
FrankenPHP поддерживает запуск под обычным пользователем в Docker.
|
||||
|
||||
Пример `Dockerfile` для этого:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Для дистрибутивов на основе Alpine используйте "adduser -D ${USER}"
|
||||
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
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
### Запуск без дополнительных прав
|
||||
|
||||
Даже при запуске без root-прав, FrankenPHP требуется возможность `CAP_NET_BIND_SERVICE` для привязки веб-сервера к зарезервированным портам (80 и 443).
|
||||
|
||||
Если вы открываете доступ к FrankenPHP на непривилегированном порту (1024 и выше), можно запустить веб-сервер от имени обычного пользователя без необходимости предоставления дополнительных возможностей:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Для 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
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
Затем установите переменную окружения `SERVER_NAME`, чтобы использовать непривилегированный порт.
|
||||
Пример: `:8000`.
|
||||
|
||||
## Обновления
|
||||
|
||||
Docker-образы обновляются:
|
||||
|
||||
- при выпуске новой версии;
|
||||
- ежедневно в 4 утра UTC, если доступны новые версии официальных PHP-образов.
|
||||
|
||||
## Версии для разработки
|
||||
|
||||
Версии для разработки доступны в Docker-репозитории [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev).
|
||||
Сборка запускается автоматически при каждом коммите в основную ветку GitHub-репозитория
|
||||
|
||||
Теги с префиксом `latest*` указывают на актуальное состояние ветки `main`.
|
||||
Также доступны теги в формате `sha-<git-commit-hash>`.
|
||||
21
docs/ru/early-hints.md
Normal file
21
docs/ru/early-hints.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Early Hints
|
||||
|
||||
FrankenPHP изначально поддерживает [Early Hints (103 HTTP статус код)](https://developer.chrome.com/blog/early-hints/).
|
||||
Использование Early Hints может улучшить время загрузки ваших веб-страниц на 30%.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
header('Link: </style.css>; rel=preload; as=style');
|
||||
headers_send(103);
|
||||
|
||||
// ваши медленные алгоритмы и SQL-запросы 🤪
|
||||
|
||||
echo <<<'HTML'
|
||||
<!DOCTYPE html>
|
||||
<title>Hello FrankenPHP</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
HTML;
|
||||
```
|
||||
|
||||
Early Hints поддерживается как в обычном, так и в [worker режиме](worker.md).
|
||||
140
docs/ru/embed.md
Normal file
140
docs/ru/embed.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# PHP-приложения как автономные бинарные файлы
|
||||
|
||||
FrankenPHP позволяет встраивать исходный код и ресурсы PHP-приложений в статический автономный бинарный файл.
|
||||
|
||||
Благодаря этой функции PHP-приложения могут распространяться как автономные бинарные файлы, которые содержат само приложение, интерпретатор PHP и Caddy — веб-сервер уровня продакшн.
|
||||
|
||||
Подробнее об этой функции [в презентации Кевина на SymfonyCon 2023](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/).
|
||||
|
||||
Для встраивания Laravel-приложений ознакомьтесь с [документацией](laravel.md#laravel-приложения-как-автономные-бинарные-файлы).
|
||||
|
||||
## Подготовка приложения
|
||||
|
||||
Перед созданием автономного бинарного файла убедитесь, что ваше приложение готово для встраивания.
|
||||
|
||||
Например, вам может понадобиться:
|
||||
|
||||
- Установить продакшн-зависимости приложения.
|
||||
- Сгенерировать автозагрузчик.
|
||||
- Включить продакшн-режим приложения (если он есть).
|
||||
- Удалить ненужные файлы, такие как `.git` или тесты, чтобы уменьшить размер итогового бинарного файла.
|
||||
|
||||
Для приложения на Symfony это может выглядеть так:
|
||||
|
||||
```console
|
||||
# Экспорт проекта, чтобы избавиться от .git/ и других ненужных файлов
|
||||
mkdir $TMPDIR/my-prepared-app
|
||||
git archive HEAD | tar -x -C $TMPDIR/my-prepared-app
|
||||
cd $TMPDIR/my-prepared-app
|
||||
|
||||
# Установить соответствующие переменные окружения
|
||||
echo APP_ENV=prod > .env.local
|
||||
echo APP_DEBUG=0 >> .env.local
|
||||
|
||||
# Удалить тесты и другие ненужные файлы
|
||||
rm -Rf tests/
|
||||
|
||||
# Установить зависимости
|
||||
composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# Оптимизировать .env
|
||||
composer dump-env prod
|
||||
```
|
||||
|
||||
### Настройка конфигурации
|
||||
|
||||
Чтобы настроить [конфигурацию](config.md), вы можете разместить файлы `Caddyfile` и `php.ini` в основной директории приложения (`$TMPDIR/my-prepared-app` в примере выше).
|
||||
|
||||
## Создание бинарного файла для Linux
|
||||
|
||||
Самый простой способ создать бинарный файл для Linux — использовать предоставленный Docker-билдер.
|
||||
|
||||
1. Создайте файл `static-build.Dockerfile` в репозитории вашего приложения:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# Скопировать приложение
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Сборка статического бинарного файла
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Некоторые `.dockerignore` файлы (например, [Symfony Docker `.dockerignore`](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))
|
||||
> игнорируют директорию `vendor/` и файлы `.env`. Перед сборкой убедитесь, что `.dockerignore` файл настроен корректно или удалён.
|
||||
|
||||
2. Соберите образ:
|
||||
|
||||
```console
|
||||
docker build -t static-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Извлеките бинарный файл:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Созданный бинарный файл сохранится в текущей директории под именем `my-app`.
|
||||
|
||||
## Создание бинарного файла для других ОС
|
||||
|
||||
Если вы не хотите использовать Docker или хотите собрать бинарный файл для macOS, используйте предоставленный скрипт:
|
||||
|
||||
```console
|
||||
git clone https://github.com/dunglas/frankenphp
|
||||
cd frankenphp
|
||||
EMBED=/path/to/your/app ./build-static.sh
|
||||
```
|
||||
|
||||
Итоговый бинарный файл будет находиться в директории `dist/` под именем `frankenphp-<os>-<arch>`.
|
||||
|
||||
## Использование бинарного файла
|
||||
|
||||
Готово! Файл `my-app` (или `dist/frankenphp-<os>-<arch>` для других ОС) содержит ваше автономное приложение.
|
||||
|
||||
Для запуска веб-приложения выполните:
|
||||
|
||||
```console
|
||||
./my-app php-server
|
||||
```
|
||||
|
||||
Если ваше приложение содержит [worker-скрипт](worker.md), запустите его следующим образом:
|
||||
|
||||
```console
|
||||
./my-app php-server --worker public/index.php
|
||||
```
|
||||
|
||||
Чтобы включить HTTPS (Let's Encrypt автоматически создаст сертификат), HTTP/2 и HTTP/3, укажите доменное имя:
|
||||
|
||||
```console
|
||||
./my-app php-server --domain localhost
|
||||
```
|
||||
|
||||
Вы также можете запускать PHP-скрипты CLI, встроенные в бинарный файл:
|
||||
|
||||
```console
|
||||
./my-app php-cli bin/console
|
||||
```
|
||||
|
||||
## PHP-расширения
|
||||
|
||||
По умолчанию скрипт собирает расширения, указанные в `composer.json` вашего проекта.
|
||||
Если файла `composer.json` нет, собираются стандартные расширения, как указано в [документации по статической сборке](static.md).
|
||||
|
||||
Чтобы настроить список расширений, используйте переменную окружения `PHP_EXTENSIONS`.
|
||||
|
||||
## Настройка сборки
|
||||
|
||||
[Ознакомьтесь с документацией по статической сборке](static.md), чтобы узнать, как настроить бинарный файл (расширения, версию PHP и т.д.).
|
||||
|
||||
## Распространение бинарного файла
|
||||
|
||||
На Linux созданный бинарный файл сжимается с помощью [UPX](https://upx.github.io).
|
||||
|
||||
На Mac для уменьшения размера файла перед отправкой его можно сжать. Рекомендуется использовать `xz`.
|
||||
30
docs/ru/github-actions.md
Normal file
30
docs/ru/github-actions.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Использование GitHub Actions
|
||||
|
||||
Этот репозиторий автоматически собирает и публикует Docker-образы в [Docker Hub](https://hub.docker.com/r/dunglas/frankenphp) для каждого одобренного pull request или вашего собственного форка после настройки.
|
||||
|
||||
## Настройка GitHub Actions
|
||||
|
||||
В настройках репозитория, в разделе "Secrets", добавьте следующие секреты:
|
||||
|
||||
- `REGISTRY_LOGIN_SERVER`: Docker-реестр, который будет использоваться (например, `docker.io`).
|
||||
- `REGISTRY_USERNAME`: Имя пользователя для входа в реестр (например, `dunglas`).
|
||||
- `REGISTRY_PASSWORD`: Пароль для входа в реестр (например, токен доступа).
|
||||
- `IMAGE_NAME`: Имя образа (например, `dunglas/frankenphp`).
|
||||
|
||||
## Сборка и загрузка образа
|
||||
|
||||
1. Создайте Pull Request или выполните push в ваш форк.
|
||||
2. GitHub Actions соберёт образ и выполнит тесты.
|
||||
3. Если сборка пройдёт успешно, образ будет отправлен в реестр с тегом `pr-x`, где `x` — номер PR.
|
||||
|
||||
## Развёртывание образа
|
||||
|
||||
1. После слияния Pull Request GitHub Actions выполнит повторные тесты и соберёт новый образ.
|
||||
2. Если сборка пройдёт успешно, тег `main` будет обновлён в Docker-реестре.
|
||||
|
||||
## Релизы
|
||||
|
||||
1. Создайте новый тег в репозитории.
|
||||
2. GitHub Actions соберёт образ и выполнит тесты.
|
||||
3. Если сборка пройдёт успешно, образ будет отправлен в реестр с именем тега (например, `v1.2.3` и `v1.2` будут созданы).
|
||||
4. Также будет обновлён тег `latest`.
|
||||
140
docs/ru/known-issues.md
Normal file
140
docs/ru/known-issues.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Известные проблемы
|
||||
|
||||
## Неподдерживаемые расширения PHP
|
||||
|
||||
Следующие расширения не совместимы с FrankenPHP:
|
||||
|
||||
| Название | Причина | Альтернативы |
|
||||
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/en/imap.installation.php) | Не поддерживает потокобезопасность | [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/) | Не поддерживает потокобезопасность | - |
|
||||
|
||||
## Проблемные расширения PHP
|
||||
|
||||
Следующие расширения имеют известные ошибки или могут вести себя непредсказуемо при использовании с FrankenPHP:
|
||||
|
||||
| Название | Проблема |
|
||||
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | При использовании статической сборки FrankenPHP (на базе musl libc) расширение OpenSSL может аварийно завершаться при высокой нагрузке. Решение — использовать динамически связанную сборку (например, ту, что используется в Docker-образах). Ошибка [отслеживается сообществом PHP](https://github.com/php/php-src/issues/13648). |
|
||||
|
||||
## `get_browser`
|
||||
|
||||
Функция [get_browser()](https://www.php.net/manual/en/function.get-browser.php) начинает работать медленно через некоторое время. Решение — кэшировать результаты для каждого User-Agent, например, с помощью [APCu](https://www.php.net/manual/en/book.apcu.php), так как они статичны.
|
||||
|
||||
## Автономные бинарные файлы и образы на базе Alpine
|
||||
|
||||
Автономные бинарные файлы и образы на базе Alpine (`dunglas/frankenphp:*-alpine`) используют [musl libc](https://musl.libc.org/) вместо [glibc](https://www.etalabs.net/compare_libcs.html) для уменьшения размера бинарных файлов. Это может вызвать проблемы совместимости. В частности, флаг `GLOB_BRACE` в функции glob [не поддерживается](https://www.php.net/manual/en/function.glob.php).
|
||||
|
||||
## Использование `https://127.0.0.1` с Docker
|
||||
|
||||
По умолчанию FrankenPHP генерирует TLS-сертификат для `localhost`, что является самым простым и рекомендуемым вариантом для локальной разработки.
|
||||
|
||||
Если вы всё же хотите использовать `127.0.0.1`, настройте генерацию сертификата, указав в переменной окружения `SERVER_NAME` значение `127.0.0.1`.
|
||||
|
||||
Однако этого может не хватить при использовании Docker из-за [особенностей его сетевой системы](https://docs.docker.com/network/). Возможна ошибка TLS вида:
|
||||
`curl: (35) LibreSSL/3.3.6: error:1404B438:SSL routines:ST_CONNECT:tlsv1 alert internal error`.
|
||||
|
||||
Если вы используете Linux, можно воспользоваться [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
|
||||
```
|
||||
|
||||
Host-драйвер не поддерживается на Mac и Windows. На этих платформах нужно определить IP-адрес контейнера и включить его в `SERVER_NAME`.
|
||||
|
||||
Выполните команду `docker network inspect bridge`, найдите ключ `Containers` и определите последний присвоенный IP из `IPv4Address`. Увеличьте его на единицу. Если контейнеров нет, первый IP обычно `172.17.0.2`.
|
||||
|
||||
Включите этот IP в переменную окружения `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]
|
||||
> Обязательно замените `172.17.0.3` на IP, который будет присвоен вашему контейнеру.
|
||||
|
||||
Теперь вы должны иметь доступ к `https://127.0.0.1`.
|
||||
|
||||
Если это не так, запустите FrankenPHP в режиме отладки:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Скрипты Composer с использованием `@php`
|
||||
|
||||
[Скрипты Composer](https://getcomposer.org/doc/articles/scripts.md) могут вызывать PHP для выполнения задач, например, в [проекте Laravel](laravel.md) для команды `@php artisan package:discover --ansi`.
|
||||
Это [на данный момент не поддерживается](https://github.com/dunglas/frankenphp/issues/483#issuecomment-1899890915) по двум причинам:
|
||||
|
||||
- Composer не знает, как вызывать бинарный файл FrankenPHP;
|
||||
- Composer может добавлять настройки PHP через флаг `-d`, который FrankenPHP пока не поддерживает.
|
||||
|
||||
Решение — создать shell-скрипт в `/usr/local/bin/php`, который удаляет неподдерживаемые параметры и вызывает 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[@]}
|
||||
```
|
||||
|
||||
Затем установите переменную окружения `PHP_BINARY` на путь к нашему скрипту `php` и запустите Composer:
|
||||
|
||||
```console
|
||||
export PHP_BINARY=/usr/local/bin/php
|
||||
composer install
|
||||
```
|
||||
|
||||
## TLS/SSL: проблемы со статическими бинарными файлами
|
||||
|
||||
При использовании статических бинарных файлов могут возникать следующие ошибки TLS, например, при отправке писем через STARTTLS:
|
||||
|
||||
```text
|
||||
Unable to connect with STARTTLS: stream_socket_enable_crypto(): SSL operation failed with code 5. OpenSSL Error messages:
|
||||
error:80000002:system library::No such file or directory
|
||||
error:80000002:system library::No such file or directory
|
||||
error:80000002:system library::No such file or directory
|
||||
error:0A000086:SSL routines::certificate verify failed
|
||||
```
|
||||
|
||||
Статический бинарный файл не включает TLS-сертификаты, поэтому необходимо указать OpenSSL местоположение локальных сертификатов CA.
|
||||
|
||||
Выполните [`openssl_get_cert_locations()`](https://www.php.net/manual/en/function.openssl-get-cert-locations.php), чтобы определить, где должны находиться сертификаты CA, и поместите их туда.
|
||||
|
||||
> [!WARNING]
|
||||
> Веб и CLI контексты могут иметь разные настройки.
|
||||
> Запустите `openssl_get_cert_locations()` в нужном контексте.
|
||||
|
||||
[Сертификаты CA, извлечённые из Mozilla, можно скачать с сайта curl](https://curl.se/docs/caextract.html).
|
||||
|
||||
Кроме того, многие дистрибутивы, такие как Debian, Ubuntu и Alpine, предоставляют пакеты `ca-certificates`, содержащие эти сертификаты.
|
||||
|
||||
Также можно использовать переменные `SSL_CERT_FILE` и `SSL_CERT_DIR`, чтобы указать OpenSSL, где искать сертификаты CA:
|
||||
|
||||
```console
|
||||
# Установите переменные окружения для TLS-сертификатов
|
||||
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
export SSL_CERT_DIR=/etc/ssl/certs
|
||||
```
|
||||
176
docs/ru/laravel.md
Normal file
176
docs/ru/laravel.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Laravel
|
||||
|
||||
## Docker
|
||||
|
||||
Запустить [Laravel](https://laravel.com) веб-приложение с FrankenPHP очень просто: достаточно смонтировать проект в директорию `/app` официального Docker-образа.
|
||||
|
||||
Выполните эту команду из корневой директории вашего Laravel-приложения:
|
||||
|
||||
```console
|
||||
docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
|
||||
```
|
||||
|
||||
И наслаждайтесь!
|
||||
|
||||
## Локальная установка
|
||||
|
||||
Вы также можете запустить ваши Laravel-проекты с FrankenPHP на локальной машине:
|
||||
|
||||
1. [Скачайте бинарный файл для вашей системы](README.md#автономный-бинарный-файл)
|
||||
2. Добавьте следующую конфигурацию в файл с именем `Caddyfile` в корневой директории вашего Laravel-проекта:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
|
||||
# Доменное имя вашего сервера
|
||||
localhost {
|
||||
# Укажите веб-корень как директорию public/
|
||||
root public/
|
||||
# Включите сжатие (опционально)
|
||||
encode zstd br gzip
|
||||
# Выполняйте PHP-файлы из директории public/ и обслуживайте статические файлы
|
||||
php_server
|
||||
}
|
||||
```
|
||||
|
||||
3. Запустите FrankenPHP из корневой директории вашего Laravel-проекта: `frankenphp run`
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
Octane можно установить с помощью менеджера пакетов Composer:
|
||||
|
||||
```console
|
||||
composer require laravel/octane
|
||||
```
|
||||
|
||||
После установки Octane выполните Artisan-команду `octane:install`, которая создаст конфигурационный файл Octane в вашем приложении:
|
||||
|
||||
```console
|
||||
php artisan octane:install --server=frankenphp
|
||||
```
|
||||
|
||||
Сервер Octane можно запустить с помощью Artisan-команды `octane:frankenphp`:
|
||||
|
||||
```console
|
||||
php artisan octane:frankenphp
|
||||
```
|
||||
|
||||
Команда `octane:frankenphp` поддерживает следующие опции:
|
||||
|
||||
- `--host`: IP-адрес, к которому должен привязаться сервер (по умолчанию: `127.0.0.1`)
|
||||
- `--port`: Порт, на котором сервер будет доступен (по умолчанию: `8000`)
|
||||
- `--admin-port`: Порт, на котором будет доступен административный сервер (по умолчанию: `2019`)
|
||||
- `--workers`: Количество worker-скриптов для обработки запросов (по умолчанию: `auto`)
|
||||
- `--max-requests`: Количество запросов, обрабатываемых перед перезагрузкой сервера (по умолчанию: `500`)
|
||||
- `--caddyfile`: Путь к файлу `Caddyfile` FrankenPHP (по умолчанию: [stubbed `Caddyfile` в Laravel Octane](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile))
|
||||
- `--https`: Включить HTTPS, HTTP/2 и HTTP/3, а также автоматически генерировать и обновлять сертификаты
|
||||
- `--http-redirect`: Включить редирект с HTTP на HTTPS (включается только при передаче --https)
|
||||
- `--watch`: Автоматически перезагружать сервер при изменении приложения
|
||||
- `--poll`: Использовать опрос файловой системы для отслеживания изменений в файлах через сеть
|
||||
- `--log-level`: Установить уровень логирования, используя встроенный логгер Caddy
|
||||
|
||||
> [!TIP]
|
||||
> Чтобы получить структурированные JSON-логи (полезно при использовании решений для анализа логов), явно укажите опцию `--log-level`.
|
||||
|
||||
Подробнее о [Laravel Octane читайте в официальной документации](https://laravel.com/docs/octane).
|
||||
|
||||
## Laravel-приложения как автономные бинарные файлы
|
||||
|
||||
Используя [возможность встраивания приложений в FrankenPHP](embed.md), можно распространять Laravel-приложения как автономные бинарные файлы.
|
||||
|
||||
Следуйте этим шагам, чтобы упаковать ваше Laravel-приложение в автономный бинарный файл для Linux:
|
||||
|
||||
1. Создайте файл с именем `static-build.Dockerfile` в репозитории вашего приложения:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# Скопируйте ваше приложение
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Удалите тесты и другие ненужные файлы, чтобы сэкономить место
|
||||
# В качестве альтернативы добавьте эти файлы в .dockerignore
|
||||
RUN rm -Rf tests/
|
||||
|
||||
# Скопируйте файл .env
|
||||
RUN cp .env.example .env
|
||||
# Измените APP_ENV и APP_DEBUG для продакшна
|
||||
RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env
|
||||
|
||||
# Внесите другие изменения в файл .env, если необходимо
|
||||
|
||||
# Установите зависимости
|
||||
RUN composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# Соберите статический бинарный файл
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Некоторые `.dockerignore` файлы могут игнорировать директорию `vendor/` и файлы `.env`. Убедитесь, что вы скорректировали или удалили `.dockerignore` перед сборкой.
|
||||
|
||||
2. Соберите:
|
||||
|
||||
```console
|
||||
docker build -t static-laravel-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Извлеките бинарный файл:
|
||||
|
||||
```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. Заполните кеши:
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan optimize
|
||||
```
|
||||
|
||||
5. Запустите миграции базы данных (если есть):
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan migrate
|
||||
```
|
||||
|
||||
6. Сгенерируйте секретный ключ приложения:
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan key:generate
|
||||
```
|
||||
|
||||
7. Запустите сервер:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Ваше приложение готово!
|
||||
|
||||
Узнайте больше о доступных опциях и о том, как собирать бинарные файлы для других ОС в [документации по встраиванию приложений](embed.md).
|
||||
|
||||
### Изменение пути хранения
|
||||
|
||||
По умолчанию Laravel сохраняет загруженные файлы, кеши, логи и другие данные в директории `storage/` приложения. Это неудобно для встроенных приложений, так как каждая новая версия будет извлекаться в другую временную директорию.
|
||||
|
||||
Установите переменную окружения `LARAVEL_STORAGE_PATH` (например, в вашем `.env` файле) или вызовите метод `Illuminate\Foundation\Application::useStoragePath()`, чтобы использовать директорию за пределами временной директории.
|
||||
|
||||
### Запуск Octane как автономный бинарный файл
|
||||
|
||||
Можно даже упаковать приложения Laravel Octane как автономный бинарный файл!
|
||||
|
||||
Для этого [установите Octane правильно](#laravel-octane) и следуйте шагам, описанным в [предыдущем разделе](#laravel-приложения-как-автономные-бинарные-файлы).
|
||||
|
||||
Затем, чтобы запустить FrankenPHP в worker-режиме через Octane, выполните:
|
||||
|
||||
```console
|
||||
PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> Для работы команды автономный бинарник **обязательно** должен быть назван `frankenphp`, так как Octane требует наличия программы с именем `frankenphp` в PATH.
|
||||
12
docs/ru/mercure.md
Normal file
12
docs/ru/mercure.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Real-time режим
|
||||
|
||||
FrankenPHP поставляется с встроенным хабом [Mercure](https://mercure.rocks)!
|
||||
Mercure позволяет отправлять события в режиме реального времени на все подключённые устройства: они мгновенно получат JavaScript-событие.
|
||||
|
||||
Не требуются JS-библиотеки или SDK!
|
||||
|
||||

|
||||
|
||||
Чтобы включить хаб Mercure, обновите `Caddyfile` в соответствии с инструкциями [на сайте Mercure](https://mercure.rocks/docs/hub/config).
|
||||
|
||||
Для отправки обновлений Mercure из вашего кода мы рекомендуем использовать [Symfony Mercure Component](https://symfony.com/components/Mercure) (для его использования не требуется полный стек Symfony).
|
||||
15
docs/ru/metrics.md
Normal file
15
docs/ru/metrics.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Метрики
|
||||
|
||||
При включении [метрик Caddy](https://caddyserver.com/docs/metrics) FrankenPHP предоставляет следующие метрики:
|
||||
|
||||
- `frankenphp_[worker]_total_workers`: Общее количество worker-скриптов.
|
||||
- `frankenphp_[worker]_busy_workers`: Количество worker-скриптов, которые в данный момент обрабатывают запрос.
|
||||
- `frankenphp_[worker]_worker_request_time`: Время, затраченное всеми worker-скриптами на обработку запросов.
|
||||
- `frankenphp_[worker]_worker_request_count`: Количество запросов, обработанных всеми worker-скриптами.
|
||||
- `frankenphp_[worker]_ready_workers`: Количество worker-скриптов, которые вызвали `frankenphp_handle_request` хотя бы один раз.
|
||||
- `frankenphp_[worker]_worker_crashes`: Количество случаев неожиданного завершения worker-скриптов.
|
||||
- `frankenphp_[worker]_worker_restarts`: Количество случаев, когда worker-скрипт был перезапущен целенаправленно.
|
||||
- `frankenphp_total_threads`: Общее количество потоков PHP.
|
||||
- `frankenphp_busy_threads`: Количество потоков PHP, которые в данный момент обрабатывают запрос (работающие worker-скрипты всегда используют поток).
|
||||
|
||||
Для метрик worker-скриптов плейсхолдер `[worker]` заменяется на путь к Worker-скрипту, указанному в Caddyfile.
|
||||
101
docs/ru/performance.md
Normal file
101
docs/ru/performance.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Производительность
|
||||
|
||||
По умолчанию FrankenPHP предлагает хороший баланс между производительностью и удобством использования.
|
||||
Однако, используя подходящую конфигурацию, можно существенно улучшить производительность.
|
||||
|
||||
## Количество потоков и worker-скриптов
|
||||
|
||||
По умолчанию FrankenPHP запускает потоков и worker-скриптов (в worker режиме) вдвое больше, чем количество доступных процессорных ядер.
|
||||
|
||||
Оптимальные значения зависят от структуры вашего приложения, его функциональности и аппаратного обеспечения.
|
||||
Мы настоятельно рекомендуем изменить эти значения.
|
||||
|
||||
Чтобы найти подходящие параметры, лучше всего провести нагрузочные тесты, имитирующие реальный трафик.
|
||||
Хорошими инструментами для этого являются [k6](https://k6.io) и [Gatling](https://gatling.io).
|
||||
|
||||
Чтобы настроить количество потоков, используйте опцию `num_threads` в директивах `php_server` и `php`.
|
||||
Для изменения количества worker-скриптов используйте опцию `num` в секции `worker` директивы `frankenphp`.
|
||||
|
||||
## Worker режим
|
||||
|
||||
Включение [Worker режима](worker.md) значительно улучшает производительность,
|
||||
но ваше приложение должно быть адаптировано для совместимости с этим режимом:
|
||||
необходимо создать worker-скрипт и убедиться, что приложение не имеет утечек памяти.
|
||||
|
||||
## Избегайте использования musl
|
||||
|
||||
Статические бинарники, которые мы предоставляем, а также Alpine Linux-вариант официальных Docker-образов используют [библиотеку musl libc](https://musl.libc.org).
|
||||
|
||||
Известно, что PHP [значительно медленнее работает](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381) с этой библиотекой по сравнению с традиционной GNU libc, особенно при компиляции в ZTS режиме (потокобезопасный режим), который требуется для FrankenPHP.
|
||||
|
||||
Кроме того, [некоторые ошибки проявляются исключительно при использовании musl](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl).
|
||||
|
||||
В производственной среде настоятельно рекомендуется использовать glibc.
|
||||
|
||||
Это можно сделать, используя Debian Docker-образы (по умолчанию) и [компилируя FrankenPHP из исходников](compile.md).
|
||||
|
||||
В качестве альтернативы мы предоставляем статические бинарники, скомпилированные с [аллокатором mimalloc](https://github.com/microsoft/mimalloc), что делает FrankenPHP+musl быстрее (но всё же медленнее, чем FrankenPHP+glibc).
|
||||
|
||||
## Настройка среды выполнения Go
|
||||
|
||||
FrankenPHP написан на языке Go.
|
||||
|
||||
В большинстве случаев среда выполнения Go не требует особой настройки, но в некоторых ситуациях специфическая конфигурация может улучшить производительность.
|
||||
|
||||
Рекомендуется установить переменную окружения `GODEBUG` в значение `cgocheck=0` (по умолчанию в Docker-образах FrankenPHP).
|
||||
|
||||
Если вы запускаете FrankenPHP в контейнерах (Docker, Kubernetes, LXC и т.д.) и ограничиваете доступную память, установите переменную окружения `GOMEMLIMIT` в значение доступного объёма памяти.
|
||||
|
||||
Для более детальной информации ознакомьтесь с [документацией Go по этой теме](https://pkg.go.dev/runtime#hdr-Environment_Variables).
|
||||
|
||||
## `file_server`
|
||||
|
||||
По умолчанию директива `php_server` автоматически настраивает файловый сервер для обслуживания статических файлов (ресурсов), хранящихся в корневой директории.
|
||||
|
||||
Эта функция удобна, но имеет издержки. Чтобы отключить её, используйте следующую конфигурацию:
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
file_server off
|
||||
}
|
||||
```
|
||||
|
||||
## Плейсхолдеры
|
||||
|
||||
Вы можете использовать [плейсхолдеры](https://caddyserver.com/docs/conventions#placeholders) в директивах `root` и `env`.
|
||||
Однако это предотвращает кеширование значений и существенно снижает производительность.
|
||||
|
||||
По возможности избегайте использования плейсхолдеров в этих директивах.
|
||||
|
||||
## `resolve_root_symlink`
|
||||
|
||||
По умолчанию, если корневая директория документа является символьной ссылкой, она автоматически разрешается FrankenPHP (это необходимо для корректной работы PHP).
|
||||
Если корневая директория документа не является символьной ссылкой, вы можете отключить эту функцию.
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
resolve_root_symlink false
|
||||
}
|
||||
```
|
||||
|
||||
Это улучшит производительность, если директива `root` содержит [плейсхолдеры](https://caddyserver.com/docs/conventions#placeholders).
|
||||
В остальных случаях прирост производительности будет минимальным.
|
||||
|
||||
## Логи
|
||||
|
||||
Логирование, безусловно, полезно, но требует операций ввода-вывода и выделения памяти, что значительно снижает производительность.
|
||||
Убедитесь, что вы [правильно настроили уровень логирования](https://caddyserver.com/docs/caddyfile/options#log) и логируете только необходимое.
|
||||
|
||||
## Производительность PHP
|
||||
|
||||
FrankenPHP использует официальный интерпретатор PHP.
|
||||
Все стандартные оптимизации производительности PHP применимы к FrankenPHP.
|
||||
|
||||
В частности:
|
||||
|
||||
- убедитесь, что [OPcache](https://www.php.net/manual/en/book.opcache.php) установлен, включён и настроен должным образом;
|
||||
- включите [оптимизацию автозагрузки Composer](https://getcomposer.org/doc/articles/autoloader-optimization.md);
|
||||
- убедитесь, что кеш `realpath` достаточно велик для нужд вашего приложения;
|
||||
- используйте [предварительную загрузку](https://www.php.net/manual/en/opcache.preloading.php).
|
||||
|
||||
Для более детальной информации ознакомьтесь с [документацией Symfony о производительности](https://symfony.com/doc/current/performance.html) (большинство советов полезны даже если вы не используете Symfony).
|
||||
125
docs/ru/production.md
Normal file
125
docs/ru/production.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Деплой в продакшен
|
||||
|
||||
В этом руководстве мы рассмотрим, как развернуть PHP-приложение на одном сервере с использованием Docker Compose.
|
||||
|
||||
Если вы используете Symfony, рекомендуется прочитать раздел "[Deploy in production](https://github.com/dunglas/symfony-docker/blob/main/docs/production.md)" документации проекта Symfony Docker (в котором используется FrankenPHP).
|
||||
|
||||
Если вы используете API Platform (который также работает с FrankenPHP), ознакомьтесь с [документацией по деплою этого фреймворка](https://api-platform.com/docs/deployment/).
|
||||
|
||||
## Подготовка приложения
|
||||
|
||||
Сначала создайте файл `Dockerfile` в корневой директории вашего PHP-проекта:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# Замените "your-domain-name.example.com" на ваш домен
|
||||
ENV SERVER_NAME=your-domain-name.example.com
|
||||
# Если вы хотите отключить HTTPS, используйте вместо этого:
|
||||
#ENV SERVER_NAME=:80
|
||||
|
||||
# Включите настройки PHP для продакшн
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
|
||||
# Скопируйте файлы PHP вашего проекта в публичную директорию
|
||||
COPY . /app/public
|
||||
# Если вы используете Symfony или Laravel, необходимо скопировать весь проект:
|
||||
#COPY . /app
|
||||
```
|
||||
|
||||
Ознакомьтесь с разделом "[Создание кастомных Docker-образов](docker.md)" для получения дополнительных подробностей и настроек, а также для установки PHP-расширений и модулей Caddy.
|
||||
|
||||
Если ваш проект использует Composer, убедитесь, что он включён в Docker-образ, и установите все зависимости.
|
||||
|
||||
Затем добавьте файл `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
|
||||
|
||||
# Томы, необходимые для сертификатов и конфигурации Caddy
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Примеры выше предназначены для использования в продакшне.
|
||||
> В процессе разработки вы можете использовать том для монтирования, другую конфигурацию PHP и другое значение для переменной окружения `SERVER_NAME`.
|
||||
>
|
||||
> Посмотрите проект [Symfony Docker](https://github.com/dunglas/symfony-docker) (который использует FrankenPHP) для более сложного примера с использованием мультистейдж-образов, Composer, дополнительных PHP-расширений и т.д.
|
||||
|
||||
Наконец, если вы используете Git, закоммитьте эти файлы и отправьте их в репозиторий.
|
||||
|
||||
## Подготовка сервера
|
||||
|
||||
Для деплоя приложения в продакшн требуется сервер. В этом руководстве мы будем использовать виртуальную машину, предоставляемую DigitalOcean, но подойдёт любой Linux-сервер.
|
||||
Если у вас уже есть Linux-сервер с установленным Docker, вы можете сразу перейти к [следующему разделу](#настройка-доменного-имени).
|
||||
|
||||
В противном случае, используйте [эту ссылку](https://m.do.co/c/5d8aabe3ab80), чтобы получить $200 на баланс, создайте аккаунт, затем нажмите "Create a Droplet".
|
||||
Перейдите во вкладку "Marketplace" в разделе "Choose an image" и найдите приложение "Docker". Это создаст сервер на Ubuntu с установленными Docker и Docker Compose.
|
||||
|
||||
Для тестов подойдут самые дешёвые тарифы. Для реального продакшна выберите тариф из раздела "general purpose" в зависимости от ваших потребностей.
|
||||
|
||||

|
||||
|
||||
После этого подключитесь к серверу через SSH:
|
||||
|
||||
```console
|
||||
ssh root@<droplet-ip>
|
||||
```
|
||||
|
||||
## Настройка доменного имени
|
||||
|
||||
В большинстве случаев вам потребуется связать доменное имя с вашим сайтом.
|
||||
Создайте запись DNS типа `A`, указывающую на IP вашего сервера:
|
||||
|
||||
```dns
|
||||
your-domain-name.example.com. IN A 207.154.233.113
|
||||
```
|
||||
|
||||
Пример настройки через DigitalOcean ("Networking" > "Domains"):
|
||||
|
||||

|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Let's Encrypt, сервис, используемый FrankenPHP для автоматической генерации TLS-сертификатов, не поддерживает использование IP-адресов. Для работы необходим домен.
|
||||
|
||||
## Деплой
|
||||
|
||||
Скопируйте ваш проект на сервер с помощью `git clone`, `scp` или любого другого инструмента.
|
||||
Если вы используете GitHub, настройте [ключи развёртывания](https://docs.github.com/en/free-pro-team@latest/developers/overview/managing-deploy-keys#deploy-keys).
|
||||
|
||||
Пример с использованием Git:
|
||||
|
||||
```console
|
||||
git clone git@github.com:<username>/<project-name>.git
|
||||
```
|
||||
|
||||
Перейдите в директорию проекта и запустите приложение в режиме продакшн:
|
||||
|
||||
```console
|
||||
docker compose up -d --wait
|
||||
```
|
||||
|
||||
Сервер готов, а HTTPS-сертификат был автоматически сгенерирован. Перейдите на `https://your-domain-name.example.com` и наслаждайтесь!
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Docker может кэшировать слои. Убедитесь, что вы используете актуальную сборку, или используйте опцию `--no-cache` для предотвращения проблем с кэшем.
|
||||
|
||||
## Деплой на несколько узлов
|
||||
|
||||
Если вам нужно развернуть приложение на кластер машин, используйте [Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/), который совместим с предоставленными файлами Compose.
|
||||
Для деплоя на Kubernetes ознакомьтесь с [Helm-чартом API Platform](https://api-platform.com/docs/deployment/kubernetes/), который использует FrankenPHP.
|
||||
100
docs/ru/static.md
Normal file
100
docs/ru/static.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Создание статических бинарных файлов
|
||||
|
||||
Вместо использования локальной установки библиотеки PHP, можно создать статическую сборку FrankenPHP благодаря проекту [static-php-cli](https://github.com/crazywhalecc/static-php-cli) (несмотря на название, проект поддерживает все SAPI, а не только CLI).
|
||||
|
||||
С помощью этого метода создаётся единый переносимый бинарник, который включает PHP-интерпретатор, веб-сервер Caddy и FrankenPHP!
|
||||
|
||||
FrankenPHP также поддерживает [встраивание PHP-приложений в статический бинарный файл](embed.md).
|
||||
|
||||
## Linux
|
||||
|
||||
Мы предоставляем Docker-образ для сборки статического бинарника для Linux:
|
||||
|
||||
```console
|
||||
docker buildx bake --load static-builder
|
||||
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
|
||||
```
|
||||
|
||||
Созданный статический бинарный файл называется `frankenphp` и будет доступен в текущей директории.
|
||||
|
||||
Чтобы собрать статический бинарный файл без Docker, используйте инструкции для macOS — они подходят и для Linux.
|
||||
|
||||
### Дополнительные расширения
|
||||
|
||||
По умолчанию компилируются самые популярные PHP-расширения.
|
||||
|
||||
Чтобы уменьшить размер бинарного файла и сократить возможные векторы атак, можно указать список расширений, которые следует включить в сборку, используя Docker-аргумент `PHP_EXTENSIONS`.
|
||||
|
||||
Например, выполните следующую команду, чтобы собрать только расширение `opcache`:
|
||||
|
||||
```console
|
||||
docker buildx bake --load --set static-builder.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder
|
||||
# ...
|
||||
```
|
||||
|
||||
Чтобы добавить библиотеки, расширяющие функциональность включённых расширений, используйте Docker-аргумент `PHP_EXTENSION_LIBS`:
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.args.PHP_EXTENSIONS=gd \
|
||||
--set static-builder.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \
|
||||
static-builder
|
||||
```
|
||||
|
||||
### Дополнительные модули Caddy
|
||||
|
||||
Чтобы добавить дополнительные модули Caddy или передать аргументы в [xcaddy](https://github.com/caddyserver/xcaddy), используйте Docker-аргумент `XCADDY_ARGS`:
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.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
|
||||
```
|
||||
|
||||
В этом примере добавляются модуль HTTP-кэширования [Souin](https://souin.io) для Caddy, а также модули [cbrotli](https://github.com/dunglas/caddy-cbrotli), [Mercure](https://mercure.rocks) и [Vulcain](https://vulcain.rocks).
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Модули cbrotli, Mercure и Vulcain включены по умолчанию, если `XCADDY_ARGS` пуст или не установлен.
|
||||
> Если вы изменяете значение `XCADDY_ARGS`, добавьте их явно, если хотите включить их в сборку.
|
||||
|
||||
См. также, как [настроить сборку](#настройка-сборки).
|
||||
|
||||
### Токен GitHub
|
||||
|
||||
Если вы достигли лимита запросов к API GitHub, задайте личный токен доступа GitHub в переменной окружения `GITHUB_TOKEN`:
|
||||
|
||||
```console
|
||||
GITHUB_TOKEN="xxx" docker --load buildx bake static-builder
|
||||
# ...
|
||||
```
|
||||
|
||||
## macOS
|
||||
|
||||
Запустите следующий скрипт, чтобы создать статический бинарный файл для macOS (должен быть установлен [Homebrew](https://brew.sh/)):
|
||||
|
||||
```console
|
||||
git clone https://github.com/dunglas/frankenphp
|
||||
cd frankenphp
|
||||
./build-static.sh
|
||||
```
|
||||
|
||||
Примечание: этот скрипт также работает на Linux (и, вероятно, на других Unix-системах) и используется внутри предоставленного Docker-образа для статической сборки.
|
||||
|
||||
## Настройка сборки
|
||||
|
||||
Следующие переменные окружения можно передать в `docker build` и скрипт `build-static.sh`, чтобы настроить статическую сборку:
|
||||
|
||||
- `FRANKENPHP_VERSION`: версия FrankenPHP
|
||||
- `PHP_VERSION`: версия PHP
|
||||
- `PHP_EXTENSIONS`: PHP-расширения для сборки ([список поддерживаемых расширений](https://static-php.dev/en/guide/extensions.html))
|
||||
- `PHP_EXTENSION_LIBS`: дополнительные библиотеки, добавляющие функциональность расширениям
|
||||
- `XCADDY_ARGS`: аргументы для [xcaddy](https://github.com/caddyserver/xcaddy), например, для добавления модулей Caddy
|
||||
- `EMBED`: путь к PHP-приложению для встраивания в бинарник
|
||||
- `CLEAN`: если задано, libphp и все его зависимости будут пересобраны с нуля (без кэша)
|
||||
- `NO_COMPRESS`: отключает сжатие результирующего бинарника с помощью UPX
|
||||
- `DEBUG_SYMBOLS`: если задано, отладочные символы не будут удалены и будут добавлены в бинарник
|
||||
- `MIMALLOC`: (экспериментально, только для Linux) заменяет musl's mallocng на [mimalloc](https://github.com/microsoft/mimalloc) для повышения производительности
|
||||
- `RELEASE`: (только для мейнтейнеров) если задано, бинарник будет загружен на GitHub
|
||||
159
docs/ru/worker.md
Normal file
159
docs/ru/worker.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Worker режим в FrankenPHP
|
||||
|
||||
Загрузите приложение один раз и держите его в памяти.
|
||||
FrankenPHP обрабатывает входящие запросы за несколько миллисекунд.
|
||||
|
||||
## Запуск worker-скриптов
|
||||
|
||||
### Docker
|
||||
|
||||
Установите значение переменной окружения `FRANKENPHP_CONFIG` на `worker /path/to/your/worker/script.php`:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker /app/path/to/your/worker/script.php" \
|
||||
-v $PWD:/app \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
### Автономный бинарный файл
|
||||
|
||||
Используйте опцию `--worker` команды `php-server`, чтобы обслуживать содержимое текущей директории через worker-скрипт:
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php
|
||||
```
|
||||
|
||||
Если ваше PHP-приложение [встроено в бинарник](embed.md), вы можете добавить пользовательский `Caddyfile` в корневую директорию приложения.
|
||||
Он будет использоваться автоматически.
|
||||
|
||||
Также можно настроить [автоматический перезапуск worker-скрипта при изменении файлов](config.md#отслеживание-изменений-файлов) с помощью опции `--watch`.
|
||||
Следующая команда выполнит перезапуск, если будет изменён любой файл с расширением `.php` в директории `/path/to/your/app/` или её поддиректориях:
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
|
||||
```
|
||||
|
||||
## Symfony Runtime
|
||||
|
||||
Worker режим FrankenPHP поддерживается компонентом [Symfony Runtime](https://symfony.com/doc/current/components/runtime.html).
|
||||
Чтобы запустить любое Symfony-приложение в worker режиме, установите пакет FrankenPHP для [PHP Runtime](https://github.com/php-runtime/runtime):
|
||||
|
||||
```console
|
||||
composer require runtime/frankenphp-symfony
|
||||
```
|
||||
|
||||
Запустите сервер приложения, задав переменную окружения `APP_RUNTIME` для использования FrankenPHP Symfony Runtime:
|
||||
|
||||
```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
|
||||
|
||||
Подробнее см. в [документации](laravel.md#laravel-octane).
|
||||
|
||||
## Пользовательские приложения
|
||||
|
||||
Следующий пример показывает, как создать собственный worker-скрипт без использования сторонних библиотек:
|
||||
|
||||
```php
|
||||
<?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);
|
||||
};
|
||||
|
||||
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
|
||||
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
|
||||
$keepRunning = \frankenphp_handle_request($handler);
|
||||
|
||||
// Действия после отправки HTTP-ответа
|
||||
$myApp->terminate();
|
||||
|
||||
// Вызов сборщика мусора, чтобы снизить вероятность его запуска в процессе генерации страницы
|
||||
gc_collect_cycles();
|
||||
|
||||
if (!$keepRunning) break;
|
||||
}
|
||||
|
||||
// Завершение
|
||||
$myApp->shutdown();
|
||||
```
|
||||
|
||||
Запустите приложение, настроив worker-скрипт с помощью переменной окружения `FRANKENPHP_CONFIG`:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Настройка количества worker-скриптов
|
||||
|
||||
По умолчанию запускается по 2 worker-скрипта на каждый CPU.
|
||||
Вы можете задать своё значение:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Перезапуск worker-скрипта после определённого количества запросов
|
||||
|
||||
PHP изначально не предназначался для долгоживущих процессов, поэтому некоторые библиотеки и устаревший код могут приводить к утечкам памяти.
|
||||
Для этого можно настроить автоматический перезапуск worker-скрипта после обработки определённого количества запросов.
|
||||
|
||||
В предыдущем примере максимальное количество запросов задаётся с помощью переменной окружения `MAX_REQUESTS`.
|
||||
|
||||
### Сбои worker-скрипта
|
||||
|
||||
Если worker-скрипт завершится с ненулевым кодом выхода, FrankenPHP перезапустит его с использованием экспоненциальной задержки.
|
||||
Если worker-скрипт проработает дольше, чем время последней задержки \* 2, он будет считаться стабильным, и задержка сбросится.
|
||||
Однако, если worker-скрипт продолжает завершаться с ненулевым кодом выхода в течение короткого промежутка времени (например, из-за опечатки в коде), FrankenPHP завершит работу с ошибкой: `too many consecutive failures`.
|
||||
|
||||
## Поведение суперглобальных переменных
|
||||
|
||||
[PHP суперглобальные переменные](https://www.php.net/manual/en/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET` и т.д.) ведут себя следующим образом:
|
||||
|
||||
- до первого вызова `frankenphp_handle_request()` суперглобальные переменные содержат значения, связанные с самим worker-скриптом
|
||||
- во время и после вызова `frankenphp_handle_request()` суперглобальные переменные содержат значения, сгенерированные на основе обработанного HTTP-запроса, каждый вызов изменяет значения суперглобальных переменных
|
||||
|
||||
Чтобы получить доступ к суперглобальным переменным worker-скрипта внутри колбэка, необходимо скопировать их и импортировать копию в область видимости колбэка:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Копирование $_SERVER worker-скрипта перед первым вызовом frankenphp_handle_request()
|
||||
$workerServer = $_SERVER;
|
||||
|
||||
$handler = static function () use ($workerServer) {
|
||||
var_dump($_SERVER); // $_SERVER для запроса
|
||||
var_dump($workerServer); // $_SERVER worker-скрипта
|
||||
};
|
||||
|
||||
// ...
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user