mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-12-24 13:38:11 +08:00
Compare commits
112 Commits
feat/frank
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e6f665d82 | ||
|
|
57c58faf1c | ||
|
|
25d9cb9600 | ||
|
|
4092ecb5b5 | ||
|
|
75ccccf1b2 | ||
|
|
6231bf4a1c | ||
|
|
e01e40fd97 | ||
|
|
175e644d10 | ||
|
|
a8f75d0eef | ||
|
|
91c553f3d9 | ||
|
|
7fca07ed67 | ||
|
|
3599299cde | ||
|
|
bb1c3678dc | ||
|
|
58a63703b4 | ||
|
|
694ab86cef | ||
|
|
e23e0c571e | ||
|
|
f02e6f2f85 | ||
|
|
11213fd1de | ||
|
|
41da660088 | ||
|
|
599c92b15d | ||
|
|
225ca409d3 | ||
|
|
d2007620a4 | ||
|
|
4ac024a1d0 | ||
|
|
e0dcf42852 | ||
|
|
7b8cf6b127 | ||
|
|
98573ed7c0 | ||
|
|
16e2bbb969 | ||
|
|
816bcc2ad6 | ||
|
|
1fbabf91c9 | ||
|
|
2fa7663d3b | ||
|
|
b1bdce359b | ||
|
|
c9ad9fc55a | ||
|
|
12d4c3d09b | ||
|
|
7fceb32f7b | ||
|
|
1b30905c26 | ||
|
|
dadeb5a628 | ||
|
|
abaf03c7f7 | ||
|
|
fc5f6ef092 | ||
|
|
65111334a1 | ||
|
|
6747aaae2d | ||
|
|
6c764ad9c5 | ||
|
|
e6b3f70d91 | ||
|
|
911e6d156b | ||
|
|
c6cadf3bf6 | ||
|
|
f28f6e8d03 | ||
|
|
01beb66573 | ||
|
|
41e0713a1b | ||
|
|
bbfb1b0a0e | ||
|
|
49e98cc8d6 | ||
|
|
c93729e136 | ||
|
|
ea042637e6 | ||
|
|
0b74507945 | ||
|
|
aa1bd23004 | ||
|
|
02f900bb97 | ||
|
|
56df2666e1 | ||
|
|
1de9073e49 | ||
|
|
36062a0dce | ||
|
|
10cf2c4a2e | ||
|
|
f224f8e391 | ||
|
|
0b2d3c913f | ||
|
|
75a48e81a7 | ||
|
|
bd943f49de | ||
|
|
8f298ab060 | ||
|
|
41cb2bbeaa | ||
|
|
853cb67e95 | ||
|
|
eeb7d1a0c4 | ||
|
|
8341cc98c6 | ||
|
|
40cb42aace | ||
|
|
1e48fbb801 | ||
|
|
4e6d67e0b4 | ||
|
|
18946308fd | ||
|
|
f7298557aa | ||
|
|
861b345b05 | ||
|
|
724c0b11ca | ||
|
|
63168e087e | ||
|
|
6225da9c18 | ||
|
|
407ef09ac3 | ||
|
|
bf4c9fe986 | ||
|
|
b22bdd987b | ||
|
|
28d17b39dc | ||
|
|
264f92835d | ||
|
|
b49aed1934 | ||
|
|
4d0fb7d0f8 | ||
|
|
5447a7a6c8 | ||
|
|
1270784cd3 | ||
|
|
9b8d215727 | ||
|
|
94e58eb215 | ||
|
|
bf6e6534f6 | ||
|
|
fb1f46808e | ||
|
|
7f64673495 | ||
|
|
f7756717b5 | ||
|
|
9aee496b96 | ||
|
|
45823c51b2 | ||
|
|
f8ea48c3b1 | ||
|
|
1fbd619597 | ||
|
|
d52ce94341 | ||
|
|
b749f52ae5 | ||
|
|
e917ab7974 | ||
|
|
5514491a18 | ||
|
|
c42d287138 | ||
|
|
1f6f768c97 | ||
|
|
a4596b7767 | ||
|
|
fa3a7032a4 | ||
|
|
ab1ec71d24 | ||
|
|
219a5407ff | ||
|
|
76511c2e19 | ||
|
|
7668a27d4b | ||
|
|
e4c1801c25 | ||
|
|
52df300f86 | ||
|
|
fe7aa2cae4 | ||
|
|
7754abcbd0 | ||
|
|
52a0be5728 |
1
.clang-format-ignore
Normal file
1
.clang-format-ignore
Normal file
@@ -0,0 +1 @@
|
||||
frankenphp_arginfo.h
|
||||
13
.editorconfig
Normal file
13
.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.sh]
|
||||
indent_style = tab
|
||||
tab_width = 4
|
||||
|
||||
[*.Dockerfile]
|
||||
indent_style = tab
|
||||
tab_width = 4
|
||||
5
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
5
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -29,8 +29,9 @@ body:
|
||||
- Docker (Debian Trixie)
|
||||
- Docker (Debian Bookworm)
|
||||
- Docker (Alpine)
|
||||
- Official static build
|
||||
- Standalone binary
|
||||
- deb packages
|
||||
- rpm packages
|
||||
- Static binary
|
||||
- Custom (tell us more in the description)
|
||||
default: 0
|
||||
validations:
|
||||
|
||||
6
.github/dependabot.yaml
vendored
6
.github/dependabot.yaml
vendored
@@ -11,6 +11,8 @@ updates:
|
||||
go-modules:
|
||||
patterns:
|
||||
- "*"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: gomod
|
||||
directory: /caddy
|
||||
schedule:
|
||||
@@ -21,6 +23,8 @@ updates:
|
||||
go-modules:
|
||||
patterns:
|
||||
- "*"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
@@ -31,3 +35,5 @@ updates:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
28
.github/workflows/docker.yaml
vendored
28
.github/workflows/docker.yaml
vendored
@@ -46,6 +46,8 @@ jobs:
|
||||
php_version: ${{ steps.check.outputs.php_version }}
|
||||
php82_version: ${{ steps.check.outputs.php82_version }}
|
||||
php83_version: ${{ steps.check.outputs.php83_version }}
|
||||
php84_version: ${{ steps.check.outputs.php84_version }}
|
||||
php85_version: ${{ steps.check.outputs.php85_version }}
|
||||
skip: ${{ steps.check.outputs.skip }}
|
||||
ref: ${{ steps.check.outputs.ref || (github.event_name == 'workflow_dispatch' && inputs.version) || '' }}
|
||||
steps:
|
||||
@@ -57,11 +59,13 @@ jobs:
|
||||
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="; "")')
|
||||
PHP_85_LATEST=$(skopeo inspect docker://docker.io/library/php:8.5 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
{
|
||||
echo php_version="${PHP_82_LATEST},${PHP_83_LATEST},${PHP_84_LATEST}"
|
||||
echo php_version="${PHP_82_LATEST},${PHP_83_LATEST},${PHP_84_LATEST},${PHP_85_LATEST}"
|
||||
echo php82_version="${PHP_82_LATEST//./-}"
|
||||
echo php83_version="${PHP_83_LATEST//./-}"
|
||||
echo php84_version="${PHP_84_LATEST//./-}"
|
||||
echo php85_version="${PHP_85_LATEST//./-}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
# Check if the Docker images must be rebuilt
|
||||
@@ -75,8 +79,9 @@ jobs:
|
||||
FRANKENPHP_82_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:"${FRANKENPHP_LATEST_TAG_NO_PREFIX}"-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:"${FRANKENPHP_LATEST_TAG_NO_PREFIX}"-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:"${FRANKENPHP_LATEST_TAG_NO_PREFIX}"-php8.4 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
FRANKENPHP_85_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:"${FRANKENPHP_LATEST_TAG_NO_PREFIX}"-php8.5 --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}" ]] && [[ "${FRANKENPHP_84_LATEST}" == "${PHP_84_LATEST}" ]]; then
|
||||
if [[ "${FRANKENPHP_82_LATEST}" == "${PHP_82_LATEST}" ]] && [[ "${FRANKENPHP_83_LATEST}" == "${PHP_83_LATEST}" ]] && [[ "${FRANKENPHP_84_LATEST}" == "${PHP_84_LATEST}" ]] && [[ "${FRANKENPHP_85_LATEST}" == "${PHP_85_LATEST}" ]]; then
|
||||
echo skip=true >> "${GITHUB_OUTPUT}"
|
||||
exit 0
|
||||
fi
|
||||
@@ -85,7 +90,7 @@ jobs:
|
||||
echo ref="${FRANKENPHP_LATEST_TAG}"
|
||||
echo skip=false
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
if: ${{ !fromJson(steps.check.outputs.skip) }}
|
||||
with:
|
||||
ref: ${{ steps.check.outputs.ref }}
|
||||
@@ -129,17 +134,25 @@ jobs:
|
||||
platform: linux/arm/v6
|
||||
- variant: php-${{ needs.prepare.outputs.php83_version }}-trixie
|
||||
platform: linux/arm/v6
|
||||
- variant: php-${{ needs.prepare.outputs.php84_version }}-trixie
|
||||
platform: linux/arm/v6
|
||||
- variant: php-${{ needs.prepare.outputs.php85_version }}-trixie
|
||||
platform: linux/arm/v6
|
||||
- variant: php-${{ needs.prepare.outputs.php82_version }}-bookworm
|
||||
platform: linux/arm/v6
|
||||
- variant: php-${{ needs.prepare.outputs.php83_version }}-bookworm
|
||||
platform: linux/arm/v6
|
||||
- variant: php-${{ needs.prepare.outputs.php84_version }}-bookworm
|
||||
platform: linux/arm/v6
|
||||
- variant: php-${{ needs.prepare.outputs.php85_version }}-bookworm
|
||||
platform: linux/arm/v6
|
||||
steps:
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: echo "sanitized_platform=${PLATFORM//\//-}" >> "${GITHUB_OUTPUT}"
|
||||
env:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
persist-credentials: false
|
||||
@@ -159,6 +172,7 @@ jobs:
|
||||
with:
|
||||
pull: true
|
||||
load: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
source: .
|
||||
targets: |
|
||||
builder-${{ matrix.variant }}
|
||||
runner-${{ matrix.variant }}
|
||||
@@ -194,7 +208,7 @@ jobs:
|
||||
VARIANT: ${{ matrix.variant }}
|
||||
- name: Upload builder metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: metadata-builder-${{ matrix.variant }}-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata/builder/*
|
||||
@@ -202,7 +216,7 @@ jobs:
|
||||
retention-days: 1
|
||||
- name: Upload runner metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: metadata-runner-${{ matrix.variant }}-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata/runner/*
|
||||
@@ -234,7 +248,7 @@ jobs:
|
||||
target: ["builder", "runner"]
|
||||
steps:
|
||||
- name: Download metadata
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: metadata-${{ matrix.target }}-${{ matrix.variant }}-*
|
||||
path: /tmp/metadata
|
||||
|
||||
8
.github/workflows/lint.yaml
vendored
8
.github/workflows/lint.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
@@ -40,8 +40,12 @@ jobs:
|
||||
VALIDATE_TERRAGRUNT: false
|
||||
VALIDATE_DOCKERFILE_HADOLINT: false
|
||||
VALIDATE_TRIVY: false
|
||||
# Prettier and StandardJS are incompatible
|
||||
# Prettier, Biome and StandardJS are incompatible
|
||||
VALIDATE_JAVASCRIPT_PRETTIER: false
|
||||
VALIDATE_TYPESCRIPT_PRETTIER: false
|
||||
VALIDATE_BIOME_FORMAT: false
|
||||
VALIDATE_BIOME_LINT: false
|
||||
# Conflicts with MARKDOWN
|
||||
VALIDATE_MARKDOWN_PRETTIER: false
|
||||
# To re-enable when https://github.com/super-linter/super-linter/issues/7244 will be closed
|
||||
VALIDATE_EDITORCONFIG: false
|
||||
|
||||
6
.github/workflows/sanitizers.yaml
vendored
6
.github/workflows/sanitizers.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
steps:
|
||||
- name: Remove local PHP
|
||||
run: sudo apt-get remove --purge --autoremove 'php*' 'libmemcached*'
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@v6
|
||||
@@ -52,12 +52,12 @@ jobs:
|
||||
- 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
|
||||
curl -fsSL 'https://www.php.net/releases/index.php?json&max=1&version=8.5' -o version.json
|
||||
echo version="$(jq -r 'keys[0]' version.json)" >> "$GITHUB_OUTPUT"
|
||||
echo archive="$(jq -r '.[] .source[] | select(.filename |endswith(".xz")) | "https://www.php.net/distributions/" + .filename' version.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: Cache PHP
|
||||
id: cache-php
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: php/target
|
||||
key: php-sanitizers-${{ matrix.sanitizer }}-${{ runner.arch }}-${{ steps.determine-php-version.outputs.version }}
|
||||
|
||||
92
.github/workflows/static.yaml
vendored
92
.github/workflows/static.yaml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
- main
|
||||
paths:
|
||||
- "docker-bake.hcl"
|
||||
- ".github/workflows/docker.yaml"
|
||||
- ".github/workflows/static.yaml"
|
||||
- "**cgo.go"
|
||||
- "**Dockerfile"
|
||||
- "**.c"
|
||||
@@ -37,6 +37,7 @@ permissions:
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
|
||||
SPC_OPT_BUILD_ARGS: --debug
|
||||
GOTOOLCHAIN: local
|
||||
|
||||
jobs:
|
||||
@@ -62,7 +63,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REF: ${{ (github.ref_type == 'tag' && github.ref_name) || (github.event_name == 'workflow_dispatch' && inputs.version) || '' }}
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ steps.check.outputs.ref }}
|
||||
persist-credentials: false
|
||||
@@ -108,7 +109,7 @@ jobs:
|
||||
run: echo "sanitized_platform=${PLATFORM//\//-}" >> "${GITHUB_OUTPUT}"
|
||||
env:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
persist-credentials: false
|
||||
@@ -122,12 +123,26 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Set VERSION
|
||||
run: |
|
||||
if [ "${GITHUB_REF_TYPE}" == "tag" ]; then
|
||||
export VERSION=${GITHUB_REF_NAME:1}
|
||||
elif [ "${GITHUB_EVENT_NAME}" == "schedule" ]; then
|
||||
export VERSION="${REF}"
|
||||
else
|
||||
export VERSION=${GITHUB_SHA}
|
||||
fi
|
||||
|
||||
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
|
||||
env:
|
||||
REF: ${{ needs.prepare.outputs.ref }}
|
||||
- name: Build
|
||||
id: build
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
pull: true
|
||||
load: ${{ !fromJson(needs.prepare.outputs.push) || matrix.debug || matrix.mimalloc }}
|
||||
source: .
|
||||
targets: static-builder-musl
|
||||
set: |
|
||||
${{ matrix.debug && 'static-builder-musl.args.DEBUG_SYMBOLS=1' || '' }}
|
||||
@@ -141,7 +156,6 @@ jobs:
|
||||
${{ (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 || 'dev' }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600
|
||||
name: Export metadata
|
||||
@@ -156,7 +170,7 @@ jobs:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
- name: Upload metadata
|
||||
if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: metadata-static-builder-musl-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata/*
|
||||
@@ -174,7 +188,7 @@ jobs:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
- name: Upload artifact
|
||||
if: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
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' || '' }}
|
||||
@@ -214,15 +228,56 @@ jobs:
|
||||
runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
needs: [prepare]
|
||||
steps:
|
||||
# Inspired by https://gist.github.com/antiphishfish/1e3fbc3f64ef6f1ab2f47457d2da5d9d and https://github.com/apache/flink/blob/master/tools/azure-pipelines/free_disk_space.sh
|
||||
- name: Free disk space
|
||||
run: |
|
||||
set -xe
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /usr/share/swift
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf /usr/local/.ghcup
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
sudo rm -rf /opt/hostedtoolcache/
|
||||
sudo rm -rf /usr/local/graalvm/
|
||||
sudo rm -rf /usr/local/share/powershell
|
||||
sudo rm -rf /usr/local/share/chromium
|
||||
sudo rm -rf /usr/local/lib/node_modules
|
||||
sudo docker image prune --all --force
|
||||
|
||||
APT_PARAMS='sudo apt -y -qq -o=Dpkg::Use-Pty=0'
|
||||
$APT_PARAMS remove -y '^dotnet-.*'
|
||||
$APT_PARAMS remove -y '^llvm-.*'
|
||||
$APT_PARAMS remove -y '^php.*'
|
||||
$APT_PARAMS remove -y '^mongodb-.*'
|
||||
$APT_PARAMS remove -y '^mysql-.*'
|
||||
$APT_PARAMS remove -y azure-cli firefox powershell mono-devel libgl1-mesa-dri
|
||||
$APT_PARAMS autoremove --purge -y
|
||||
$APT_PARAMS autoclean
|
||||
$APT_PARAMS clean
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: echo "sanitized_platform=${PLATFORM//\//-}" >> "${GITHUB_OUTPUT}"
|
||||
env:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
persist-credentials: false
|
||||
- name: Set VERSION
|
||||
run: |
|
||||
if [ "${GITHUB_REF_TYPE}" == "tag" ]; then
|
||||
export VERSION=${GITHUB_REF_NAME:1}
|
||||
elif [ "${GITHUB_EVENT_NAME}" == "schedule" ]; then
|
||||
export VERSION="${REF}"
|
||||
else
|
||||
export VERSION=${GITHUB_SHA}
|
||||
fi
|
||||
|
||||
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
|
||||
env:
|
||||
REF: ${{ needs.prepare.outputs.ref }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
@@ -239,10 +294,10 @@ jobs:
|
||||
with:
|
||||
pull: true
|
||||
load: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
source: .
|
||||
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
|
||||
@@ -251,7 +306,6 @@ jobs:
|
||||
${{ 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
|
||||
@@ -266,7 +320,7 @@ jobs:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
- name: Upload metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: metadata-static-builder-gnu-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata-gnu/*
|
||||
@@ -290,7 +344,7 @@ jobs:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
- name: Upload artifact
|
||||
if: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}-gnu-files
|
||||
path: gh-output/*
|
||||
@@ -326,13 +380,13 @@ jobs:
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
steps:
|
||||
- name: Download metadata
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: metadata-static-builder-musl-*
|
||||
path: /tmp/metadata
|
||||
merge-multiple: true
|
||||
- name: Download GNU metadata
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: metadata-static-builder-gnu-*
|
||||
path: /tmp/metadata-gnu
|
||||
@@ -384,12 +438,12 @@ jobs:
|
||||
matrix:
|
||||
platform: ["arm64", "x86_64"]
|
||||
name: Build macOS ${{ matrix.platform }} binaries
|
||||
runs-on: ${{ matrix.platform == 'arm64' && 'macos-14' || 'macos-13' }}
|
||||
runs-on: ${{ matrix.platform == 'arm64' && 'macos-15' || 'macos-15-intel' }}
|
||||
needs: [prepare]
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
persist-credentials: false
|
||||
@@ -419,13 +473,19 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE: ${{ (needs.prepare.outputs.ref || github.ref_type == 'tag') && '1' || '' }}
|
||||
NO_COMPRESS: ${{ github.event_name == 'pull_request' && '1' || '' }}
|
||||
- name: Upload logs
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
path: dist/static-php-cli/log
|
||||
name: static-php-cli-log-${{ matrix.platform }}-${{ github.sha }}
|
||||
- if: needs.prepare.outputs.ref || github.ref_type == 'tag'
|
||||
uses: actions/attest-build-provenance@v3
|
||||
with:
|
||||
subject-path: ${{ github.workspace }}/dist/frankenphp-mac-*
|
||||
- name: Upload artifact
|
||||
if: github.ref_type == 'branch'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: frankenphp-mac-${{ matrix.platform }}
|
||||
path: dist/frankenphp-mac-${{ matrix.platform }}
|
||||
|
||||
72
.github/workflows/tests.yaml
vendored
72
.github/workflows/tests.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
tests-linux:
|
||||
name: Tests (Linux, PHP ${{ matrix.php-versions }})
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.experimental || false }}
|
||||
continue-on-error: false
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -32,12 +32,11 @@ jobs:
|
||||
- php-versions: "8.3"
|
||||
- php-versions: "8.4"
|
||||
- php-versions: "8.5"
|
||||
experimental: true
|
||||
env:
|
||||
GOMAXPROCS: 10
|
||||
LIBRARY_PATH: ${{ github.workspace }}/watcher/target/lib
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@v6
|
||||
@@ -83,35 +82,78 @@ jobs:
|
||||
- name: Run integrations tests
|
||||
run: ./reload_test.sh
|
||||
- name: Lint Go code
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
if: matrix.php-versions == '8.4'
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
if: matrix.php-versions == '8.5'
|
||||
with:
|
||||
version: latest
|
||||
- name: Ensure go.mod is tidy
|
||||
if: matrix.php-versions == '8.4'
|
||||
if: matrix.php-versions == '8.5'
|
||||
run: go mod tidy -diff
|
||||
- name: Ensure caddy/go.mod is tidy
|
||||
if: matrix.php-versions == '8.4'
|
||||
if: matrix.php-versions == '8.5'
|
||||
run: go mod tidy -diff
|
||||
working-directory: caddy/
|
||||
tests-mac:
|
||||
name: Tests (macOS, PHP 8.4)
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
integration-tests:
|
||||
name: Integration Tests (Linux, PHP ${{ matrix.php-versions }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ["8.3", "8.4", "8.5"]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25"
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.4
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
ini-file: development
|
||||
coverage: none
|
||||
tools: none
|
||||
env:
|
||||
phpts: ts
|
||||
debug: true
|
||||
- name: Install PHP development libraries
|
||||
run: sudo apt-get update && sudo apt-get install -y libkrb5-dev libsodium-dev libargon2-dev
|
||||
- name: Install xcaddy
|
||||
run: go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
|
||||
- name: Download PHP sources
|
||||
run: |
|
||||
PHP_VERSION=$(php -r "echo PHP_VERSION;")
|
||||
wget -q "https://www.php.net/distributions/php-${PHP_VERSION}.tar.gz"
|
||||
tar xzf "php-${PHP_VERSION}.tar.gz"
|
||||
echo "GEN_STUB_SCRIPT=${PWD}/php-${PHP_VERSION}/build/gen_stub.php" >> "${GITHUB_ENV}"
|
||||
- name: Set CGO flags
|
||||
run: |
|
||||
echo "CGO_CFLAGS=$(php-config --includes)" >> "${GITHUB_ENV}"
|
||||
echo "CGO_LDFLAGS=$(php-config --ldflags) $(php-config --libs)" >> "${GITHUB_ENV}"
|
||||
- name: Run integration tests
|
||||
working-directory: internal/extgen/
|
||||
run: go test -tags integration -v -timeout 30m
|
||||
tests-mac:
|
||||
name: Tests (macOS, PHP 8.5)
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25"
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.5
|
||||
ini-file: development
|
||||
coverage: none
|
||||
tools: none
|
||||
|
||||
1
.gitleaksignore
Normal file
1
.gitleaksignore
Normal file
@@ -0,0 +1 @@
|
||||
/github/workspace/docs/mercure.md:jwt:88
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
no-hard-tabs: false
|
||||
MD010: false
|
||||
MD013: false
|
||||
MD033: false
|
||||
MD060: false
|
||||
|
||||
@@ -110,7 +110,7 @@ RUN go mod download
|
||||
WORKDIR /go/src/app
|
||||
COPY --link . ./
|
||||
|
||||
# See https://github.com/docker-library/php/blob/master/8.4/trixie/zts/Dockerfile#L57-L59 for PHP values
|
||||
# See https://github.com/docker-library/php/blob/master/8.5/trixie/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"
|
||||
|
||||
94
README.md
94
README.md
@@ -16,21 +16,77 @@ FrankenPHP can also be used as a standalone Go library to embed PHP in any app u
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 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/php/frankenphp/releases) or copy this line into your
|
||||
terminal to automatically install the version appropriate for your platform:
|
||||
### Install Script
|
||||
|
||||
You can copy this line into your terminal to automatically
|
||||
install an appropriate version for your platform:
|
||||
|
||||
```console
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
mv frankenphp /usr/local/bin/
|
||||
```
|
||||
|
||||
### Standalone Binary
|
||||
|
||||
We provide static FrankenPHP binaries for development purposes on Linux and macOS
|
||||
containing [PHP 8.4](https://www.php.net/releases/8.4/en.php) and most popular PHP extensions.
|
||||
|
||||
[Download FrankenPHP](https://github.com/php/frankenphp/releases)
|
||||
|
||||
**Installing extensions:** Most common extensions are bundled. It's not possible to install more extensions.
|
||||
|
||||
### rpm Packages
|
||||
|
||||
Our maintainers offer rpm packages for all systems using `dnf`. To install, run:
|
||||
|
||||
```console
|
||||
sudo dnf install https://rpm.henderkes.com/static-php-1-0.noarch.rpm
|
||||
sudo dnf module enable php-zts:static-8.4 # 8.2-8.5 available
|
||||
sudo dnf install frankenphp
|
||||
```
|
||||
|
||||
**Installing extensions:** `sudo dnf install php-zts-<extension>`
|
||||
|
||||
For extensions not available by default, use [PIE](https://github.com/php/pie):
|
||||
|
||||
```console
|
||||
sudo dnf install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### deb Packages
|
||||
|
||||
Our maintainers offer deb packages for all systems using `apt`. To install, run:
|
||||
|
||||
```console
|
||||
sudo curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg && \
|
||||
echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | sudo tee /etc/apt/sources.list.d/static-php.list && \
|
||||
sudo apt update
|
||||
sudo apt install frankenphp
|
||||
```
|
||||
|
||||
**Installing extensions:** `sudo apt install php-zts-<extension>`
|
||||
|
||||
For extensions not available by default, use [PIE](https://github.com/php/pie):
|
||||
|
||||
```console
|
||||
sudo apt install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### Homebrew
|
||||
|
||||
FrankenPHP is also available as a [Homebrew](https://brew.sh) package for macOS and Linux.
|
||||
|
||||
```console
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
**Installing extensions:** Use [PIE](https://github.com/php/pie).
|
||||
|
||||
### Usage
|
||||
|
||||
To serve the content of the current directory, run:
|
||||
|
||||
```console
|
||||
@@ -43,6 +99,12 @@ You can also run command-line scripts with:
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
```
|
||||
|
||||
For the deb and rpm packages, you can also start the systemd service:
|
||||
|
||||
```console
|
||||
sudo systemctl start frankenphp
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Alternatively, [Docker images](https://frankenphp.dev/docs/docker/) are available:
|
||||
@@ -60,22 +122,6 @@ 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.
|
||||
|
||||
### Homebrew
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
- [Classic mode](https://frankenphp.dev/docs/classic/)
|
||||
|
||||
@@ -66,12 +66,12 @@ ENV GOTOOLCHAIN=local
|
||||
RUN apk add --no-cache --virtual .build-deps \
|
||||
$PHPIZE_DEPS \
|
||||
argon2-dev \
|
||||
# Needed for the custom Go build
|
||||
# Needed for the custom Go build \
|
||||
bash \
|
||||
brotli-dev \
|
||||
coreutils \
|
||||
curl-dev \
|
||||
# Needed for the custom Go build
|
||||
# Needed for the custom Go build \
|
||||
git \
|
||||
gnu-libiconv-dev \
|
||||
libsodium-dev \
|
||||
@@ -89,20 +89,20 @@ RUN apk add --no-cache --virtual .build-deps \
|
||||
# Install e-dant/watcher (necessary for file watching)
|
||||
WORKDIR /usr/local/src/watcher
|
||||
RUN --mount=type=secret,id=github-token \
|
||||
if [ -f /run/secrets/github-token ] && [ -s /run/secrets/github-token ]; then \
|
||||
curl -s -H "Authorization: Bearer $(cat /run/secrets/github-token)" https://api.github.com/repos/e-dant/watcher/releases/latest; \
|
||||
else \
|
||||
curl -s https://api.github.com/repos/e-dant/watcher/releases/latest; \
|
||||
fi | \
|
||||
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
|
||||
if [ -f /run/secrets/github-token ] && [ -s /run/secrets/github-token ]; then \
|
||||
curl -s -H "Authorization: Bearer $(cat /run/secrets/github-token)" https://api.github.com/repos/e-dant/watcher/releases/latest; \
|
||||
else \
|
||||
curl -s https://api.github.com/repos/e-dant/watcher/releases/latest; \
|
||||
fi | \
|
||||
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
|
||||
|
||||
@@ -123,7 +123,7 @@ ENV CGO_LDFLAGS="-lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLA
|
||||
|
||||
WORKDIR /go/src/app/caddy/frankenphp
|
||||
RUN GOBIN=/usr/local/bin \
|
||||
../../go.sh install -ldflags "-w -s -extldflags '-Wl,-z,stack-size=0x80000' -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" -buildvcs=true && \
|
||||
../../go.sh install -ldflags "-w -s -extldflags '-Wl,-z,stack-size=0x80000' -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" -buildvcs=true && \
|
||||
setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
([ -z "${NO_COMPRESS}" ] && upx --best /usr/local/bin/frankenphp || true) && \
|
||||
frankenphp version && \
|
||||
|
||||
51
backoff.go
51
backoff.go
@@ -1,51 +0,0 @@
|
||||
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.maxConsecutiveFailures != -1 && 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)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
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")
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
#!/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 ..
|
||||
188
build-static.sh
188
build-static.sh
@@ -8,8 +8,11 @@ if ! type "git" >/dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_DIR=$(pwd)
|
||||
|
||||
arch="$(uname -m)"
|
||||
os="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
[ "$os" = "darwin" ] && os="mac"
|
||||
|
||||
# Supported variables:
|
||||
# - PHP_VERSION: PHP version to build (default: "8.4")
|
||||
@@ -73,27 +76,8 @@ if [ -z "${PHP_VERSION}" ]; then
|
||||
export PHP_VERSION
|
||||
fi
|
||||
# default extension set
|
||||
defaultExtensions="amqp,apcu,ast,bcmath,brotli,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,lz4,mbregex,mbstring,memcache,memcached,mysqli,mysqlnd,opcache,openssl,password-argon2,parallel,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pdo_sqlsrv,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,ssh2,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,xlswriter,xml,xmlreader,xmlwriter,xz,zip,zlib,yaml,zstd"
|
||||
defaultExtensionLibs="libavif,nghttp2,nghttp3,ngtcp2"
|
||||
|
||||
md5binary="md5sum"
|
||||
if [ "${os}" = "darwin" ]; then
|
||||
os="mac"
|
||||
md5binary="md5 -q"
|
||||
fi
|
||||
|
||||
if [ "${os}" = "linux" ] && ! type "cmake" >/dev/null 2>&1; then
|
||||
echo "The \"cmake\" command must be installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${os}" = "linux" ] && { [[ "${arch}" =~ "aarch" ]] || [[ "${arch}" =~ "arm" ]]; }; then
|
||||
fpic="-fPIC"
|
||||
fpie="-fPIE"
|
||||
else
|
||||
fpic="-fpic"
|
||||
fpie="-fpie"
|
||||
fi
|
||||
defaultExtensions="amqp,apcu,ast,bcmath,brotli,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,lz4,mbregex,mbstring,memcache,memcached,mysqli,mysqlnd,opcache,openssl,password-argon2,parallel,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pdo_sqlsrv,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,ssh2,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,xlswriter,xml,xmlreader,xmlwriter,xsl,xz,zip,zlib,yaml,zstd"
|
||||
defaultExtensionLibs="libavif,nghttp2,nghttp3,ngtcp2,watcher"
|
||||
|
||||
if [ -z "${FRANKENPHP_VERSION}" ]; then
|
||||
FRANKENPHP_VERSION="$(git rev-parse --verify HEAD)"
|
||||
@@ -115,8 +99,6 @@ elif [ -d ".git/" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
bin="frankenphp-${os}-${arch}"
|
||||
|
||||
if [ -n "${CLEAN}" ]; then
|
||||
rm -Rf dist/
|
||||
go clean -cache
|
||||
@@ -194,153 +176,51 @@ if [ -n "${MIMALLOC}" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build libphp if necessary
|
||||
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
|
||||
${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}"
|
||||
|
||||
echo -n "${cache_key}" >../cache_key
|
||||
fi
|
||||
|
||||
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}"
|
||||
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"
|
||||
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
|
||||
|
||||
export CGO_LDFLAGS
|
||||
|
||||
LIBPHP_VERSION="$(./buildroot/bin/php-config --version)"
|
||||
|
||||
cd ../
|
||||
|
||||
if [ -z "${DEBUG_SYMBOLS}" ]; then
|
||||
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 | awk '{printf $1}' >app_checksum.txt
|
||||
SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --with-frankenphp-app=${EMBED}"
|
||||
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"
|
||||
SPC_OPT_INSTALL_ARGS="go-xcaddy"
|
||||
if [ -z "${DEBUG_SYMBOLS}" ] && [ -z "${NO_COMPRESS}" ] && [ "${os}" = "linux" ]; then
|
||||
SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --with-upx-pack"
|
||||
SPC_OPT_INSTALL_ARGS="${SPC_OPT_INSTALL_ARGS} upx"
|
||||
fi
|
||||
|
||||
XCADDY_DEBUG=0
|
||||
export SPC_DEFAULT_C_FLAGS="-fPIC -O2"
|
||||
if [ -n "${DEBUG_SYMBOLS}" ]; then
|
||||
XCADDY_DEBUG=1
|
||||
fi
|
||||
|
||||
if [ "${SPC_LIBC}" = "musl" ]; then
|
||||
muslStackSizeFix="-Wl,-z,stack-size=0x80000"
|
||||
fi
|
||||
|
||||
go env
|
||||
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'\""
|
||||
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="${SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS} -fPIE -g"
|
||||
else
|
||||
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="${SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS} -fPIE -fstack-protector-strong -O2 -w -s"
|
||||
fi
|
||||
export SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS
|
||||
export SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli"
|
||||
|
||||
# Build FrankenPHP
|
||||
${spcCommand} doctor --auto-fix
|
||||
for pkg in ${SPC_OPT_INSTALL_ARGS}; do
|
||||
${spcCommand} install-pkg "${pkg}"
|
||||
done
|
||||
# shellcheck disable=SC2086
|
||||
CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS=${xcaddyGoBuildFlags} \
|
||||
XCADDY_DEBUG="${XCADDY_DEBUG}" \
|
||||
${XCADDY_COMMAND} build \
|
||||
--output "../dist/${bin}" \
|
||||
${XCADDY_ARGS} \
|
||||
--with github.com/dunglas/frankenphp=.. \
|
||||
--with github.com/dunglas/frankenphp/caddy=.
|
||||
cd ..
|
||||
${spcCommand} download --with-php="${PHP_VERSION}" --for-extensions="${PHP_EXTENSIONS}" --for-libs="${PHP_EXTENSION_LIBS}" ${SPC_OPT_DOWNLOAD_ARGS}
|
||||
export FRANKENPHP_SOURCE_PATH="${CURRENT_DIR}"
|
||||
# shellcheck disable=SC2086
|
||||
${spcCommand} build --enable-zts --build-embed --build-frankenphp ${SPC_OPT_BUILD_ARGS} "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}"
|
||||
|
||||
if [ -d "${EMBED}" ]; then
|
||||
truncate -s 0 app.tar
|
||||
truncate -s 0 app_checksum.txt
|
||||
if [ -n "$CI" ]; then
|
||||
rm -rf ./downloads
|
||||
rm -rf ./source
|
||||
fi
|
||||
|
||||
if type "upx" >/dev/null 2>&1 && [ -z "${DEBUG_SYMBOLS}" ] && [ -z "${NO_COMPRESS}" ]; then
|
||||
upx --best "dist/${bin}"
|
||||
fi
|
||||
cd ../..
|
||||
|
||||
"dist/${bin}" version
|
||||
"dist/${bin}" build-info
|
||||
bin="dist/frankenphp-${os}-${arch}"
|
||||
cp "dist/static-php-cli/buildroot/bin/frankenphp" "${bin}"
|
||||
"${bin}" version
|
||||
"${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}" "${bin}" --repo dunglas/frankenphp --clobber
|
||||
fi
|
||||
|
||||
if [ -n "${CURRENT_REF}" ]; then
|
||||
|
||||
@@ -4,12 +4,13 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dunglas/frankenphp/internal/fastabs"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/dunglas/frankenphp/internal/fastabs"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -94,7 +95,10 @@ func TestAutoScaleWorkerThreads(t *testing.T) {
|
||||
frankenphp {
|
||||
max_threads 10
|
||||
num_threads 2
|
||||
worker ../testdata/sleep.php 1
|
||||
worker ../testdata/sleep.php {
|
||||
num 1
|
||||
max_threads 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,8 +132,8 @@ func TestAutoScaleWorkerThreads(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// assert that there are now more threads than before
|
||||
assert.NotEqual(t, amountOfThreads, 2)
|
||||
assert.NotEqual(t, amountOfThreads, 2, "at least one thread should have been auto-scaled")
|
||||
assert.LessOrEqual(t, amountOfThreads, 4, "at most 3 max_threads + 1 regular thread should be present")
|
||||
}
|
||||
|
||||
// Note this test requires at least 2x40MB available memory for the process
|
||||
|
||||
90
caddy/app.go
90
caddy/app.go
@@ -1,12 +1,14 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
@@ -18,6 +20,22 @@ import (
|
||||
"github.com/dunglas/frankenphp/internal/fastabs"
|
||||
)
|
||||
|
||||
var (
|
||||
options []frankenphp.Option
|
||||
optionsMU sync.RWMutex
|
||||
)
|
||||
|
||||
// EXPERIMENTAL: RegisterWorkers provides a way for extensions to register frankenphp.Workers
|
||||
func RegisterWorkers(name, fileName string, num int, wo ...frankenphp.WorkerOption) frankenphp.Workers {
|
||||
w, opt := frankenphp.WithExtensionWorkers(name, fileName, num, wo...)
|
||||
|
||||
optionsMU.Lock()
|
||||
options = append(options, opt)
|
||||
optionsMU.Unlock()
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
// FrankenPHPApp represents the global "frankenphp" directive in the Caddyfile
|
||||
// it's responsible for starting up the global PHP instance and all threads
|
||||
//
|
||||
@@ -31,18 +49,20 @@ type FrankenPHPApp struct {
|
||||
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 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"`
|
||||
|
||||
opts []frankenphp.Option
|
||||
metrics frankenphp.Metrics
|
||||
ctx context.Context
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
var iniError = errors.New("'php_ini' must be in the format: php_ini \"<key>\" \"<value>\"")
|
||||
var iniError = errors.New(`"php_ini" must be in the format: php_ini "<key>" "<value>"`)
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (f FrankenPHPApp) CaddyModule() caddy.ModuleInfo {
|
||||
@@ -54,8 +74,12 @@ func (f FrankenPHPApp) CaddyModule() caddy.ModuleInfo {
|
||||
|
||||
// Provision sets up the module.
|
||||
func (f *FrankenPHPApp) Provision(ctx caddy.Context) error {
|
||||
f.ctx = ctx
|
||||
f.logger = ctx.Slogger()
|
||||
|
||||
// We have at least 7 hardcoded options
|
||||
f.opts = make([]frankenphp.Option, 0, 7+len(options))
|
||||
|
||||
if httpApp, err := ctx.AppIfConfigured("http"); err == nil {
|
||||
if httpApp.(*caddyhttp.App).Metrics != nil {
|
||||
f.metrics = frankenphp.NewPrometheusMetrics(ctx.GetMetricsRegistry())
|
||||
@@ -94,42 +118,54 @@ retry:
|
||||
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()
|
||||
|
||||
opts := []frankenphp.Option{
|
||||
optionsMU.RLock()
|
||||
f.opts = append(f.opts, options...)
|
||||
optionsMU.RUnlock()
|
||||
|
||||
f.opts = append(f.opts,
|
||||
frankenphp.WithContext(f.ctx),
|
||||
frankenphp.WithLogger(f.logger),
|
||||
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) {
|
||||
workerOpts := []frankenphp.WorkerOption{
|
||||
)
|
||||
|
||||
for _, w := range f.Workers {
|
||||
w.options = append(w.options,
|
||||
frankenphp.WithWorkerEnv(w.Env),
|
||||
frankenphp.WithWorkerWatchMode(w.Watch),
|
||||
frankenphp.WithWorkerMaxFailures(w.MaxConsecutiveFailures),
|
||||
}
|
||||
frankenphp.WithWorkerMaxThreads(w.MaxThreads),
|
||||
frankenphp.WithWorkerRequestOptions(w.requestOptions...),
|
||||
)
|
||||
|
||||
opts = append(opts, frankenphp.WithWorkers(w.Name, repl.ReplaceKnown(w.FileName, ""), w.Num, workerOpts...))
|
||||
f.opts = append(f.opts, frankenphp.WithWorkers(w.Name, repl.ReplaceKnown(w.FileName, ""), w.Num, w.options...))
|
||||
}
|
||||
|
||||
frankenphp.Shutdown()
|
||||
if err := frankenphp.Init(opts...); err != nil {
|
||||
if err := frankenphp.Init(f.opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -137,7 +173,11 @@ func (f *FrankenPHPApp) Start() error {
|
||||
}
|
||||
|
||||
func (f *FrankenPHPApp) Stop() error {
|
||||
f.logger.Info("FrankenPHP stopped 🐘")
|
||||
ctx := caddy.ActiveContext()
|
||||
|
||||
if f.logger.Enabled(caddy.ActiveContext(), slog.LevelInfo) {
|
||||
f.logger.LogAttrs(ctx, slog.LevelInfo, "FrankenPHP stopped 🐘")
|
||||
}
|
||||
|
||||
// attempt a graceful shutdown if caddy is exiting
|
||||
// note: Exiting() is currently marked as 'experimental'
|
||||
@@ -151,6 +191,10 @@ func (f *FrankenPHPApp) Stop() error {
|
||||
f.NumThreads = 0
|
||||
f.MaxWaitTime = 0
|
||||
|
||||
optionsMU.Lock()
|
||||
options = nil
|
||||
optionsMU.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -194,7 +238,7 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
|
||||
v, err := time.ParseDuration(d.Val())
|
||||
if err != nil {
|
||||
return errors.New("max_wait_time must be a valid duration (example: 10s)")
|
||||
return d.Err("max_wait_time must be a valid duration (example: 10s)")
|
||||
}
|
||||
|
||||
f.MaxWaitTime = v
|
||||
@@ -202,14 +246,14 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
parseIniLine := func(d *caddyfile.Dispenser) error {
|
||||
key := d.Val()
|
||||
if !d.NextArg() {
|
||||
return iniError
|
||||
return d.WrapErr(iniError)
|
||||
}
|
||||
if f.PhpIni == nil {
|
||||
f.PhpIni = make(map[string]string)
|
||||
}
|
||||
f.PhpIni[key] = d.Val()
|
||||
if d.NextArg() {
|
||||
return iniError
|
||||
return d.WrapErr(iniError)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -226,7 +270,7 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
|
||||
if !isBlock {
|
||||
if !d.NextArg() {
|
||||
return iniError
|
||||
return d.WrapErr(iniError)
|
||||
}
|
||||
err := parseIniLine(d)
|
||||
if err != nil {
|
||||
@@ -235,7 +279,7 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
}
|
||||
|
||||
case "worker":
|
||||
wc, err := parseWorkerConfig(d)
|
||||
wc, err := unmarshalWorker(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -243,25 +287,24 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
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)
|
||||
return d.Errf(`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)
|
||||
return d.Errf("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())
|
||||
return wrongSubDirectiveError("frankenphp", "num_threads, max_threads, php_ini, worker, max_wait_time", 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 d.Err(`"max_threads"" must be greater than or equal to "num_threads"`)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -279,3 +322,8 @@ func parseGlobalOption(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||
Value: caddyconfig.JSON(app, nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ caddy.App = (*FrankenPHPApp)(nil)
|
||||
_ caddy.Provisioner = (*FrankenPHPApp)(nil)
|
||||
)
|
||||
|
||||
@@ -7,14 +7,12 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDocumentRoot = "public"
|
||||
defaultWatchPattern = "./**/*.{php,yaml,yml,twig,env}"
|
||||
defaultWatchPattern = "./**/*.{env,php,twig,yaml,yml}"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -31,16 +29,7 @@ func init() {
|
||||
httpcaddyfile.RegisterDirectiveOrder("php_server", "before", "file_server")
|
||||
}
|
||||
|
||||
// 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)
|
||||
// wrongSubDirectiveError returns a nice error message.
|
||||
func wrongSubDirectiveError(module string, allowedDirectives string, wrongValue string) error {
|
||||
return fmt.Errorf("unknown %q subdirective: %s (allowed directives are: %s)", module, wrongValue, allowedDirectives)
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.App = (*FrankenPHPApp)(nil)
|
||||
_ caddy.Provisioner = (*FrankenPHPApp)(nil)
|
||||
_ caddy.Provisioner = (*FrankenPHPModule)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*FrankenPHPModule)(nil)
|
||||
_ caddyfile.Unmarshaler = (*FrankenPHPModule)(nil)
|
||||
)
|
||||
|
||||
@@ -168,6 +168,28 @@ func TestGlobalAndModuleWorker(t *testing.T) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestModuleWorkerInheritsEnv(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
}
|
||||
|
||||
http://localhost:`+testPort+` {
|
||||
route {
|
||||
php {
|
||||
root ../testdata
|
||||
env APP_ENV inherit_this
|
||||
worker worker-with-env.php
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/worker-with-env.php", http.StatusOK, "Worker has APP_ENV=inherit_this")
|
||||
}
|
||||
|
||||
func TestNamedModuleWorkers(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
testPortNum, _ := strconv.Atoi(testPort)
|
||||
@@ -385,7 +407,7 @@ func TestPHPServerDirective(t *testing.T) {
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:"+testPort, http.StatusOK, "I am by birth a Genevese (i not set)")
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/hello.txt", http.StatusOK, "Hello")
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/hello.txt", http.StatusOK, "Hello\n")
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/not-found.txt", http.StatusOK, "I am by birth a Genevese (i not set)")
|
||||
}
|
||||
|
||||
@@ -943,7 +965,7 @@ func TestMaxWaitTime(t *testing.T) {
|
||||
for range 10 {
|
||||
go func() {
|
||||
statusCode := getStatusCode("http://localhost:"+testPort+"/sleep.php?sleep=10", t)
|
||||
if statusCode == http.StatusGatewayTimeout {
|
||||
if statusCode == http.StatusServiceUnavailable {
|
||||
success.Store(true)
|
||||
}
|
||||
wg.Done()
|
||||
@@ -951,7 +973,7 @@ func TestMaxWaitTime(t *testing.T) {
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
require.True(t, success.Load(), "At least one request should have failed with a 504 Gateway Timeout status")
|
||||
require.True(t, success.Load(), "At least one request should have failed with a 503 Service Unavailable status")
|
||||
}
|
||||
|
||||
func TestMaxWaitTimeWorker(t *testing.T) {
|
||||
@@ -990,23 +1012,26 @@ func TestMaxWaitTimeWorker(t *testing.T) {
|
||||
for range 10 {
|
||||
go func() {
|
||||
statusCode := getStatusCode("http://localhost:"+testPort+"/sleep.php?sleep=10&iteration=1", t)
|
||||
if statusCode == http.StatusGatewayTimeout {
|
||||
if statusCode == http.StatusServiceUnavailable {
|
||||
success.Store(true)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
require.True(t, success.Load(), "At least one request should have failed with a 504 Gateway Timeout status")
|
||||
require.True(t, success.Load(), "At least one request should have failed with a 503 Service Unavailable status")
|
||||
|
||||
// Fetch metrics
|
||||
resp, err := http.Get("http://localhost:2999/metrics")
|
||||
require.NoError(t, err, "failed to fetch metrics")
|
||||
defer resp.Body.Close()
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, resp.Body.Close())
|
||||
})
|
||||
|
||||
// Read and parse metrics
|
||||
metrics := new(bytes.Buffer)
|
||||
_, err = metrics.ReadFrom(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedMetrics := `
|
||||
# TYPE frankenphp_worker_queue_depth gauge
|
||||
@@ -1337,7 +1362,7 @@ func TestWorkerMatchDirective(t *testing.T) {
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// worker is outside of public directory, match anyways
|
||||
// worker is outside public directory, match anyway
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/matched-path", http.StatusOK, "requests:1")
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/matched-path/anywhere", http.StatusOK, "requests:2")
|
||||
|
||||
@@ -1423,3 +1448,55 @@ func TestWorkerMatchDirectiveWithoutFileServer(t *testing.T) {
|
||||
// the request should completely fall through the php_server module
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/static.txt", http.StatusNotFound, "Request falls through")
|
||||
}
|
||||
|
||||
func TestDd(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
}
|
||||
|
||||
http://localhost:`+testPort+` {
|
||||
php {
|
||||
worker ../testdata/dd.php 1 {
|
||||
match *
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// simulate Symfony's dd()
|
||||
tester.AssertGetResponse(
|
||||
"http://localhost:"+testPort+"/some-path?output=dump123",
|
||||
http.StatusInternalServerError,
|
||||
"dump123",
|
||||
)
|
||||
}
|
||||
|
||||
func TestLog(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
}
|
||||
|
||||
http://localhost:`+testPort+` {
|
||||
log {
|
||||
output stdout
|
||||
format json
|
||||
}
|
||||
|
||||
root ../testdata
|
||||
php_server {
|
||||
worker ../testdata/log-frankenphp_log.php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertGetResponse(
|
||||
"http://localhost:"+testPort+"/log-frankenphp_log.php?i=0",
|
||||
http.StatusOK,
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func TestModuleWorkerWithWatchConfiguration(t *testing.T) {
|
||||
|
||||
// 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, defaultWatchPattern, 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")
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ package caddy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/dunglas/frankenphp/internal/extgen"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/dunglas/frankenphp/internal/extgen"
|
||||
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -16,7 +17,7 @@ func init() {
|
||||
caddycmd.RegisterCommand(caddycmd.Command{
|
||||
Name: "extension-init",
|
||||
Usage: "go_extension.go [--verbose]",
|
||||
Short: "(Experimental) Initializes a PHP extension from a Go file",
|
||||
Short: "Initializes a PHP extension from a Go file (EXPERIMENTAL)",
|
||||
Long: `
|
||||
Initializes a PHP extension from a Go file. This command generates the necessary C files for the extension, including the header and source files, as well as the arginfo file.`,
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
@@ -27,27 +28,21 @@ Initializes a PHP extension from a Go file. This command generates the necessary
|
||||
})
|
||||
}
|
||||
|
||||
func cmdInitExtension(fs caddycmd.Flags) (int, error) {
|
||||
func cmdInitExtension(_ caddycmd.Flags) (int, error) {
|
||||
if len(os.Args) < 3 {
|
||||
return 1, errors.New("the path to the Go source is required")
|
||||
}
|
||||
|
||||
sourceFile := os.Args[2]
|
||||
baseName := extgen.SanitizePackageName(strings.TrimSuffix(filepath.Base(sourceFile), ".go"))
|
||||
|
||||
baseName := strings.TrimSuffix(filepath.Base(sourceFile), ".go")
|
||||
|
||||
baseName = extgen.SanitizePackageName(baseName)
|
||||
|
||||
sourceDir := filepath.Dir(sourceFile)
|
||||
buildDir := filepath.Join(sourceDir, "build")
|
||||
|
||||
generator := extgen.Generator{BaseName: baseName, SourceFile: sourceFile, BuildDir: buildDir}
|
||||
generator := extgen.Generator{BaseName: baseName, SourceFile: sourceFile, BuildDir: filepath.Dir(sourceFile)}
|
||||
|
||||
if err := generator.Generate(); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
||||
log.Printf("PHP extension %q initialized successfully in %q", baseName, generator.BuildDir)
|
||||
log.Printf("PHP extension %q initialized successfully in directory %q", baseName, generator.BuildDir)
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
# Uncomment the following lines to enable Mercure and Vulcain modules
|
||||
#mercure {
|
||||
# # Publisher JWT key
|
||||
# transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
|
||||
# # Publisher JWT key
|
||||
# publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
|
||||
# # Subscriber JWT key
|
||||
# subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
|
||||
|
||||
176
caddy/go.mod
176
caddy/go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/dunglas/frankenphp/caddy
|
||||
|
||||
go 1.25.0
|
||||
go 1.25.4
|
||||
|
||||
replace github.com/dunglas/frankenphp => ../
|
||||
|
||||
@@ -8,49 +8,49 @@ retract v1.0.0-rc.1 // Human error
|
||||
|
||||
require (
|
||||
github.com/caddyserver/caddy/v2 v2.10.2
|
||||
github.com/caddyserver/certmagic v0.24.0
|
||||
github.com/caddyserver/certmagic v0.25.0
|
||||
github.com/dunglas/caddy-cbrotli v1.0.1
|
||||
github.com/dunglas/frankenphp v1.9.1
|
||||
github.com/dunglas/mercure/caddy v0.20.2
|
||||
github.com/dunglas/frankenphp v1.11.1
|
||||
github.com/dunglas/mercure v0.21.4
|
||||
github.com/dunglas/mercure/caddy v0.21.4
|
||||
github.com/dunglas/vulcain/caddy v1.2.1
|
||||
github.com/prometheus/client_golang v1.23.0
|
||||
github.com/spf13/cobra v1.10.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
)
|
||||
|
||||
require github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca // indirect
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cloud.google.com/go/auth v0.16.5 // indirect
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
cloud.google.com/go/auth v0.18.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.8.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.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.5.0 // indirect
|
||||
github.com/KimMachineGun/automemlimit v0.7.4 // indirect
|
||||
github.com/KimMachineGun/automemlimit v0.7.5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 // indirect
|
||||
github.com/MicahParks/jwkset v0.9.6 // indirect
|
||||
github.com/MicahParks/keyfunc/v3 v3.6.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.9.4 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
|
||||
github.com/MicahParks/jwkset v0.11.0 // indirect
|
||||
github.com/MicahParks/keyfunc/v3 v3.7.0 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.21.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.24.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/ccoveille/go-safecast v1.6.1 // indirect
|
||||
github.com/ccoveille/go-safecast/v2 v2.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // 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/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.15.0 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 // 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
|
||||
@@ -58,64 +58,60 @@ require (
|
||||
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.1.0 // indirect
|
||||
github.com/dunglas/mercure v0.20.2 // indirect
|
||||
github.com/dunglas/skipfilter v1.0.0 // indirect
|
||||
github.com/dunglas/vulcain v1.2.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/gammazero/deque v1.1.0 // indirect
|
||||
github.com/getkin/kin-openapi v0.133.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.3 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.2 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/gofrs/uuid/v5 v5.3.2 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.4.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/brotli/go/cbrotli v1.1.0 // indirect
|
||||
github.com/google/cel-go v0.26.1 // indirect
|
||||
github.com/google/certificate-transparency-go v1.3.2 // indirect
|
||||
github.com/google/go-tpm v0.9.5 // indirect
|
||||
github.com/google/go-tpm v0.9.7 // indirect
|
||||
github.com/google/go-tspi v0.3.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.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.27.2 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.6 // 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.18.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // 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/libdns/libdns v1.1.1 // indirect
|
||||
github.com/mailru/easyjson v0.9.1 // indirect
|
||||
github.com/manifoldco/promptui v0.9.0 // 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.4 // indirect
|
||||
github.com/maypok86/otter/v2 v2.2.1 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.2 // indirect
|
||||
github.com/miekg/dns v1.1.68 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.4 // indirect
|
||||
github.com/miekg/dns v1.1.69 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
@@ -131,34 +127,35 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // 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.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.57.1 // indirect
|
||||
github.com/rs/cors v1.11.1 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.10.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.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.9.6 // indirect
|
||||
github.com/smallstep/certificates v0.28.4 // indirect
|
||||
github.com/smallstep/cli-utils v0.12.1 // indirect
|
||||
github.com/smallstep/linkedca v0.23.0 // indirect
|
||||
github.com/slackhq/nebula v1.9.7 // indirect
|
||||
github.com/smallstep/certificates v0.29.0 // indirect
|
||||
github.com/smallstep/cli-utils v0.12.2 // indirect
|
||||
github.com/smallstep/linkedca v0.25.0 // indirect
|
||||
github.com/smallstep/nosql v0.7.0 // indirect
|
||||
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.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/spf13/pflag v1.0.8 // indirect
|
||||
github.com/spf13/viper v1.20.1 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spf13/viper v1.21.0 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.1 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 // indirect
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||
github.com/tailscale/tscert v0.0.0-20251216020129-aea342f6d747 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/unrolled/secure v1.17.0 // indirect
|
||||
@@ -170,44 +167,45 @@ require (
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.62.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.37.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.37.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.37.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
go.step.sm/crypto v0.70.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.64.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.step.sm/crypto v0.75.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20250826074233-8f580defa01d // indirect
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
google.golang.org/api v0.248.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
|
||||
google.golang.org/grpc v1.75.0 // indirect
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20251210140736-7dacc380ba00 // indirect
|
||||
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/term v0.38.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
google.golang.org/api v0.257.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||
google.golang.org/grpc v1.77.0 // indirect
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.1 // indirect
|
||||
|
||||
581
caddy/go.sum
581
caddy/go.sum
@@ -1,119 +1,101 @@
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
|
||||
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
|
||||
cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
|
||||
cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
|
||||
cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
|
||||
cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=
|
||||
cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
|
||||
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
|
||||
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
|
||||
cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk=
|
||||
cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8=
|
||||
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
|
||||
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
||||
cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k=
|
||||
cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g=
|
||||
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
|
||||
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/KimMachineGun/automemlimit v0.7.4 h1:UY7QYOIfrr3wjjOAqahFmC3IaQCLWvur9nmfIn6LnWk=
|
||||
github.com/KimMachineGun/automemlimit v0.7.4/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
|
||||
github.com/KimMachineGun/automemlimit v0.7.5 h1:RkbaC0MwhjL1ZuBKunGDjE/ggwAX43DwZrJqVwyveTk=
|
||||
github.com/KimMachineGun/automemlimit v0.7.5/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/MauriceGit/skiplist v0.0.0-20191117202105-643e379adb62/go.mod h1:877WBceefKn14QwVVn4xRFUsHsZb9clICgdeTj4XsUg=
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 h1:1yw6O62BReQ+uA1oyk9XaQTvLhcoHWmoQAgXmDFXpIY=
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145/go.mod h1:877WBceefKn14QwVVn4xRFUsHsZb9clICgdeTj4XsUg=
|
||||
github.com/MicahParks/jwkset v0.9.6 h1:Tf8l2/MOby5Kh3IkrqzThPQKfLytMERoAsGZKlyYZxg=
|
||||
github.com/MicahParks/jwkset v0.9.6/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0=
|
||||
github.com/MicahParks/keyfunc/v3 v3.6.1 h1:A8A5zGZ8XmRyxizSY7s5FLY/aSplrnEBLCOrC0D1ojM=
|
||||
github.com/MicahParks/keyfunc/v3 v3.6.1/go.mod h1:y6Ed3dMgNKTcpxbaQHD8mmrYDUZWJAxteddA6OQj+ag=
|
||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/MicahParks/jwkset v0.11.0 h1:yc0zG+jCvZpWgFDFmvs8/8jqqVBG9oyIbmBtmjOhoyQ=
|
||||
github.com/MicahParks/jwkset v0.11.0/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0=
|
||||
github.com/MicahParks/keyfunc/v3 v3.7.0 h1:pdafUNyq+p3ZlvjJX1HWFP7MA3+cLpDtg69U3kITJGM=
|
||||
github.com/MicahParks/keyfunc/v3 v3.7.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
||||
github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv2QzDdQ=
|
||||
github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4 h1:4aKySrrg9G/5oRtJ3TrZLObVqxgQ9f1znCRBwEwjuVw=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
|
||||
github.com/alecthomas/chroma/v2 v2.21.0 h1:YVW9qQAFnQm2OFPPFQg6G/TpMxKSsUr/KUPDi/BEqtY=
|
||||
github.com/alecthomas/chroma/v2 v2.21.0/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
|
||||
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.0 h1:UCRQ5mlqcFk9HJDIqENSLR3wiG1VTWlyUfLDEvY7RxU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.0 h1:9yH0xiY5fUnVNLRWO0AtayqwU1ndriZdN78LlhruJR4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.0/go.mod h1:VeV3K72nXnhbe4EuxxhzsDc/ByrCSlZwUnWH52Nde/I=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.4 h1:IPd0Algf1b+Qy9BcDp0sCUcIWdCQPSzDoMK3a8pcbUM=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.4/go.mod h1:nwg78FjH2qvsRM1EVZlX9WuGUJOL5od+0qvm0adEzHk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 h1:GicIdnekoJsjq9wqnvyi2elW6CGMSYKhdozE7/Svh78=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3/go.mod h1:R7BIi6WNC5mc1kfRM7XM/VHC3uRWkjc396sfabq4iOo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 h1:o9RnO+YZ4X+kt5Z7Nvcishlz0nksIt2PIzDglLMP0vA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3/go.mod h1:+6aLJzOG1fvMOyzIySYjOFjcguGvVRL68R+uoRencN4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 h1:joyyUFhiTQQmVK6ImzNU9TQSNRNeD9kOklqTzyk5v6s=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3/go.mod h1:+vNIyZQP3b3B1tSLI0lxvrU9cfM7gpdRXMFfm67ZcPc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 h1:ieRzyHXypu5ByllM7Sp4hC5f/1Fy5wqxqY0yB85hC7s=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3/go.mod h1:O5ROz8jHiOAKAwx179v+7sHMhfobFVi6nZt8DEyiYoM=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.44.0 h1:Z95XCqqSnwXr0AY7PgsiOUBhUG2GoDM5getw6RfD1Lg=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.44.0/go.mod h1:DqcSngL7jJeU1fOzh5Ll5rSvX/MlMV6OZlE4mVdFAQc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 h1:Mc/MKBf2m4VynyJkABoVEN+QzkfLqGj0aiJuEe7cMeM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.28.0/go.mod h1:iS5OmxEcN4QIPXARGhavH7S8kETNL11kym6jhoS7IUQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 h1:6csaS/aJmqZQbKhi1EyEMM7yBW653Wy/B9hnBofW+sw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0/go.mod h1:59qHWaY5B+Rs7HGTuVGaC32m0rdpQ68N8QCN3khYiqs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 h1:MG9VFW43M4A8BYeAfaJJZWrroinxeTi2r3+SnmLQfSA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.37.0/go.mod h1:JdeBDPgpJfuS6rU/hNglmOigKhyEZtBmbraLE4GK1J8=
|
||||
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
|
||||
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.1 h1:iODUDLgk3q8/flEC7ymhmxjfoAnBDwEEYEVyKZ9mzjU=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.1/go.mod h1:xoAgo17AGrPpJBSLg81W+ikM0cpOZG8ad04T2r+d5P0=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.1 h1:JeW+EwmtTE0yXFK8SmklrFh/cGTTXsQJumgMZNlbxfM=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.1/go.mod h1:BOoXiStwTF+fT2XufhO0Efssbi1CNIO/ZXpZu87N0pw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.48.0 h1:pQgVxqqNOacqb19+xaoih/wNLil4d8tgi+FxtBi/qQY=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.48.0/go.mod h1:VJcNH6BLr+3VJwinRKdotLOMglHO8mIKlD3ea5c7hbw=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.4/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.9 h1:LU8S9W/mPDAU9q0FjCLi0TrCheLMGwzbRpvUMwYspcA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.9/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 h1:GdGmKtG+/Krag7VfyOXV17xjTCz0i9NT+JnqLTOI5nA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.1/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso=
|
||||
github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
|
||||
github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM=
|
||||
github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
||||
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/caddyserver/caddy/v2 v2.10.2 h1:g/gTYjGMD0dec+UgMw8SnfmJ3I9+M2TdvoRL/Ovu6U8=
|
||||
github.com/caddyserver/caddy/v2 v2.10.2/go.mod h1:TXLQHx+ev4HDpkO6PnVVHUbL6OXt6Dfe7VcIBdQnPL0=
|
||||
github.com/caddyserver/certmagic v0.24.0 h1:EfXTWpxHAUKgDfOj6MHImJN8Jm4AMFfMT6ITuKhrDF0=
|
||||
github.com/caddyserver/certmagic v0.24.0/go.mod h1:xPT7dC1DuHHnS2yuEQCEyks+b89sUkMENh8dJF+InLE=
|
||||
github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
|
||||
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/ccoveille/go-safecast v1.6.1 h1:Nb9WMDR8PqhnKCVs2sCB+OqhohwO5qaXtCviZkIff5Q=
|
||||
github.com/ccoveille/go-safecast v1.6.1/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8=
|
||||
github.com/ccoveille/go-safecast/v2 v2.0.0 h1:+5eyITXAUj3wMjad6cRVJKGnC7vDS55zk0INzJagub0=
|
||||
github.com/ccoveille/go-safecast/v2 v2.0.0/go.mod h1:JIYA4CAR33blIDuE6fSwCp2sz1oOBahXnvmdBhOAABs=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
@@ -129,15 +111,13 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg=
|
||||
github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
@@ -161,16 +141,16 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dunglas/caddy-cbrotli v1.0.1 h1:mkg7EB1GmoyfBt3kY3mq4o/0bfnBeq7ZLQjmVmdBE3Y=
|
||||
github.com/dunglas/caddy-cbrotli v1.0.1/go.mod h1:uXABy3tjy1FABF+3JWKVh1ajFvIO/kfpwHaeZGSBaAY=
|
||||
github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54=
|
||||
github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
|
||||
github.com/dunglas/mercure v0.20.2 h1:zQRJ7QgjtQafOc9q3XtYVDgsfbUokvHeBhiFBpNTiIs=
|
||||
github.com/dunglas/mercure v0.20.2/go.mod h1:sJtQEtezUTKlbDCK1Sqgt9sUrcwBNZJxVc6oHkqvt3w=
|
||||
github.com/dunglas/mercure/caddy v0.20.2 h1:CGpcT+arSzxr39s2qRvPpS2/yjZV26cElgee+9Au7lg=
|
||||
github.com/dunglas/mercure/caddy v0.20.2/go.mod h1:96MOkU7u42rFRIQSpzQibC2LT1vwo/AgLA5jivuw7aE=
|
||||
github.com/dunglas/mercure v0.21.4 h1:mXPXHfB+4cYfFFCRRDY198mfef5+MQcdCpUnAHBUW2M=
|
||||
github.com/dunglas/mercure v0.21.4/go.mod h1:l/dglCjp/OQx8/quRyceRPx2hqZQ3CNviwoLMRQiJ/k=
|
||||
github.com/dunglas/mercure/caddy v0.21.4 h1:7o+6rDqfwj1EOmXOgfBFsayvJvOUP37xujQHaxuX4ps=
|
||||
github.com/dunglas/mercure/caddy v0.21.4/go.mod h1:EM2s+OVGExbSXObUdAPDwPsbQw4t/icLtQv9CFylDvY=
|
||||
github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4=
|
||||
github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w=
|
||||
github.com/dunglas/vulcain v1.2.1 h1:pkPwvIfoa/xmWSVUyhntbIKT+XO2VFMyhLKv1gA61O8=
|
||||
github.com/dunglas/vulcain v1.2.1/go.mod h1:Qv1hxHP8rMDp6ZrpQQPSOh1OibuAYScNNCL/46sCRXU=
|
||||
github.com/dunglas/vulcain/caddy v1.2.1 h1:Bh5awZ9WoNqsBv7OZfs1SktJqRgJaF5avI5oDmxs6lI=
|
||||
@@ -178,11 +158,10 @@ github.com/dunglas/vulcain/caddy v1.2.1/go.mod h1:8QrmLTfURmW2VgjTR6Gb9a53FrZjsp
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 h1:h3vVM6X45PK0mAk8NqiYNQGXTyhvXy1HQ5GhuQN4eeA=
|
||||
github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@@ -190,28 +169,25 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gammazero/deque v1.1.0 h1:OyiyReBbnEG2PP0Bnv1AASLIYvyKqIFN5xfl1t8oGLo=
|
||||
github.com/gammazero/deque v1.1.0/go.mod h1:JVrR+Bj1NMQbPnYclvDlvSX0nVGReLrQZ0aUMuWLctg=
|
||||
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
||||
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA=
|
||||
github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
|
||||
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
|
||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
@@ -220,16 +196,10 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
|
||||
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
||||
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
@@ -238,7 +208,6 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/brotli/go/cbrotli v1.1.0 h1:YwHD/rwSgUSL4b2S3ZM2jnNymm+tmwKQqjUIC63nmHU=
|
||||
github.com/google/brotli/go/cbrotli v1.1.0/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ=
|
||||
@@ -246,43 +215,30 @@ github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PU
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A=
|
||||
github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU=
|
||||
github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/go-tpm-tools v0.4.5 h1:3fhthtyMDbIZFR5/0y1hvUoZ1Kf4i1eZ7C73R4Pvd+k=
|
||||
github.com/google/go-tpm-tools v0.4.5/go.mod h1:ktjTNq8yZFD6TzdBFefUfen96rF3NpYwpSb2d8bc+Y8=
|
||||
github.com/google/go-tpm v0.9.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA=
|
||||
github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
|
||||
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
|
||||
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
|
||||
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
|
||||
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
@@ -295,22 +251,16 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kevburnsjr/skipfilter v0.0.1 h1:EWl1lWUJfIehrKYIEkps0Cl67lCfS2pUM9iZFNajp7g=
|
||||
github.com/kevburnsjr/skipfilter v0.0.1/go.mod h1:jfaRyFOYVUtIa6IIC+0mB1qiZqhHw+DKvFowCBuijSk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -318,35 +268,30 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU=
|
||||
github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
||||
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
||||
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/maypok86/otter v1.2.4 h1:HhW1Pq6VdJkmWwcZZq19BlEQkHtI8xgsQzBVXJU0nfc=
|
||||
github.com/maypok86/otter v1.2.4/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4=
|
||||
github.com/maypok86/otter/v2 v2.2.1 h1:hnGssisMFkdisYcvQ8L019zpYQcdtPse+g0ps2i7cfI=
|
||||
github.com/maypok86/otter/v2 v2.2.1/go.mod h1:1NKY9bY+kB5jwCXBJfE59u+zAwOt6C7ni1FTlFFMqVs=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||
github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ=
|
||||
github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
|
||||
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@@ -355,21 +300,16 @@ github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
@@ -389,75 +329,49 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
|
||||
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10=
|
||||
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc=
|
||||
github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E=
|
||||
github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/slackhq/nebula v1.9.6 h1:Fl0LE2dHDeVEK3R+un59Z3V4ZzbZ6q2e/zF4ClaD5yo=
|
||||
github.com/slackhq/nebula v1.9.6/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ=
|
||||
github.com/slackhq/nebula v1.9.7 h1:v5u46efIyYHGdfjFnozQbRRhMdaB9Ma1SSTcUcE2lfE=
|
||||
github.com/slackhq/nebula v1.9.7/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ=
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
|
||||
github.com/smallstep/certificates v0.28.4 h1:JTU6/A5Xes6m+OsR6fw1RACSA362vJc9SOFVG7poBEw=
|
||||
github.com/smallstep/certificates v0.28.4/go.mod h1:LUqo+7mKZE7FZldlTb0zhU4A0bq4G4+akieFMcTaWvA=
|
||||
github.com/smallstep/cli-utils v0.12.1 h1:D9QvfbFqiKq3snGZ2xDcXEFrdFJ1mQfPHZMq/leerpE=
|
||||
github.com/smallstep/cli-utils v0.12.1/go.mod h1:skV2Neg8qjiKPu2fphM89H9bIxNpKiiRTnX9Q6Lc+20=
|
||||
github.com/smallstep/certificates v0.29.0 h1:f90szTKYTW62bmCc+qE5doGqIGPVxTQb8Ba37e/K8Zs=
|
||||
github.com/smallstep/certificates v0.29.0/go.mod h1:27WI0od6gu84mvE4mYQ/QZGyYwHXvhsiSRNC+y3t+mo=
|
||||
github.com/smallstep/cli-utils v0.12.2 h1:lGzM9PJrH/qawbzMC/s2SvgLdJPKDWKwKzx9doCVO+k=
|
||||
github.com/smallstep/cli-utils v0.12.2/go.mod h1:uCPqefO29goHLGqFnwk0i8W7XJu18X3WHQFRtOm/00Y=
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca h1:VX8L0r8vybH0bPeaIxh4NQzafKQiqvlOn8pmOXbFLO4=
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
|
||||
github.com/smallstep/linkedca v0.23.0 h1:5W/7EudlK1HcCIdZM68dJlZ7orqCCCyv6bm2l/0JmLU=
|
||||
github.com/smallstep/linkedca v0.23.0/go.mod h1:7cyRM9soAYySg9ag65QwytcgGOM+4gOlkJ/YA58A9E8=
|
||||
github.com/smallstep/linkedca v0.25.0 h1:txT9QHGbCsJq0MhAghBq7qhurGY727tQuqUi+n4BVBo=
|
||||
github.com/smallstep/linkedca v0.25.0/go.mod h1:Q3jVAauFKNlF86W5/RFtgQeyDKz98GL/KN3KG4mJOvc=
|
||||
github.com/smallstep/nosql v0.7.0 h1:YiWC9ZAHcrLCrayfaF+QJUv16I2bZ7KdLC3RpJcnAnE=
|
||||
github.com/smallstep/nosql v0.7.0/go.mod h1:H5VnKMCbeq9QA6SRY5iqPylfxLfYcLwvUff3onQ8+HU=
|
||||
github.com/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA=
|
||||
@@ -466,29 +380,26 @@ github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492 h1:k23+s51sgYix4Zgb
|
||||
github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492/go.mod h1:QQhwLqCS13nhv8L5ov7NgusowENUtXdEzdytjmJHdZQ=
|
||||
github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
|
||||
github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0=
|
||||
github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.8 h1:/v546uKZ4gFGHpyXvV6CNKDeJBu4l5PRvxwQvdWrc0I=
|
||||
github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
|
||||
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -508,14 +419,16 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ=
|
||||
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||
github.com/tailscale/tscert v0.0.0-20251216020129-aea342f6d747 h1:RnBbFMmodYzhC6adOjTbtUQXyzV8dcvKYbolzs6Qch0=
|
||||
github.com/tailscale/tscert v0.0.0-20251216020129-aea342f6d747/go.mod h1:ejPAJui3kVK4u5TgMtqtXlWf5HnKh9fLy5kvpaeuas0=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
@@ -528,8 +441,6 @@ github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbW
|
||||
github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
|
||||
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc=
|
||||
github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
@@ -551,41 +462,40 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.62.0 h1:1+EHlhAe/tukctfePZRrDruB9vn7MdwyC+rf36nUSPM=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.62.0/go.mod h1:skzESZBY3IYcqJgImc+fwXQWflvVe+jZxoA/uw60NaI=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.37.0 h1:cp8AFiM/qjBm10C/ATIRnEDXpD5MBknrA0ANw4T2/ss=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.37.0/go.mod h1:Cy8Hk2E2iSGEbsLnPUdeigrexaAOAGIAmBFK919EQs0=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.37.0 h1:0aGKdIuVhy5l4GClAjl72ntkZJhijf2wg1S7b5oLoYA=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.37.0/go.mod h1:nhyrxEJEOQdwR15zXrCKI6+cJK60PXAkJ/jRyfhr2mg=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.37.0 h1:pW+qDVo0jB0rLsNeaP85xLuz20cvsECUcN7TE+D8YTM=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.37.0/go.mod h1:x7bd+t034hxLTve1hF9Yn9qQJlO/pP8H5pWIt7+gsFM=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.37.0 h1:tVjnBF6EiTDMXoq2Xuc2vK0I7MTbEs05II/0j9mMK+E=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.37.0/go.mod h1:MQjyNXtxAC8PGN9gzPtO4GY5zuP+RI3XX53uWbCTvEQ=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.step.sm/crypto v0.70.0 h1:Q9Ft7N637mucyZcHZd1+0VVQJVwDCKqcb9CYcYi7cds=
|
||||
go.step.sm/crypto v0.70.0/go.mod h1:pzfUhS5/ue7ev64PLlEgXvhx1opwbhFCjkvlhsxVds0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.64.0 h1:VVrb1ErDD0Tlh/0K0rUqjky1e8AekjspTFN9sU2ekaA=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.64.0/go.mod h1:QCsOQk+9Ep8Mkp4/aPtSzUT0dc8SaPYzBAE6o1jYuSE=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.39.0 h1:IvNR8pAVGpkK1CHMjU/YE6B6TlnAPGFvogkMWRWU6wo=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.39.0/go.mod h1:TUsFCERuGM4IGhJG9w+9l0nzmHUKHuaDYYNF6mtNgjY=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.39.0 h1:PI7pt9pkSnimWcp5sQhUA9OzLbc3Ba4sL+VEUTNsxrk=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.39.0/go.mod h1:5gV/EzPnfYIwjzj+6y8tbGW2PKWhcsz5e/7twptRVQY=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 h1:Gz3yKzfMSEFzF0Vy5eIpu9ndpo4DhXMCxsLMF0OOApo=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.39.0/go.mod h1:2D/cxxCqTlrday0rZrPujjg5aoAdqk1NaNyoXn8FJn8=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.39.0 h1:vKTve1W/WKPVp1fzJamhCDDECt+5upJJ65bPyWoddGg=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.39.0/go.mod h1:FH5VB2N19duNzh1Q8ks6CsZFyu3LFhNLiA9lPxyEkvU=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.step.sm/crypto v0.75.0 h1:UAHYD6q6ggYyzLlIKHv1MCUVjZIesXRZpGTlRC/HSHw=
|
||||
go.step.sm/crypto v0.75.0/go.mod h1:wwQ57+ajmDype9mrI/2hRyrvJd7yja5xVgWYqpUN3PE=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -594,46 +504,34 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20250826074233-8f580defa01d h1:earVicuMOnaYH+t9T1PeRhC0LHHPGXXTDD7NEEP/Pb4=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20250826074233-8f580defa01d/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20251210140736-7dacc380ba00 h1:qObov2/X4yIpr98j5t6samg3mMF12Rl4taUJd1rWj+c=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20251210140736-7dacc380ba00/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18=
|
||||
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM=
|
||||
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
@@ -642,19 +540,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -662,17 +551,12 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -686,8 +570,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -697,10 +581,9 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
@@ -709,76 +592,46 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/api v0.248.0 h1:hUotakSkcwGdYUqzCRc5yGYsg4wXxpkKlW5ryVqvC1Y=
|
||||
google.golang.org/api v0.248.0/go.mod h1:yAFUAF56Li7IuIQbTFoLwXTCI6XCFKueOlS7S9e4F9k=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA=
|
||||
google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 h1:APHvLLYBhtZvsbnpkfknDZ7NyH4z5+ub/I0u8L3Oz6g=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1/go.mod h1:xUjFWUnWDpZ/C0Gu0qloASKFb6f8/QXiiXhSPFsD668=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 h1:6Al3kEFFP9VJhRz3DID6quisgPnTeZVr4lep9kkxdPA=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0/go.mod h1:QLvsjh0OIR0TYBeiu2bkWGTJBUNQ64st52iWj/yA93I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
||||
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
|
||||
20
caddy/hotreload-skip.go
Normal file
20
caddy/hotreload-skip.go
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build nowatcher || nomercure
|
||||
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
type hotReloadContext struct {
|
||||
}
|
||||
|
||||
func (_ *FrankenPHPModule) configureHotReload(_ *FrankenPHPApp) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_ *FrankenPHPModule) unmarshalHotReload(d *caddyfile.Dispenser) error {
|
||||
return errors.New("hot reload support disabled")
|
||||
}
|
||||
104
caddy/hotreload.go
Normal file
104
caddy/hotreload.go
Normal file
@@ -0,0 +1,104 @@
|
||||
//go:build !nowatcher && !nomercure
|
||||
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"net/url"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
const defaultHotReloadPattern = "./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}"
|
||||
|
||||
type hotReloadContext struct {
|
||||
// HotReload specifies files to watch for file changes to trigger hot reloads updates. Supports the glob syntax.
|
||||
HotReload *hotReloadConfig `json:"hot_reload,omitempty"`
|
||||
}
|
||||
|
||||
type hotReloadConfig struct {
|
||||
Topic string `json:"topic"`
|
||||
Watch []string `json:"watch"`
|
||||
}
|
||||
|
||||
func (f *FrankenPHPModule) configureHotReload(app *FrankenPHPApp) error {
|
||||
if f.HotReload == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.mercureHub == nil {
|
||||
return errors.New("unable to enable hot reloading: no Mercure hub configured")
|
||||
}
|
||||
|
||||
if len(f.HotReload.Watch) == 0 {
|
||||
f.HotReload.Watch = []string{defaultHotReloadPattern}
|
||||
}
|
||||
|
||||
if f.HotReload.Topic == "" {
|
||||
uid, err := uniqueID(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.HotReload.Topic = "https://frankenphp.dev/hot-reload/" + uid
|
||||
}
|
||||
|
||||
app.opts = append(app.opts, frankenphp.WithHotReload(f.HotReload.Topic, f.mercureHub, f.HotReload.Watch))
|
||||
f.preparedEnv["FRANKENPHP_HOT_RELOAD\x00"] = "/.well-known/mercure?topic=" + url.QueryEscape(f.HotReload.Topic)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FrankenPHPModule) unmarshalHotReload(d *caddyfile.Dispenser) error {
|
||||
f.HotReload = &hotReloadConfig{
|
||||
Watch: d.RemainingArgs(),
|
||||
}
|
||||
|
||||
for d.NextBlock(1) {
|
||||
switch v := d.Val(); v {
|
||||
case "topic":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
if f.HotReload == nil {
|
||||
f.HotReload = &hotReloadConfig{}
|
||||
}
|
||||
|
||||
f.HotReload.Topic = d.Val()
|
||||
|
||||
case "watch":
|
||||
patterns := d.RemainingArgs()
|
||||
if len(patterns) == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
f.HotReload.Watch = append(f.HotReload.Watch, patterns...)
|
||||
|
||||
default:
|
||||
return wrongSubDirectiveError("hot_reload", "topic, watch", v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func uniqueID(s any) (string, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
if err := gob.NewEncoder(&b).Encode(s); err != nil {
|
||||
return "", fmt.Errorf("unable to generate unique name: %w", err)
|
||||
}
|
||||
|
||||
h := fnv.New64a()
|
||||
if _, err := h.Write(b.Bytes()); err != nil {
|
||||
return "", fmt.Errorf("unable to generate unique name: %w", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%016x", h.Sum64()), nil
|
||||
}
|
||||
88
caddy/hotreload_test.go
Normal file
88
caddy/hotreload_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
//go:build !nowatcher && !nomercure
|
||||
|
||||
package caddy_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHotReload(t *testing.T) {
|
||||
const topic = "https://frankenphp.dev/hot-reload/test"
|
||||
|
||||
u := "/.well-known/mercure?topic=" + url.QueryEscape(topic)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
indexFile := filepath.Join(tmpDir, "index.php")
|
||||
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
debug
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
}
|
||||
|
||||
http://localhost:`+testPort+` {
|
||||
mercure {
|
||||
transport local
|
||||
subscriber_jwt TestKey
|
||||
anonymous
|
||||
}
|
||||
|
||||
php_server {
|
||||
root `+tmpDir+`
|
||||
hot_reload {
|
||||
topic `+topic+`
|
||||
watch `+tmpDir+`/*.php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
var connected, received sync.WaitGroup
|
||||
|
||||
connected.Add(1)
|
||||
received.Go(func() {
|
||||
cx, cancel := context.WithCancel(t.Context())
|
||||
req, _ := http.NewRequest(http.MethodGet, "http://localhost:"+testPort+u, nil)
|
||||
req = req.WithContext(cx)
|
||||
resp := tester.AssertResponseCode(req, http.StatusOK)
|
||||
|
||||
connected.Done()
|
||||
|
||||
var receivedBody strings.Builder
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
_, err := resp.Body.Read(buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
receivedBody.Write(buf)
|
||||
|
||||
if strings.Contains(receivedBody.String(), "index.php") {
|
||||
cancel()
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
require.NoError(t, resp.Body.Close())
|
||||
})
|
||||
|
||||
connected.Wait()
|
||||
|
||||
require.NoError(t, os.WriteFile(indexFile, []byte("<?=$_SERVER['FRANKENPHP_HOT_RELOAD'];"), 0644))
|
||||
|
||||
received.Wait()
|
||||
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/index.php", http.StatusOK, u)
|
||||
}
|
||||
13
caddy/mercure-skip.go
Normal file
13
caddy/mercure-skip.go
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build nomercure
|
||||
|
||||
package caddy
|
||||
|
||||
type mercureContext struct {
|
||||
}
|
||||
|
||||
func (f *FrankenPHPModule) configureHotReload(_ *FrankenPHPApp) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FrankenPHPModule) assignMercureHub(_ caddy.Context) {
|
||||
}
|
||||
34
caddy/mercure.go
Normal file
34
caddy/mercure.go
Normal file
@@ -0,0 +1,34 @@
|
||||
//go:build !nomercure
|
||||
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"github.com/dunglas/mercure"
|
||||
mercureCaddy "github.com/dunglas/mercure/caddy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
mercureCaddy.AllowNoPublish = true
|
||||
}
|
||||
|
||||
type mercureContext struct {
|
||||
mercureHub *mercure.Hub
|
||||
}
|
||||
|
||||
func (f *FrankenPHPModule) assignMercureHub(ctx caddy.Context) {
|
||||
if f.mercureHub = mercureCaddy.FindHub(ctx.Modules()); f.mercureHub == nil {
|
||||
return
|
||||
}
|
||||
|
||||
opt := frankenphp.WithMercureHub(f.mercureHub)
|
||||
f.mercureHubRequestOption = &opt
|
||||
|
||||
for i, wc := range f.Workers {
|
||||
wc.mercureHub = f.mercureHub
|
||||
wc.options = append(wc.options, frankenphp.WithWorkerMercureHub(wc.mercureHub))
|
||||
|
||||
f.Workers[i] = wc
|
||||
}
|
||||
}
|
||||
106
caddy/module.go
106
caddy/module.go
@@ -2,6 +2,7 @@ package caddy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -21,6 +22,8 @@ import (
|
||||
"github.com/dunglas/frankenphp/internal/fastabs"
|
||||
)
|
||||
|
||||
var serverHeader = []string{"FrankenPHP Caddy"}
|
||||
|
||||
// FrankenPHPModule represents the "php_server" and "php" directives in the Caddyfile
|
||||
// they are responsible for forwarding requests to FrankenPHP via "ServeHTTP"
|
||||
//
|
||||
@@ -30,6 +33,9 @@ import (
|
||||
// }
|
||||
// }
|
||||
type FrankenPHPModule struct {
|
||||
mercureContext
|
||||
hotReloadContext
|
||||
|
||||
// Root sets the root folder to the site. Default: `root` directive, or the path of the public directory of the embed app it exists.
|
||||
Root string `json:"root,omitempty"`
|
||||
// SplitPath 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 CGI script to use. Default: `.php`.
|
||||
@@ -45,6 +51,7 @@ type FrankenPHPModule struct {
|
||||
preparedEnv frankenphp.PreparedEnv
|
||||
preparedEnvNeedsReplacement bool
|
||||
logger *slog.Logger
|
||||
mercureHubRequestOption *frankenphp.RequestOption
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
@@ -70,10 +77,12 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
|
||||
return fmt.Errorf(`expected ctx.App("frankenphp") to return *FrankenPHPApp, got nil`)
|
||||
}
|
||||
|
||||
for i, wc := range f.Workers {
|
||||
f.assignMercureHub(ctx)
|
||||
|
||||
loggerOpt := frankenphp.WithRequestLogger(f.logger)
|
||||
for i, wc := range f.Workers {
|
||||
// make the file path absolute from the public directory
|
||||
// this can only be done if the root is definied inside php_server
|
||||
// this can only be done if the root is defined inside php_server
|
||||
if !filepath.IsAbs(wc.FileName) && f.Root != "" {
|
||||
wc.FileName = filepath.Join(f.Root, wc.FileName)
|
||||
}
|
||||
@@ -82,6 +91,8 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
|
||||
if f.Env != nil {
|
||||
wc.inheritEnv(f.Env)
|
||||
}
|
||||
|
||||
wc.requestOptions = append(wc.requestOptions, loggerOpt)
|
||||
f.Workers[i] = wc
|
||||
}
|
||||
|
||||
@@ -95,14 +106,13 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
|
||||
if frankenphp.EmbeddedAppPath == "" {
|
||||
f.Root = "{http.vars.root}"
|
||||
} else {
|
||||
rrs := false
|
||||
f.Root = filepath.Join(frankenphp.EmbeddedAppPath, defaultDocumentRoot)
|
||||
|
||||
var rrs bool
|
||||
f.ResolveRootSymlink = &rrs
|
||||
}
|
||||
} else {
|
||||
if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(f.Root) {
|
||||
f.Root = filepath.Join(frankenphp.EmbeddedAppPath, f.Root)
|
||||
}
|
||||
} else if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(f.Root) {
|
||||
f.Root = filepath.Join(frankenphp.EmbeddedAppPath, f.Root)
|
||||
}
|
||||
|
||||
if len(f.SplitPath) == 0 {
|
||||
@@ -143,6 +153,10 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.configureHotReload(fapp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -153,11 +167,15 @@ func needReplacement(s string) bool {
|
||||
|
||||
// ServeHTTP implements caddyhttp.MiddlewareHandler.
|
||||
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)
|
||||
ctx := r.Context()
|
||||
origReq := ctx.Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
|
||||
repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
|
||||
var (
|
||||
documentRootOption frankenphp.RequestOption
|
||||
documentRoot string
|
||||
)
|
||||
|
||||
var documentRootOption frankenphp.RequestOption
|
||||
var documentRoot string
|
||||
if f.resolvedDocumentRoot == "" {
|
||||
documentRoot = repl.ReplaceKnown(f.Root, "")
|
||||
if documentRoot == "" && frankenphp.EmbeddedAppPath != "" {
|
||||
@@ -185,16 +203,39 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c
|
||||
}
|
||||
}
|
||||
|
||||
fr, err := frankenphp.NewRequestWithContext(
|
||||
r,
|
||||
documentRootOption,
|
||||
frankenphp.WithRequestSplitPath(f.SplitPath),
|
||||
frankenphp.WithRequestPreparedEnv(env),
|
||||
frankenphp.WithOriginalRequest(&origReq),
|
||||
frankenphp.WithWorkerName(workerName),
|
||||
var (
|
||||
err error
|
||||
fr *http.Request
|
||||
)
|
||||
|
||||
if err = frankenphp.ServeHTTP(w, fr); err != nil {
|
||||
if f.mercureHubRequestOption == nil {
|
||||
fr, err = frankenphp.NewRequestWithContext(
|
||||
r,
|
||||
documentRootOption,
|
||||
frankenphp.WithRequestSplitPath(f.SplitPath),
|
||||
frankenphp.WithRequestPreparedEnv(env),
|
||||
frankenphp.WithOriginalRequest(&origReq),
|
||||
frankenphp.WithWorkerName(workerName),
|
||||
)
|
||||
} else {
|
||||
fr, err = frankenphp.NewRequestWithContext(
|
||||
r,
|
||||
documentRootOption,
|
||||
frankenphp.WithRequestSplitPath(f.SplitPath),
|
||||
frankenphp.WithRequestPreparedEnv(env),
|
||||
frankenphp.WithOriginalRequest(&origReq),
|
||||
frankenphp.WithWorkerName(workerName),
|
||||
*f.mercureHubRequestOption,
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return caddyhttp.Error(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
// TODO: set caddyhttp.ServerHeader when https://github.com/caddyserver/caddy/pull/7338 will be released
|
||||
w.Header()["Server"] = serverHeader
|
||||
if err = frankenphp.ServeHTTP(w, fr); err != nil && !errors.As(err, &frankenphp.ErrRejected{}) {
|
||||
return caddyhttp.Error(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
@@ -203,7 +244,6 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c
|
||||
|
||||
// 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() {
|
||||
@@ -245,15 +285,20 @@ func (f *FrankenPHPModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
f.ResolveRootSymlink = &v
|
||||
|
||||
case "worker":
|
||||
wc, err := parseWorkerConfig(d)
|
||||
wc, err := unmarshalWorker(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Workers = append(f.Workers, wc)
|
||||
|
||||
case "hot_reload":
|
||||
if err := f.unmarshalHotReload(d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
allowedDirectives := "root, split, env, resolve_root_symlink, worker"
|
||||
return wrongSubDirectiveError("php or php_server", allowedDirectives, d.Val())
|
||||
return wrongSubDirectiveError("php or php_server", "hot_reload, name, root, split, env, resolve_root_symlink, worker", d.Val())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,7 +307,7 @@ func (f *FrankenPHPModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
fileNames := make(map[string]struct{}, len(f.Workers))
|
||||
for _, w := range f.Workers {
|
||||
if _, ok := fileNames[w.FileName]; ok {
|
||||
return fmt.Errorf(`workers in a single "php_server" block must not have duplicate filenames: %q`, w.FileName)
|
||||
return fmt.Errorf(`workers in a single "php" or "php_server" block must not have duplicate filenames: %q`, w.FileName)
|
||||
}
|
||||
|
||||
if len(w.MatchPath) == 0 {
|
||||
@@ -566,7 +611,7 @@ func parsePhpServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
|
||||
// workers can also match a path without being in the public directory
|
||||
// in this case we need to prepend the worker routes to the existing routes
|
||||
func prependWorkerRoutes(routes caddyhttp.RouteList, h httpcaddyfile.Helper, f FrankenPHPModule, fsrv caddy.Module, disableFsrv bool) caddyhttp.RouteList {
|
||||
allWorkerMatches := caddyhttp.MatchPath{}
|
||||
var allWorkerMatches caddyhttp.MatchPath
|
||||
for _, w := range f.Workers {
|
||||
for _, path := range w.MatchPath {
|
||||
allWorkerMatches = append(allWorkerMatches, path)
|
||||
@@ -581,7 +626,7 @@ func prependWorkerRoutes(routes caddyhttp.RouteList, h httpcaddyfile.Helper, f F
|
||||
if !disableFsrv {
|
||||
routes = append(routes, caddyhttp.Route{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{
|
||||
caddy.ModuleMap{
|
||||
{
|
||||
"file": h.JSON(fileserver.MatchFile{
|
||||
TryFiles: []string{"{http.request.uri.path}"},
|
||||
Root: f.Root,
|
||||
@@ -602,7 +647,7 @@ func prependWorkerRoutes(routes caddyhttp.RouteList, h httpcaddyfile.Helper, f F
|
||||
// forward matching routes to the PHP handler
|
||||
routes = append(routes, caddyhttp.Route{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{
|
||||
caddy.ModuleMap{"path": h.JSON(allWorkerMatches)},
|
||||
{"path": h.JSON(allWorkerMatches)},
|
||||
},
|
||||
HandlersRaw: []json.RawMessage{
|
||||
caddyconfig.JSONModuleObject(f, "handler", "php", nil),
|
||||
@@ -611,3 +656,10 @@ func prependWorkerRoutes(routes caddyhttp.RouteList, h httpcaddyfile.Helper, f F
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Provisioner = (*FrankenPHPModule)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*FrankenPHPModule)(nil)
|
||||
_ caddyfile.Unmarshaler = (*FrankenPHPModule)(nil)
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -23,12 +22,16 @@ import (
|
||||
// }
|
||||
// }
|
||||
type workerConfig struct {
|
||||
mercureContext
|
||||
|
||||
// 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"`
|
||||
// MaxThreads sets the maximum number of threads for this worker.
|
||||
MaxThreads int `json:"max_threads,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
|
||||
@@ -37,9 +40,12 @@ type workerConfig struct {
|
||||
MatchPath []string `json:"match_path,omitempty"`
|
||||
// MaxConsecutiveFailures sets the maximum number of consecutive failures before panicking (defaults to 6, set to -1 to never panick)
|
||||
MaxConsecutiveFailures int `json:"max_consecutive_failures,omitempty"`
|
||||
|
||||
options []frankenphp.WorkerOption
|
||||
requestOptions []frankenphp.RequestOption
|
||||
}
|
||||
|
||||
func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
|
||||
func unmarshalWorker(d *caddyfile.Dispenser) (workerConfig, error) {
|
||||
wc := workerConfig{}
|
||||
if d.NextArg() {
|
||||
wc.FileName = d.Val()
|
||||
@@ -59,12 +65,11 @@ func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
|
||||
}
|
||||
|
||||
if d.NextArg() {
|
||||
return wc, errors.New(`FrankenPHP: too many "worker" arguments: ` + d.Val())
|
||||
return wc, d.Errf(`FrankenPHP: too many "worker" arguments: %s`, d.Val())
|
||||
}
|
||||
|
||||
for d.NextBlock(1) {
|
||||
v := d.Val()
|
||||
switch v {
|
||||
switch v := d.Val(); v {
|
||||
case "name":
|
||||
if !d.NextArg() {
|
||||
return wc, d.ArgErr()
|
||||
@@ -82,10 +87,21 @@ func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
|
||||
|
||||
v, err := strconv.ParseUint(d.Val(), 10, 32)
|
||||
if err != nil {
|
||||
return wc, err
|
||||
return wc, d.WrapErr(err)
|
||||
}
|
||||
|
||||
wc.Num = int(v)
|
||||
case "max_threads":
|
||||
if !d.NextArg() {
|
||||
return wc, d.ArgErr()
|
||||
}
|
||||
|
||||
v, err := strconv.ParseUint(d.Val(), 10, 32)
|
||||
if err != nil {
|
||||
return wc, d.WrapErr(err)
|
||||
}
|
||||
|
||||
wc.MaxThreads = int(v)
|
||||
case "env":
|
||||
args := d.RemainingArgs()
|
||||
if len(args) != 2 {
|
||||
@@ -96,18 +112,22 @@ func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
|
||||
}
|
||||
wc.Env[args[0]] = args[1]
|
||||
case "watch":
|
||||
if !d.NextArg() {
|
||||
patterns := d.RemainingArgs()
|
||||
if len(patterns) == 0 {
|
||||
// the default if the watch directory is left empty:
|
||||
wc.Watch = append(wc.Watch, defaultWatchPattern)
|
||||
} else {
|
||||
wc.Watch = append(wc.Watch, d.Val())
|
||||
wc.Watch = append(wc.Watch, patterns...)
|
||||
}
|
||||
case "match":
|
||||
// provision the path so it's identical to Caddy match rules
|
||||
// see: https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/matchers.go
|
||||
caddyMatchPath := (caddyhttp.MatchPath)(d.RemainingArgs())
|
||||
caddyMatchPath.Provision(caddy.Context{})
|
||||
wc.MatchPath = ([]string)(caddyMatchPath)
|
||||
if err := caddyMatchPath.Provision(caddy.Context{}); err != nil {
|
||||
return wc, d.WrapErr(err)
|
||||
}
|
||||
|
||||
wc.MatchPath = caddyMatchPath
|
||||
case "max_consecutive_failures":
|
||||
if !d.NextArg() {
|
||||
return wc, d.ArgErr()
|
||||
@@ -115,21 +135,20 @@ func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
|
||||
|
||||
v, err := strconv.Atoi(d.Val())
|
||||
if err != nil {
|
||||
return wc, err
|
||||
return wc, d.WrapErr(err)
|
||||
}
|
||||
if v < -1 {
|
||||
return wc, errors.New("max_consecutive_failures must be >= -1")
|
||||
return wc, d.Errf("max_consecutive_failures must be >= -1")
|
||||
}
|
||||
|
||||
wc.MaxConsecutiveFailures = int(v)
|
||||
wc.MaxConsecutiveFailures = v
|
||||
default:
|
||||
allowedDirectives := "name, file, num, env, watch, match, max_consecutive_failures"
|
||||
return wc, wrongSubDirectiveError("worker", allowedDirectives, v)
|
||||
return wc, wrongSubDirectiveError("worker", "name, file, num, env, watch, match, max_consecutive_failures, max_threads", v)
|
||||
}
|
||||
}
|
||||
|
||||
if wc.FileName == "" {
|
||||
return wc, errors.New(`the "file" argument must be specified`)
|
||||
return wc, d.Err(`the "file" argument must be specified`)
|
||||
}
|
||||
|
||||
if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(wc.FileName) {
|
||||
@@ -139,7 +158,7 @@ func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
|
||||
return wc, nil
|
||||
}
|
||||
|
||||
func (wc workerConfig) inheritEnv(env map[string]string) {
|
||||
func (wc *workerConfig) inheritEnv(env map[string]string) {
|
||||
if wc.Env == nil {
|
||||
wc.Env = make(map[string]string, len(env))
|
||||
}
|
||||
@@ -151,7 +170,7 @@ func (wc workerConfig) inheritEnv(env map[string]string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (wc workerConfig) matchesPath(r *http.Request, documentRoot string) bool {
|
||||
func (wc *workerConfig) matchesPath(r *http.Request, documentRoot string) bool {
|
||||
|
||||
// try to match against a pattern if one is assigned
|
||||
if len(wc.MatchPath) != 0 {
|
||||
|
||||
34
cgi.go
34
cgi.go
@@ -12,8 +12,10 @@ package frankenphp
|
||||
// #include "frankenphp.h"
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unsafe"
|
||||
@@ -66,7 +68,7 @@ var knownServerKeys = []string{
|
||||
//
|
||||
// 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 addKnownVariablesToServer(thread *phpThread, fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
request := fc.request
|
||||
keys := mainThread.knownServerKeys
|
||||
// Separate remote IP and port; more lenient than net.SplitHostPort
|
||||
@@ -82,10 +84,8 @@ func addKnownVariablesToServer(thread *phpThread, fc *frankenPHPContext, trackVa
|
||||
ip = strings.Replace(ip, "[", "", 1)
|
||||
ip = strings.Replace(ip, "]", "", 1)
|
||||
|
||||
var https string
|
||||
var sslProtocol string
|
||||
var sslCipher string
|
||||
var rs string
|
||||
var https, sslProtocol, sslCipher, rs string
|
||||
|
||||
if request.TLS == nil {
|
||||
rs = "http"
|
||||
https = ""
|
||||
@@ -186,8 +186,8 @@ 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 {
|
||||
func addHeadersToServer(ctx context.Context, request *http.Request, trackVarsArray *C.zval) {
|
||||
for field, val := range 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)
|
||||
@@ -196,7 +196,7 @@ func addHeadersToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
|
||||
// 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)
|
||||
k := phpheaders.GetUnCommonHeader(ctx, field)
|
||||
v := strings.Join(val, ", ")
|
||||
C.frankenphp_register_variable_safe(toUnsafeChar(k), toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
|
||||
}
|
||||
@@ -212,10 +212,12 @@ func addPreparedEnvToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
//export go_register_variables
|
||||
func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
|
||||
thread := phpThreads[threadIndex]
|
||||
fc := thread.getRequestContext()
|
||||
fc := thread.frankenPHPContext()
|
||||
|
||||
addKnownVariablesToServer(thread, fc, trackVarsArray)
|
||||
addHeadersToServer(fc, trackVarsArray)
|
||||
if fc.request != nil {
|
||||
addKnownVariablesToServer(fc, trackVarsArray)
|
||||
addHeadersToServer(thread.context(), fc.request, trackVarsArray)
|
||||
}
|
||||
|
||||
// The Prepared Environment is registered last and can overwrite any previous values
|
||||
addPreparedEnvToServer(fc, trackVarsArray)
|
||||
@@ -275,11 +277,15 @@ func splitPos(path string, splitPath []string) int {
|
||||
// See: https://github.com/php/php-src/blob/345e04b619c3bc11ea17ee02cdecad6ae8ce5891/main/SAPI.h#L72
|
||||
//
|
||||
//export go_update_request_info
|
||||
func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info) C.bool {
|
||||
func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info) {
|
||||
thread := phpThreads[threadIndex]
|
||||
fc := thread.getRequestContext()
|
||||
fc := thread.frankenPHPContext()
|
||||
request := fc.request
|
||||
|
||||
if request == nil {
|
||||
return
|
||||
}
|
||||
|
||||
authUser, authPassword, ok := request.BasicAuth()
|
||||
if ok {
|
||||
if authPassword != "" {
|
||||
@@ -305,8 +311,6 @@ func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info)
|
||||
info.request_uri = thread.pinCString(request.URL.RequestURI())
|
||||
|
||||
info.proto_num = C.int(request.ProtoMajor*1000 + request.ProtoMinor)
|
||||
|
||||
return C.bool(fc.worker != nil)
|
||||
}
|
||||
|
||||
// SanitizedPathJoin performs filepath.Join(root, reqPath) that
|
||||
|
||||
66
context.go
66
context.go
@@ -2,6 +2,8 @@ package frankenphp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -12,6 +14,8 @@ import (
|
||||
|
||||
// frankenPHPContext provides contextual information about the Request to handle.
|
||||
type frankenPHPContext struct {
|
||||
mercureContext
|
||||
|
||||
documentRoot string
|
||||
splitPath []string
|
||||
env PreparedEnv
|
||||
@@ -28,25 +32,37 @@ type frankenPHPContext struct {
|
||||
// Whether the request is already closed by us
|
||||
isDone bool
|
||||
|
||||
responseWriter http.ResponseWriter
|
||||
responseWriter http.ResponseWriter
|
||||
handlerParameters any
|
||||
handlerReturn any
|
||||
|
||||
done chan any
|
||||
startedAt time.Time
|
||||
}
|
||||
|
||||
type contextHolder struct {
|
||||
ctx context.Context
|
||||
frankenPHPContext *frankenPHPContext
|
||||
}
|
||||
|
||||
// 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{
|
||||
func newFrankenPHPContext() *frankenPHPContext {
|
||||
return &frankenPHPContext{
|
||||
done: make(chan any),
|
||||
startedAt: time.Now(),
|
||||
request: r,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRequestWithContext creates a new FrankenPHP request context.
|
||||
func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Request, error) {
|
||||
fc := newFrankenPHPContext()
|
||||
fc.request = r
|
||||
|
||||
for _, o := range opts {
|
||||
if err := o(fc); err != nil {
|
||||
return nil, err
|
||||
@@ -54,7 +70,7 @@ func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Reques
|
||||
}
|
||||
|
||||
if fc.logger == nil {
|
||||
fc.logger = logger
|
||||
fc.logger = globalLogger
|
||||
}
|
||||
|
||||
if fc.documentRoot == "" {
|
||||
@@ -84,7 +100,7 @@ func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Reques
|
||||
|
||||
// newDummyContext creates a fake context from a request path
|
||||
func newDummyContext(requestPath string, opts ...RequestOption) (*frankenPHPContext, error) {
|
||||
r, err := http.NewRequest(http.MethodGet, requestPath, nil)
|
||||
r, err := http.NewRequestWithContext(globalCtx, http.MethodGet, requestPath, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,26 +126,32 @@ func (fc *frankenPHPContext) closeContext() {
|
||||
}
|
||||
|
||||
// validate checks if the request should be outright rejected
|
||||
func (fc *frankenPHPContext) validate() bool {
|
||||
func (fc *frankenPHPContext) validate() error {
|
||||
if strings.Contains(fc.request.URL.Path, "\x00") {
|
||||
fc.rejectBadRequest("Invalid request path")
|
||||
fc.reject(ErrInvalidRequestPath)
|
||||
|
||||
return false
|
||||
return ErrInvalidRequestPath
|
||||
}
|
||||
|
||||
contentLengthStr := fc.request.Header.Get("Content-Length")
|
||||
if contentLengthStr != "" {
|
||||
if contentLength, err := strconv.Atoi(contentLengthStr); err != nil || contentLength < 0 {
|
||||
fc.rejectBadRequest("invalid Content-Length header: " + contentLengthStr)
|
||||
e := fmt.Errorf("%w: %q", ErrInvalidContentLengthHeader, contentLengthStr)
|
||||
|
||||
return false
|
||||
fc.reject(e)
|
||||
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fc *frankenPHPContext) clientHasClosed() bool {
|
||||
if fc.request == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
select {
|
||||
case <-fc.request.Context().Done():
|
||||
return true
|
||||
@@ -138,16 +160,22 @@ func (fc *frankenPHPContext) clientHasClosed() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// reject sends a response with the given status code and message
|
||||
func (fc *frankenPHPContext) reject(statusCode int, message string) {
|
||||
// reject sends a response with the given status code and error
|
||||
func (fc *frankenPHPContext) reject(err error) {
|
||||
if fc.isDone {
|
||||
return
|
||||
}
|
||||
|
||||
re := &ErrRejected{}
|
||||
if !errors.As(err, re) {
|
||||
// Should never happen
|
||||
panic("only instance of ErrRejected can be passed to reject")
|
||||
}
|
||||
|
||||
rw := fc.responseWriter
|
||||
if rw != nil {
|
||||
rw.WriteHeader(statusCode)
|
||||
_, _ = rw.Write([]byte(message))
|
||||
rw.WriteHeader(re.status)
|
||||
_, _ = rw.Write([]byte(err.Error()))
|
||||
|
||||
if f, ok := rw.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
@@ -156,7 +184,3 @@ func (fc *frankenPHPContext) reject(statusCode int, message string) {
|
||||
|
||||
fc.closeContext()
|
||||
}
|
||||
|
||||
func (fc *frankenPHPContext) rejectBadRequest(message string) {
|
||||
fc.reject(http.StatusBadRequest, message)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package frankenphp
|
||||
|
||||
import (
|
||||
"github.com/dunglas/frankenphp/internal/state"
|
||||
)
|
||||
|
||||
// EXPERIMENTAL: ThreadDebugState prints the state of a single PHP thread - debugging purposes only
|
||||
type ThreadDebugState struct {
|
||||
Index int
|
||||
@@ -23,7 +27,7 @@ func DebugState() FrankenPHPDebugState {
|
||||
ReservedThreadCount: 0,
|
||||
}
|
||||
for _, thread := range phpThreads {
|
||||
if thread.state.is(stateReserved) {
|
||||
if thread.state.Is(state.Reserved) {
|
||||
fullState.ReservedThreadCount++
|
||||
continue
|
||||
}
|
||||
@@ -38,9 +42,9 @@ 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(),
|
||||
State: thread.state.Name(),
|
||||
IsWaiting: thread.state.IsInWaitingState(),
|
||||
IsBusy: !thread.state.IsInWaitingState(),
|
||||
WaitingSinceMilliseconds: thread.state.WaitTime(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ RUN apk add --no-cache \
|
||||
zlib-dev \
|
||||
bison \
|
||||
nss-tools \
|
||||
# file watcher
|
||||
# file watcher \
|
||||
libstdc++ \
|
||||
linux-headers \
|
||||
# Dev tools \
|
||||
@@ -48,8 +48,8 @@ RUN apk add --no-cache \
|
||||
echo 'set auto-load safe-path /' > /root/.gdbinit
|
||||
|
||||
WORKDIR /usr/local/src/php
|
||||
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
|
||||
RUN git clone --branch=PHP-8.5 https://github.com/php/php-src.git . && \
|
||||
# --enable-embed is necessary to generate libphp.so, but we don't use this SAPI directly
|
||||
./buildconf --force && \
|
||||
EXTENSION_DIR=/usr/lib/frankenphp/modules ./configure \
|
||||
--enable-embed \
|
||||
@@ -71,7 +71,7 @@ RUN git clone --branch=PHP-8.4 https://github.com/php/php-src.git . && \
|
||||
# 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 -S . -B build -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build build/ && \
|
||||
cmake --install build
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ RUN apt-get update && \
|
||||
apt-get clean
|
||||
|
||||
WORKDIR /usr/local/src/php
|
||||
RUN git clone --branch=PHP-8.4 https://github.com/php/php-src.git . && \
|
||||
RUN git clone --branch=PHP-8.5 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 && \
|
||||
EXTENSION_DIR=/usr/lib/frankenphp/modules ./configure \
|
||||
|
||||
@@ -7,13 +7,17 @@ variable "VERSION" {
|
||||
}
|
||||
|
||||
variable "PHP_VERSION" {
|
||||
default = "8.2,8.3,8.4"
|
||||
default = "8.2,8.3,8.4,8.5"
|
||||
}
|
||||
|
||||
variable "GO_VERSION" {
|
||||
default = "1.25"
|
||||
}
|
||||
|
||||
variable "SPC_OPT_BUILD_ARGS" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "SHA" {}
|
||||
|
||||
variable "LATEST" {
|
||||
@@ -24,8 +28,13 @@ variable "CACHE" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "CI" {
|
||||
# CI flag coming from the environment or --set; empty by default
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable DEFAULT_PHP_VERSION {
|
||||
default = "8.4"
|
||||
default = "8.5"
|
||||
}
|
||||
|
||||
function "tag" {
|
||||
@@ -91,8 +100,7 @@ target "default" {
|
||||
platforms = os == "alpine" ? [
|
||||
"linux/amd64",
|
||||
"linux/386",
|
||||
# FIXME: armv6 doesn't build in GitHub actions because we use a custom Go build
|
||||
#"linux/arm/v6",
|
||||
"linux/arm/v6",
|
||||
"linux/arm/v7",
|
||||
"linux/arm64",
|
||||
] : [
|
||||
@@ -141,6 +149,8 @@ target "static-builder-musl" {
|
||||
}
|
||||
args = {
|
||||
FRANKENPHP_VERSION = VERSION
|
||||
CI = CI
|
||||
SPC_OPT_BUILD_ARGS = SPC_OPT_BUILD_ARGS
|
||||
}
|
||||
secret = ["id=github-token,env=GITHUB_TOKEN"]
|
||||
}
|
||||
@@ -165,6 +175,8 @@ target "static-builder-gnu" {
|
||||
args = {
|
||||
FRANKENPHP_VERSION = VERSION
|
||||
GO_VERSION = GO_VERSION
|
||||
CI = CI
|
||||
SPC_OPT_BUILD_ARGS = SPC_OPT_BUILD_ARGS
|
||||
}
|
||||
secret = ["id=github-token,env=GITHUB_TOKEN"]
|
||||
}
|
||||
|
||||
@@ -115,22 +115,22 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
|
||||
1. 从 GitHub 下载 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
|
||||
```
|
||||
```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` 替换为 debug FrankenPHP 可执行文件
|
||||
3. 照常启动 FrankenPHP(或者,你可以直接使用 GDB 启动 FrankenPHP: `gdb --args frankenphp run`)
|
||||
4. 使用 GDB 附加到进程:
|
||||
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
|
||||
5. 如有必要,请在 GDB shell 中输入 `continue`
|
||||
6. 使 FrankenPHP 崩溃
|
||||
@@ -142,13 +142,13 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
1. 打开 `.github/workflows/tests.yml`
|
||||
2. 启用 PHP 调试符号
|
||||
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
|
||||
3. 启用 `tmate` 以连接到容器
|
||||
|
||||
@@ -166,18 +166,18 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
5. 打开 `frankenphp.go`
|
||||
6. 启用 `cgosymbolizer`
|
||||
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/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.test -test.run ^MyTest$
|
||||
```
|
||||
```console
|
||||
go test -tags watcher -c -ldflags=-w
|
||||
gdb --args frankenphp.test -test.run ^MyTest$
|
||||
```
|
||||
|
||||
9. 当错误修复后,恢复所有这些更改
|
||||
|
||||
|
||||
@@ -16,29 +16,62 @@ FrankenPHP 也可以用作独立的 Go 库,将 PHP 嵌入到任何使用 `net/
|
||||
|
||||
## 开始
|
||||
|
||||
### 独立二进制
|
||||
|
||||
我们为 Linux 和 macOS 提供包含 [PHP 8.4](https://www.php.net/releases/8.4/zh.php) 以及大多数常用 PHP 扩展的 FrankenPHP 静态二进制文件。
|
||||
|
||||
在 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/
|
||||
```
|
||||
|
||||
要提供当前目录的内容,请运行:
|
||||
### 独立二进制
|
||||
|
||||
我们为 Linux 和 macOS 提供用于开发的 FrankenPHP 静态二进制文件,
|
||||
包含 [PHP 8.4](https://www.php.net/releases/8.4/zh.php) 以及大多数常用 PHP 扩展。
|
||||
|
||||
[下载 FrankenPHP](https://github.com/dunglas/frankenphp/releases)
|
||||
|
||||
**安装扩展:** 常见扩展已内置,无法再安装更多扩展。
|
||||
|
||||
### rpm 软件包
|
||||
|
||||
我们的维护者为所有使用 `dnf` 的系统提供 rpm 包。安装方式:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
sudo dnf install https://rpm.henderkes.com/static-php-1-0.noarch.rpm
|
||||
sudo dnf module enable php-zts:static-8.4 # 可用 8.2-8.5
|
||||
sudo dnf install frankenphp
|
||||
```
|
||||
|
||||
你还可以使用以下命令运行命令行脚本:
|
||||
**安装扩展:** `sudo dnf install php-zts-<extension>`
|
||||
|
||||
对于默认不可用的扩展,请使用 [PIE](https://github.com/php/pie):
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
sudo dnf install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### deb 软件包
|
||||
|
||||
我们的维护者为所有使用 `apt` 的系统提供 deb 包。安装方式:
|
||||
|
||||
```console
|
||||
sudo curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg && \
|
||||
echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | sudo tee /etc/apt/sources.list.d/static-php.list && \
|
||||
sudo apt update
|
||||
sudo apt install frankenphp
|
||||
```
|
||||
|
||||
**安装扩展:** `sudo apt install php-zts-<extension>`
|
||||
|
||||
对于默认不可用的扩展,请使用 [PIE](https://github.com/php/pie):
|
||||
|
||||
```console
|
||||
sudo apt install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### Docker
|
||||
@@ -68,12 +101,28 @@ FrankenPHP 也作为 [Homebrew](https://brew.sh) 软件包提供,适用于 mac
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
**安装扩展:** 使用 [PIE](https://github.com/php/pie)。
|
||||
|
||||
### 用法
|
||||
|
||||
要提供当前目录的内容,请运行:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
你还可以使用以下命令运行命令行脚本:
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
```
|
||||
|
||||
对于 deb 和 rpm 软件包,还可以启动 systemd 服务:
|
||||
|
||||
```console
|
||||
sudo systemctl start frankenphp
|
||||
```
|
||||
|
||||
## 文档
|
||||
|
||||
- [Classic 模式](classic.md)
|
||||
|
||||
@@ -79,10 +79,10 @@ 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 |
|
||||
| 功能 | 依赖项 | 用于禁用的构建标签 |
|
||||
| --------------------- | --------------------------------------------------------------------- | ------------------ |
|
||||
| Brotli 压缩 | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| 文件更改时重启 worker | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
|
||||
## 编译 Go 应用
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[FrankenPHP Docker 镜像](https://hub.docker.com/r/dunglas/frankenphp) 基于 [官方 PHP 镜像](https://hub.docker.com/_/php/)。提供适用于流行架构的 Debian 和 Alpine Linux 变体。推荐使用 Debian 变体。
|
||||
|
||||
提供 PHP 8.2、8.3 和 8.4 的变体。
|
||||
提供 PHP 8.2、8.3、8.4 和 8.5 的变体。
|
||||
|
||||
标签遵循此模式:`dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ FrankenPHP 能够将 PHP 应用程序的源代码和资源文件嵌入到静态
|
||||
|
||||
例如,你可能希望:
|
||||
|
||||
* 给应用安装生产环境的依赖
|
||||
* 导出 autoloader
|
||||
* 如果可能,为应用启用生产模式
|
||||
* 丢弃不需要的文件,例如 `.git` 或测试文件,以减小最终二进制文件的大小
|
||||
- 给应用安装生产环境的依赖
|
||||
- 导出 autoloader
|
||||
- 如果可能,为应用启用生产模式
|
||||
- 丢弃不需要的文件,例如 `.git` 或测试文件,以减小最终二进制文件的大小
|
||||
|
||||
例如,对于 Symfony 应用程序,你可以使用以下命令:
|
||||
|
||||
@@ -53,34 +53,35 @@ composer dump-env prod
|
||||
|
||||
1. 在准备好的应用的存储库中创建一个名为 `static-build.Dockerfile` 的文件。
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# 如果你打算在 glibc 系统上运行该二进制文件,请使用 static-builder-gnu
|
||||
|
||||
# 复制应用代码
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
# 复制应用代码
|
||||
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` 文件。
|
||||
> [!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 .
|
||||
```
|
||||
```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
|
||||
```
|
||||
```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` 的文件。
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
|
||||
这里有两个重要的事情要注意:
|
||||
|
||||
* 指令注释 `//export_php:function` 定义了 PHP 中的函数签名。这是生成器知道如何使用正确的参数和返回类型生成 PHP 函数的方式;
|
||||
* 函数必须返回 `unsafe.Pointer`。FrankenPHP 提供了一个 API 来帮助你在 C 和 Go 之间进行类型转换。
|
||||
- 指令注释 `//export_php:function` 定义了 PHP 中的函数签名。这是生成器知道如何使用正确的参数和返回类型生成 PHP 函数的方式;
|
||||
- 函数必须返回 `unsafe.Pointer`。FrankenPHP 提供了一个 API 来帮助你在 C 和 Go 之间进行类型转换。
|
||||
|
||||
虽然第一点不言自明,但第二点可能更难理解。让我们在下一节中深入了解类型转换。
|
||||
|
||||
@@ -82,16 +82,17 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
虽然一些变量类型在 C/PHP 和 Go 之间具有相同的内存表示,但某些类型需要更多逻辑才能直接使用。这可能是编写扩展时最困难的部分,因为它需要了解 Zend 引擎的内部结构以及变量在 PHP 中的内部存储方式。此表总结了你需要知道的内容:
|
||||
|
||||
| PHP 类型 | Go 类型 | 直接转换 | C 到 Go 助手 | Go 到 C 助手 | 类方法支持 |
|
||||
|--------------------|---------------------|----------|----------------------|----------------------|------------|
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
|
||||
| `object` | `struct` | ❌ | _尚未实现_ | _尚未实现_ | ❌ |
|
||||
| ------------------ | ------------------- | -------- | --------------------- | ---------------------- | ---------- |
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `object` | `struct` | ❌ | _尚未实现_ | _尚未实现_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
> 此表尚不详尽,将随着 FrankenPHP 类型 API 变得更加完整而完善。
|
||||
@@ -111,16 +112,16 @@ FrankenPHP 通过 `frankenphp.Array` 类型为 PHP 数组提供原生支持。
|
||||
func process_data(arr *C.zval) unsafe.Pointer {
|
||||
// 将 PHP 数组转换为 Go
|
||||
goArray := frankenphp.GoArray(unsafe.Pointer(arr))
|
||||
|
||||
|
||||
result := &frankenphp.Array{}
|
||||
|
||||
|
||||
result.SetInt(0, "first")
|
||||
result.SetInt(1, "second")
|
||||
result.Append("third") // 自动分配下一个整数键
|
||||
|
||||
|
||||
result.SetString("name", "John")
|
||||
result.SetString("age", int64(30))
|
||||
|
||||
|
||||
for i := uint32(0); i < goArray.Len(); i++ {
|
||||
key, value := goArray.At(i)
|
||||
if key.Type == frankenphp.PHPStringKey {
|
||||
@@ -129,7 +130,7 @@ func process_data(arr *C.zval) unsafe.Pointer {
|
||||
result.SetInt(key.Int+100, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 转换回 PHP 数组
|
||||
return frankenphp.PHPArray(result)
|
||||
}
|
||||
@@ -137,20 +138,20 @@ func process_data(arr *C.zval) unsafe.Pointer {
|
||||
|
||||
**`frankenphp.Array` 的关键特性:**
|
||||
|
||||
* **有序键值对** - 像 PHP 数组一样维护插入顺序
|
||||
* **混合键类型** - 在同一数组中支持整数和字符串键
|
||||
* **类型安全** - `PHPKey` 类型确保正确的键处理
|
||||
* **自动列表检测** - 转换为 PHP 时,自动检测数组应该是打包列表还是哈希映射
|
||||
* **不支持对象** - 目前,只有标量类型和数组可以用作值。提供对象将导致 PHP 数组中的 `null` 值。
|
||||
- **有序键值对** - 像 PHP 数组一样维护插入顺序
|
||||
- **混合键类型** - 在同一数组中支持整数和字符串键
|
||||
- **类型安全** - `PHPKey` 类型确保正确的键处理
|
||||
- **自动列表检测** - 转换为 PHP 时,自动检测数组应该是打包列表还是哈希映射
|
||||
- **不支持对象** - 目前,只有标量类型和数组可以用作值。提供对象将导致 PHP 数组中的 `null` 值。
|
||||
|
||||
**可用方法:**
|
||||
|
||||
* `SetInt(key int64, value interface{})` - 使用整数键设置值
|
||||
* `SetString(key string, value interface{})` - 使用字符串键设置值
|
||||
* `Append(value interface{})` - 使用下一个可用整数键添加值
|
||||
* `Len() uint32` - 获取元素数量
|
||||
* `At(index uint32) (PHPKey, interface{})` - 获取索引处的键值对
|
||||
* `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - 转换为 PHP 数组
|
||||
- `SetInt(key int64, value any)` - 使用整数键设置值
|
||||
- `SetString(key string, value any)` - 使用字符串键设置值
|
||||
- `Append(value any)` - 使用下一个可用整数键添加值
|
||||
- `Len() uint32` - 获取元素数量
|
||||
- `At(index uint32) (PHPKey, any)` - 获取索引处的键值对
|
||||
- `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - 转换为 PHP 数组
|
||||
|
||||
### 声明原生 PHP 类
|
||||
|
||||
@@ -168,11 +169,11 @@ type UserStruct struct {
|
||||
|
||||
**不透明类**是内部结构(属性)对 PHP 代码隐藏的类。这意味着:
|
||||
|
||||
* **无直接属性访问**:你不能直接从 PHP 读取或写入属性(`$user->name` 不起作用)
|
||||
* **仅方法接口** - 所有交互必须通过你定义的方法进行
|
||||
* **更好的封装** - 内部数据结构完全由 Go 代码控制
|
||||
* **类型安全** - 没有 PHP 代码使用错误类型破坏内部状态的风险
|
||||
* **更清晰的 API** - 强制设计适当的公共接口
|
||||
- **无直接属性访问**:你不能直接从 PHP 读取或写入属性(`$user->name` 不起作用)
|
||||
- **仅方法接口** - 所有交互必须通过你定义的方法进行
|
||||
- **更好的封装** - 内部数据结构完全由 Go 代码控制
|
||||
- **类型安全** - 没有 PHP 代码使用错误类型破坏内部状态的风险
|
||||
- **更清晰的 API** - 强制设计适当的公共接口
|
||||
|
||||
这种方法提供了更好的封装,并防止 PHP 代码意外破坏 Go 对象的内部状态。与对象的所有交互都必须通过你明确定义的方法进行。
|
||||
|
||||
@@ -219,12 +220,12 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
if name != nil {
|
||||
us.Name = frankenphp.GoString(unsafe.Pointer(name))
|
||||
}
|
||||
|
||||
|
||||
// 检查是否提供了 age(不为 null)
|
||||
if age != nil {
|
||||
us.Age = int(*age)
|
||||
}
|
||||
|
||||
|
||||
// 检查是否提供了 active(不为 null)
|
||||
if active != nil {
|
||||
us.Active = *active
|
||||
@@ -234,10 +235,10 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
|
||||
**关于可空参数的要点:**
|
||||
|
||||
* **可空原始类型**(`?int`、`?float`、`?bool`)在 Go 中变成指针(`*int64`、`*float64`、`*bool`)
|
||||
* **可空字符串**(`?string`)仍然是 `*C.zend_string`,但可以是 `nil`
|
||||
* **在解引用指针值之前检查 `nil`**
|
||||
* **PHP `null` 变成 Go `nil`** - 当 PHP 传递 `null` 时,你的 Go 函数接收 `nil` 指针
|
||||
- **可空原始类型**(`?int`、`?float`、`?bool`)在 Go 中变成指针(`*int64`、`*float64`、`*bool`)
|
||||
- **可空字符串**(`?string`)仍然是 `*C.zend_string`,但可以是 `nil`
|
||||
- **在解引用指针值之前检查 `nil`**
|
||||
- **PHP `null` 变成 Go `nil`** - 当 PHP 传递 `null` 时,你的 Go 函数接收 `nil` 指针
|
||||
|
||||
> [!WARNING]
|
||||
> 目前,类方法有以下限制。**不支持对象**作为参数类型或返回类型。**完全支持数组**作为参数和返回类型。支持的类型:`string`、`int`、`float`、`bool`、`array` 和 `void`(用于返回类型)。**完全支持可空参数类型**,适用于所有标量类型(`?string`、`?int`、`?float`、`?bool`)。
|
||||
@@ -269,7 +270,7 @@ $user->updateInfo(null, 25, null); // Name 和 active 为 null
|
||||
|
||||
### 声明常量
|
||||
|
||||
生成器支持使用两个指令将 Go 常量导出到 PHP:`//export_php:const` 用于全局常量,`//export_php:classconstant` 用于类常量。这允许你在 Go 和 PHP 代码之间共享配置值、状态代码和其他常量。
|
||||
生成器支持使用两个指令将 Go 常量导出到 PHP:`//export_php:const` 用于全局常量,`//export_php:classconst` 用于类常量。这允许你在 Go 和 PHP 代码之间共享配置值、状态代码和其他常量。
|
||||
|
||||
#### 全局常量
|
||||
|
||||
@@ -291,25 +292,25 @@ const STATUS_ERROR = iota
|
||||
|
||||
#### 类常量
|
||||
|
||||
使用 `//export_php:classconstant ClassName` 指令创建属于特定 PHP 类的常量:
|
||||
使用 `//export_php:classconst ClassName` 指令创建属于特定 PHP 类的常量:
|
||||
|
||||
```go
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const STATUS_ACTIVE = 1
|
||||
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const STATUS_INACTIVE = 0
|
||||
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const ROLE_ADMIN = "admin"
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_PENDING = iota
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_PROCESSING = iota
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_COMPLETED = iota
|
||||
```
|
||||
|
||||
@@ -345,10 +346,10 @@ const STR_REVERSE = iota
|
||||
//export_php:const
|
||||
const STR_NORMAL = iota
|
||||
|
||||
//export_php:classconstant StringProcessor
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_LOWERCASE = 1
|
||||
|
||||
//export_php:classconstant StringProcessor
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_UPPERCASE = 2
|
||||
|
||||
//export_php:function repeat_this(string $str, int $count, int $mode): string
|
||||
@@ -356,7 +357,7 @@ func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
if mode == STR_REVERSE {
|
||||
// 反转字符串
|
||||
}
|
||||
|
||||
@@ -375,14 +376,14 @@ type StringProcessorStruct struct {
|
||||
//export_php:method StringProcessor::process(string $input, int $mode): string
|
||||
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
|
||||
|
||||
return frankenphp.PHPString(str, false)
|
||||
}
|
||||
```
|
||||
@@ -437,17 +438,17 @@ echo My\Extension\STATUS_ACTIVE; // 1
|
||||
|
||||
#### 重要说明
|
||||
|
||||
* 每个文件只允许**一个**命名空间指令。如果找到多个命名空间指令,生成器将返回错误。
|
||||
* 命名空间适用于文件中的**所有**导出符号:函数、类、方法和常量。
|
||||
* 命名空间名称遵循 PHP 命名空间约定,使用反斜杠(`\`)作为分隔符。
|
||||
* 如果没有声明命名空间,符号将照常导出到全局命名空间。
|
||||
- 每个文件只允许**一个**命名空间指令。如果找到多个命名空间指令,生成器将返回错误。
|
||||
- 命名空间适用于文件中的**所有**导出符号:函数、类、方法和常量。
|
||||
- 命名空间名称遵循 PHP 命名空间约定,使用反斜杠(`\`)作为分隔符。
|
||||
- 如果没有声明命名空间,符号将照常导出到全局命名空间。
|
||||
|
||||
### 生成扩展
|
||||
|
||||
这就是魔法发生的地方,现在可以生成你的扩展。你可以使用以下命令运行生成器:
|
||||
|
||||
```console
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -567,9 +568,9 @@ extern zend_module_entry ext_module_entry;
|
||||
|
||||
接下来,创建一个名为 `extension.c` 的文件,该文件将执行以下步骤:
|
||||
|
||||
* 包含 PHP 头文件;
|
||||
* 声明我们的新原生 PHP 函数 `go_print()`;
|
||||
* 声明扩展元数据。
|
||||
- 包含 PHP 头文件;
|
||||
- 声明我们的新原生 PHP 函数 `go_print()`;
|
||||
- 声明扩展元数据。
|
||||
|
||||
让我们首先包含所需的头文件:
|
||||
|
||||
@@ -701,9 +702,9 @@ import "strings"
|
||||
//export go_upper
|
||||
func go_upper(s *C.zend_string) *C.zend_string {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
|
||||
upper := strings.ToUpper(str)
|
||||
|
||||
|
||||
return (*C.zend_string)(frankenphp.PHPString(upper, false))
|
||||
}
|
||||
```
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
已知以下扩展与 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/) | 不安全的线程 | - |
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
以下扩展在与 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
|
||||
|
||||
@@ -19,23 +19,23 @@ docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
|
||||
1. [下载与你的系统相对应的二进制文件](https://github.com/php/frankenphp/releases)
|
||||
2. 将以下配置添加到 Laravel 项目根目录中名为 `Caddyfile` 的文件中:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
|
||||
# 服务器的域名
|
||||
localhost {
|
||||
# 将 webroot 设置为 public/ 目录
|
||||
root public/
|
||||
# 启用压缩(可选)
|
||||
encode zstd br gzip
|
||||
# 执行当前目录中的 PHP 文件并提供资源
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
}
|
||||
}
|
||||
```
|
||||
# 服务器的域名
|
||||
localhost {
|
||||
# 将 webroot 设置为 public/ 目录
|
||||
root public/
|
||||
# 启用压缩(可选)
|
||||
encode zstd br gzip
|
||||
# 执行当前目录中的 PHP 文件并提供资源
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. 从 Laravel 项目的根目录启动 FrankenPHP:`frankenphp run`
|
||||
|
||||
@@ -61,17 +61,17 @@ php artisan octane:frankenphp
|
||||
|
||||
`octane:frankenphp` 命令可以采用以下选项:
|
||||
|
||||
* `--host`: 服务器应绑定到的 IP 地址(默认值: `127.0.0.1`)
|
||||
* `--port`: 服务器应可用的端口(默认值: `8000`)
|
||||
* `--admin-port`: 管理服务器应可用的端口(默认值: `2019`)
|
||||
* `--workers`: 应可用于处理请求的 worker 数(默认值: `auto`)
|
||||
* `--max-requests`: 在 worker 重启之前要处理的请求数(默认值: `500`)
|
||||
* `--caddyfile`:FrankenPHP `Caddyfile` 文件的路径(默认: [Laravel Octane 中的存根 `Caddyfile`](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`: 在指定日志级别或高于指定日志级别的日志消息
|
||||
- `--host`: 服务器应绑定到的 IP 地址(默认值: `127.0.0.1`)
|
||||
- `--port`: 服务器应可用的端口(默认值: `8000`)
|
||||
- `--admin-port`: 管理服务器应可用的端口(默认值: `2019`)
|
||||
- `--workers`: 应可用于处理请求的 worker 数(默认值: `auto`)
|
||||
- `--max-requests`: 在 worker 重启之前要处理的请求数(默认值: `500`)
|
||||
- `--caddyfile`:FrankenPHP `Caddyfile` 文件的路径(默认: [Laravel Octane 中的存根 `Caddyfile`](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`: 在指定日志级别或高于指定日志级别的日志消息
|
||||
|
||||
> [!TIP]
|
||||
> 要获取结构化的 JSON 日志(在使用日志分析解决方案时非常有用),请明确传递 `--log-level` 选项。
|
||||
@@ -88,7 +88,8 @@ php artisan octane:frankenphp
|
||||
1. 在您的应用程序的存储库中创建一个名为 `static-build.Dockerfile` 的文件:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# 如果你打算在 musl-libc 系统上运行该二进制文件,请使用 static-builder-musl
|
||||
|
||||
# 复制你的应用
|
||||
WORKDIR /go/src/app/dist/app
|
||||
|
||||
@@ -117,17 +117,17 @@ cd frankenphp
|
||||
以下环境变量可以传递给 `docker build` 和 `build-static.sh`
|
||||
脚本来自定义静态构建:
|
||||
|
||||
* `FRANKENPHP_VERSION`: 要使用的 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 及其所有依赖项都是重新构建的(不使用缓存)
|
||||
* `NO_COMPRESS`: 不要使用UPX压缩生成的二进制文件
|
||||
* `DEBUG_SYMBOLS`: 设置后,调试符号将被保留在二进制文件内
|
||||
* `MIMALLOC`: (实验性,仅限Linux) 用[mimalloc](https://github.com/microsoft/mimalloc)替换musl的mallocng,以提高性能。我们仅建议在musl目标构建中使用此选项,对于glibc,建议禁用此选项,并在运行二进制文件时使用[`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html)。
|
||||
* `RELEASE`: (仅限维护者)设置后,生成的二进制文件将上传到 GitHub 上
|
||||
- `FRANKENPHP_VERSION`: 要使用的 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 及其所有依赖项都是重新构建的(不使用缓存)
|
||||
- `NO_COMPRESS`: 不要使用UPX压缩生成的二进制文件
|
||||
- `DEBUG_SYMBOLS`: 设置后,调试符号将被保留在二进制文件内
|
||||
- `MIMALLOC`: (实验性,仅限Linux) 用[mimalloc](https://github.com/microsoft/mimalloc)替换musl的mallocng,以提高性能。我们仅建议在musl目标构建中使用此选项,对于glibc,建议禁用此选项,并在运行二进制文件时使用[`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html)。
|
||||
- `RELEASE`: (仅限维护者)设置后,生成的二进制文件将上传到 GitHub 上
|
||||
|
||||
## 扩展
|
||||
|
||||
|
||||
@@ -79,10 +79,11 @@ sudo make install
|
||||
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 |
|
||||
| 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 |
|
||||
| [Mercure](mercure.md) | [Mercure Go library](https://pkg.go.dev/github.com/dunglas/mercure) (automatically installed, AGPL licensed) | nomercure |
|
||||
|
||||
## Compile the Go App
|
||||
|
||||
@@ -102,8 +103,13 @@ xcaddy build \
|
||||
--output frankenphp \
|
||||
--with github.com/dunglas/frankenphp/caddy \
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
--with github.com/dunglas/vulcain/caddy \
|
||||
--with github.com/dunglas/caddy-cbrotli
|
||||
# Add extra Caddy modules and FrankenPHP extensions here
|
||||
# optionally, if you would like to compile from your frankenphp sources:
|
||||
# --with github.com/dunglas/frankenphp=$(pwd) \
|
||||
# --with github.com/dunglas/frankenphp/caddy=$(pwd)/caddy
|
||||
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
|
||||
112
docs/config.md
112
docs/config.md
@@ -1,17 +1,38 @@
|
||||
# Configuration
|
||||
|
||||
FrankenPHP, Caddy as well as the Mercure and Vulcain modules can be configured using [the formats supported by Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
FrankenPHP, Caddy as well as the [Mercure](mercure.md) and [Vulcain](https://vulcain.rocks) modules can be configured using [the formats supported by Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
|
||||
In [the Docker images](docker.md), the `Caddyfile` is located at `/etc/frankenphp/Caddyfile`.
|
||||
The static binary will also look for the `Caddyfile` in the directory where the `frankenphp run` command is executed.
|
||||
The most common format is the `Caddyfile`, which is a simple, human-readable text format.
|
||||
By default, FrankenPHP will look for a `Caddyfile` in the current directory.
|
||||
You can specify a custom path with the `-c` or `--config` option.
|
||||
|
||||
A minimal `Caddyfile` to serve a PHP application is shown below:
|
||||
|
||||
```caddyfile
|
||||
# The hostname to respond to
|
||||
localhost
|
||||
|
||||
# Optionaly, the directory to serve files from, otherwise defaults to the current directory
|
||||
#root public/
|
||||
php_server
|
||||
```
|
||||
|
||||
A more advanced `Caddyfile` enabling more features and providing convenient environment variables is provided [in the FrankenPHP repository](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile),
|
||||
and with Docker images.
|
||||
|
||||
PHP itself can be configured [using a `php.ini` file](https://www.php.net/manual/en/configuration.file.php).
|
||||
|
||||
Depending on your installation method, the PHP interpreter will look for configuration files in locations described below.
|
||||
Depending on your installation method, FrankenPHP and the PHP interpreter will look for configuration files in locations described below.
|
||||
|
||||
## Docker
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: the main configuration file
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: additional configuration files that are loaded automatically
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/usr/local/etc/php/php.ini` (no `php.ini` is provided by default)
|
||||
- additional configuration files: `/usr/local/etc/php/conf.d/*.ini`
|
||||
- PHP extensions: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
@@ -29,12 +50,24 @@ 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/`
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: the main configuration file
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: additional configuration files that are loaded automatically
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/etc/php-zts/php.ini` (a `php.ini` file with production presets is provided by default)
|
||||
- additional configuration files: `/etc/php-zts/conf.d/*.ini`
|
||||
|
||||
## Static binary
|
||||
|
||||
FrankenPHP:
|
||||
|
||||
- In the current working directory: `Caddyfile`
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: The directory in which `frankenphp run` or `frankenphp php-server` is executed, then `/etc/frankenphp/php.ini`
|
||||
- additional configuration files: `/etc/frankenphp/php.d/*.ini`
|
||||
- PHP extensions: cannot be loaded, bundle them in the binary itself
|
||||
@@ -229,34 +262,6 @@ and otherwise forward the request to the worker matching the path pattern.
|
||||
}
|
||||
```
|
||||
|
||||
### Full Duplex (HTTP/1)
|
||||
|
||||
When using HTTP/1.x, it may be desirable to enable full-duplex mode to allow writing a response before the entire body
|
||||
has been read. (for example: WebSocket, Server-Sent Events, etc.)
|
||||
|
||||
This is an opt-in configuration that needs to be added to the global options in the `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Enabling this option may cause old HTTP/1.x clients that don't support full-duplex to deadlock.
|
||||
> This can also be configured using the `CADDY_GLOBAL_OPTIONS` environment config:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
You can find more information about this setting in the [Caddy documentation](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The following environment variables can be used to inject Caddy directives in the `Caddyfile` without modifying it:
|
||||
@@ -293,6 +298,43 @@ You can also change the PHP configuration using the `php_ini` directive in the `
|
||||
}
|
||||
```
|
||||
|
||||
### Disabling HTTPS
|
||||
|
||||
By default, FrankenPHP will automatically enable HTTPS using for all the hostnames, including `localhost`.
|
||||
If you want to disable HTTPS (for example in a development environment), you can set the `SERVER_NAME` environment variable to `http://` or `:80`:
|
||||
|
||||
Alternatively, you can use all other methods described in the [Caddy documentation](https://caddyserver.com/docs/automatic-https#activation).
|
||||
|
||||
If you want to use HTTPS with the `127.0.0.1` IP address instead of the `localhost` hostname, please read the [known issues](known-issues.md#using-https127001-with-docker) section.
|
||||
|
||||
### Full Duplex (HTTP/1)
|
||||
|
||||
When using HTTP/1.x, it may be desirable to enable full-duplex mode to allow writing a response before the entire body
|
||||
has been read. (for example: [Mercure](mercure.md), WebSocket, Server-Sent Events, etc.)
|
||||
|
||||
This is an opt-in configuration that needs to be added to the global options in the `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Enabling this option may cause old HTTP/1.x clients that don't support full-duplex to deadlock.
|
||||
> This can also be configured using the `CADDY_GLOBAL_OPTIONS` environment config:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
You can find more information about this setting in the [Caddy documentation](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
|
||||
|
||||
## Enable the Debug Mode
|
||||
|
||||
When using the Docker image, set the `CADDY_GLOBAL_OPTIONS` environment variable to `debug` to enable the debug mode:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Building Custom Docker Image
|
||||
|
||||
[FrankenPHP Docker images](https://hub.docker.com/r/dunglas/frankenphp) are based on [official PHP images](https://hub.docker.com/_/php/). Debian and Alpine Linux variants are provided for popular architectures. Debian variants are recommended.
|
||||
[FrankenPHP Docker images](https://hub.docker.com/r/dunglas/frankenphp) are based on [official PHP images](https://hub.docker.com/_/php/).
|
||||
Debian and Alpine Linux variants are provided for popular architectures.
|
||||
Debian variants are recommended.
|
||||
|
||||
Variants for PHP 8.2, 8.3 and 8.4 are provided.
|
||||
Variants for PHP 8.2, 8.3, 8.4 and 8.5 are provided.
|
||||
|
||||
The tags follow this pattern: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
|
||||
@@ -28,6 +30,11 @@ docker build -t my-php-app .
|
||||
docker run -it --rm --name my-running-app my-php-app
|
||||
```
|
||||
|
||||
## How to Tweak the Configuration
|
||||
|
||||
For convenience, [a default `Caddyfile`](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile) containing
|
||||
useful environment variables is provided in the image.
|
||||
|
||||
## How to Install More PHP Extensions
|
||||
|
||||
The [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) script is provided in the base image.
|
||||
|
||||
@@ -54,7 +54,8 @@ The easiest way to create a Linux binary is to use the Docker-based builder we p
|
||||
1. Create a file named `static-build.Dockerfile` in the repository of your app:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# If you intend to run the binary on musl-libc systems, use static-builder-musl instead
|
||||
|
||||
# Copy your app
|
||||
WORKDIR /go/src/app/dist/app
|
||||
|
||||
@@ -33,7 +33,7 @@ As covered in the manual implementation section below as well, you need to [get
|
||||
The first step to writing a PHP extension in Go is to create a new Go module. You can use the following command for this:
|
||||
|
||||
```console
|
||||
go mod init github.com/my-account/my-module
|
||||
go mod init example.com/example
|
||||
```
|
||||
|
||||
The second step is to [get the PHP sources](https://www.php.net/downloads.php) for the next steps. Once you have them, decompress them into the directory of your choice, not inside your Go module:
|
||||
@@ -47,10 +47,15 @@ tar xf php-*
|
||||
Everything is now setup to write your native function in Go. Create a new file named `stringext.go`. Our first function will take a string as an argument, the number of times to repeat it, a boolean to indicate whether to reverse the string, and return the resulting string. This should look like this:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"C"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function repeat_this(string $str, int $count, bool $reverse): string
|
||||
@@ -72,30 +77,34 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
|
||||
There are two important things to note here:
|
||||
|
||||
* A directive comment `//export_php:function` defines the function signature in PHP. This is how the generator knows how to generate the PHP function with the right parameters and return type;
|
||||
* The function must return an `unsafe.Pointer`. FrankenPHP provides an API to help you with type juggling between C and Go.
|
||||
- A directive comment `//export_php:function` defines the function signature in PHP. This is how the generator knows how to generate the PHP function with the right parameters and return type;
|
||||
- The function must return an `unsafe.Pointer`. FrankenPHP provides an API to help you with type juggling between C and Go.
|
||||
|
||||
While the first point speaks for itself, the second may be harder to apprehend. Let's take a deeper dive to type juggling in the next section.
|
||||
|
||||
### Type Juggling
|
||||
|
||||
While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding internals of the Zend Engine and how variables are stored internally in PHP. This table summarizes what you need to know:
|
||||
While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding internals of the Zend Engine and how variables are stored internally in PHP.
|
||||
This table summarizes what you need to know:
|
||||
|
||||
| PHP type | Go type | Direct conversion | C to Go helper | Go to C helper | Class Methods Support |
|
||||
|--------------------|-------------------------------|-------------------|-----------------------------------|------------------------------------|-----------------------|
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ |
|
||||
| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ |
|
||||
| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ |
|
||||
| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ |
|
||||
| `object` | `struct` | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ |
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ |
|
||||
| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ |
|
||||
| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ |
|
||||
| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `callable` | `*C.zval` | ❌ | - | frankenphp.CallPHPCallable() | ❌ |
|
||||
| `object` | `struct` | ❌ | _Not yet implemented_ | _Not yet implemented_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This table is not exhaustive yet and will be completed as the FrankenPHP types API gets more complete.
|
||||
>
|
||||
> For class methods specifically, primitive types and arrays are currently supported. Objects cannot be used as method parameters or return types yet.
|
||||
@@ -113,10 +122,23 @@ If order or association are not needed, it's also possible to directly convert t
|
||||
**Creating and manipulating arrays in Go:**
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
// export_php:function process_data_ordered(array $input): array
|
||||
func process_data_ordered_map(arr *C.zval) unsafe.Pointer {
|
||||
func process_data_ordered_map(arr *C.zend_array) unsafe.Pointer {
|
||||
// Convert PHP associative array to Go while keeping the order
|
||||
associativeArray := frankenphp.GoAssociativeArray(unsafe.Pointer(arr))
|
||||
associativeArray, err := frankenphp.GoAssociativeArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// loop over the entries in order
|
||||
for _, key := range associativeArray.Order {
|
||||
@@ -126,8 +148,8 @@ func process_data_ordered_map(arr *C.zval) unsafe.Pointer {
|
||||
|
||||
// return an ordered array
|
||||
// if 'Order' is not empty, only the key-value pairs in 'Order' will be respected
|
||||
return frankenphp.PHPAssociativeArray(AssociativeArray{
|
||||
Map: map[string]any{
|
||||
return frankenphp.PHPAssociativeArray[string](frankenphp.AssociativeArray[string]{
|
||||
Map: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
@@ -136,10 +158,13 @@ func process_data_ordered_map(arr *C.zval) unsafe.Pointer {
|
||||
}
|
||||
|
||||
// export_php:function process_data_unordered(array $input): array
|
||||
func process_data_unordered_map(arr *C.zval) unsafe.Pointer {
|
||||
func process_data_unordered_map(arr *C.zend_array) unsafe.Pointer {
|
||||
// Convert PHP associative array to a Go map without keeping the order
|
||||
// ignoring the order will be more performant
|
||||
goMap := frankenphp.GoMap(unsafe.Pointer(arr))
|
||||
goMap, err := frankenphp.GoMap[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// loop over the entries in no specific order
|
||||
for key, value := range goMap {
|
||||
@@ -147,16 +172,19 @@ func process_data_unordered_map(arr *C.zval) unsafe.Pointer {
|
||||
}
|
||||
|
||||
// return an unordered array
|
||||
return frankenphp.PHPMap(map[string]any{
|
||||
return frankenphp.PHPMap(map[string]string {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
})
|
||||
}
|
||||
|
||||
// export_php:function process_data_packed(array $input): array
|
||||
func process_data_packed(arr *C.zval) unsafe.Pointer {
|
||||
func process_data_packed(arr *C.zend_array) unsafe.Pointer {
|
||||
// Convert PHP packed array to Go
|
||||
goSlice := frankenphp.GoPackedArray(unsafe.Pointer(arr), false)
|
||||
goSlice, err := frankenphp.GoPackedArray(unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// loop over the slice in order
|
||||
for index, value := range goSlice {
|
||||
@@ -164,32 +192,71 @@ func process_data_packed(arr *C.zval) unsafe.Pointer {
|
||||
}
|
||||
|
||||
// return a packed array
|
||||
return frankenphp.PHPackedArray([]any{"value1", "value2", "value3"})
|
||||
return frankenphp.PHPPackedArray([]string{"value1", "value2", "value3"})
|
||||
}
|
||||
```
|
||||
|
||||
**Key features of array conversion:**
|
||||
|
||||
* **Ordered key-value pairs** - Option to keep the order of the associative array
|
||||
* **Optimized for multiple cases** - Option to ditch the order for better performance or convert straight to a slice
|
||||
* **Automatic list detection** - When converting to PHP, automatically detects if array should be a packed list or hashmap
|
||||
* **Nested Arrays** - Arrays can be nested and will convert all support types automatically (`int64`,`float64`,`string`,`bool`,`nil`,`AssociativeArray`,`map[string]any`,`[]any`)
|
||||
* **Objects are not supported** - Currently, only scalar types and arrays can be used as values. Providing an object will result in a `null` value in the PHP array.
|
||||
- **Ordered key-value pairs** - Option to keep the order of the associative array
|
||||
- **Optimized for multiple cases** - Option to ditch the order for better performance or convert straight to a slice
|
||||
- **Automatic list detection** - When converting to PHP, automatically detects if array should be a packed list or hashmap
|
||||
- **Nested Arrays** - Arrays can be nested and will convert all support types automatically (`int64`,`float64`,`string`,`bool`,`nil`,`AssociativeArray`,`map[string]any`,`[]any`)
|
||||
- **Objects are not supported** - Currently, only scalar types and arrays can be used as values. Providing an object will result in a `null` value in the PHP array.
|
||||
|
||||
##### Available methods: Packed and Associative
|
||||
|
||||
* `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convert to an ordered PHP array with key-value pairs
|
||||
* `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convert a map to an unordered PHP array with key-value pairs
|
||||
* `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convert a slice to a PHP packed array with indexed values only
|
||||
* `frankenphp.GoAssociativeArray(arr unsafe.Pointer, ordered bool) frankenphp.AssociativeArray` - Convert a PHP array to an ordered Go `AssociativeArray` (map with order)
|
||||
* `frankenphp.GoMap(arr unsafe.Pointer) map[string]any` - Convert a PHP array to an unordered Go map
|
||||
* `frankenphp.GoPackedArray(arr unsafe.Pointer) []any` - Convert a PHP array to a Go slice
|
||||
- `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convert to an ordered PHP array with key-value pairs
|
||||
- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convert a map to an unordered PHP array with key-value pairs
|
||||
- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convert a slice to a PHP packed array with indexed values only
|
||||
- `frankenphp.GoAssociativeArray(arr unsafe.Pointer, ordered bool) frankenphp.AssociativeArray` - Convert a PHP array to an ordered Go `AssociativeArray` (map with order)
|
||||
- `frankenphp.GoMap(arr unsafe.Pointer) map[string]any` - Convert a PHP array to an unordered Go map
|
||||
- `frankenphp.GoPackedArray(arr unsafe.Pointer) []any` - Convert a PHP array to a Go slice
|
||||
- `frankenphp.IsPacked(zval *C.zend_array) bool` - Check if a PHP array is packed (indexed only) or associative (key-value pairs)
|
||||
|
||||
### Working with Callables
|
||||
|
||||
FrankenPHP provides a way to work with PHP callables using the `frankenphp.CallPHPCallable` helper. This allows you to call PHP functions or methods from Go code.
|
||||
|
||||
To showcase this, let's create our own `array_map()` function that takes a callable and an array, applies the callable to each element of the array, and returns a new array with the results:
|
||||
|
||||
```go
|
||||
// export_php:function my_array_map(array $data, callable $callback): array
|
||||
func my_array_map(arr *C.zend_array, callback *C.zval) unsafe.Pointer {
|
||||
goSlice, err := frankenphp.GoPackedArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
result := make([]any, len(goSlice))
|
||||
|
||||
for index, value := range goSlice {
|
||||
result[index] = frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value})
|
||||
}
|
||||
|
||||
return frankenphp.PHPPackedArray(result)
|
||||
}
|
||||
```
|
||||
|
||||
Notice how we use `frankenphp.CallPHPCallable()` to call the PHP callable passed as a parameter. This function takes a pointer to the callable and an array of arguments, and it returns the result of the callable execution. You can use the callable syntax you're used to:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$result = my_array_map([1, 2, 3], function($x) { return $x * 2; });
|
||||
// $result will be [2, 4, 6]
|
||||
|
||||
$result = my_array_map(['hello', 'world'], 'strtoupper');
|
||||
// $result will be ['HELLO', 'WORLD']
|
||||
```
|
||||
|
||||
### Declaring a Native PHP Class
|
||||
|
||||
The generator supports declaring **opaque classes** as Go structs, which can be used to create PHP objects. You can use the `//export_php:class` directive comment to define a PHP class. For example:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
//export_php:class User
|
||||
type UserStruct struct {
|
||||
Name string
|
||||
@@ -201,11 +268,11 @@ type UserStruct struct {
|
||||
|
||||
**Opaque classes** are classes where the internal structure (properties) is hidden from PHP code. This means:
|
||||
|
||||
* **No direct property access**: You cannot read or write properties directly from PHP (`$user->name` won't work)
|
||||
* **Method-only interface** - All interactions must go through methods you define
|
||||
* **Better encapsulation** - Internal data structure is completely controlled by Go code
|
||||
* **Type safety** - No risk of PHP code corrupting internal state with wrong types
|
||||
* **Cleaner API** - Forces to design a proper public interface
|
||||
- **No direct property access**: You cannot read or write properties directly from PHP (`$user->name` won't work)
|
||||
- **Method-only interface** - All interactions must go through methods you define
|
||||
- **Better encapsulation** - Internal data structure is completely controlled by Go code
|
||||
- **Type safety** - No risk of PHP code corrupting internal state with wrong types
|
||||
- **Cleaner API** - Forces to design a proper public interface
|
||||
|
||||
This approach provides better encapsulation and prevents PHP code from accidentally corrupting the internal state of your Go objects. All interactions with the object must go through the methods you explicitly define.
|
||||
|
||||
@@ -214,6 +281,16 @@ This approach provides better encapsulation and prevents PHP code from accidenta
|
||||
Since properties are not directly accessible, you **must define methods** to interact with your opaque classes. Use the `//export_php:method` directive to define behavior:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:class User
|
||||
type UserStruct struct {
|
||||
Name string
|
||||
@@ -246,18 +323,28 @@ func (us *UserStruct) SetNamePrefix(prefix *C.zend_string) {
|
||||
The generator supports nullable parameters using the `?` prefix in PHP signatures. When a parameter is nullable, it becomes a pointer in your Go function, allowing you to check if the value was `null` in PHP:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:method User::updateInfo(?string $name, ?int $age, ?bool $active): void
|
||||
func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool) {
|
||||
// Check if name was provided (not null)
|
||||
if name != nil {
|
||||
us.Name = frankenphp.GoString(unsafe.Pointer(name))
|
||||
}
|
||||
|
||||
|
||||
// Check if age was provided (not null)
|
||||
if age != nil {
|
||||
us.Age = int(*age)
|
||||
}
|
||||
|
||||
|
||||
// Check if active was provided (not null)
|
||||
if active != nil {
|
||||
us.Active = *active
|
||||
@@ -267,12 +354,13 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
|
||||
**Key points about nullable parameters:**
|
||||
|
||||
* **Nullable primitive types** (`?int`, `?float`, `?bool`) become pointers (`*int64`, `*float64`, `*bool`) in Go
|
||||
* **Nullable strings** (`?string`) remain as `*C.zend_string` but can be `nil`
|
||||
* **Check for `nil`** before dereferencing pointer values
|
||||
* **PHP `null` becomes Go `nil`** - when PHP passes `null`, your Go function receives a `nil` pointer
|
||||
- **Nullable primitive types** (`?int`, `?float`, `?bool`) become pointers (`*int64`, `*float64`, `*bool`) in Go
|
||||
- **Nullable strings** (`?string`) remain as `*C.zend_string` but can be `nil`
|
||||
- **Check for `nil`** before dereferencing pointer values
|
||||
- **PHP `null` becomes Go `nil`** - when PHP passes `null`, your Go function receives a `nil` pointer
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> Currently, class methods have the following limitations. **Objects are not supported** as parameter types or return types. **Arrays are fully supported** for both parameters and return types. Supported types: `string`, `int`, `float`, `bool`, `array`, and `void` (for return type). **Nullable parameter types are fully supported** for all scalar types (`?string`, `?int`, `?float`, `?bool`).
|
||||
|
||||
After generating the extension, you will be allowed to use the class and its methods in PHP. Note that you **cannot access properties directly**:
|
||||
@@ -302,13 +390,15 @@ This design ensures that your Go code has complete control over how the object's
|
||||
|
||||
### Declaring Constants
|
||||
|
||||
The generator supports exporting Go constants to PHP using two directives: `//export_php:const` for global constants and `//export_php:classconstant` for class constants. This allows you to share configuration values, status codes, and other constants between Go and PHP code.
|
||||
The generator supports exporting Go constants to PHP using two directives: `//export_php:const` for global constants and `//export_php:classconst` for class constants. This allows you to share configuration values, status codes, and other constants between Go and PHP code.
|
||||
|
||||
#### Global Constants
|
||||
|
||||
Use the `//export_php:const` directive to create global PHP constants:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
//export_php:const
|
||||
const MAX_CONNECTIONS = 100
|
||||
|
||||
@@ -324,25 +414,27 @@ const STATUS_ERROR = iota
|
||||
|
||||
#### Class Constants
|
||||
|
||||
Use the `//export_php:classconstant ClassName` directive to create constants that belong to a specific PHP class:
|
||||
Use the `//export_php:classconst ClassName` directive to create constants that belong to a specific PHP class:
|
||||
|
||||
```go
|
||||
//export_php:classconstant User
|
||||
package example
|
||||
|
||||
//export_php:classconst User
|
||||
const STATUS_ACTIVE = 1
|
||||
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const STATUS_INACTIVE = 0
|
||||
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const ROLE_ADMIN = "admin"
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_PENDING = iota
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_PROCESSING = iota
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_COMPLETED = iota
|
||||
```
|
||||
|
||||
@@ -366,10 +458,15 @@ The directive supports various value types including strings, integers, booleans
|
||||
You can use constants just like you are used to in the Go code. For example, let's take the `repeat_this()` function we declared earlier and change the last argument to an integer:
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"C"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"strings"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:const
|
||||
@@ -378,45 +475,45 @@ const STR_REVERSE = iota
|
||||
//export_php:const
|
||||
const STR_NORMAL = iota
|
||||
|
||||
//export_php:classconstant StringProcessor
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_LOWERCASE = 1
|
||||
|
||||
//export_php:classconstant StringProcessor
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_UPPERCASE = 2
|
||||
|
||||
//export_php:function repeat_this(string $str, int $count, int $mode): string
|
||||
func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
// reverse the string
|
||||
}
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
// reverse the string
|
||||
}
|
||||
|
||||
if mode == STR_NORMAL {
|
||||
// no-op, just to showcase the constant
|
||||
}
|
||||
if mode == STR_NORMAL {
|
||||
// no-op, just to showcase the constant
|
||||
}
|
||||
|
||||
return frankenphp.PHPString(result, false)
|
||||
return frankenphp.PHPString(result, false)
|
||||
}
|
||||
|
||||
//export_php:class StringProcessor
|
||||
type StringProcessorStruct struct {
|
||||
// internal fields
|
||||
// internal fields
|
||||
}
|
||||
|
||||
//export_php:method StringProcessor::process(string $input, int $mode): string
|
||||
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
|
||||
return frankenphp.PHPString(str, false)
|
||||
str := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
|
||||
return frankenphp.PHPString(str, false)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -430,9 +527,13 @@ Use the `//export_php:namespace` directive at the top of your Go file to place a
|
||||
|
||||
```go
|
||||
//export_php:namespace My\Extension
|
||||
package main
|
||||
package example
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function hello(): string
|
||||
func hello() string {
|
||||
@@ -470,17 +571,17 @@ echo My\Extension\STATUS_ACTIVE; // 1
|
||||
|
||||
#### Important Notes
|
||||
|
||||
* Only **one** namespace directive is allowed per file. If multiple namespace directives are found, the generator will return an error.
|
||||
* The namespace applies to **all** exported symbols in the file: functions, classes, methods, and constants.
|
||||
* Namespace names follow PHP namespace conventions using backslashes (`\`) as separators.
|
||||
* If no namespace is declared, symbols are exported to the global namespace as usual.
|
||||
- Only **one** namespace directive is allowed per file. If multiple namespace directives are found, the generator will return an error.
|
||||
- The namespace applies to **all** exported symbols in the file: functions, classes, methods, and constants.
|
||||
- Namespace names follow PHP namespace conventions using backslashes (`\`) as separators.
|
||||
- If no namespace is declared, symbols are exported to the global namespace as usual.
|
||||
|
||||
### Generating the Extension
|
||||
|
||||
This is where the magic happens, and your extension can now be generated. You can run the generator with the following command:
|
||||
|
||||
```console
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -535,25 +636,26 @@ We'll see how to write a simple PHP extension in Go that defines a new native fu
|
||||
In your module, you need to define a new native function that will be called from PHP. To do this, create a file with the name you want, for example, `extension.go`, and add the following code:
|
||||
|
||||
```go
|
||||
package ext_go
|
||||
package example
|
||||
|
||||
//#include "extension.h"
|
||||
// #include "extension.h"
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"log/slog"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry))
|
||||
frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry))
|
||||
}
|
||||
|
||||
//export go_print_something
|
||||
func go_print_something() {
|
||||
go func() {
|
||||
caddy.Log().Info("Hello from a goroutine!")
|
||||
}()
|
||||
go func() {
|
||||
slog.Info("Hello from a goroutine!")
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -600,9 +702,9 @@ extern zend_module_entry ext_module_entry;
|
||||
|
||||
Next, create a file named `extension.c` that will perform the following steps:
|
||||
|
||||
* Include PHP headers;
|
||||
* Declare our new native PHP function `go_print()`;
|
||||
* Declare the extension metadata.
|
||||
- Include PHP headers;
|
||||
- Declare our new native PHP function `go_print()`;
|
||||
- Declare the extension metadata.
|
||||
|
||||
Let's start by including the required headers:
|
||||
|
||||
@@ -729,21 +831,33 @@ There's only one thing left to do: implement the `go_upper` function in Go.
|
||||
Our Go function will take a `*C.zend_string` as a parameter, convert it to a Go string using FrankenPHP's helper function, process it, and return the result as a new `*C.zend_string`. The helper functions handle all the memory management and conversion complexity for us.
|
||||
|
||||
```go
|
||||
import "strings"
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
"strings"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export go_upper
|
||||
func go_upper(s *C.zend_string) *C.zend_string {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
|
||||
upper := strings.ToUpper(str)
|
||||
|
||||
|
||||
return (*C.zend_string)(frankenphp.PHPString(upper, false))
|
||||
}
|
||||
```
|
||||
|
||||
This approach is much cleaner and safer than manual memory management. FrankenPHP's helper functions handle the conversion between PHP's `zend_string` format and Go strings automatically. The `false` parameter in `PHPString()` indicates that we want to create a new non-persistent string (freed at the end of the request).
|
||||
This approach is much cleaner and safer than manual memory management.
|
||||
FrankenPHP's helper functions handle the conversion between PHP's `zend_string` format and Go strings automatically.
|
||||
The `false` parameter in `PHPString()` indicates that we want to create a new non-persistent string (freed at the end of the request).
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> In this example, we don't perform any error handling, but you should always check that pointers are not `nil` and that the data is valid before using it in your Go functions.
|
||||
|
||||
### Integrating the Extension into FrankenPHP
|
||||
|
||||
@@ -16,30 +16,63 @@ Découvrez plus de détails sur ce serveur d’application dans le replay de cet
|
||||
|
||||
## Pour Commencer
|
||||
|
||||
### Binaire autonome
|
||||
Sur Windows, utilisez [WSL](https://learn.microsoft.com/windows/wsl/) pour exécuter FrankenPHP.
|
||||
|
||||
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.
|
||||
### Script d'installation
|
||||
|
||||
Sous Windows, utilisez [WSL](https://learn.microsoft.com/windows/wsl/) pour exécuter FrankenPHP.
|
||||
|
||||
[Téléchargez FrankenPHP](https://github.com/php/frankenphp/releases) ou copiez cette ligne dans votre terminal pour installer automatiquement la version appropriée à votre plateforme :
|
||||
Vous pouvez copier cette ligne dans votre terminal pour installer automatiquement
|
||||
une version adaptée à votre plateforme :
|
||||
|
||||
```console
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
mv frankenphp /usr/local/bin/
|
||||
```
|
||||
|
||||
Pour servir le contenu du répertoire courant, exécutez :
|
||||
### Binaire autonome
|
||||
|
||||
Nous fournissons des binaires statiques de FrankenPHP pour le développement, pour Linux et macOS,
|
||||
contenant [PHP 8.4](https://www.php.net/releases/8.4/fr.php) et la plupart des extensions PHP populaires.
|
||||
|
||||
[Télécharger FrankenPHP](https://github.com/php/frankenphp/releases)
|
||||
|
||||
**Installation d'extensions :** Les extensions les plus courantes sont incluses. Il n'est pas possible d'en installer davantage.
|
||||
|
||||
### Paquets rpm
|
||||
|
||||
Nos mainteneurs proposent des paquets rpm pour tous les systèmes utilisant `dnf`. Pour installer, exécutez :
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
sudo dnf install https://rpm.henderkes.com/static-php-1-0.noarch.rpm
|
||||
sudo dnf module enable php-zts:static-8.4 # 8.2-8.5 disponibles
|
||||
sudo dnf install frankenphp
|
||||
```
|
||||
|
||||
Vous pouvez également exécuter des scripts en ligne de commande avec :
|
||||
**Installation d'extensions :** `sudo dnf install php-zts-<extension>`
|
||||
|
||||
Pour les extensions non disponibles par défaut, utilisez [PIE](https://github.com/php/pie) :
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
sudo dnf install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### Paquets deb
|
||||
|
||||
Nos mainteneurs proposent des paquets deb pour tous les systèmes utilisant `apt`. Pour installer, exécutez :
|
||||
|
||||
```console
|
||||
sudo curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg && \
|
||||
echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | sudo tee /etc/apt/sources.list.d/static-php.list && \
|
||||
sudo apt update
|
||||
sudo apt install frankenphp
|
||||
```
|
||||
|
||||
**Installation d'extensions :** `sudo apt install php-zts-<extension>`
|
||||
|
||||
Pour les extensions non disponibles par défaut, utilisez [PIE](https://github.com/php/pie) :
|
||||
|
||||
```console
|
||||
sudo apt install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### Docker
|
||||
@@ -69,12 +102,28 @@ Pour l'installer :
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
**Installation d'extensions :** Utilisez [PIE](https://github.com/php/pie).
|
||||
|
||||
### Utilisation
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Pour les paquets deb et rpm, vous pouvez aussi démarrer le service systemd :
|
||||
|
||||
```console
|
||||
sudo systemctl start frankenphp
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Le mode classique](classic.md)
|
||||
|
||||
@@ -82,7 +82,7 @@ Certaines fonctionnalités de FrankenPHP nécessitent des dépendances optionnel
|
||||
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 |
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 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).
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
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, 8.3 et 8.4 sont disponibles. [Parcourir les tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
Des variantes pour PHP 8.2, 8.3, 8.4 et 8.5 sont disponibles. [Parcourir les tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
Les tags suivent le pattern suivant: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
|
||||
|
||||
@@ -56,13 +56,13 @@ La manière la plus simple de créer un binaire Linux est d'utiliser le builder
|
||||
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
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# Si vous envisagez d'exécuter le binaire sur des systèmes musl-libc, utilisez plutôt static-builder-musl
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
@@ -47,10 +47,15 @@ tar xf php-*
|
||||
Tout est maintenant configuré pour écrire votre fonction native en Go. Créez un nouveau fichier nommé `stringext.go`. Notre première fonction prendra une chaîne comme argument, le nombre de fois à la répéter, un booléen pour indiquer s'il faut inverser la chaîne, et retournera la chaîne résultante. Cela devrait ressembler à ceci :
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"C"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function repeat_this(string $str, int $count, bool $reverse): string
|
||||
@@ -72,26 +77,29 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
|
||||
Il y a deux choses importantes à noter ici :
|
||||
|
||||
* Une directive `//export_php:function` définit la signature de la fonction en PHP. C'est ainsi que le générateur sait comment générer la fonction PHP avec les bons paramètres et le bon type de retour ;
|
||||
* La fonction doit retourner un `unsafe.Pointer`. FrankenPHP fournit une API pour vous aider avec le jonglage de types entre C et Go.
|
||||
- Une directive `//export_php:function` définit la signature de la fonction en PHP. C'est ainsi que le générateur sait comment générer la fonction PHP avec les bons paramètres et le bon type de retour ;
|
||||
- La fonction doit retourner un `unsafe.Pointer`. FrankenPHP fournit une API pour vous aider avec le jonglage de types entre C et Go.
|
||||
|
||||
Alors que le premier point parle de lui-même, le second peut être plus difficile à appréhender. Plongeons plus profondément dans la jonglage de types dans la section suivante.
|
||||
Alors que le premier point parle de lui-même, le second peut être plus difficile à appréhender. Plongeons plus profondément dans le jonglage de types dans la section suivante.
|
||||
|
||||
### Jonglage de Types
|
||||
|
||||
Bien que certains types de variables aient la même représentation mémoire entre C/PHP et Go, certains types nécessitent plus de logique pour être directement utilisés. C'est peut-être la partie la plus difficile quand il s'agit d'écrire des extensions car cela nécessite de comprendre les fonctionnements internes du moteur Zend et comment les variables sont stockées dans le moteur de PHP. Ce tableau résume ce que vous devez savoir :
|
||||
|
||||
| Type PHP | Type Go | Conversion directe | Assistant C vers Go | Assistant Go vers C | Support des Méthodes de Classe |
|
||||
|--------------------|---------------------|--------------------|-------------------------|-------------------------|--------------------------------|
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `*frankenphp.Array` | ❌ | frankenphp.GoArray() | frankenphp.PHPArray() | ✅ |
|
||||
| `object` | `struct` | ❌ | _Pas encore implémenté_ | _Pas encore implémenté_ | ❌ |
|
||||
| Type PHP | Type Go | Conversion directe | Assistant C vers Go | Assistant Go vers C | Support des Méthodes de Classe |
|
||||
| ------------------ | ----------------------------- | ------------------ | --------------------------------- | ---------------------------------- | ------------------------------ |
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ |
|
||||
| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ |
|
||||
| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `object` | `struct` | ❌ | _Pas encore implémenté_ | _Pas encore implémenté_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
> Ce tableau n'est pas encore exhaustif et sera complété au fur et à mesure que l'API de types FrankenPHP deviendra plus complète.
|
||||
@@ -102,61 +110,150 @@ Si vous vous référez à l'extrait de code de la section précédente, vous pou
|
||||
|
||||
#### Travailler avec les Tableaux
|
||||
|
||||
FrankenPHP fournit un support natif pour les tableaux PHP à travers le type `frankenphp.Array`. Ce type représente à la fois les tableaux indexés PHP (listes) et les tableaux associatifs (hashmaps) avec des paires clé-valeur ordonnées.
|
||||
FrankenPHP fournit un support natif pour les tableaux PHP à travers `frankenphp.AssociativeArray` ou une conversion directe vers une map ou un slice.
|
||||
|
||||
`AssociativeArray` représente une [hash map](https://fr.wikipedia.org/wiki/Table_de_hachage) composée d'un champ `Map: map[string]any` et d'un champ optionnel `Order: []string` (contrairement aux "tableaux associatifs" PHP, les maps Go ne sont pas ordonnées).
|
||||
|
||||
Si l'ordre ou l'association ne sont pas nécessaires, il est également possible de convertir directement vers un slice `[]any` ou une map non ordonnée `map[string]any`.
|
||||
|
||||
**Créer et manipuler des tableaux en Go :**
|
||||
|
||||
```go
|
||||
//export_php:function process_data(array $input): array
|
||||
func process_data(arr *C.zval) unsafe.Pointer {
|
||||
// Convertir le tableau PHP vers Go
|
||||
goArray := frankenphp.GoArray(unsafe.Pointer(arr))
|
||||
|
||||
result := &frankenphp.Array{}
|
||||
|
||||
result.SetInt(0, "first")
|
||||
result.SetInt(1, "second")
|
||||
result.Append("third") // Assigne automatiquement la prochaine clé entière
|
||||
|
||||
result.SetString("name", "John")
|
||||
result.SetString("age", int64(30))
|
||||
|
||||
for i := uint32(0); i < goArray.Len(); i++ {
|
||||
key, value := goArray.At(i)
|
||||
if key.Type == frankenphp.PHPStringKey {
|
||||
result.SetString("processed_"+key.Str, value)
|
||||
} else {
|
||||
result.SetInt(key.Int+100, value)
|
||||
}
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
// export_php:function process_data_ordered(array $input): array
|
||||
func process_data_ordered_map(arr *C.zval) unsafe.Pointer {
|
||||
// Convertir le tableau associatif PHP vers Go en conservant l'ordre
|
||||
associativeArray, err := frankenphp.GoAssociativeArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// gérer l'erreur
|
||||
}
|
||||
|
||||
// Reconvertir vers un tableau PHP
|
||||
return frankenphp.PHPArray(result)
|
||||
|
||||
// parcourir les entrées dans l'ordre
|
||||
for _, key := range associativeArray.Order {
|
||||
value, _ = associativeArray.Map[key]
|
||||
// faire quelque chose avec key et value
|
||||
}
|
||||
|
||||
// retourner un tableau ordonné
|
||||
// si 'Order' n'est pas vide, seules les paires clé-valeur dans 'Order' seront respectées
|
||||
return frankenphp.PHPAssociativeArray[string](frankenphp.AssociativeArray[string]{
|
||||
Map: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
Order: []string{"key1", "key2"},
|
||||
})
|
||||
}
|
||||
|
||||
// export_php:function process_data_unordered(array $input): array
|
||||
func process_data_unordered_map(arr *C.zval) unsafe.Pointer {
|
||||
// Convertir le tableau associatif PHP vers une map Go sans conserver l'ordre
|
||||
// ignorer l'ordre sera plus performant
|
||||
goMap, err := frankenphp.GoMap[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// gérer l'erreur
|
||||
}
|
||||
|
||||
// parcourir les entrées sans ordre spécifique
|
||||
for key, value := range goMap {
|
||||
// faire quelque chose avec key et value
|
||||
}
|
||||
|
||||
// retourner un tableau non ordonné
|
||||
return frankenphp.PHPMap(map[string]string {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
})
|
||||
}
|
||||
|
||||
// export_php:function process_data_packed(array $input): array
|
||||
func process_data_packed(arr *C.zval) unsafe.Pointer {
|
||||
// Convertir le tableau packed PHP vers Go
|
||||
goSlice, err := frankenphp.GoPackedArray(unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// gérer l'erreur
|
||||
}
|
||||
|
||||
// parcourir le slice dans l'ordre
|
||||
for index, value := range goSlice {
|
||||
// faire quelque chose avec index et value
|
||||
}
|
||||
|
||||
// retourner un tableau packed
|
||||
return frankenphp.PHPPackedArray([]string{"value1", "value2", "value3"})
|
||||
}
|
||||
```
|
||||
|
||||
**Fonctionnalités clés de `frankenphp.Array` :**
|
||||
**Fonctionnalités clés de la conversion de tableaux :**
|
||||
|
||||
* **Paires clé-valeur ordonnées** - Maintient l'ordre d'insertion comme les tableaux PHP
|
||||
* **Types de clés mixtes** - Supporte les clés entières et chaînes dans le même tableau
|
||||
* **Sécurité de type** - Le type `PHPKey` assure une gestion appropriée des clés
|
||||
* **Détection automatique de liste** - Lors de la conversion vers PHP, détecte automatiquement si le tableau doit être une liste compacte ou un hashmap
|
||||
* **Les objets ne sont pas supportés** - Actuellement, seuls les types scalaires et les tableaux sont supportés. Passer un objet en tant qu'élément du tableau résultera d'une valeur `null` dans le tableau PHP.
|
||||
- **Paires clé-valeur ordonnées** - Option pour conserver l'ordre du tableau associatif
|
||||
- **Optimisé pour plusieurs cas** - Option de ne pas conserver l'ordre pour de meilleures performances ou conversion directe vers un slice
|
||||
- **Détection automatique de liste** - Lors de la conversion vers PHP, détecte automatiquement si le tableau doit être une liste packed ou un hashmap
|
||||
- **Tableaux imbriqués** - Les tableaux peuvent être imbriqués et convertiront automatiquement tous les types supportés (`int64`, `float64`, `string`, `bool`, `nil`, `AssociativeArray`, `map[string]any`, `[]any`)
|
||||
- **Les objets ne sont pas supportés** - Actuellement, seuls les types scalaires et les tableaux peuvent être utilisés comme valeurs. Fournir un objet résultera en une valeur `null` dans le tableau PHP.
|
||||
|
||||
**Méthodes disponibles :**
|
||||
##### Méthodes disponibles : Packed et Associatif
|
||||
|
||||
* `SetInt(key int64, value interface{})` - Définir une valeur avec une clé entière
|
||||
* `SetString(key string, value interface{})` - Définir une valeur avec une clé chaîne
|
||||
* `Append(value interface{})` - Ajouter une valeur avec la prochaine clé entière disponible
|
||||
* `Len() uint32` - Obtenir le nombre d'éléments
|
||||
* `At(index uint32) (PHPKey, interface{})` - Obtenir la paire clé-valeur à l'index
|
||||
* `frankenphp.PHPArray(arr *frankenphp.Array) unsafe.Pointer` - Convertir vers un tableau PHP
|
||||
- `frankenphp.PHPAssociativeArray(arr frankenphp.AssociativeArray) unsafe.Pointer` - Convertir vers un tableau PHP ordonné avec des paires clé-valeur
|
||||
- `frankenphp.PHPMap(arr map[string]any) unsafe.Pointer` - Convertir une map vers un tableau PHP non ordonné avec des paires clé-valeur
|
||||
- `frankenphp.PHPPackedArray(slice []any) unsafe.Pointer` - Convertir un slice vers un tableau PHP packed avec uniquement des valeurs indexées
|
||||
- `frankenphp.GoAssociativeArray(arr unsafe.Pointer, ordered bool) frankenphp.AssociativeArray` - Convertir un tableau PHP vers un `AssociativeArray` Go ordonné (map avec ordre)
|
||||
- `frankenphp.GoMap(arr unsafe.Pointer) map[string]any` - Convertir un tableau PHP vers une map Go non ordonnée
|
||||
- `frankenphp.GoPackedArray(arr unsafe.Pointer) []any` - Convertir un tableau PHP vers un slice Go
|
||||
- `frankenphp.IsPacked(zval *C.zend_array) bool` - Vérifie si le tableau PHP est une liste ou un tableau associatif
|
||||
|
||||
### Travailler avec des Callables
|
||||
|
||||
FrankenPHP propose un moyen de travailler avec les _callables_ PHP grâce au helper `frankenphp.CallPHPCallable()`. Cela permet d'appeler des fonctions ou des méthodes PHP depuis du code Go.
|
||||
|
||||
Pour illustrer cela, créons notre propre fonction `array_map()` qui prend un _callable_ et un tableau, applique le _callable_ à chaque élément du tableau, et retourne un nouveau tableau avec les résultats :
|
||||
|
||||
```go
|
||||
// export_php:function my_array_map(array $data, callable $callback): array
|
||||
func my_array_map(arr *C.zend_array, callback *C.zval) unsafe.Pointer {
|
||||
goSlice, err := frankenphp.GoPackedArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
result := make([]any, len(goSlice))
|
||||
|
||||
for index, value := range goSlice {
|
||||
result[index] = frankenphp.CallPHPCallable(unsafe.Pointer(callback), []interface{}{value})
|
||||
}
|
||||
|
||||
return frankenphp.PHPPackedArray(result)
|
||||
}
|
||||
```
|
||||
|
||||
Remarquez comment nous utilisons `frankenphp.CallPHPCallable()` pour appeler le _callable_ PHP passé en paramètre. Cette fonction prend un pointeur vers le _callable_ et un tableau d'arguments, et elle retourne le résultat de l'exécution du _callable_. Vous pouvez utiliser la syntaxe habituelle des _callables_ :
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$result = my_array_map([1, 2, 3], function($x) { return $x * 2; });
|
||||
// $result vaudra [2, 4, 6]
|
||||
|
||||
$result = my_array_map(['hello', 'world'], 'strtoupper');
|
||||
// $result vaudra ['HELLO', 'WORLD']
|
||||
```
|
||||
|
||||
### Déclarer une Classe PHP Native
|
||||
|
||||
Le générateur prend en charge la déclaration de **classes opaques** comme structures Go, qui peuvent être utilisées pour créer des objets PHP. Vous pouvez utiliser la directive `//export_php:class` pour définir une classe PHP. Par exemple :
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
//export_php:class User
|
||||
type UserStruct struct {
|
||||
Name string
|
||||
@@ -168,11 +265,11 @@ type UserStruct struct {
|
||||
|
||||
Les **classes opaques** sont des classes avec lesquelles la structure interne (comprendre : les propriétés) est cachée du code PHP. Cela signifie :
|
||||
|
||||
* **Pas d'accès direct aux propriétés** : Vous ne pouvez pas lire ou écrire des propriétés directement depuis PHP (`$user->name` ne fonctionnera pas)
|
||||
* **Interface uniquement par méthodes** - Toutes les interactions doivent passer par les méthodes que vous définissez
|
||||
* **Meilleure encapsulation** - La structure de données interne est complètement contrôlée par le code Go
|
||||
* **Sécurité de type** - Aucun risque que le code PHP corrompe l'état interne avec de mauvais types
|
||||
* **API plus propre** - Force à concevoir une interface publique appropriée
|
||||
- **Pas d'accès direct aux propriétés** : Vous ne pouvez pas lire ou écrire des propriétés directement depuis PHP (`$user->name` ne fonctionnera pas)
|
||||
- **Interface uniquement par méthodes** - Toutes les interactions doivent passer par les méthodes que vous définissez
|
||||
- **Meilleure encapsulation** - La structure de données interne est complètement contrôlée par le code Go
|
||||
- **Sécurité de type** - Aucun risque que le code PHP corrompe l'état interne avec de mauvais types
|
||||
- **API plus propre** - Force à concevoir une interface publique appropriée
|
||||
|
||||
Cette approche fournit une meilleure encapsulation et empêche le code PHP de corrompre accidentellement l'état interne de vos objets Go. Toutes les interactions avec l'objet doivent passer par les méthodes que vous définissez explicitement.
|
||||
|
||||
@@ -181,6 +278,16 @@ Cette approche fournit une meilleure encapsulation et empêche le code PHP de co
|
||||
Puisque les propriétés ne sont pas directement accessibles, vous **devez définir des méthodes** pour interagir avec vos classes opaques. Utilisez la directive `//export_php:method` pour définir cela :
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:class User
|
||||
type UserStruct struct {
|
||||
Name string
|
||||
@@ -213,19 +320,29 @@ func (us *UserStruct) SetNamePrefix(prefix *C.zend_string) {
|
||||
Le générateur prend en charge les paramètres nullables en utilisant le préfixe `?` dans les signatures PHP. Quand un paramètre est nullable, il devient un pointeur dans votre fonction Go, vous permettant de vérifier si la valeur était `null` en PHP :
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:method User::updateInfo(?string $name, ?int $age, ?bool $active): void
|
||||
func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool) {
|
||||
// $name est null?
|
||||
// Vérifier si name a été fourni (pas null)
|
||||
if name != nil {
|
||||
us.Name = frankenphp.GoString(unsafe.Pointer(name))
|
||||
}
|
||||
|
||||
// $age est null?
|
||||
|
||||
// Vérifier si age a été fourni (pas null)
|
||||
if age != nil {
|
||||
us.Age = int(*age)
|
||||
}
|
||||
|
||||
// $active est null?
|
||||
|
||||
// Vérifier si active a été fourni (pas null)
|
||||
if active != nil {
|
||||
us.Active = *active
|
||||
}
|
||||
@@ -234,10 +351,10 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
|
||||
**Points clés sur les paramètres nullables :**
|
||||
|
||||
* **Types primitifs nullables** (`?int`, `?float`, `?bool`) deviennent des pointeurs (`*int64`, `*float64`, `*bool`) en Go
|
||||
* **Chaînes nullables** (`?string`) restent comme `*C.zend_string` mais peuvent être `nil`
|
||||
* **Vérifiez `nil`** avant de déréférencer les valeurs de pointeur
|
||||
* **PHP `null` devient Go `nil`** - quand PHP passe `null`, votre fonction Go reçoit un pointeur `nil`
|
||||
- **Types primitifs nullables** (`?int`, `?float`, `?bool`) deviennent des pointeurs (`*int64`, `*float64`, `*bool`) en Go
|
||||
- **Chaînes nullables** (`?string`) restent comme `*C.zend_string` mais peuvent être `nil`
|
||||
- **Vérifiez `nil`** avant de déréférencer les valeurs de pointeur
|
||||
- **PHP `null` devient Go `nil`** - quand PHP passe `null`, votre fonction Go reçoit un pointeur `nil`
|
||||
|
||||
> [!WARNING]
|
||||
> Actuellement, les méthodes de classe ont les limitations suivantes. **Les objets ne sont pas supportés** comme types de paramètres ou types de retour. **Les tableaux sont entièrement supportés** pour les paramètres et types de retour. Types supportés : `string`, `int`, `float`, `bool`, `array`, et `void` (pour le type de retour). **Les types de paramètres nullables sont entièrement supportés** pour tous les types scalaires (`?string`, `?int`, `?float`, `?bool`).
|
||||
@@ -269,13 +386,15 @@ Cette conception garantit que votre code Go a un contrôle complet sur la façon
|
||||
|
||||
### Déclarer des Constantes
|
||||
|
||||
Le générateur prend en charge l'exportation de constantes Go vers PHP en utilisant deux directives : `//export_php:const` pour les constantes globales et `//export_php:classconstant` pour les constantes de classe. Cela vous permet de partager des valeurs de configuration, des codes de statut et d'autres constantes entre le code Go et PHP.
|
||||
Le générateur prend en charge l'exportation de constantes Go vers PHP en utilisant deux directives : `//export_php:const` pour les constantes globales et `//export_php:classconst` pour les constantes de classe. Cela vous permet de partager des valeurs de configuration, des codes de statut et d'autres constantes entre le code Go et PHP.
|
||||
|
||||
#### Constantes Globales
|
||||
|
||||
Utilisez la directive `//export_php:const` pour créer des constantes PHP globales :
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
//export_php:const
|
||||
const MAX_CONNECTIONS = 100
|
||||
|
||||
@@ -291,25 +410,27 @@ const STATUS_ERROR = iota
|
||||
|
||||
#### Constantes de Classe
|
||||
|
||||
Utilisez la directive `//export_php:classconstant ClassName` pour créer des constantes qui appartiennent à une classe PHP spécifique :
|
||||
Utilisez la directive `//export_php:classconst ClassName` pour créer des constantes qui appartiennent à une classe PHP spécifique :
|
||||
|
||||
```go
|
||||
//export_php:classconstant User
|
||||
package example
|
||||
|
||||
//export_php:classconst User
|
||||
const STATUS_ACTIVE = 1
|
||||
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const STATUS_INACTIVE = 0
|
||||
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const ROLE_ADMIN = "admin"
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_PENDING = iota
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_PROCESSING = iota
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_COMPLETED = iota
|
||||
```
|
||||
|
||||
@@ -333,10 +454,15 @@ La directive prend en charge divers types de valeurs incluant les chaînes, enti
|
||||
Vous pouvez utiliser les constantes comme vous êtes habitué dans le code Go. Par exemple, prenons la fonction `repeat_this()` que nous avons déclarée plus tôt et changeons le dernier argument en entier :
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"C"
|
||||
"github.com/dunglas/frankenphp"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:const
|
||||
@@ -345,10 +471,10 @@ const STR_REVERSE = iota
|
||||
//export_php:const
|
||||
const STR_NORMAL = iota
|
||||
|
||||
//export_php:classconstant StringProcessor
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_LOWERCASE = 1
|
||||
|
||||
//export_php:classconstant StringProcessor
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_UPPERCASE = 2
|
||||
|
||||
//export_php:function repeat_this(string $str, int $count, int $mode): string
|
||||
@@ -356,7 +482,7 @@ func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
if mode == STR_REVERSE {
|
||||
// inverser la chaîne
|
||||
}
|
||||
|
||||
@@ -375,14 +501,14 @@ type StringProcessorStruct struct {
|
||||
//export_php:method StringProcessor::process(string $input, int $mode): string
|
||||
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
|
||||
|
||||
return frankenphp.PHPString(str, false)
|
||||
}
|
||||
```
|
||||
@@ -397,9 +523,13 @@ Utilisez la directive `//export_php:namespace` en haut de votre fichier Go pour
|
||||
|
||||
```go
|
||||
//export_php:namespace My\Extension
|
||||
package main
|
||||
package example
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export_php:function hello(): string
|
||||
func hello() string {
|
||||
@@ -437,17 +567,17 @@ echo My\Extension\STATUS_ACTIVE; // 1
|
||||
|
||||
#### Notes Importantes
|
||||
|
||||
* Seule **une** directive d'espace de noms est autorisée par fichier. Si plusieurs directives d'espace de noms sont trouvées, le générateur retournera une erreur.
|
||||
* L'espace de noms s'applique à **tous** les symboles exportés dans le fichier : fonctions, classes, méthodes et constantes.
|
||||
* Les noms d'espaces de noms suivent les conventions des espaces de noms PHP en utilisant les barres obliques inverses (`\`) comme séparateurs.
|
||||
* Si aucun espace de noms n'est déclaré, les symboles sont exportés vers l'espace de noms global comme d'habitude.
|
||||
- Seule **une** directive d'espace de noms est autorisée par fichier. Si plusieurs directives d'espace de noms sont trouvées, le générateur retournera une erreur.
|
||||
- L'espace de noms s'applique à **tous** les symboles exportés dans le fichier : fonctions, classes, méthodes et constantes.
|
||||
- Les noms d'espaces de noms suivent les conventions des espaces de noms PHP en utilisant les barres obliques inverses (`\`) comme séparateurs.
|
||||
- Si aucun espace de noms n'est déclaré, les symboles sont exportés vers l'espace de noms global comme d'habitude.
|
||||
|
||||
### Générer l'Extension
|
||||
|
||||
C'est là que la magie opère, et votre extension peut maintenant être générée. Vous pouvez exécuter le générateur avec la commande suivante :
|
||||
|
||||
```console
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -499,16 +629,17 @@ Nous allons voir comment écrire une extension PHP simple en Go qui définit une
|
||||
|
||||
#### Définir la Fonction Go
|
||||
|
||||
Dans votre module Go vide, vous devez définir une nouvelle fonction native qui sera appelée depuis PHP. Pour ce faire, créez un fichier avec le nom que vous voulez, par exemple, `extension.go`, et ajoutez le code suivant :
|
||||
Dans votre module, vous devez définir une nouvelle fonction native qui sera appelée depuis PHP. Pour ce faire, créez un fichier avec le nom que vous voulez, par exemple, `extension.go`, et ajoutez le code suivant :
|
||||
|
||||
```go
|
||||
package ext_go
|
||||
package example
|
||||
|
||||
//#include "extension.h"
|
||||
// #include "extension.h"
|
||||
import "C"
|
||||
import (
|
||||
"log/slog"
|
||||
"unsafe"
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
@@ -519,7 +650,7 @@ func init() {
|
||||
//export go_print_something
|
||||
func go_print_something() {
|
||||
go func() {
|
||||
caddy.Log().Info("Hello from a goroutine!")
|
||||
slog.Info("Hello from a goroutine!")
|
||||
}()
|
||||
}
|
||||
```
|
||||
@@ -567,9 +698,9 @@ extern zend_module_entry ext_module_entry;
|
||||
|
||||
Ensuite, créez un fichier nommé `extension.c` qui effectuera les étapes suivantes :
|
||||
|
||||
* Inclure les en-têtes PHP ;
|
||||
* Déclarer notre nouvelle fonction PHP native `go_print()` ;
|
||||
* Déclarer les métadonnées de l'extension.
|
||||
- Inclure les en-têtes PHP ;
|
||||
- Déclarer notre nouvelle fonction PHP native `go_print()` ;
|
||||
- Déclarer les métadonnées de l'extension.
|
||||
|
||||
Commençons par inclure les en-têtes requis :
|
||||
|
||||
@@ -696,14 +827,23 @@ Il ne reste qu'une chose à faire : implémenter la fonction `go_upper` en Go.
|
||||
Notre fonction Go prendra un `*C.zend_string` comme paramètre, le convertira en chaîne Go en utilisant la fonction d'aide de FrankenPHP, le traitera, et retournera le résultat comme un nouveau `*C.zend_string`. Les fonctions d'aide gèrent toute la complexité de gestion de mémoire et de conversion pour nous.
|
||||
|
||||
```go
|
||||
import "strings"
|
||||
package example
|
||||
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
"strings"
|
||||
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
//export go_upper
|
||||
func go_upper(s *C.zend_string) *C.zend_string {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
|
||||
upper := strings.ToUpper(str)
|
||||
|
||||
|
||||
return (*C.zend_string)(frankenphp.PHPString(upper, false))
|
||||
}
|
||||
```
|
||||
|
||||
@@ -88,7 +88,8 @@ Suivez ces étapes pour empaqueter votre application Laravel en tant que binaire
|
||||
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
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# Si vous avez l'intention d'exécuter le binaire sur des systèmes musl-libc, utilisez plutôt static-builder-musl
|
||||
|
||||
# Copiez votre application
|
||||
WORKDIR /go/src/app/dist/app
|
||||
|
||||
@@ -37,6 +37,9 @@ frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to
|
||||
|
||||
## Runtime Symfony
|
||||
|
||||
> [!TIP]
|
||||
> La section suivante est nécessaire uniquement avant Symfony 7.4, où le support natif du mode worker de FrankenPHP a été introduit.
|
||||
|
||||
Le mode worker de FrankenPHP est pris en charge par le [Composant Runtime de Symfony](https://symfony.com/doc/current/components/runtime.html).
|
||||
Pour démarrer une application Symfony dans un worker, installez le package FrankenPHP de [PHP Runtime](https://github.com/php-runtime/runtime) :
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ 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...).
|
||||
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é.
|
||||
|
||||
@@ -16,31 +16,62 @@ FrankenPHPはあらゆるPHPアプリと連携し、ワーカーモードの公
|
||||
|
||||
## はじめに
|
||||
|
||||
### スタンドアロンバイナリ
|
||||
|
||||
LinuxとmacOS向けに、[PHP 8.4](https://www.php.net/releases/8.4/en.php)と人気のPHP拡張モジュールを含む静的な
|
||||
FrankenPHPバイナリを提供しています。
|
||||
|
||||
Windowsをお使いの場合は、[WSL](https://learn.microsoft.com/windows/wsl/)を使用してFrankenPHPを実行してください。
|
||||
|
||||
[FrankenPHPをダウンロード](https://github.com/php/frankenphp/releases)するか、以下のコマンドを
|
||||
ターミナルにコピーして実行すると、環境に合ったバージョンが自動的にインストールされます:
|
||||
### インストールスクリプト
|
||||
|
||||
以下のコマンドをターミナルに貼り付けると、環境に合ったバージョンが自動的にインストールされます:
|
||||
|
||||
```console
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
mv frankenphp /usr/local/bin/
|
||||
```
|
||||
|
||||
現在のディレクトリのコンテンツを配信するには、以下を実行してください:
|
||||
### スタンドアロンバイナリ
|
||||
|
||||
LinuxとmacOS向けに、開発用途の静的FrankenPHPバイナリを提供しています。
|
||||
[PHP 8.4](https://www.php.net/releases/8.4/en.php)と主要なPHP拡張が含まれます。
|
||||
|
||||
[FrankenPHPをダウンロード](https://github.com/php/frankenphp/releases)
|
||||
|
||||
**拡張のインストール:** よく使われる拡張は同梱されています。追加の拡張をインストールすることはできません。
|
||||
|
||||
### rpm パッケージ
|
||||
|
||||
メンテナーが `dnf` を使用するすべてのシステム向けに rpm パッケージを提供しています。インストール方法:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
sudo dnf install https://rpm.henderkes.com/static-php-1-0.noarch.rpm
|
||||
sudo dnf module enable php-zts:static-8.4 # 8.2-8.5 利用可能
|
||||
sudo dnf install frankenphp
|
||||
```
|
||||
|
||||
コマンドラインスクリプトも実行できます:
|
||||
**拡張のインストール:** `sudo dnf install php-zts-<extension>`
|
||||
|
||||
デフォルトで提供されていない拡張については [PIE](https://github.com/php/pie) を使用してください:
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
sudo dnf install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### deb パッケージ
|
||||
|
||||
メンテナーが `apt` を使用するすべてのシステム向けに deb パッケージを提供しています。インストール方法:
|
||||
|
||||
```console
|
||||
sudo curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg && \
|
||||
echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | sudo tee /etc/apt/sources.list.d/static-php.list && \
|
||||
sudo apt update
|
||||
sudo apt install frankenphp
|
||||
```
|
||||
|
||||
**拡張のインストール:** `sudo apt install php-zts-<extension>`
|
||||
|
||||
デフォルトで提供されていない拡張については [PIE](https://github.com/php/pie) を使用してください:
|
||||
|
||||
```console
|
||||
sudo apt install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### Docker
|
||||
@@ -70,12 +101,28 @@ FrankenPHPはmacOSおよびLinux向けに[Homebrew](https://brew.sh)パッケー
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
**拡張のインストール:** [PIE](https://github.com/php/pie) を使用してください。
|
||||
|
||||
### 使い方
|
||||
|
||||
現在のディレクトリのコンテンツを配信するには、以下を実行してください:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
コマンドラインスクリプトも実行できます:
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
```
|
||||
|
||||
deb / rpm パッケージの場合は、systemd サービスを起動することもできます:
|
||||
|
||||
```console
|
||||
sudo systemctl start frankenphp
|
||||
```
|
||||
|
||||
## ドキュメント
|
||||
|
||||
- [クラシックモード](https://frankenphp.dev/docs/classic/)
|
||||
|
||||
@@ -79,10 +79,10 @@ sudo make install
|
||||
FrankenPHPの一部の機能は、システムにインストールされているオプションの依存パッケージに依存しています。
|
||||
または、Goコンパイラにビルドタグを渡すことで、これらの機能を無効にできます。
|
||||
|
||||
| 機能 | 依存関係 | 無効にするためのビルドタグ |
|
||||
|--------------------------------|-----------------------------------------------------------------------|-------------------------|
|
||||
| Brotli圧縮 | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| ファイル変更時のワーカー再起動 | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
| 機能 | 依存関係 | 無効にするためのビルドタグ |
|
||||
| ------------------------------ | --------------------------------------------------------------------- | -------------------------- |
|
||||
| Brotli圧縮 | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| ファイル変更時のワーカー再起動 | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
|
||||
## Goアプリのコンパイル
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[FrankenPHPのDockerイメージ](https://hub.docker.com/r/dunglas/frankenphp)は、[公式PHPイメージ](https://hub.docker.com/_/php/)をベースにしています。主要なアーキテクチャに対してDebianとAlpine Linuxのバリアントを提供しており、Debianバリアントの使用を推奨しています。
|
||||
|
||||
PHP 8.2、8.3、8.4向けのバリアントが提供されています。
|
||||
PHP 8.2、8.3、8.4、8.5向けのバリアントが提供されています。
|
||||
|
||||
タグは次のパターンに従います:`dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ Linux用バイナリを作成する最も簡単な方法は、提供されてい
|
||||
1. アプリのリポジトリに`static-build.Dockerfile`というファイルを作成します:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# バイナリをmusl-libcシステムで実行する場合は、static-builder-musl を使用してください
|
||||
|
||||
# アプリをコピー
|
||||
WORKDIR /go/src/app/dist/app
|
||||
|
||||
@@ -72,8 +72,8 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
|
||||
ここで重要なポイントが2つあります:
|
||||
|
||||
* ディレクティブコメント`//export_php:function`はPHPでの関数シグネチャを定義します。これにより、ジェネレーターは適切なパラメータと戻り値の型でPHP関数を生成する方法を知ることができます。
|
||||
* 関数は`unsafe.Pointer`を返さなければなりません。FrankenPHPはCとGo間の型変換を支援するAPIを提供しています。
|
||||
- ディレクティブコメント`//export_php:function`はPHPでの関数シグネチャを定義します。これにより、ジェネレーターは適切なパラメータと戻り値の型でPHP関数を生成する方法を知ることができます。
|
||||
- 関数は`unsafe.Pointer`を返さなければなりません。FrankenPHPはCとGo間の型変換を支援するAPIを提供しています。
|
||||
|
||||
前者は理解しやすいですが、後者は少し複雑かもしれません。次のセクションで型変換について詳しく説明します。
|
||||
|
||||
@@ -81,17 +81,18 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer {
|
||||
|
||||
C/PHPとGoの間でメモリ表現が同じ変数型もありますが、直接使用するにはより多くのロジックが必要な型もあります。これは拡張モジュールを書く際の最も挑戦的な部分かもしれません。Zendエンジンの内部仕組みや、変数がPHP内でどのように格納されているかを理解する必要があるためです。以下の表は、知っておくべき重要な情報をまとめています:
|
||||
|
||||
| PHP型 | Go型 | 直接変換 | CからGoヘルパー | GoからCヘルパー | クラスメソッドサポート |
|
||||
|--------------------|------------------|-------------------|-----------------------|------------------------|-----------------------|
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `slice`/`map` | ❌ | _未実装_ | _未実装_ | ❌ |
|
||||
| `object` | `struct` | ❌ | _未実装_ | _未実装_ | ❌ |
|
||||
| PHP型 | Go型 | 直接変換 | CからGoヘルパー | GoからCヘルパー | クラスメソッドサポート |
|
||||
| ------------------ | ---------------- | -------- | --------------------- | ---------------------- | ---------------------- |
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ |
|
||||
| `array` | `slice`/`map` | ❌ | _未実装_ | _未実装_ | ❌ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `object` | `struct` | ❌ | _未実装_ | _未実装_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
> この表はまだ完全ではなく、FrankenPHPの型APIがより完全になるにつれて完成されます。
|
||||
@@ -116,11 +117,11 @@ type UserStruct struct {
|
||||
|
||||
**不透明クラス(opaque classes)**は、内部構造(プロパティ)がPHPコードから隠されているクラスです。これは以下を意味します:
|
||||
|
||||
* **プロパティへの直接アクセス不可** :PHPから直接プロパティを読み書きできません(`$user->name`は機能しません)
|
||||
* **メソッド経由のみで操作** - すべてのやりとりはGoで定義したメソッドを通じて行う必要があります
|
||||
* **より良いカプセル化** - 内部データ構造は完全にGoコードによって制御されます
|
||||
* **型安全性** - PHP側から誤った型で内部状態が破壊されるリスクがありません
|
||||
* **よりクリーンなAPI** - 適切な公開インターフェースを設計することを強制します
|
||||
- **プロパティへの直接アクセス不可** :PHPから直接プロパティを読み書きできません(`$user->name`は機能しません)
|
||||
- **メソッド経由のみで操作** - すべてのやりとりはGoで定義したメソッドを通じて行う必要があります
|
||||
- **より良いカプセル化** - 内部データ構造は完全にGoコードによって制御されます
|
||||
- **型安全性** - PHP側から誤った型で内部状態が破壊されるリスクがありません
|
||||
- **よりクリーンなAPI** - 適切な公開インターフェースを設計することを強制します
|
||||
|
||||
このアプローチは優れたカプセル化を実現し、PHPコードがGoオブジェクトの内部状態を意図せずに破壊してしまうことを防ぎます。オブジェクトとのすべてのやりとりは、明示的に定義したメソッドを通じて行う必要があります。
|
||||
|
||||
@@ -167,12 +168,12 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
if name != nil {
|
||||
us.Name = frankenphp.GoString(unsafe.Pointer(name))
|
||||
}
|
||||
|
||||
|
||||
// ageが渡された(nullではない)かチェック
|
||||
if age != nil {
|
||||
us.Age = int(*age)
|
||||
}
|
||||
|
||||
|
||||
// activeが渡された(nullではない)かチェック
|
||||
if active != nil {
|
||||
us.Active = *active
|
||||
@@ -182,10 +183,10 @@ func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool)
|
||||
|
||||
**Nullableパラメータの重要なポイント:**
|
||||
|
||||
* **プリミティブ型のnullable** (`?int`, `?float`, `?bool`) はGoではそれぞれポインタ (`*int64`, `*float64`, `*bool`) になります
|
||||
* **nullable文字列** (`?string`) は `*C.zend_string` のままですが、`nil` になることがあります
|
||||
* ポインタ値を逆参照する前に **`nil`をチェック** してください
|
||||
* **PHPの`null`はGoの`nil`になります** - PHPが`null`を渡すと、Go関数は`nil`ポインタを受け取ります
|
||||
- **プリミティブ型のnullable** (`?int`, `?float`, `?bool`) はGoではそれぞれポインタ (`*int64`, `*float64`, `*bool`) になります
|
||||
- **nullable文字列** (`?string`) は `*C.zend_string` のままですが、`nil` になることがあります
|
||||
- ポインタ値を逆参照する前に **`nil`をチェック** してください
|
||||
- **PHPの`null`はGoの`nil`になります** - PHPが`null`を渡すと、Go関数は`nil`ポインタを受け取ります
|
||||
|
||||
> [!WARNING]
|
||||
> 現在、クラスメソッドには次の制限があります。**配列とオブジェクトはパラメータ型や戻り値の型としてサポートされていません**。サポートされるのは`string`、`int`、`float`、`bool`、`void`(戻り値の型)といったスカラー型のみです。**nullableなスカラー型はすべてサポートされています** (`?string`、`?int`、`?float`、`?bool`)。
|
||||
@@ -217,7 +218,7 @@ $user->updateInfo(null, 25, null); // Nameとactiveがnull
|
||||
|
||||
### 定数の宣言
|
||||
|
||||
ジェネレーターは、2つのディレクティブを使用してGo定数をPHPにエクスポートすることをサポートしています:グローバル定数用の`//export_php:const`とクラス定数用の`//export_php:classconstant`です。これにより、GoとPHPコード間で設定値、ステータスコード、その他の定数を共有できます。
|
||||
ジェネレーターは、2つのディレクティブを使用してGo定数をPHPにエクスポートすることをサポートしています:グローバル定数用の`//export_php:const`とクラス定数用の`//export_php:classconst`です。これにより、GoとPHPコード間で設定値、ステータスコード、その他の定数を共有できます。
|
||||
|
||||
#### グローバル定数
|
||||
|
||||
@@ -239,25 +240,25 @@ const STATUS_ERROR = iota
|
||||
|
||||
#### クラス定数
|
||||
|
||||
`//export_php:classconstant ClassName`ディレクティブを使用して、特定のPHPクラスに属する定数を作成できます:
|
||||
`//export_php:classconst ClassName`ディレクティブを使用して、特定のPHPクラスに属する定数を作成できます:
|
||||
|
||||
```go
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const STATUS_ACTIVE = 1
|
||||
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const STATUS_INACTIVE = 0
|
||||
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const ROLE_ADMIN = "admin"
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_PENDING = iota
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_PROCESSING = iota
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_COMPLETED = iota
|
||||
```
|
||||
|
||||
@@ -293,10 +294,10 @@ const STR_REVERSE = iota
|
||||
//export_php:const
|
||||
const STR_NORMAL = iota
|
||||
|
||||
//export_php:classconstant StringProcessor
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_LOWERCASE = 1
|
||||
|
||||
//export_php:classconstant StringProcessor
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_UPPERCASE = 2
|
||||
|
||||
//export_php:function repeat_this(string $str, int $count, int $mode): string
|
||||
@@ -304,7 +305,7 @@ func repeat_this(s *C.zend_string, count int64, mode int) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
result := strings.Repeat(str, int(count))
|
||||
if mode == STR_REVERSE {
|
||||
if mode == STR_REVERSE {
|
||||
// 文字列を逆転
|
||||
}
|
||||
|
||||
@@ -323,14 +324,14 @@ type StringProcessorStruct struct {
|
||||
//export_php:method StringProcessor::process(string $input, int $mode): string
|
||||
func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsafe.Pointer {
|
||||
str := frankenphp.GoString(unsafe.Pointer(input))
|
||||
|
||||
|
||||
switch mode {
|
||||
case MODE_LOWERCASE:
|
||||
str = strings.ToLower(str)
|
||||
case MODE_UPPERCASE:
|
||||
str = strings.ToUpper(str)
|
||||
}
|
||||
|
||||
|
||||
return frankenphp.PHPString(str, false)
|
||||
}
|
||||
```
|
||||
@@ -340,11 +341,10 @@ func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsaf
|
||||
ここでいよいよ、拡張モジュールを生成できるようになります。以下のコマンドでジェネレーターを実行できます:
|
||||
|
||||
```console
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
GEN_STUB_SCRIPT=php-src/build/gen_stub.php frankenphp extension-init my_extension.go
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> `GEN_STUB_SCRIPT`環境変数に、先ほどダウンロードしたPHPソースの`gen_stub.php`ファイルのパスを設定するのを忘れないでください。これは手動実装セクションで言及されているのと同じ`gen_stub.php`スクリプトです。
|
||||
> [!NOTE] > `GEN_STUB_SCRIPT`環境変数に、先ほどダウンロードしたPHPソースの`gen_stub.php`ファイルのパスを設定するのを忘れないでください。これは手動実装セクションで言及されているのと同じ`gen_stub.php`スクリプトです。
|
||||
|
||||
すべてがうまくいけば、`build`という名前の新しいディレクトリが作成されているはずです。このディレクトリには、生成されたPHP関数スタブを含む`my_extension.go`ファイルなど、拡張用の生成されたファイルが含まれています。
|
||||
|
||||
@@ -460,9 +460,9 @@ extern zend_module_entry ext_module_entry;
|
||||
|
||||
次に、以下のステップを実行する`extension.c`という名前のファイルを作成します:
|
||||
|
||||
* PHPヘッダーをインクルードする
|
||||
* 新しいネイティブPHP関数`go_print()`を宣言する
|
||||
* 拡張モジュールのメタデータを宣言する
|
||||
- PHPヘッダーをインクルードする
|
||||
- 新しいネイティブPHP関数`go_print()`を宣言する
|
||||
- 拡張モジュールのメタデータを宣言する
|
||||
|
||||
まずは必要なヘッダーのインクルードから始めましょう:
|
||||
|
||||
@@ -594,9 +594,9 @@ import "strings"
|
||||
//export go_upper
|
||||
func go_upper(s *C.zend_string) *C.zend_string {
|
||||
str := frankenphp.GoString(unsafe.Pointer(s))
|
||||
|
||||
|
||||
upper := strings.ToUpper(str)
|
||||
|
||||
|
||||
return (*C.zend_string)(frankenphp.PHPString(upper, false))
|
||||
}
|
||||
```
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
以下の拡張モジュールは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/) | スレッドセーフでない | - |
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
以下の拡張モジュールは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
|
||||
|
||||
@@ -88,7 +88,8 @@ LaravelアプリをLinux用のスタンドアロンバイナリとしてパッ
|
||||
1. アプリのリポジトリに`static-build.Dockerfile`という名前のファイルを作成します:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# バイナリをmusl-libcシステムで実行する場合は、static-builder-musl を使用してください
|
||||
|
||||
# アプリをコピー
|
||||
WORKDIR /go/src/app/dist/app
|
||||
|
||||
@@ -13,9 +13,9 @@ The following extensions are known not to be compatible with FrankenPHP:
|
||||
|
||||
The following extensions have known bugs and unexpected behaviors when used with FrankenPHP:
|
||||
|
||||
| Name | Problem |
|
||||
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | When using a static build of FrankenPHP (built with the musl libc), the OpenSSL extension may crash under heavy loads. A workaround is to use a dynamically linked build (like the one used in Docker images). This bug is [being tracked by PHP](https://github.com/php/php-src/issues/13648). |
|
||||
| Name | Problem |
|
||||
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | When using musl libc, the OpenSSL extension may crash under heavy loads. The problem doesn't occur when using the more popular GNU libc. This bug is [being tracked by PHP](https://github.com/php/php-src/issues/13648). |
|
||||
|
||||
## get_browser
|
||||
|
||||
@@ -23,7 +23,11 @@ The [get_browser()](https://www.php.net/manual/en/function.get-browser.php) func
|
||||
|
||||
## Standalone Binary and Alpine-based Docker Images
|
||||
|
||||
The standalone binary and Alpine-based Docker images (`dunglas/frankenphp:*-alpine`) use [musl libc](https://musl.libc.org/) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), to keep a smaller binary size. This may lead to some compatibility issues. In particular, the glob flag `GLOB_BRACE` is [not available](https://www.php.net/manual/en/function.glob.php)
|
||||
The fully binary and Alpine-based Docker images (`dunglas/frankenphp:*-alpine`) use [musl libc](https://musl.libc.org/) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), to keep a smaller binary size.
|
||||
This may lead to some compatibility issues.
|
||||
In particular, the glob flag `GLOB_BRACE` is [not available](https://www.php.net/manual/en/function.glob.php)
|
||||
|
||||
Prefer using the GNU variant of the static binary and Debian-based Docker images if you encounter issues.
|
||||
|
||||
## Using `https://127.0.0.1` with Docker
|
||||
|
||||
|
||||
@@ -76,6 +76,8 @@ The `octane:frankenphp` command can take the following options:
|
||||
> [!TIP]
|
||||
> To get structured JSON logs (useful when using log analytics solutions), explicitly the pass `--log-level` option.
|
||||
|
||||
See also [how to use Mercure with Octane](#mercure-support).
|
||||
|
||||
Learn more about [Laravel Octane in its official documentation](https://laravel.com/docs/octane).
|
||||
|
||||
## Laravel Apps As Standalone Binaries
|
||||
@@ -88,7 +90,8 @@ 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
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# If you intend to run the binary on musl-libc systems, use static-builder-musl instead
|
||||
|
||||
# Copy your app
|
||||
WORKDIR /go/src/app/dist/app
|
||||
@@ -166,6 +169,34 @@ This is not suitable for embedded applications, as each new version will be extr
|
||||
|
||||
Set the `LARAVEL_STORAGE_PATH` environment variable (for example, in your `.env` file) or call the `Illuminate\Foundation\Application::useStoragePath()` method to use a directory outside the temporary directory.
|
||||
|
||||
### Mercure Support
|
||||
|
||||
[Mercure](https://mercure.rocks) is a great way to add real-time capabilities to your Laravel apps.
|
||||
FrankenPHP includes [Mercure support out of the box](mercure.md).
|
||||
|
||||
If you are not using [Octane](#laravel-octane), see [the Mercure documentation entry](mercure.md).
|
||||
|
||||
If you are using Octane, you can use enable Mercure support by adding the following lines to your `config/octane.php` file:
|
||||
|
||||
```php
|
||||
// ...
|
||||
|
||||
return [
|
||||
// ...
|
||||
|
||||
'mercure' => [
|
||||
'anonymous' => true,
|
||||
'publisher_jwt' => '!ChangeThisMercureHubJWTSecretKey!',
|
||||
'subscriber_jwt' => '!ChangeThisMercureHubJWTSecretKey!',
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
You can use [all directives supported by Mercure](https://mercure.rocks/docs/hub/config#directives) in this array.
|
||||
|
||||
To publish and subscribe to updates, we recommend using the [Laravel Mercure Broadcaster](https://github.com/mvanduijker/laravel-mercure-broadcaster) library.
|
||||
Alternatively, see [the Mercure documentation](mercure.md) to do it in pure PHP and JavaScript.
|
||||
|
||||
### Running Octane With Standalone Binaries
|
||||
|
||||
It's even possible to package Laravel Octane apps as standalone binaries!
|
||||
|
||||
144
docs/mercure.md
144
docs/mercure.md
@@ -3,13 +3,147 @@
|
||||
FrankenPHP comes with a built-in [Mercure](https://mercure.rocks) hub!
|
||||
Mercure allows you to push real-time events to all the connected devices: they will receive a JavaScript event instantly.
|
||||
|
||||
No JS library or SDK is required!
|
||||
It's a convenient alternative to WebSockets that is simple to use and is natively supported by all modern web browsers!
|
||||
|
||||

|
||||
|
||||
To enable the Mercure hub, update the `Caddyfile` as described [on Mercure's site](https://mercure.rocks/docs/hub/config).
|
||||
## Enabling Mercure
|
||||
|
||||
The path of the Mercure hub is `/.well-known/mercure`.
|
||||
When running FrankenPHP inside Docker, the full send URL would look like `http://php/.well-known/mercure` (with `php` being the container's name running FrankenPHP).
|
||||
Mercure support is disabled by default.
|
||||
Here is a minimal example of a `Caddyfile` enabling both FrankenPHP and the Mercure hub:
|
||||
|
||||
To push Mercure updates from your code, we recommend the [Symfony Mercure Component](https://symfony.com/components/Mercure) (you don't need the Symfony full-stack framework to use it).
|
||||
```caddyfile
|
||||
# The hostname to respond to
|
||||
localhost
|
||||
|
||||
mercure {
|
||||
# The secret key used to sign the JWT tokens for publishers
|
||||
publisher_jwt !ChangeThisMercureHubJWTSecretKey!
|
||||
# Allows anonymous subscribers (without JWT)
|
||||
anonymous
|
||||
}
|
||||
|
||||
root public/
|
||||
php_server
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> The [sample `Caddyfile`](https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile)
|
||||
> provided by [the Docker images](docker.md) already includes a commented Mercure configuration
|
||||
> with convenient environment variables to configure it.
|
||||
>
|
||||
> Uncomment the Mercure section in `/etc/frankenphp/Caddyfile` to enable it.
|
||||
|
||||
## Subscribing to Updates
|
||||
|
||||
By default, the Mercure hub is available on the `/.well-known/mercure` path of your FrankenPHP server.
|
||||
To subscribe to updates, use the native [`EventSource`](https://developer.mozilla.org/docs/Web/API/EventSource) JavaScript class:
|
||||
|
||||
```html
|
||||
<!-- public/index.html -->
|
||||
<!doctype html>
|
||||
<title>Mercure Example</title>
|
||||
<script>
|
||||
const eventSource = new EventSource("/.well-known/mercure?topic=my-topic");
|
||||
eventSource.onmessage = function (event) {
|
||||
console.log("New message:", event.data);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## Publishing Updates
|
||||
|
||||
### Using `mercure_publish()`
|
||||
|
||||
FrankenPHP provides a convenient `mercure_publish()` function to publish updates to the built-in Mercure hub:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// public/publish.php
|
||||
|
||||
$updateID = mercure_publish('my-topic', json_encode(['key' => 'value']));
|
||||
|
||||
// Write to FrankenPHP's logs
|
||||
error_log("update $updateID published", 4);
|
||||
```
|
||||
|
||||
The full function signature is:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @param string|string[] $topics
|
||||
*/
|
||||
function mercure_publish(string|array $topics, string $data = '', bool $private = false, ?string $id = null, ?string $type = null, ?int $retry = null): string {}
|
||||
```
|
||||
|
||||
### Using `file_get_contents()`
|
||||
|
||||
To dispatch an update to connected subscribers, send an authenticated POST request to the Mercure hub with the `topic` and `data` parameters:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// public/publish.php
|
||||
|
||||
const JWT = 'eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.PXwpfIGng6KObfZlcOXvcnWCJOWTFLtswGI5DZuWSK4';
|
||||
|
||||
$updateID = file_get_contents('https://localhost/.well-known/mercure', context: stream_context_create(['http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer " . JWT,
|
||||
'content' => http_build_query([
|
||||
'topic' => 'my-topic',
|
||||
'data' => json_encode(['key' => 'value']),
|
||||
]),
|
||||
]]));
|
||||
|
||||
// Write to FrankenPHP's logs
|
||||
error_log("update $updateID published", 4);
|
||||
```
|
||||
|
||||
The key passed as parameter of the `mercure.publisher_jwt` option in the `Caddyfile` must used to sign the JWT token used in the `Authorization` header.
|
||||
|
||||
The JWT must include a `mercure` claim with a `publish` permission for the topics you want to publish to.
|
||||
See [the Mercure documentation](https://mercure.rocks/spec#publishers) about authorization.
|
||||
|
||||
To generate your own tokens, you can use [this jwt.io link](https://www.jwt.io/#token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.PXwpfIGng6KObfZlcOXvcnWCJOWTFLtswGI5DZuWSK4),
|
||||
but for production apps, it's recommended to use short-lived tokens generated aerodynamically using with a trusted [JWT library](https://www.jwt.io/libraries?programming_language=php).
|
||||
|
||||
### Using Symfony Mercure
|
||||
|
||||
Alternatively, you can use the [Symfony Mercure Component](https://symfony.com/components/Mercure), a standalone PHP library.
|
||||
|
||||
This library handled the JWT generation, update publishing as well as cookie-based authorization for subscribers.
|
||||
|
||||
First, install the library using Composer:
|
||||
|
||||
```console
|
||||
composer require symfony/mercure lcobucci/jwt
|
||||
```
|
||||
|
||||
Then, you can use it like this:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// public/publish.php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
const JWT_SECRET = '!ChangeThisMercureHubJWTSecretKey!'; // Must be the same as mercure.publisher_jwt in Caddyfile
|
||||
|
||||
// Set up the JWT token provider
|
||||
$jwFactory = new \Symfony\Component\Mercure\Jwt\LcobucciFactory(JWT_SECRET);
|
||||
$provider = new \Symfony\Component\Mercure\Jwt\FactoryTokenProvider($jwFactory, publish: ['*']);
|
||||
|
||||
$hub = new \Symfony\Component\Mercure\Hub('https://localhost/.well-known/mercure', $provider);
|
||||
// Serialize the update, and dispatch it to the hub, that will broadcast it to the clients
|
||||
$updateID = $hub->publish(new \Symfony\Component\Mercure\Update('my-topic', json_encode(['key' => 'value'])));
|
||||
|
||||
// Write to FrankenPHP's logs
|
||||
error_log("update $updateID published", 4);
|
||||
```
|
||||
|
||||
Mercure is also natively supported by:
|
||||
|
||||
- [Laravel](laravel.md#mercure-support)
|
||||
- [Symfony](https://symfony.com/doc/current/mercure.html)
|
||||
- [API Platform](https://api-platform.com/docs/core/mercure/)
|
||||
|
||||
@@ -41,11 +41,9 @@ especially when compiled in ZTS mode (thread-safe), which is required for Franke
|
||||
|
||||
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.
|
||||
In production environments, we recommend using FrankenPHP linked against glibc, compiled with an appropriate optimization level.
|
||||
|
||||
This can be achieved by using the Debian Docker images (the default), downloading the -gnu suffix binary from our [Releases](https://github.com/php/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.
|
||||
This can be achieved by using the Debian Docker images, using our maintainers [.deb](https://debs.henderkes.com) or [.rpm](https://rpms.henderkes.com) packages, or by [compiling FrankenPHP from sources](compile.md).
|
||||
|
||||
## Go Runtime Configuration
|
||||
|
||||
|
||||
@@ -24,22 +24,64 @@ conjunto de slides:
|
||||
|
||||
## Começando
|
||||
|
||||
### Binário independente
|
||||
|
||||
Fornecemos binários estáticos do FrankenPHP para Linux e macOS contendo o
|
||||
[PHP 8.4](https://www.php.net/releases/8.4/pt_BR.php) e as extensões PHP mais
|
||||
populares.
|
||||
|
||||
No Windows, use [WSL](https://learn.microsoft.com/pt-br/windows/wsl/) para
|
||||
executar o FrankenPHP.
|
||||
|
||||
[Baixe o FrankenPHP](https://github.com/php/frankenphp/releases) ou copie esta
|
||||
linha no seu terminal para instalar automaticamente a versão apropriada para sua
|
||||
plataforma:
|
||||
### Script de instalação
|
||||
|
||||
Você pode copiar esta linha no seu terminal para instalar automaticamente a
|
||||
versão apropriada para sua plataforma:
|
||||
|
||||
```console
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
mv frankenphp /usr/local/bin/
|
||||
```
|
||||
|
||||
### Binário independente
|
||||
|
||||
Fornecemos binários estáticos do FrankenPHP para desenvolvimento em Linux e macOS contendo o
|
||||
[PHP 8.4](https://www.php.net/releases/8.4/pt_BR.php) e as extensões PHP mais populares.
|
||||
|
||||
[Baixe o FrankenPHP](https://github.com/php/frankenphp/releases)
|
||||
|
||||
**Instalação de extensões:** As extensões mais comuns já estão incluídas. Não é possível instalar mais extensões.
|
||||
|
||||
### Pacotes rpm
|
||||
|
||||
Nossos mantenedores oferecem pacotes rpm para todos os sistemas que usam `dnf`. Para instalar, execute:
|
||||
|
||||
```console
|
||||
sudo dnf install https://rpm.henderkes.com/static-php-1-0.noarch.rpm
|
||||
sudo dnf module enable php-zts:static-8.4 # 8.2-8.5 disponíveis
|
||||
sudo dnf install frankenphp
|
||||
```
|
||||
|
||||
**Instalação de extensões:** `sudo dnf install php-zts-<extension>`
|
||||
|
||||
Para extensões não disponíveis por padrão, use o [PIE](https://github.com/php/pie):
|
||||
|
||||
```console
|
||||
sudo dnf install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### Pacotes deb
|
||||
|
||||
Nossos mantenedores oferecem pacotes deb para todos os sistemas que usam `apt`. Para instalar, execute:
|
||||
|
||||
```console
|
||||
sudo curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg && \
|
||||
echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | sudo tee /etc/apt/sources.list.d/static-php.list && \
|
||||
sudo apt update
|
||||
sudo apt install frankenphp
|
||||
```
|
||||
|
||||
**Instalação de extensões:** `sudo apt install php-zts-<extension>`
|
||||
|
||||
Para extensões não disponíveis por padrão, use o [PIE](https://github.com/php/pie):
|
||||
|
||||
```console
|
||||
sudo apt install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
Para servir o conteúdo do diretório atual, execute:
|
||||
@@ -85,12 +127,28 @@ Para instalá-lo:
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
**Instalação de extensões:** Use o [PIE](https://github.com/php/pie).
|
||||
|
||||
### Uso
|
||||
|
||||
Para servir o conteúdo do diretório atual, execute:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Você também pode executar scripts de linha de comando com:
|
||||
|
||||
```console
|
||||
frankenphp php-cli /caminho/para/seu/script.php
|
||||
```
|
||||
|
||||
Para os pacotes deb e rpm, você também pode iniciar o serviço systemd:
|
||||
|
||||
```console
|
||||
sudo systemctl start frankenphp
|
||||
```
|
||||
|
||||
## Documentação
|
||||
|
||||
- [Modo clássico](classic.md)
|
||||
|
||||
@@ -92,7 +92,7 @@ Alternativamente, esses recursos podem ser desabilitados passando as tags de
|
||||
compilação para o compilador Go.
|
||||
|
||||
| Recurso | Dependência | Tag de compilação para desabilitá-lo |
|
||||
|---------------------------------------|-----------------------------------------------------------------------|--------------------------------------|
|
||||
| ------------------------------------- | --------------------------------------------------------------------- | ------------------------------------ |
|
||||
| Compressão Brotli | [Brotli](https://github.com/google/brotli) | `nobrotli` |
|
||||
| Reiniciar workers ao alterar arquivos | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | `nowatcher` |
|
||||
|
||||
@@ -126,8 +126,8 @@ xcaddy build \
|
||||
> Se você estiver usando a `libc` `musl` (o padrão no Alpine Linux) e Symfony,
|
||||
> pode ser necessário aumentar o tamanho da pilha padrão.
|
||||
> Caso contrário, você poderá receber erros como `PHP Fatal error: Maximum call
|
||||
> stack size of 83360 bytes reached during compilation.
|
||||
> Try splitting expression`.
|
||||
stack size of 83360 bytes reached during compilation.
|
||||
Try splitting expression`.
|
||||
>
|
||||
> Para fazer isso, altere a variável de ambiente `XCADDY_GO_BUILD_FLAGS` para
|
||||
> algo como
|
||||
|
||||
@@ -6,7 +6,7 @@ Variantes do Debian e do Alpine Linux são fornecidas para arquiteturas
|
||||
populares.
|
||||
Variantes do Debian são recomendadas.
|
||||
|
||||
Variantes para PHP 8.2, 8.3 e 8.4 são fornecidas.
|
||||
Variantes para PHP 8.2, 8.3, 8.4 e 8.5 são fornecidas.
|
||||
|
||||
As tags seguem este padrão:
|
||||
`dunglas/frankenphp:<versao-do-frankenphp>-php<versao-do-php>-<so>`.
|
||||
|
||||
@@ -66,7 +66,8 @@ Docker que fornecemos.
|
||||
aplicação:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# Se você pretende executar o binário em sistemas musl-libc, use o static-builder-musl
|
||||
|
||||
# Copia sua aplicação
|
||||
WORKDIR /go/src/app/dist/app
|
||||
|
||||
@@ -132,19 +132,20 @@ variáveis são armazenadas internamente no PHP.
|
||||
Esta tabela resume o que você precisa saber:
|
||||
|
||||
| Tipo PHP | Tipo Go | Conversão direta | Auxiliar de C para Go | Auxiliar de Go para C | Suporte a métodos de classe |
|
||||
|--------------------|-------------------------------|------------------|-----------------------------------|------------------------------------|-----------------------------|
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ |
|
||||
| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ |
|
||||
| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ |
|
||||
| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ |
|
||||
| `object` | `struct` | ❌ | _Ainda não implementado_ | _Ainda não implementado_ | ❌ |
|
||||
| ------------------ | ----------------------------- | ---------------- | --------------------------------- | ---------------------------------- | --------------------------- |
|
||||
| `int` | `int64` | ✅ | - | - | ✅ |
|
||||
| `?int` | `*int64` | ✅ | - | - | ✅ |
|
||||
| `float` | `float64` | ✅ | - | - | ✅ |
|
||||
| `?float` | `*float64` | ✅ | - | - | ✅ |
|
||||
| `bool` | `bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `?bool` | `*bool` | ✅ | - | - | ✅ |
|
||||
| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ |
|
||||
| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ |
|
||||
| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ |
|
||||
| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `object` | `struct` | ❌ | _Ainda não implementado_ | _Ainda não implementado_ | ❌ |
|
||||
|
||||
> [!NOTE]
|
||||
> Esta tabela ainda não é exaustiva e será completada à medida que a API de
|
||||
@@ -411,7 +412,7 @@ segurança de tipos.
|
||||
### Declarando constantes
|
||||
|
||||
O gerador suporta a exportação de constantes Go para PHP usando duas diretivas:
|
||||
`//export_php:const` para constantes globais e `//export_php:classconstant` para
|
||||
`//export_php:const` para constantes globais e `//export_php:classconst` para
|
||||
constantes de classe.
|
||||
Isso permite que você compartilhe valores de configuração, códigos de status e
|
||||
outras constantes entre código Go e PHP.
|
||||
@@ -436,26 +437,26 @@ const STATUS_ERROR = iota
|
||||
|
||||
#### Constantes de classe
|
||||
|
||||
Use a diretiva `//export_php:classconstant ClassName` para criar constantes que
|
||||
Use a diretiva `//export_php:classconst ClassName` para criar constantes que
|
||||
pertencem a uma classe PHP específica:
|
||||
|
||||
```go
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const STATUS_ACTIVE = 1
|
||||
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const STATUS_INACTIVE = 0
|
||||
|
||||
//export_php:classconstant User
|
||||
//export_php:classconst User
|
||||
const ROLE_ADMIN = "admin"
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_PENDING = iota
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_PROCESSING = iota
|
||||
|
||||
//export_php:classconstant Order
|
||||
//export_php:classconst Order
|
||||
const STATE_COMPLETED = iota
|
||||
```
|
||||
|
||||
@@ -501,10 +502,10 @@ const STR_REVERSE = iota
|
||||
//export_php:const
|
||||
const STR_NORMAL = iota
|
||||
|
||||
//export_php:classconstant StringProcessor
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_LOWERCASE = 1
|
||||
|
||||
//export_php:classconstant StringProcessor
|
||||
//export_php:classconst StringProcessor
|
||||
const MODE_UPPERCASE = 2
|
||||
|
||||
//export_php:function repeat_this(string $str, int $count, int $mode): string
|
||||
|
||||
@@ -6,7 +6,7 @@ As seguintes extensões são conhecidas por não serem compatíveis com o
|
||||
FrankenPHP:
|
||||
|
||||
| Nome | Motivo | Alternativas |
|
||||
|-------------------------------------------------------------------------------------------------------------|-------------------|----------------------------------------------------------------------------------------------------------------------|
|
||||
| ----------------------------------------------------------------------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/pt_BR/imap.installation.php) | Não é 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/) | Não é thread-safe | - |
|
||||
|
||||
@@ -16,7 +16,7 @@ As seguintes extensões apresentam falhas conhecidas e comportamentos inesperado
|
||||
quando usadas com o FrankenPHP:
|
||||
|
||||
| Nome | Problema |
|
||||
|------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ext-openssl](https://www.php.net/manual/pt_BR/book.openssl.php) | Ao usar uma versão estática do FrankenPHP (compilada com a `libc` `musl`), a extensão OpenSSL pode quebrar sob cargas pesadas. Uma solução alternativa é usar uma versão vinculada dinamicamente (como a usada em imagens Docker). Esta falha está [sendo monitorada pelo PHP](https://github.com/php/php-src/issues/13648) |
|
||||
|
||||
## `get_browser`
|
||||
|
||||
@@ -108,7 +108,8 @@ independente para Linux:
|
||||
aplicação:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# Se você pretende executar o binário em sistemas musl-libc, use o static-builder-musl
|
||||
|
||||
# Copia sua aplicação
|
||||
WORKDIR /go/src/app/dist/app
|
||||
|
||||
@@ -16,6 +16,63 @@ FrankenPHP также может использоваться как автон
|
||||
|
||||
## Начало работы
|
||||
|
||||
В Windows используйте [WSL](https://learn.microsoft.com/windows/wsl/) для запуска FrankenPHP.
|
||||
|
||||
### Скрипт установки
|
||||
|
||||
Скопируйте и выполните эту команду в терминале, чтобы автоматически установить подходящую версию для вашей платформы:
|
||||
|
||||
```console
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
```
|
||||
|
||||
### Автономный бинарный файл
|
||||
|
||||
Если вы предпочитаете не использовать Docker, мы предоставляем автономные статические бинарные файлы FrankenPHP для Linux и macOS, включающие [PHP 8.4](https://www.php.net/releases/8.4/en.php) и большинство популярных PHP‑расширений.
|
||||
|
||||
[Скачать FrankenPHP](https://github.com/php/frankenphp/releases)
|
||||
|
||||
**Установка расширений:** Наиболее распространенные расширения уже включены. Устанавливать дополнительные расширения невозможно.
|
||||
|
||||
### Пакеты rpm
|
||||
|
||||
Наши мейнтейнеры предлагают rpm‑пакеты для всех систем с `dnf`. Для установки выполните:
|
||||
|
||||
```console
|
||||
sudo dnf install https://rpm.henderkes.com/static-php-1-0.noarch.rpm
|
||||
sudo dnf module enable php-zts:static-8.4 # доступны 8.2–8.5
|
||||
sudo dnf install frankenphp
|
||||
```
|
||||
|
||||
**Установка расширений:** `sudo dnf install php-zts-<extension>`
|
||||
|
||||
Для расширений, недоступных по умолчанию, используйте [PIE](https://github.com/php/pie):
|
||||
|
||||
```console
|
||||
sudo dnf install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### Пакеты deb
|
||||
|
||||
Наши мейнтейнеры предлагают deb‑пакеты для всех систем с `apt`. Для установки выполните:
|
||||
|
||||
```console
|
||||
sudo curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg && \
|
||||
echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | sudo tee /etc/apt/sources.list.d/static-php.list && \
|
||||
sudo apt update
|
||||
sudo apt install frankenphp
|
||||
```
|
||||
|
||||
**Установка расширений:** `sudo apt install php-zts-<extension>`
|
||||
|
||||
Для расширений, недоступных по умолчанию, используйте [PIE](https://github.com/php/pie):
|
||||
|
||||
```console
|
||||
sudo apt install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```console
|
||||
@@ -31,31 +88,36 @@ docker run -v .:/app/public \
|
||||
> Не используйте `https://127.0.0.1`. Используйте `https://localhost` и настройте самоподписанный сертификат.
|
||||
> Чтобы изменить используемый домен, настройте переменную окружения [`SERVER_NAME`](config.md#переменные-окружения).
|
||||
|
||||
### Автономный бинарный файл
|
||||
### Homebrew
|
||||
|
||||
Если вы предпочитаете не использовать 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/php/frankenphp/releases) или выполните следующую команду для автоматической установки подходящей версии:
|
||||
FrankenPHP также доступен как пакет [Homebrew](https://brew.sh) для macOS и Linux.
|
||||
|
||||
```console
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
mv frankenphp /usr/local/bin/
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
Для запуска содержимого текущей директории выполните:
|
||||
**Установка расширений:** Используйте [PIE](https://github.com/php/pie).
|
||||
|
||||
### Использование
|
||||
|
||||
Для запуска содержимого текущего каталога выполните:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Вы также можете запускать CLI-скрипты:
|
||||
Также можно запускать CLI‑скрипты:
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
```
|
||||
|
||||
Для пакетов deb и rpm можно запустить сервис systemd:
|
||||
|
||||
```console
|
||||
sudo systemctl start frankenphp
|
||||
```
|
||||
|
||||
## Документация
|
||||
|
||||
- [Worker режим](https://frankenphp.dev/docs/worker/)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[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.
|
||||
Доступны версии для PHP 8.2, 8.3, 8.4 и 8.5.
|
||||
|
||||
Теги следуют следующему шаблону: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`.
|
||||
|
||||
|
||||
@@ -52,7 +52,8 @@ composer dump-env prod
|
||||
1. Создайте файл `static-build.Dockerfile` в репозитории вашего приложения:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# Если вы планируете запускать бинарный файл на системах с musl-libc, используйте static-builder-musl
|
||||
|
||||
# Скопировать приложение
|
||||
WORKDIR /go/src/app/dist/app
|
||||
|
||||
@@ -85,7 +85,8 @@ php artisan octane:frankenphp
|
||||
1. Создайте файл с именем `static-build.Dockerfile` в репозитории вашего приложения:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# Если вы планируете запускать бинарный файл на системах с musl-libc, используйте static-builder-musl
|
||||
|
||||
# Скопируйте ваше приложение
|
||||
WORKDIR /go/src/app/dist/app
|
||||
|
||||
@@ -16,6 +16,64 @@ FrankenPHP, PHP'yi `net/http` kullanarak herhangi bir uygulamaya yerleştirmek i
|
||||
|
||||
## Başlarken
|
||||
|
||||
Windows üzerinde FrankenPHP çalıştırmak için [WSL](https://learn.microsoft.com/windows/wsl/) kullanın.
|
||||
|
||||
### Kurulum Betiği
|
||||
|
||||
Platformunuza uygun sürümü otomatik olarak kurmak için bu satırı terminalinize kopyalayabilirsiniz:
|
||||
|
||||
```console
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
```
|
||||
|
||||
### Binary Çıktısı
|
||||
|
||||
Docker kullanmayı tercih etmiyorsanız, Linux ve macOS için geliştirme amaçlı bağımsız (statik) FrankenPHP binary dosyaları sağlıyoruz;
|
||||
[PHP 8.4](https://www.php.net/releases/8.4/en.php) ve en popüler PHP eklentilerinin çoğu dahildir.
|
||||
|
||||
[FrankenPHP'yi indirin](https://github.com/php/frankenphp/releases)
|
||||
|
||||
**Eklenti kurulumu:** Yaygın eklentiler paketle birlikte gelir. Daha fazla eklenti yüklemek mümkün değildir.
|
||||
|
||||
### rpm Paketleri
|
||||
|
||||
Bakımcılarımız `dnf` kullanan tüm sistemler için rpm paketleri sunuyor. Kurulum için:
|
||||
|
||||
```console
|
||||
sudo dnf install https://rpm.henderkes.com/static-php-1-0.noarch.rpm
|
||||
sudo dnf module enable php-zts:static-8.4 # 8.2-8.5 mevcut
|
||||
sudo dnf install frankenphp
|
||||
```
|
||||
|
||||
**Eklenti kurulumu:** `sudo dnf install php-zts-<extension>`
|
||||
|
||||
Varsayılan olarak mevcut olmayan eklentiler için [PIE](https://github.com/php/pie) kullanın:
|
||||
|
||||
```console
|
||||
sudo dnf install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### deb Paketleri
|
||||
|
||||
Bakımcılarımız `apt` kullanan tüm sistemler için deb paketleri sunuyor. Kurulum için:
|
||||
|
||||
```console
|
||||
sudo curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg && \
|
||||
echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" | sudo tee /etc/apt/sources.list.d/static-php.list && \
|
||||
sudo apt update
|
||||
sudo apt install frankenphp
|
||||
```
|
||||
|
||||
**Eklenti kurulumu:** `sudo apt install php-zts-<extension>`
|
||||
|
||||
Varsayılan olarak mevcut olmayan eklentiler için [PIE](https://github.com/php/pie) kullanın:
|
||||
|
||||
```console
|
||||
sudo apt install pie-zts
|
||||
sudo pie-zts install asgrim/example-pie-extension
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```console
|
||||
@@ -31,23 +89,36 @@ docker run -v $PWD:/app/public \
|
||||
> `https://127.0.0.1` kullanmaya çalışmayın. `https://localhost` kullanın ve kendinden imzalı sertifikayı kabul edin.
|
||||
> Kullanılacak alan adını değiştirmek için [`SERVER_NAME` ortam değişkenini](https://frankenphp.dev/tr/docs/config#ortam-değişkenleri) kullanın.
|
||||
|
||||
### Binary Çıktısı
|
||||
### Homebrew
|
||||
|
||||
Docker kullanmayı tercih etmiyorsanız, Linux ve macOS için bağımsız FrankenPHP binary dosyası sağlıyoruz
|
||||
[PHP 8.4](https://www.php.net/releases/8.4/en.php) ve en popüler PHP eklentilerini de içermekte: [FrankenPHP](https://github.com/php/frankenphp/releases) indirin
|
||||
FrankenPHP, macOS ve Linux için [Homebrew](https://brew.sh) paketi olarak da mevcuttur.
|
||||
|
||||
Geçerli dizinin içeriğini başlatmak için çalıştırın:
|
||||
```console
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
**Eklenti kurulumu:** [PIE](https://github.com/php/pie) kullanın.
|
||||
|
||||
### Kullanım
|
||||
|
||||
Geçerli dizinin içeriğini sunmak için çalıştırın:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Ayrıca aşağıdaki tek komut satırı ile de çalıştırabilirsiniz:
|
||||
Komut satırı betiklerini şu şekilde çalıştırabilirsiniz:
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
```
|
||||
|
||||
deb ve rpm paketleri için systemd servisini de başlatabilirsiniz:
|
||||
|
||||
```console
|
||||
sudo systemctl start frankenphp
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
- [Worker modu](worker.md)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[Resmi PHP imajları](https://hub.docker.com/_/php/) temel alınarak [FrankenPHP Docker imajları](https://hub.docker.com/r/dunglas/frankenphp) hazırlanmıştır. Popüler mimariler için Debian ve Alpine Linux varyantları sağlanmıştır. Debian dağıtımı tavsiye edilir.
|
||||
|
||||
PHP 8.2, 8.3 ve 8.4 için varyantlar sağlanmıştır. [Etiketlere göz atın](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
PHP 8.2, 8.3, 8.4 ve 8.5 için varyantlar sağlanmıştır. [Etiketlere göz atın](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
## İmajlar Nasıl Kullanılır
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ Bir Linux binary çıktısı almanın en kolay yolu, sağladığımız Docker ta
|
||||
1. Hazırladığınız uygulamanın deposunda `static-build.Dockerfile` adlı bir dosya oluşturun:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# İkili dosyayı musl-libc sistemlerinde çalıştırmayı düşünüyorsanız static-builder-musl kullanın
|
||||
|
||||
# Uygulamanızı kopyalayın
|
||||
WORKDIR /go/src/app/dist/app
|
||||
@@ -55,7 +56,6 @@ Bir Linux binary çıktısı almanın en kolay yolu, sağladığımız Docker ta
|
||||
# Statik binary dosyasını oluşturun, yalnızca istediğiniz PHP eklentilerini seçtiğinizden emin olun
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ \
|
||||
PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \
|
||||
./build-static.sh
|
||||
```
|
||||
|
||||
@@ -86,7 +86,6 @@ Docker kullanmak istemiyorsanız veya bir macOS binary dosyası oluşturmak isti
|
||||
git clone https://github.com/php/frankenphp
|
||||
cd frankenphp
|
||||
EMBED=/path/to/your/app \
|
||||
PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \
|
||||
./build-static.sh
|
||||
```
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ Bu komutu Laravel uygulamanızın ana dizininden çalıştırın:
|
||||
docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
|
||||
```
|
||||
|
||||
And tadını çıkarın!
|
||||
Ve tadını çıkarın!
|
||||
|
||||
## Yerel Kurulum
|
||||
|
||||
Alternatif olarak, Laravel projelerinizi FrankenPHP ile yerel makinenizden çalıştırabilirsiniz:
|
||||
|
||||
1. [Sisteminize karşılık gelen binary dosyayı indirin](https://github.com/php/frankenphp/releases)
|
||||
1. [Sisteminize karşılık gelen ikili dosyayı indirin](../#standalone-binary)
|
||||
2. Aşağıdaki yapılandırmayı Laravel projenizin kök dizinindeki `Caddyfile` adlı bir dosyaya ekleyin:
|
||||
|
||||
```caddyfile
|
||||
@@ -30,8 +30,10 @@ Alternatif olarak, Laravel projelerinizi FrankenPHP ile yerel makinenizden çal
|
||||
root public/
|
||||
# Sıkıştırmayı etkinleştir (isteğe bağlı)
|
||||
encode zstd br gzip
|
||||
# PHP dosyalarını public/ dizininden çalıştırın ve varlıkları sunun
|
||||
php_server
|
||||
# public/ dizininden PHP dosyalarını çalıştırın ve statik dosyaları servis edin
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -64,11 +66,118 @@ php artisan octane:frankenphp
|
||||
- `--admin-port`: Yönetici sunucusunun erişilebilir olması gereken port (varsayılan: `2019`)
|
||||
- `--workers`: İstekleri işlemek için hazır olması gereken worker sayısı (varsayılan: `auto`)
|
||||
- `--max-requests`: Sunucu yeniden yüklenmeden önce işlenecek istek sayısı (varsayılan: `500`)
|
||||
- `--caddyfile`: FrankenPHP `Caddyfile` dosyasının yolu
|
||||
- `--caddyfile`: FrankenPHP `Caddyfile` dosyasının yolu (varsayılan: [Laravel Octane içinde bulunan şablon `Caddyfile`](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile))
|
||||
- `--https`: HTTPS, HTTP/2 ve HTTP/3'ü etkinleştirin ve sertifikaları otomatik olarak oluşturup yenileyin
|
||||
- `--http-redirect`: HTTP'den HTTPS'ye yeniden yönlendirmeyi etkinleştir (yalnızca --https geçilirse etkinleştirilir)
|
||||
- `--watch`: Uygulamada kod değişikliği olduğunda sunucuyu otomatik olarak yeniden yükle
|
||||
- `--http-redirect`: HTTP'den HTTPS'ye yeniden yönlendirmeyi etkinleştir (yalnızca --https ile birlikte geçilirse etkinleşir)
|
||||
- `--watch`: Uygulama değiştirildiğinde sunucuyu otomatik olarak yeniden yükle
|
||||
- `--poll`: Dosyaları bir ağ üzerinden izlemek için izleme sırasında dosya sistemi yoklamasını kullanın
|
||||
- `--log-level`: Belirtilen günlük seviyesinde veya üzerinde günlük mesajları
|
||||
- `--log-level`: Yerel Caddy günlüğünü kullanarak belirtilen günlük seviyesinde veya üzerinde mesajları kaydedin
|
||||
|
||||
Laravel Octane hakkında daha fazla bilgi edinmek için [Laravel Octane resmi belgelerine](https://laravel.com/docs/octane) göz atın.
|
||||
> [!TIP]
|
||||
> Yapılandırılmış JSON günlükleri elde etmek için (log analitik çözümleri kullanırken faydalıdır), `--log-level` seçeneğini açıkça geçin.
|
||||
|
||||
[Laravel Octane hakkında daha fazla bilgiyi resmi belgelerde bulabilirsiniz](https://laravel.com/docs/octane).
|
||||
|
||||
## Laravel Uygulamalarını Bağımsız Çalıştırılabilir Dosyalar Olarak Dağıtma
|
||||
|
||||
[FrankenPHP'nin uygulama gömme özelliğini](embed.md) kullanarak, Laravel
|
||||
uygulamalarını bağımsız çalıştırılabilir dosyalar olarak dağıtmak mümkündür.
|
||||
|
||||
Linux için Laravel uygulamanızı bağımsız bir çalıştırılabilir olarak paketlemek için şu adımları izleyin:
|
||||
|
||||
1. Uygulamanızın deposunda `static-build.Dockerfile` adında bir dosya oluşturun:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu
|
||||
# İkiliyi musl-libc sistemlerinde çalıştırmayı düşünüyorsanız, bunun yerine static-builder-musl kullanın
|
||||
|
||||
# Uygulamanızı kopyalayın
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Yer kaplamamak için testleri ve diğer gereksiz dosyaları kaldırın
|
||||
# Alternatif olarak, bu dosyaları bir .dockerignore dosyasına ekleyin
|
||||
RUN rm -Rf tests/
|
||||
|
||||
# .env dosyasını kopyalayın
|
||||
RUN cp .env.example .env
|
||||
# APP_ENV ve APP_DEBUG değerlerini production için uygun hale getirin
|
||||
RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env
|
||||
|
||||
# Gerekirse .env dosyanıza diğer değişiklikleri yapın
|
||||
|
||||
# Bağımlılıkları yükleyin
|
||||
RUN composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# Statik ikiliyi derleyin
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> Bazı `.dockerignore` dosyaları
|
||||
> `vendor/` dizinini ve `.env` dosyalarını yok sayar. Derlemeden önce `.dockerignore` dosyasını buna göre ayarladığınızdan veya kaldırdığınızdan emin olun.
|
||||
|
||||
2. İmajı oluşturun:
|
||||
|
||||
```console
|
||||
docker build -t static-laravel-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. İkili dosyayı dışa aktarın:
|
||||
|
||||
```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. Önbellekleri doldurun:
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan optimize
|
||||
```
|
||||
|
||||
5. Veritabanı migration'larını çalıştırın (varsa):
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan migrate
|
||||
```
|
||||
|
||||
6. Uygulamanın gizli anahtarını oluşturun:
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan key:generate
|
||||
```
|
||||
|
||||
7. Sunucuyu başlatın:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Uygulamanız artık hazır!
|
||||
|
||||
Mevcut seçenekler hakkında daha fazla bilgi edinin ve diğer işletim sistemleri için nasıl ikili derleneceğini [uygulama gömme](embed.md)
|
||||
belgelerinde öğrenin.
|
||||
|
||||
### Depolama Yolunu Değiştirme
|
||||
|
||||
Varsayılan olarak, Laravel yüklenen dosyaları, önbellekleri, logları vb. uygulamanın `storage/` dizininde saklar.
|
||||
Gömülü uygulamalar için bu uygun değildir, çünkü her yeni sürüm farklı bir geçici dizine çıkarılacaktır.
|
||||
|
||||
Geçici dizin dışında bir dizin kullanmak için `LARAVEL_STORAGE_PATH` ortam değişkenini ayarlayın (örneğin, `.env` dosyanızda) veya `Illuminate\Foundation\Application::useStoragePath()` metodunu çağırın.
|
||||
|
||||
### Bağımsız Çalıştırılabilir Dosyalarla Octane'i Çalıştırma
|
||||
|
||||
Laravel Octane uygulamalarını bağımsız çalıştırılabilir dosyalar olarak paketlemek bile mümkündür!
|
||||
|
||||
Bunu yapmak için, [Octane'i doğru şekilde kurun](#laravel-octane) ve [önceki bölümde](#laravel-uygulamalarını-bağımsız-çalıştırılabilir-dosyalar-olarak-dağıtma) açıklanan adımları izleyin.
|
||||
|
||||
Ardından, Octane üzerinden FrankenPHP'yi worker modunda başlatmak için şunu çalıştırın:
|
||||
|
||||
```console
|
||||
PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> Komutun çalışması için, bağımsız ikili dosya mutlaka `frankenphp` olarak adlandırılmış olmalıdır,
|
||||
> çünkü Octane, yol üzerinde `frankenphp` adlı bir programın mevcut olmasını bekler.
|
||||
|
||||
@@ -37,6 +37,9 @@ frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to
|
||||
|
||||
## Symfony Runtime
|
||||
|
||||
> [!TIP]
|
||||
> The following section is only necessary prior to Symfony 7.4, where native support for FrankenPHP worker mode was introduced.
|
||||
|
||||
The worker mode of FrankenPHP is supported by the [Symfony Runtime Component](https://symfony.com/doc/current/components/runtime.html).
|
||||
To start any Symfony application in a worker, install the FrankenPHP package of [PHP Runtime](https://github.com/php-runtime/runtime):
|
||||
|
||||
@@ -78,9 +81,15 @@ $myApp->boot();
|
||||
|
||||
// Handler outside the loop for better performance (doing less work)
|
||||
$handler = static function () use ($myApp) {
|
||||
// Called when a request is received,
|
||||
// superglobals, php://input and the like are reset
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
try {
|
||||
// Called when a request is received,
|
||||
// superglobals, php://input and the like are reset
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
} catch (\Throwable $exception) {
|
||||
// `set_exception_handler` is called only when the worker script ends,
|
||||
// which may not be what you expect, so catch and handle exceptions here
|
||||
(new \MyCustomExceptionHandler)->handleException($exception);
|
||||
}
|
||||
};
|
||||
|
||||
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
|
||||
|
||||
5
env.go
5
env.go
@@ -1,10 +1,9 @@
|
||||
package frankenphp
|
||||
|
||||
// #cgo nocallback frankenphp_init_persistent_string
|
||||
// #cgo nocallback frankenphp_add_assoc_str_ex
|
||||
// #cgo noescape frankenphp_init_persistent_string
|
||||
// #cgo noescape frankenphp_add_assoc_str_ex
|
||||
// #include "frankenphp.h"
|
||||
// #include <Zend/zend_API.h>
|
||||
import "C"
|
||||
import (
|
||||
"os"
|
||||
@@ -98,7 +97,7 @@ func go_getfullenv(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
|
||||
env := getSandboxedEnv(thread)
|
||||
|
||||
for key, val := range env {
|
||||
C.frankenphp_add_assoc_str_ex(trackVarsArray, toUnsafeChar(key), C.size_t(len(key)), val)
|
||||
C.add_assoc_str_ex(trackVarsArray, toUnsafeChar(key), C.size_t(len(key)), val)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
143
frankenphp.c
143
frankenphp.c
@@ -51,7 +51,6 @@ frankenphp_version frankenphp_get_version() {
|
||||
|
||||
frankenphp_config frankenphp_get_config() {
|
||||
return (frankenphp_config){
|
||||
frankenphp_get_version(),
|
||||
#ifdef ZTS
|
||||
true,
|
||||
#else
|
||||
@@ -75,6 +74,10 @@ __thread uintptr_t thread_index;
|
||||
__thread bool is_worker_thread = false;
|
||||
__thread zval *os_environment = NULL;
|
||||
|
||||
void frankenphp_update_local_thread_context(bool is_worker) {
|
||||
is_worker_thread = is_worker;
|
||||
}
|
||||
|
||||
static void frankenphp_update_request_context() {
|
||||
/* the server context is stored on the go side, still SG(server_context) needs
|
||||
* to not be NULL */
|
||||
@@ -82,7 +85,7 @@ static void frankenphp_update_request_context() {
|
||||
/* status It is not reset by zend engine, set it to 200. */
|
||||
SG(sapi_headers).http_response_code = 200;
|
||||
|
||||
is_worker_thread = go_update_request_info(thread_index, &SG(request_info));
|
||||
go_update_request_info(thread_index, &SG(request_info));
|
||||
}
|
||||
|
||||
static void frankenphp_free_request_context() {
|
||||
@@ -206,11 +209,6 @@ PHPAPI void get_full_env(zval *track_vars_array) {
|
||||
go_getfullenv(thread_index, track_vars_array);
|
||||
}
|
||||
|
||||
void frankenphp_add_assoc_str_ex(zval *track_vars_array, char *key,
|
||||
size_t keylen, zend_string *val) {
|
||||
add_assoc_str_ex(track_vars_array, key, keylen, val);
|
||||
}
|
||||
|
||||
/* Adapted from php_request_startup() */
|
||||
static int frankenphp_worker_request_startup() {
|
||||
int retval = SUCCESS;
|
||||
@@ -432,10 +430,11 @@ PHP_FUNCTION(frankenphp_handle_request) {
|
||||
zend_unset_timeout();
|
||||
#endif
|
||||
|
||||
bool has_request = go_frankenphp_worker_handle_request_start(thread_index);
|
||||
struct go_frankenphp_worker_handle_request_start_return result =
|
||||
go_frankenphp_worker_handle_request_start(thread_index);
|
||||
if (frankenphp_worker_request_startup() == FAILURE
|
||||
/* Shutting down */
|
||||
|| !has_request) {
|
||||
|| !result.r0) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
@@ -450,24 +449,39 @@ PHP_FUNCTION(frankenphp_handle_request) {
|
||||
|
||||
/* Call the PHP func passed to frankenphp_handle_request() */
|
||||
zval retval = {0};
|
||||
zval *callback_ret = NULL;
|
||||
|
||||
fci.size = sizeof fci;
|
||||
fci.retval = &retval;
|
||||
if (zend_call_function(&fci, &fcc) == SUCCESS) {
|
||||
zval_ptr_dtor(&retval);
|
||||
fci.params = result.r1;
|
||||
fci.param_count = result.r1 == NULL ? 0 : 1;
|
||||
|
||||
if (zend_call_function(&fci, &fcc) == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
|
||||
callback_ret = &retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* If an exception occurred, print the message to the client before
|
||||
* closing the connection and bailout.
|
||||
* closing the connection.
|
||||
*/
|
||||
if (EG(exception) && !zend_is_unwind_exit(EG(exception)) &&
|
||||
!zend_is_graceful_exit(EG(exception))) {
|
||||
zend_exception_error(EG(exception), E_ERROR);
|
||||
zend_bailout();
|
||||
if (EG(exception)) {
|
||||
if (!zend_is_unwind_exit(EG(exception)) &&
|
||||
!zend_is_graceful_exit(EG(exception))) {
|
||||
zend_exception_error(EG(exception), E_ERROR);
|
||||
} else {
|
||||
/* exit() will jump directly to after php_execute_script */
|
||||
zend_bailout();
|
||||
}
|
||||
}
|
||||
|
||||
frankenphp_worker_request_shutdown();
|
||||
go_frankenphp_finish_worker_request(thread_index);
|
||||
go_frankenphp_finish_worker_request(thread_index, callback_ret);
|
||||
if (result.r1 != NULL) {
|
||||
zval_ptr_dtor(result.r1);
|
||||
}
|
||||
if (callback_ret != NULL) {
|
||||
zval_ptr_dtor(&retval);
|
||||
}
|
||||
|
||||
RETURN_TRUE;
|
||||
}
|
||||
@@ -493,7 +507,72 @@ PHP_FUNCTION(headers_send) {
|
||||
RETURN_LONG(sapi_send_headers());
|
||||
}
|
||||
|
||||
PHP_FUNCTION(mercure_publish) {
|
||||
zval *topics;
|
||||
zend_string *data = NULL, *id = NULL, *type = NULL;
|
||||
zend_bool private = 0;
|
||||
zend_long retry = 0;
|
||||
bool retry_is_null = 1;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(1, 6)
|
||||
Z_PARAM_ZVAL(topics)
|
||||
Z_PARAM_OPTIONAL
|
||||
Z_PARAM_STR_OR_NULL(data)
|
||||
Z_PARAM_BOOL(private)
|
||||
Z_PARAM_STR_OR_NULL(id)
|
||||
Z_PARAM_STR_OR_NULL(type)
|
||||
Z_PARAM_LONG_OR_NULL(retry, retry_is_null)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
if (Z_TYPE_P(topics) != IS_ARRAY && Z_TYPE_P(topics) != IS_STRING) {
|
||||
zend_argument_type_error(1, "must be of type array|string");
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
struct go_mercure_publish_return result =
|
||||
go_mercure_publish(thread_index, topics, data, private, id, type, retry);
|
||||
|
||||
switch (result.r1) {
|
||||
case 0:
|
||||
RETURN_STR(result.r0);
|
||||
case 1:
|
||||
zend_throw_exception(spl_ce_RuntimeException, "No Mercure hub configured",
|
||||
0);
|
||||
RETURN_THROWS();
|
||||
case 2:
|
||||
zend_throw_exception(spl_ce_RuntimeException, "Publish failed", 0);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
zend_throw_exception(spl_ce_RuntimeException,
|
||||
"FrankenPHP not built with Mercure support", 0);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
PHP_FUNCTION(frankenphp_log) {
|
||||
zend_string *message = NULL;
|
||||
zend_long level = 0;
|
||||
zval *context = NULL;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(1, 3)
|
||||
Z_PARAM_STR(message)
|
||||
Z_PARAM_OPTIONAL
|
||||
Z_PARAM_LONG(level)
|
||||
Z_PARAM_ARRAY(context)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
char *ret = NULL;
|
||||
ret = go_log_attrs(thread_index, message, level, context);
|
||||
if (ret != NULL) {
|
||||
zend_throw_exception(spl_ce_RuntimeException, ret, 0);
|
||||
free(ret);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
}
|
||||
|
||||
PHP_MINIT_FUNCTION(frankenphp) {
|
||||
register_frankenphp_symbols(module_number);
|
||||
|
||||
zend_function *func;
|
||||
|
||||
// Override putenv
|
||||
@@ -594,8 +673,9 @@ static char *frankenphp_read_cookies(void) {
|
||||
}
|
||||
|
||||
/* all variables with well defined keys can safely be registered like this */
|
||||
void frankenphp_register_trusted_var(zend_string *z_key, char *value,
|
||||
size_t val_len, HashTable *ht) {
|
||||
static inline void frankenphp_register_trusted_var(zend_string *z_key,
|
||||
char *value, size_t val_len,
|
||||
HashTable *ht) {
|
||||
if (value == NULL) {
|
||||
zval empty;
|
||||
ZVAL_EMPTY_STRING(&empty);
|
||||
@@ -786,7 +866,7 @@ static void frankenphp_register_variables(zval *track_vars_array) {
|
||||
}
|
||||
|
||||
static void frankenphp_log_message(const char *message, int syslog_type_int) {
|
||||
go_log((char *)message, syslog_type_int);
|
||||
go_log(thread_index, (char *)message, syslog_type_int);
|
||||
}
|
||||
|
||||
static char *frankenphp_getenv(const char *name, size_t name_len) {
|
||||
@@ -1029,8 +1109,7 @@ static char **cli_argv;
|
||||
* <johannes@php.net> Parts based on CGI SAPI Module by Rasmus Lerdorf, Stig
|
||||
* Bakken and Zeev Suraski
|
||||
*/
|
||||
static void cli_register_file_handles(bool no_close) /* {{{ */
|
||||
{
|
||||
static void cli_register_file_handles(void) {
|
||||
php_stream *s_in, *s_out, *s_err;
|
||||
php_stream_context *sc_in = NULL, *sc_out = NULL, *sc_err = NULL;
|
||||
zend_constant ic, oc, ec;
|
||||
@@ -1039,6 +1118,17 @@ static void cli_register_file_handles(bool no_close) /* {{{ */
|
||||
s_out = php_stream_open_wrapper_ex("php://stdout", "wb", 0, NULL, sc_out);
|
||||
s_err = php_stream_open_wrapper_ex("php://stderr", "wb", 0, NULL, sc_err);
|
||||
|
||||
/* Release stream resources, but don't free the underlying handles. Othewrise,
|
||||
* extensions which write to stderr or company during mshutdown/gshutdown
|
||||
* won't have the expected functionality.
|
||||
*/
|
||||
if (s_in)
|
||||
s_in->flags |= PHP_STREAM_FLAG_NO_RSCR_DTOR_CLOSE;
|
||||
if (s_out)
|
||||
s_out->flags |= PHP_STREAM_FLAG_NO_RSCR_DTOR_CLOSE;
|
||||
if (s_err)
|
||||
s_err->flags |= PHP_STREAM_FLAG_NO_RSCR_DTOR_CLOSE;
|
||||
|
||||
if (s_in == NULL || s_out == NULL || s_err == NULL) {
|
||||
if (s_in)
|
||||
php_stream_close(s_in);
|
||||
@@ -1049,12 +1139,6 @@ static void cli_register_file_handles(bool no_close) /* {{{ */
|
||||
return;
|
||||
}
|
||||
|
||||
if (no_close) {
|
||||
s_in->flags |= PHP_STREAM_FLAG_NO_CLOSE;
|
||||
s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE;
|
||||
s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE;
|
||||
}
|
||||
|
||||
/*s_in_process = s_in;*/
|
||||
|
||||
php_stream_to_zval(s_in, &ic.value);
|
||||
@@ -1073,7 +1157,6 @@ static void cli_register_file_handles(bool no_close) /* {{{ */
|
||||
ec.name = zend_string_init_interned("STDERR", sizeof("STDERR") - 1, 0);
|
||||
zend_register_constant(&ec);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static void sapi_cli_register_variables(zval *track_vars_array) /* {{{ */
|
||||
{
|
||||
@@ -1118,7 +1201,7 @@ static void *execute_script_cli(void *arg) {
|
||||
|
||||
php_embed_init(cli_argc, cli_argv);
|
||||
|
||||
cli_register_file_handles(false);
|
||||
cli_register_file_handles();
|
||||
zend_first_try {
|
||||
if (eval) {
|
||||
/* evaluate the cli_script as literal PHP code (php-cli -r "...") */
|
||||
|
||||
415
frankenphp.go
415
frankenphp.go
@@ -41,55 +41,71 @@ import (
|
||||
|
||||
type contextKeyStruct struct{}
|
||||
|
||||
var contextKey = contextKeyStruct{}
|
||||
|
||||
var (
|
||||
ErrInvalidRequest = errors.New("not a FrankenPHP request")
|
||||
ErrAlreadyStarted = errors.New("FrankenPHP is already started")
|
||||
ErrInvalidPHPVersion = errors.New("FrankenPHP is only compatible with PHP 8.2+")
|
||||
ErrMainThreadCreation = errors.New("error creating the main thread")
|
||||
ErrRequestContextCreation = errors.New("error during request context creation")
|
||||
ErrScriptExecution = errors.New("error during PHP script execution")
|
||||
ErrNotRunning = errors.New("FrankenPHP is not running. For proper configuration visit: https://frankenphp.dev/docs/config/#caddyfile-config")
|
||||
ErrInvalidRequest = errors.New("not a FrankenPHP request")
|
||||
ErrAlreadyStarted = errors.New("FrankenPHP is already started")
|
||||
ErrInvalidPHPVersion = errors.New("FrankenPHP is only compatible with PHP 8.2+")
|
||||
ErrMainThreadCreation = errors.New("error creating the main thread")
|
||||
ErrScriptExecution = errors.New("error during PHP script execution")
|
||||
ErrNotRunning = errors.New("FrankenPHP is not running. For proper configuration visit: https://frankenphp.dev/docs/config/#caddyfile-config")
|
||||
|
||||
isRunning bool
|
||||
ErrInvalidRequestPath = ErrRejected{"invalid request path", http.StatusBadRequest}
|
||||
ErrInvalidContentLengthHeader = ErrRejected{"invalid Content-Length header", http.StatusBadRequest}
|
||||
ErrMaxWaitTimeExceeded = ErrRejected{"maximum request handling time exceeded", http.StatusServiceUnavailable}
|
||||
|
||||
loggerMu sync.RWMutex
|
||||
logger *slog.Logger
|
||||
contextKey = contextKeyStruct{}
|
||||
serverHeader = []string{"FrankenPHP"}
|
||||
|
||||
isRunning bool
|
||||
onServerShutdown []func()
|
||||
|
||||
// Set default values to make Shutdown() idempotent
|
||||
globalMu sync.Mutex
|
||||
globalCtx = context.Background()
|
||||
globalLogger = slog.Default()
|
||||
|
||||
metrics Metrics = nullMetrics{}
|
||||
|
||||
maxWaitTime time.Duration
|
||||
)
|
||||
|
||||
type ErrRejected struct {
|
||||
message string
|
||||
status int
|
||||
}
|
||||
|
||||
func (e ErrRejected) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
type syslogLevel int
|
||||
|
||||
const (
|
||||
emerg syslogLevel = iota // system is unusable
|
||||
alert // action must be taken immediately
|
||||
crit // critical conditions
|
||||
err // error conditions
|
||||
warning // warning conditions
|
||||
notice // normal but significant condition
|
||||
info // informational
|
||||
debug // debug-level messages
|
||||
syslogLevelEmerg syslogLevel = iota // system is unusable
|
||||
syslogLevelAlert // action must be taken immediately
|
||||
syslogLevelCrit // critical conditions
|
||||
syslogLevelErr // error conditions
|
||||
syslogLevelWarn // warning conditions
|
||||
syslogLevelNotice // normal but significant condition
|
||||
syslogLevelInfo // informational
|
||||
syslogLevelDebug // debug-level messages
|
||||
)
|
||||
|
||||
func (l syslogLevel) String() string {
|
||||
switch l {
|
||||
case emerg:
|
||||
case syslogLevelEmerg:
|
||||
return "emerg"
|
||||
case alert:
|
||||
case syslogLevelAlert:
|
||||
return "alert"
|
||||
case crit:
|
||||
case syslogLevelCrit:
|
||||
return "crit"
|
||||
case err:
|
||||
case syslogLevelErr:
|
||||
return "err"
|
||||
case warning:
|
||||
case syslogLevelWarn:
|
||||
return "warning"
|
||||
case notice:
|
||||
case syslogLevelNotice:
|
||||
return "notice"
|
||||
case debug:
|
||||
case syslogLevelDebug:
|
||||
return "debug"
|
||||
default:
|
||||
return "info"
|
||||
@@ -137,10 +153,10 @@ func Config() PHPConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func calculateMaxThreads(opt *opt) (int, int, int, error) {
|
||||
func calculateMaxThreads(opt *opt) (numWorkers int, _ error) {
|
||||
maxProcs := runtime.GOMAXPROCS(0) * 2
|
||||
maxThreadsFromWorkers := 0
|
||||
|
||||
var numWorkers int
|
||||
for i, w := range opt.workers {
|
||||
if w.num <= 0 {
|
||||
// https://github.com/php/frankenphp/issues/126
|
||||
@@ -149,33 +165,53 @@ func calculateMaxThreads(opt *opt) (int, int, int, error) {
|
||||
metrics.TotalWorkers(w.name, w.num)
|
||||
|
||||
numWorkers += opt.workers[i].num
|
||||
|
||||
if w.maxThreads > 0 {
|
||||
if w.maxThreads < w.num {
|
||||
return 0, fmt.Errorf("worker max_threads (%d) must be greater or equal to worker num (%d) (%q)", w.maxThreads, w.num, w.fileName)
|
||||
}
|
||||
|
||||
if w.maxThreads > opt.maxThreads && opt.maxThreads > 0 {
|
||||
return 0, fmt.Errorf("worker max_threads (%d) cannot be greater than total max_threads (%d) (%q)", w.maxThreads, opt.maxThreads, w.fileName)
|
||||
}
|
||||
|
||||
maxThreadsFromWorkers += w.maxThreads - w.num
|
||||
}
|
||||
}
|
||||
|
||||
numThreadsIsSet := opt.numThreads > 0
|
||||
maxThreadsIsSet := opt.maxThreads != 0
|
||||
maxThreadsIsAuto := opt.maxThreads < 0 // maxthreads < 0 signifies auto mode (see phpmaintread.go)
|
||||
|
||||
// if max_threads is only defined in workers, scale up to the sum of all worker max_threads
|
||||
if !maxThreadsIsSet && maxThreadsFromWorkers > 0 {
|
||||
maxThreadsIsSet = true
|
||||
if numThreadsIsSet {
|
||||
opt.maxThreads = opt.numThreads + maxThreadsFromWorkers
|
||||
} else {
|
||||
opt.maxThreads = numWorkers + 1 + maxThreadsFromWorkers
|
||||
}
|
||||
}
|
||||
|
||||
if numThreadsIsSet && !maxThreadsIsSet {
|
||||
opt.maxThreads = opt.numThreads
|
||||
if opt.numThreads <= numWorkers {
|
||||
err := fmt.Errorf("num_threads (%d) must be greater than the number of worker threads (%d)", opt.numThreads, numWorkers)
|
||||
return 0, 0, 0, err
|
||||
return 0, fmt.Errorf("num_threads (%d) must be greater than the number of worker threads (%d)", opt.numThreads, numWorkers)
|
||||
}
|
||||
|
||||
return opt.numThreads, numWorkers, opt.maxThreads, nil
|
||||
return numWorkers, nil
|
||||
}
|
||||
|
||||
if maxThreadsIsSet && !numThreadsIsSet {
|
||||
opt.numThreads = numWorkers + 1
|
||||
if !maxThreadsIsAuto && opt.numThreads > opt.maxThreads {
|
||||
err := fmt.Errorf("max_threads (%d) must be greater than the number of worker threads (%d)", opt.maxThreads, numWorkers)
|
||||
return 0, 0, 0, err
|
||||
return 0, fmt.Errorf("max_threads (%d) must be greater than the number of worker threads (%d)", opt.maxThreads, numWorkers)
|
||||
}
|
||||
|
||||
return opt.numThreads, numWorkers, opt.maxThreads, nil
|
||||
return numWorkers, nil
|
||||
}
|
||||
|
||||
if !numThreadsIsSet {
|
||||
if !maxThreadsIsSet && !numThreadsIsSet {
|
||||
if numWorkers >= maxProcs {
|
||||
// Start at least as many threads as workers, and keep a free thread to handle requests in non-worker mode
|
||||
opt.numThreads = numWorkers + 1
|
||||
@@ -184,21 +220,19 @@ func calculateMaxThreads(opt *opt) (int, int, int, error) {
|
||||
}
|
||||
opt.maxThreads = opt.numThreads
|
||||
|
||||
return opt.numThreads, numWorkers, opt.maxThreads, nil
|
||||
return numWorkers, nil
|
||||
}
|
||||
|
||||
// both num_threads and max_threads are set
|
||||
if opt.numThreads <= numWorkers {
|
||||
err := fmt.Errorf("num_threads (%d) must be greater than the number of worker threads (%d)", opt.numThreads, numWorkers)
|
||||
return 0, 0, 0, err
|
||||
return 0, fmt.Errorf("num_threads (%d) must be greater than the number of worker threads (%d)", opt.numThreads, numWorkers)
|
||||
}
|
||||
|
||||
if !maxThreadsIsAuto && opt.maxThreads < opt.numThreads {
|
||||
err := fmt.Errorf("max_threads (%d) must be greater than or equal to num_threads (%d)", opt.maxThreads, opt.numThreads)
|
||||
return 0, 0, 0, err
|
||||
return 0, fmt.Errorf("max_threads (%d) must be greater than or equal to num_threads (%d)", opt.maxThreads, opt.numThreads)
|
||||
}
|
||||
|
||||
return opt.numThreads, numWorkers, opt.maxThreads, nil
|
||||
return numWorkers, nil
|
||||
}
|
||||
|
||||
// Init starts the PHP runtime and the configured workers.
|
||||
@@ -217,73 +251,102 @@ func Init(options ...Option) error {
|
||||
opt := &opt{}
|
||||
for _, o := range options {
|
||||
if err := o(opt); err != nil {
|
||||
Shutdown()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opt.logger == nil {
|
||||
// set a default logger
|
||||
// to disable logging, set the logger to slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
l := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
globalMu.Lock()
|
||||
|
||||
loggerMu.Lock()
|
||||
logger = l
|
||||
loggerMu.Unlock()
|
||||
} else {
|
||||
loggerMu.Lock()
|
||||
logger = opt.logger
|
||||
loggerMu.Unlock()
|
||||
if opt.ctx != nil {
|
||||
globalCtx = opt.ctx
|
||||
opt.ctx = nil
|
||||
}
|
||||
|
||||
if opt.logger != nil {
|
||||
globalLogger = opt.logger
|
||||
opt.logger = nil
|
||||
}
|
||||
|
||||
globalMu.Unlock()
|
||||
|
||||
if opt.metrics != nil {
|
||||
metrics = opt.metrics
|
||||
}
|
||||
|
||||
maxWaitTime = opt.maxWaitTime
|
||||
|
||||
totalThreadCount, workerThreadCount, maxThreadCount, err := calculateMaxThreads(opt)
|
||||
workerThreadCount, err := calculateMaxThreads(opt)
|
||||
if err != nil {
|
||||
Shutdown()
|
||||
return err
|
||||
}
|
||||
|
||||
metrics.TotalThreads(totalThreadCount)
|
||||
metrics.TotalThreads(opt.numThreads)
|
||||
|
||||
config := Config()
|
||||
|
||||
if config.Version.MajorVersion < 8 || (config.Version.MajorVersion == 8 && config.Version.MinorVersion < 2) {
|
||||
Shutdown()
|
||||
return ErrInvalidPHPVersion
|
||||
}
|
||||
|
||||
if config.ZTS {
|
||||
if !config.ZendMaxExecutionTimers && runtime.GOOS == "linux" {
|
||||
logger.Warn(`Zend Max Execution Timers are not enabled, timeouts (e.g. "max_execution_time") are disabled, recompile PHP with the "--enable-zend-max-execution-timers" configuration option to fix this issue`)
|
||||
if globalLogger.Enabled(globalCtx, slog.LevelWarn) {
|
||||
globalLogger.LogAttrs(globalCtx, slog.LevelWarn, `Zend Max Execution Timers are not enabled, timeouts (e.g. "max_execution_time") are disabled, recompile PHP with the "--enable-zend-max-execution-timers" configuration option to fix this issue`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
totalThreadCount = 1
|
||||
logger.Warn(`ZTS is not enabled, only 1 thread will be available, recompile PHP using the "--enable-zts" configuration option or performance will be degraded`)
|
||||
opt.numThreads = 1
|
||||
|
||||
if globalLogger.Enabled(globalCtx, slog.LevelWarn) {
|
||||
globalLogger.LogAttrs(globalCtx, slog.LevelWarn, `ZTS is not enabled, only 1 thread will be available, recompile PHP using the "--enable-zts" configuration option or performance will be degraded`)
|
||||
}
|
||||
}
|
||||
|
||||
mainThread, err := initPHPThreads(totalThreadCount, maxThreadCount, opt.phpIni)
|
||||
mainThread, err := initPHPThreads(opt.numThreads, opt.maxThreads, opt.phpIni)
|
||||
if err != nil {
|
||||
Shutdown()
|
||||
return err
|
||||
}
|
||||
|
||||
regularRequestChan = make(chan *frankenPHPContext, totalThreadCount-workerThreadCount)
|
||||
regularThreads = make([]*phpThread, 0, totalThreadCount-workerThreadCount)
|
||||
for i := 0; i < totalThreadCount-workerThreadCount; i++ {
|
||||
regularRequestChan = make(chan contextHolder)
|
||||
regularThreads = make([]*phpThread, 0, opt.numThreads-workerThreadCount)
|
||||
for i := 0; i < opt.numThreads-workerThreadCount; i++ {
|
||||
convertToRegularThread(getInactivePHPThread())
|
||||
}
|
||||
|
||||
if err := initWorkers(opt.workers); err != nil {
|
||||
Shutdown()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := initWatchers(opt); err != nil {
|
||||
Shutdown()
|
||||
return err
|
||||
}
|
||||
|
||||
initAutoScaling(mainThread)
|
||||
|
||||
ctx := context.Background()
|
||||
logger.LogAttrs(ctx, slog.LevelInfo, "FrankenPHP started 🐘", slog.String("php_version", Version().Version), slog.Int("num_threads", mainThread.numThreads), slog.Int("max_threads", mainThread.maxThreads))
|
||||
if EmbeddedAppPath != "" {
|
||||
logger.LogAttrs(ctx, slog.LevelInfo, "embedded PHP app 📦", slog.String("path", EmbeddedAppPath))
|
||||
if globalLogger.Enabled(globalCtx, slog.LevelInfo) {
|
||||
globalLogger.LogAttrs(globalCtx, slog.LevelInfo, "FrankenPHP started 🐘", slog.String("php_version", Version().Version), slog.Int("num_threads", mainThread.numThreads), slog.Int("max_threads", mainThread.maxThreads))
|
||||
|
||||
if EmbeddedAppPath != "" {
|
||||
globalLogger.LogAttrs(globalCtx, slog.LevelInfo, "embedded PHP app 📦", slog.String("path", EmbeddedAppPath))
|
||||
}
|
||||
}
|
||||
|
||||
// register the startup/shutdown hooks (mainly useful for extensions)
|
||||
onServerShutdown = nil
|
||||
for _, w := range opt.workers {
|
||||
if w.onServerStartup != nil {
|
||||
w.onServerStartup()
|
||||
}
|
||||
if w.onServerShutdown != nil {
|
||||
onServerShutdown = append(onServerShutdown, w.onServerShutdown)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -295,7 +358,12 @@ func Shutdown() {
|
||||
return
|
||||
}
|
||||
|
||||
drainWatcher()
|
||||
// call the shutdown hooks (mainly useful for extensions)
|
||||
for _, fn := range onServerShutdown {
|
||||
fn()
|
||||
}
|
||||
|
||||
drainWatchers()
|
||||
drainAutoScaling()
|
||||
drainPHPThreads()
|
||||
|
||||
@@ -307,41 +375,52 @@ func Shutdown() {
|
||||
}
|
||||
|
||||
isRunning = false
|
||||
logger.Debug("FrankenPHP shut down")
|
||||
if globalLogger.Enabled(globalCtx, slog.LevelDebug) {
|
||||
globalLogger.LogAttrs(globalCtx, slog.LevelDebug, "FrankenPHP shut down")
|
||||
}
|
||||
|
||||
resetGlobals()
|
||||
}
|
||||
|
||||
// ServeHTTP executes a PHP script according to the given context.
|
||||
func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error {
|
||||
h := responseWriter.Header()
|
||||
if h["Server"] == nil {
|
||||
h["Server"] = serverHeader
|
||||
}
|
||||
|
||||
if !isRunning {
|
||||
return ErrNotRunning
|
||||
}
|
||||
|
||||
fc, ok := fromContext(request.Context())
|
||||
ctx := request.Context()
|
||||
fc, ok := fromContext(ctx)
|
||||
|
||||
ch := contextHolder{ctx, fc}
|
||||
|
||||
if !ok {
|
||||
return ErrInvalidRequest
|
||||
}
|
||||
|
||||
fc.responseWriter = responseWriter
|
||||
|
||||
if !fc.validate() {
|
||||
return nil
|
||||
if err := fc.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Detect if a worker is available to handle this request
|
||||
if fc.worker != nil {
|
||||
fc.worker.handleRequest(fc)
|
||||
|
||||
return nil
|
||||
return fc.worker.handleRequest(ch)
|
||||
}
|
||||
|
||||
// If no worker was available, send the request to non-worker threads
|
||||
handleRequestWithRegularPHPThreads(fc)
|
||||
return nil
|
||||
return handleRequestWithRegularPHPThreads(ch)
|
||||
}
|
||||
|
||||
//export go_ub_write
|
||||
func go_ub_write(threadIndex C.uintptr_t, cBuf *C.char, length C.int) (C.size_t, C.bool) {
|
||||
fc := phpThreads[threadIndex].getRequestContext()
|
||||
thread := phpThreads[threadIndex]
|
||||
fc := thread.frankenPHPContext()
|
||||
|
||||
if fc.isDone {
|
||||
return 0, C.bool(true)
|
||||
@@ -356,14 +435,27 @@ func go_ub_write(threadIndex C.uintptr_t, cBuf *C.char, length C.int) (C.size_t,
|
||||
writer = fc.responseWriter
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
|
||||
i, e := writer.Write(unsafe.Slice((*byte)(unsafe.Pointer(cBuf)), length))
|
||||
if e != nil {
|
||||
fc.logger.LogAttrs(context.Background(), slog.LevelWarn, "write error", slog.Any("error", e))
|
||||
ctx = thread.context()
|
||||
|
||||
if fc.logger.Enabled(ctx, slog.LevelWarn) {
|
||||
fc.logger.LogAttrs(ctx, slog.LevelWarn, "write error", slog.Any("error", e))
|
||||
}
|
||||
}
|
||||
|
||||
if fc.responseWriter == nil {
|
||||
// probably starting a worker script, log the output
|
||||
fc.logger.Info(writer.(*bytes.Buffer).String())
|
||||
|
||||
if ctx == nil {
|
||||
ctx = thread.context()
|
||||
}
|
||||
|
||||
if fc.logger.Enabled(ctx, slog.LevelInfo) {
|
||||
fc.logger.LogAttrs(ctx, slog.LevelInfo, writer.(*bytes.Buffer).String())
|
||||
}
|
||||
}
|
||||
|
||||
return C.size_t(i), C.bool(fc.clientHasClosed())
|
||||
@@ -372,12 +464,15 @@ func go_ub_write(threadIndex C.uintptr_t, cBuf *C.char, length C.int) (C.size_t,
|
||||
//export go_apache_request_headers
|
||||
func go_apache_request_headers(threadIndex C.uintptr_t) (*C.go_string, C.size_t) {
|
||||
thread := phpThreads[threadIndex]
|
||||
fc := thread.getRequestContext()
|
||||
ctx := thread.context()
|
||||
fc := thread.frankenPHPContext()
|
||||
|
||||
if fc.responseWriter == nil {
|
||||
// worker mode, not handling a request
|
||||
|
||||
logger.LogAttrs(context.Background(), slog.LevelDebug, "apache_request_headers() called in non-HTTP context", slog.String("worker", fc.scriptFilename))
|
||||
if globalLogger.Enabled(ctx, slog.LevelDebug) {
|
||||
globalLogger.LogAttrs(ctx, slog.LevelDebug, "apache_request_headers() called in non-HTTP context", slog.String("worker", fc.worker.name))
|
||||
}
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
@@ -405,10 +500,13 @@ func go_apache_request_headers(threadIndex C.uintptr_t) (*C.go_string, C.size_t)
|
||||
return sd, C.size_t(len(fc.request.Header))
|
||||
}
|
||||
|
||||
func addHeader(fc *frankenPHPContext, cString *C.char, length C.int) {
|
||||
func addHeader(ctx context.Context, fc *frankenPHPContext, cString *C.char, length C.int) {
|
||||
key, val := splitRawHeader(cString, int(length))
|
||||
if key == "" {
|
||||
fc.logger.LogAttrs(context.Background(), slog.LevelDebug, "invalid header", slog.String("header", C.GoStringN(cString, length)))
|
||||
if fc.logger.Enabled(ctx, slog.LevelDebug) {
|
||||
fc.logger.LogAttrs(ctx, slog.LevelDebug, "invalid header", slog.String("header", C.GoStringN(cString, length)))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
fc.responseWriter.Header().Add(key, val)
|
||||
@@ -447,8 +545,8 @@ func splitRawHeader(rawHeader *C.char, length int) (string, string) {
|
||||
|
||||
//export go_write_headers
|
||||
func go_write_headers(threadIndex C.uintptr_t, status C.int, headers *C.zend_llist) C.bool {
|
||||
fc := phpThreads[threadIndex].getRequestContext()
|
||||
|
||||
thread := phpThreads[threadIndex]
|
||||
fc := thread.frankenPHPContext()
|
||||
if fc == nil {
|
||||
return C.bool(false)
|
||||
}
|
||||
@@ -466,13 +564,27 @@ func go_write_headers(threadIndex C.uintptr_t, status C.int, headers *C.zend_lli
|
||||
for current != nil {
|
||||
h := (*C.sapi_header_struct)(unsafe.Pointer(&(current.data)))
|
||||
|
||||
addHeader(fc, h.header, C.int(h.header_len))
|
||||
addHeader(thread.context(), fc, h.header, C.int(h.header_len))
|
||||
current = current.next
|
||||
}
|
||||
|
||||
fc.responseWriter.WriteHeader(int(status))
|
||||
goStatus := int(status)
|
||||
|
||||
if status >= 100 && status < 200 {
|
||||
// go panics on invalid status code
|
||||
// https://github.com/golang/go/blob/9b8742f2e79438b9442afa4c0a0139d3937ea33f/src/net/http/server.go#L1162
|
||||
if goStatus < 100 || goStatus > 999 {
|
||||
ctx := thread.context()
|
||||
|
||||
if globalLogger.Enabled(ctx, slog.LevelWarn) {
|
||||
globalLogger.LogAttrs(ctx, slog.LevelWarn, "Invalid response status code", slog.Int("status_code", goStatus))
|
||||
}
|
||||
|
||||
goStatus = 500
|
||||
}
|
||||
|
||||
fc.responseWriter.WriteHeader(goStatus)
|
||||
|
||||
if goStatus < 200 {
|
||||
// Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses
|
||||
h := fc.responseWriter.Header()
|
||||
for k := range h {
|
||||
@@ -485,8 +597,13 @@ func go_write_headers(threadIndex C.uintptr_t, status C.int, headers *C.zend_lli
|
||||
|
||||
//export go_sapi_flush
|
||||
func go_sapi_flush(threadIndex C.uintptr_t) bool {
|
||||
fc := phpThreads[threadIndex].getRequestContext()
|
||||
if fc == nil || fc.responseWriter == nil {
|
||||
thread := phpThreads[threadIndex]
|
||||
fc := thread.frankenPHPContext()
|
||||
if fc == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if fc.responseWriter == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -495,7 +612,11 @@ func go_sapi_flush(threadIndex C.uintptr_t) bool {
|
||||
}
|
||||
|
||||
if err := http.NewResponseController(fc.responseWriter).Flush(); err != nil {
|
||||
logger.LogAttrs(context.Background(), slog.LevelWarn, "the current responseWriter is not a flusher, if you are not using a custom build, please report this issue", slog.Any("error", err))
|
||||
ctx := thread.context()
|
||||
|
||||
if globalLogger.Enabled(ctx, slog.LevelWarn) {
|
||||
globalLogger.LogAttrs(ctx, slog.LevelWarn, "the current responseWriter is not a flusher, if you are not using a custom build, please report this issue", slog.Any("error", err))
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -503,7 +624,7 @@ func go_sapi_flush(threadIndex C.uintptr_t) bool {
|
||||
|
||||
//export go_read_post
|
||||
func go_read_post(threadIndex C.uintptr_t, cBuf *C.char, countBytes C.size_t) (readBytes C.size_t) {
|
||||
fc := phpThreads[threadIndex].getRequestContext()
|
||||
fc := phpThreads[threadIndex].frankenPHPContext()
|
||||
|
||||
if fc.responseWriter == nil {
|
||||
return 0
|
||||
@@ -522,8 +643,12 @@ func go_read_post(threadIndex C.uintptr_t, cBuf *C.char, countBytes C.size_t) (r
|
||||
|
||||
//export go_read_cookies
|
||||
func go_read_cookies(threadIndex C.uintptr_t) *C.char {
|
||||
cookies := phpThreads[threadIndex].getRequestContext().request.Header.Values("Cookie")
|
||||
cookie := strings.Join(cookies, "; ")
|
||||
request := phpThreads[threadIndex].frankenPHPContext().request
|
||||
if request == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cookie := strings.Join(request.Header.Values("Cookie"), "; ")
|
||||
if cookie == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -535,34 +660,97 @@ func go_read_cookies(threadIndex C.uintptr_t) *C.char {
|
||||
return C.CString(cookie)
|
||||
}
|
||||
|
||||
//export go_log
|
||||
func go_log(message *C.char, level C.int) {
|
||||
m := C.GoString(message)
|
||||
func getLogger(threadIndex C.uintptr_t) (*slog.Logger, context.Context) {
|
||||
ctxHolder := phpThreads[threadIndex]
|
||||
if ctxHolder == nil {
|
||||
return globalLogger, globalCtx
|
||||
}
|
||||
|
||||
var le syslogLevel
|
||||
if level < C.int(emerg) || level > C.int(debug) {
|
||||
le = info
|
||||
} else {
|
||||
ctx := ctxHolder.context()
|
||||
if ctxHolder.handler == nil {
|
||||
return globalLogger, ctx
|
||||
}
|
||||
|
||||
fCtx := ctxHolder.frankenPHPContext()
|
||||
if fCtx == nil || fCtx.logger == nil {
|
||||
return globalLogger, ctx
|
||||
}
|
||||
|
||||
return fCtx.logger, ctx
|
||||
}
|
||||
|
||||
//export go_log
|
||||
func go_log(threadIndex C.uintptr_t, message *C.char, level C.int) {
|
||||
logger, ctx := getLogger(threadIndex)
|
||||
|
||||
m := C.GoString(message)
|
||||
le := syslogLevelInfo
|
||||
|
||||
if level >= C.int(syslogLevelEmerg) && level <= C.int(syslogLevelDebug) {
|
||||
le = syslogLevel(level)
|
||||
}
|
||||
|
||||
switch le {
|
||||
case emerg, alert, crit, err:
|
||||
logger.LogAttrs(context.Background(), slog.LevelError, m, slog.String("syslog_level", syslogLevel(level).String()))
|
||||
case syslogLevelEmerg, syslogLevelAlert, syslogLevelCrit, syslogLevelErr:
|
||||
if logger.Enabled(ctx, slog.LevelError) {
|
||||
logger.LogAttrs(ctx, slog.LevelError, m, slog.String("syslog_level", le.String()))
|
||||
}
|
||||
|
||||
case warning:
|
||||
logger.LogAttrs(context.Background(), slog.LevelWarn, m, slog.String("syslog_level", syslogLevel(level).String()))
|
||||
case debug:
|
||||
logger.LogAttrs(context.Background(), slog.LevelDebug, m, slog.String("syslog_level", syslogLevel(level).String()))
|
||||
case syslogLevelWarn:
|
||||
if logger.Enabled(ctx, slog.LevelWarn) {
|
||||
logger.LogAttrs(ctx, slog.LevelWarn, m, slog.String("syslog_level", le.String()))
|
||||
}
|
||||
|
||||
case syslogLevelDebug:
|
||||
if logger.Enabled(ctx, slog.LevelDebug) {
|
||||
logger.LogAttrs(ctx, slog.LevelDebug, m, slog.String("syslog_level", le.String()))
|
||||
}
|
||||
|
||||
default:
|
||||
logger.LogAttrs(context.Background(), slog.LevelInfo, m, slog.String("syslog_level", syslogLevel(level).String()))
|
||||
if logger.Enabled(ctx, slog.LevelInfo) {
|
||||
logger.LogAttrs(ctx, slog.LevelInfo, m, slog.String("syslog_level", le.String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//export go_log_attrs
|
||||
func go_log_attrs(threadIndex C.uintptr_t, message *C.zend_string, cLevel C.zend_long, cAttrs *C.zval) *C.char {
|
||||
logger, ctx := getLogger(threadIndex)
|
||||
|
||||
level := slog.Level(cLevel)
|
||||
|
||||
if !logger.Enabled(ctx, level) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var attrs map[string]any
|
||||
|
||||
if cAttrs != nil {
|
||||
var err error
|
||||
if attrs, err = GoMap[any](unsafe.Pointer(*(**C.zend_array)(unsafe.Pointer(&cAttrs.value[0])))); err != nil {
|
||||
// PHP exception message.
|
||||
return C.CString("Failed to log message: converting attrs: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogAttrs(ctx, level, GoString(unsafe.Pointer(message)), mapToAttr(attrs)...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapToAttr(input map[string]any) []slog.Attr {
|
||||
out := make([]slog.Attr, 0, len(input))
|
||||
|
||||
for key, val := range input {
|
||||
out = append(out, slog.Any(key, val))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
//export go_is_context_done
|
||||
func go_is_context_done(threadIndex C.uintptr_t) C.bool {
|
||||
return C.bool(phpThreads[threadIndex].getRequestContext().isDone)
|
||||
return C.bool(phpThreads[threadIndex].frankenPHPContext().isDone)
|
||||
}
|
||||
|
||||
// ExecuteScriptCLI executes the PHP script passed as parameter.
|
||||
@@ -611,3 +799,12 @@ func timeoutChan(timeout time.Duration) <-chan time.Time {
|
||||
|
||||
return time.After(timeout)
|
||||
}
|
||||
|
||||
func resetGlobals() {
|
||||
globalMu.Lock()
|
||||
globalCtx = context.Background()
|
||||
globalLogger = slog.Default()
|
||||
workers = nil
|
||||
watcherIsEnabled = false
|
||||
globalMu.Unlock()
|
||||
}
|
||||
|
||||
10
frankenphp.h
10
frankenphp.h
@@ -23,12 +23,6 @@ typedef struct ht_key_value_pair {
|
||||
size_t val_len;
|
||||
} ht_key_value_pair;
|
||||
|
||||
typedef struct php_variable {
|
||||
const char *var;
|
||||
size_t data_len;
|
||||
char *data;
|
||||
} php_variable;
|
||||
|
||||
typedef struct frankenphp_version {
|
||||
unsigned char major_version;
|
||||
unsigned char minor_version;
|
||||
@@ -40,7 +34,6 @@ typedef struct frankenphp_version {
|
||||
frankenphp_version frankenphp_get_version();
|
||||
|
||||
typedef struct frankenphp_config {
|
||||
frankenphp_version version;
|
||||
bool zts;
|
||||
bool zend_signals;
|
||||
bool zend_max_execution_timers;
|
||||
@@ -52,6 +45,7 @@ bool frankenphp_new_php_thread(uintptr_t thread_index);
|
||||
|
||||
bool frankenphp_shutdown_dummy_request(void);
|
||||
int frankenphp_execute_script(char *file_name);
|
||||
void frankenphp_update_local_thread_context(bool is_worker);
|
||||
|
||||
int frankenphp_execute_script_cli(char *script, int argc, char **argv,
|
||||
bool eval);
|
||||
@@ -65,8 +59,6 @@ void frankenphp_register_variable_safe(char *key, char *var, size_t val_len,
|
||||
zend_string *frankenphp_init_persistent_string(const char *string, size_t len);
|
||||
int frankenphp_reset_opcache(void);
|
||||
int frankenphp_get_current_memory_limit();
|
||||
void frankenphp_add_assoc_str_ex(zval *track_vars_array, char *key,
|
||||
size_t keylen, zend_string *val);
|
||||
|
||||
void frankenphp_register_single(zend_string *z_key, char *value, size_t val_len,
|
||||
zval *track_vars_array);
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
|
||||
/** @generate-class-entries */
|
||||
|
||||
/** @var int */
|
||||
const FRANKENPHP_LOG_LEVEL_DEBUG = -4;
|
||||
|
||||
/** @var int */
|
||||
const FRANKENPHP_LOG_LEVEL_INFO = 0;
|
||||
|
||||
/** @var int */
|
||||
const FRANKENPHP_LOG_LEVEL_WARN = 4;
|
||||
|
||||
/** @var int */
|
||||
const FRANKENPHP_LOG_LEVEL_ERROR = 8;
|
||||
|
||||
function frankenphp_handle_request(callable $callback): bool {}
|
||||
|
||||
function headers_send(int $status = 200): int {}
|
||||
@@ -32,3 +44,13 @@ function frankenphp_response_headers(): array|bool {}
|
||||
*/
|
||||
function apache_response_headers(): array|bool {}
|
||||
|
||||
/**
|
||||
* @param string|string[] $topics
|
||||
*/
|
||||
function mercure_publish(string|array $topics, string $data = '', bool $private = false, ?string $id = null, ?string $type = null, ?int $retry = null): string {}
|
||||
|
||||
/**
|
||||
* @param int $level The importance or severity of a log event. The higher the level, the more important or severe the event. For more details, see: https://pkg.go.dev/log/slog#Level
|
||||
* array<string, any> $context Values of the array will be converted to the corresponding Go type (if supported by FrankenPHP) and added to the context of the structured logs using https://pkg.go.dev/log/slog#Attr
|
||||
*/
|
||||
function frankenphp_log(string $message, int $level = 0, array $context = []): void {}
|
||||
|
||||
@@ -1,52 +1,75 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 05ebde17137c559e891362fba6524fad1e0a2dfe */
|
||||
* Stub hash: 60f0d27c04f94d7b24c052e91ef294595a2bc421 */
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_handle_request, 0, 1,
|
||||
_IS_BOOL, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_handle_request, 0, 1, _IS_BOOL, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_headers_send, 0, 0, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, status, IS_LONG, 0, "200")
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, status, IS_LONG, 0, "200")
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_finish_request, 0, 0,
|
||||
_IS_BOOL, 0)
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_finish_request, 0, 0, _IS_BOOL, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_fastcgi_finish_request arginfo_frankenphp_finish_request
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_request_headers, 0,
|
||||
0, IS_ARRAY, 0)
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_request_headers, 0, 0, IS_ARRAY, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_apache_request_headers arginfo_frankenphp_request_headers
|
||||
|
||||
#define arginfo_getallheaders arginfo_frankenphp_request_headers
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_frankenphp_response_headers, 0,
|
||||
0, MAY_BE_ARRAY | MAY_BE_BOOL)
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_frankenphp_response_headers, 0, 0, MAY_BE_ARRAY|MAY_BE_BOOL)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_apache_response_headers arginfo_frankenphp_response_headers
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_mercure_publish, 0, 1, IS_STRING, 0)
|
||||
ZEND_ARG_TYPE_MASK(0, topics, MAY_BE_STRING|MAY_BE_ARRAY, NULL)
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, data, IS_STRING, 0, "\'\'")
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, private, _IS_BOOL, 0, "false")
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, id, IS_STRING, 1, "null")
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, type, IS_STRING, 1, "null")
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, retry, IS_LONG, 1, "null")
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_log, 0, 1, IS_VOID, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, message, IS_STRING, 0)
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, level, IS_LONG, 0, "0")
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, context, IS_ARRAY, 0, "[]")
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
|
||||
ZEND_FUNCTION(frankenphp_handle_request);
|
||||
ZEND_FUNCTION(headers_send);
|
||||
ZEND_FUNCTION(frankenphp_finish_request);
|
||||
ZEND_FUNCTION(frankenphp_request_headers);
|
||||
ZEND_FUNCTION(frankenphp_response_headers);
|
||||
ZEND_FUNCTION(mercure_publish);
|
||||
ZEND_FUNCTION(frankenphp_log);
|
||||
|
||||
|
||||
// clang-format off
|
||||
static const zend_function_entry ext_functions[] = {
|
||||
ZEND_FE(frankenphp_handle_request, arginfo_frankenphp_handle_request)
|
||||
ZEND_FE(headers_send, arginfo_headers_send)
|
||||
ZEND_FE(frankenphp_finish_request, arginfo_frankenphp_finish_request)
|
||||
ZEND_FALIAS(fastcgi_finish_request, frankenphp_finish_request, arginfo_fastcgi_finish_request)
|
||||
ZEND_FE(frankenphp_request_headers, arginfo_frankenphp_request_headers)
|
||||
ZEND_FALIAS(apache_request_headers, frankenphp_request_headers, arginfo_apache_request_headers)
|
||||
ZEND_FALIAS(getallheaders, frankenphp_request_headers, arginfo_getallheaders)
|
||||
ZEND_FE(frankenphp_response_headers, arginfo_frankenphp_response_headers)
|
||||
ZEND_FALIAS(apache_response_headers, frankenphp_response_headers, arginfo_apache_response_headers)
|
||||
ZEND_FE_END
|
||||
ZEND_FE(frankenphp_handle_request, arginfo_frankenphp_handle_request)
|
||||
ZEND_FE(headers_send, arginfo_headers_send)
|
||||
ZEND_FE(frankenphp_finish_request, arginfo_frankenphp_finish_request)
|
||||
ZEND_FALIAS(fastcgi_finish_request, frankenphp_finish_request, arginfo_fastcgi_finish_request)
|
||||
ZEND_FE(frankenphp_request_headers, arginfo_frankenphp_request_headers)
|
||||
ZEND_FALIAS(apache_request_headers, frankenphp_request_headers, arginfo_apache_request_headers)
|
||||
ZEND_FALIAS(getallheaders, frankenphp_request_headers, arginfo_getallheaders)
|
||||
ZEND_FE(frankenphp_response_headers, arginfo_frankenphp_response_headers)
|
||||
ZEND_FALIAS(apache_response_headers, frankenphp_response_headers, arginfo_apache_response_headers)
|
||||
ZEND_FE(mercure_publish, arginfo_mercure_publish)
|
||||
ZEND_FE(frankenphp_log, arginfo_frankenphp_log)
|
||||
ZEND_FE_END
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static void register_frankenphp_symbols(int module_number)
|
||||
{
|
||||
REGISTER_LONG_CONSTANT("FRANKENPHP_LOG_LEVEL_DEBUG", -4, CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("FRANKENPHP_LOG_LEVEL_INFO", 0, CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("FRANKENPHP_LOG_LEVEL_WARN", 4, CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("FRANKENPHP_LOG_LEVEL_ERROR", 8, CONST_PERSISTENT);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -31,10 +32,6 @@ import (
|
||||
"github.com/dunglas/frankenphp/internal/fastabs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap/exp/zapslog"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"go.uber.org/zap/zaptest"
|
||||
"go.uber.org/zap/zaptest/observer"
|
||||
)
|
||||
|
||||
type testOptions struct {
|
||||
@@ -46,6 +43,7 @@ type testOptions struct {
|
||||
realServer bool
|
||||
logger *slog.Logger
|
||||
initOpts []frankenphp.Option
|
||||
requestOpts []frankenphp.RequestOption
|
||||
phpIni map[string]string
|
||||
}
|
||||
|
||||
@@ -60,10 +58,6 @@ func runTest(t *testing.T, test func(func(http.ResponseWriter, *http.Request), *
|
||||
cwd, _ := os.Getwd()
|
||||
testDataDir := cwd + "/testdata/"
|
||||
|
||||
if opts.logger == nil {
|
||||
opts.logger = slog.New(zapslog.NewHandler(zaptest.NewLogger(t).Core()))
|
||||
}
|
||||
|
||||
initOpts := []frankenphp.Option{frankenphp.WithLogger(opts.logger)}
|
||||
if opts.workerScript != "" {
|
||||
workerOpts := []frankenphp.WorkerOption{
|
||||
@@ -78,15 +72,19 @@ func runTest(t *testing.T, test func(func(http.ResponseWriter, *http.Request), *
|
||||
}
|
||||
|
||||
err := frankenphp.Init(initOpts...)
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
defer frankenphp.Shutdown()
|
||||
|
||||
opts.requestOpts = append(opts.requestOpts, frankenphp.WithRequestDocumentRoot(testDataDir, false))
|
||||
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := frankenphp.NewRequestWithContext(r, frankenphp.WithRequestDocumentRoot(testDataDir, false))
|
||||
req, err := frankenphp.NewRequestWithContext(r, opts.requestOpts...)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = frankenphp.ServeHTTP(w, req)
|
||||
assert.NoError(t, err)
|
||||
if err != nil && !errors.As(err, &frankenphp.ErrRejected{}) {
|
||||
assert.Fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err))
|
||||
}
|
||||
}
|
||||
|
||||
var ts *httptest.Server
|
||||
@@ -109,6 +107,7 @@ func runTest(t *testing.T, test func(func(http.ResponseWriter, *http.Request), *
|
||||
|
||||
func testRequest(req *http.Request, handler func(http.ResponseWriter, *http.Request), t *testing.T) (string, *http.Response) {
|
||||
t.Helper()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
handler(w, req)
|
||||
resp := w.Result()
|
||||
@@ -133,6 +132,16 @@ func testPost(url string, body string, handler func(http.ResponseWriter, *http.R
|
||||
return testRequest(req, handler, t)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
|
||||
if !testing.Verbose() {
|
||||
slog.SetDefault(slog.New(slog.DiscardHandler))
|
||||
}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestHelloWorld_module(t *testing.T) { testHelloWorld(t, nil) }
|
||||
func TestHelloWorld_worker(t *testing.T) {
|
||||
testHelloWorld(t, &testOptions{workerScript: "index.php"})
|
||||
@@ -406,36 +415,61 @@ my_autoloader`, i), body)
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func TestLog_module(t *testing.T) { testLog(t, &testOptions{}) }
|
||||
func TestLog_worker(t *testing.T) {
|
||||
testLog(t, &testOptions{workerScript: "log.php"})
|
||||
func TestLog_error_log_module(t *testing.T) { testLog_error_log(t, &testOptions{}) }
|
||||
func TestLog_error_log_worker(t *testing.T) {
|
||||
testLog_error_log(t, &testOptions{workerScript: "log-error_log.php"})
|
||||
}
|
||||
func testLog(t *testing.T, opts *testOptions) {
|
||||
logger, logs := observer.New(zapcore.InfoLevel)
|
||||
opts.logger = slog.New(zapslog.NewHandler(logger))
|
||||
func testLog_error_log(t *testing.T, opts *testOptions) {
|
||||
var buf fmt.Stringer
|
||||
opts.logger, buf = newTestLogger(t)
|
||||
|
||||
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/log.php?i=%d", i), nil)
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/log-error_log.php?i=%d", i), nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler(w, req)
|
||||
|
||||
for logs.FilterMessage(fmt.Sprintf("request %d", i)).Len() <= 0 {
|
||||
assert.Contains(t, buf.String(), fmt.Sprintf("request %d", i))
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func TestLog_frankenphp_log_module(t *testing.T) { testLog_frankenphp_log(t, &testOptions{}) }
|
||||
func TestLog_frankenphp_log_worker(t *testing.T) {
|
||||
testLog_frankenphp_log(t, &testOptions{workerScript: "log-frankenphp_log.php"})
|
||||
}
|
||||
func testLog_frankenphp_log(t *testing.T, opts *testOptions) {
|
||||
var buf fmt.Stringer
|
||||
opts.logger, buf = newTestLogger(t)
|
||||
|
||||
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/log-frankenphp_log.php?i=%d", i), nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler(w, req)
|
||||
|
||||
logs := buf.String()
|
||||
for _, message := range []string{
|
||||
`level=INFO msg="default level message"`,
|
||||
fmt.Sprintf(`level=DEBUG msg="some debug message %d" "key int"=1`, i),
|
||||
fmt.Sprintf(`level=INFO msg="some info message %d" "key string"=string`, i),
|
||||
fmt.Sprintf(`level=WARN msg="some warn message %d"`, i),
|
||||
fmt.Sprintf(`level=ERROR msg="some error message %d" err="[a v]"`, i),
|
||||
} {
|
||||
assert.Contains(t, logs, message)
|
||||
}
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func TestConnectionAbort_module(t *testing.T) { testConnectionAbort(t, &testOptions{}) }
|
||||
func TestConnectionAbort_worker(t *testing.T) {
|
||||
testConnectionAbort(t, &testOptions{workerScript: "connectionStatusLog.php"})
|
||||
testConnectionAbort(t, &testOptions{workerScript: "connection_status.php"})
|
||||
}
|
||||
func testConnectionAbort(t *testing.T, opts *testOptions) {
|
||||
testFinish := func(finish string) {
|
||||
t.Run(fmt.Sprintf("finish=%s", finish), func(t *testing.T) {
|
||||
logger, logs := observer.New(zapcore.InfoLevel)
|
||||
opts.logger = slog.New(zapslog.NewHandler(logger))
|
||||
var buf syncBuffer
|
||||
opts.logger = slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
||||
|
||||
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/connectionStatusLog.php?i=%d&finish=%s", i, finish), nil)
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/connection_status.php?i=%d&finish=%s", i, finish), nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
@@ -443,7 +477,7 @@ func testConnectionAbort(t *testing.T, opts *testOptions) {
|
||||
cancel()
|
||||
handler(w, req)
|
||||
|
||||
for logs.FilterMessage(fmt.Sprintf("request %d: 1", i)).Len() <= 0 {
|
||||
for !strings.Contains(buf.String(), fmt.Sprintf("request %d: 1", i)) {
|
||||
}
|
||||
}, opts)
|
||||
})
|
||||
@@ -601,10 +635,13 @@ func testRequestHeaders(t *testing.T, opts *testOptions) {
|
||||
}
|
||||
|
||||
func TestFailingWorker(t *testing.T) {
|
||||
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
|
||||
body, _ := testGet("http://example.com/failing-worker.php", handler, t)
|
||||
assert.Contains(t, body, "ok")
|
||||
}, &testOptions{workerScript: "failing-worker.php"})
|
||||
t.Cleanup(frankenphp.Shutdown)
|
||||
|
||||
err := frankenphp.Init(
|
||||
frankenphp.WithWorkers("failing worker", "testdata/failing-worker.php", 4, frankenphp.WithWorkerMaxFailures(1)),
|
||||
frankenphp.WithNumThreads(5),
|
||||
)
|
||||
assert.Error(t, err, "should return an immediate error if workers fail on startup")
|
||||
}
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
@@ -759,22 +796,18 @@ func ExampleExecuteScriptCLI() {
|
||||
}
|
||||
|
||||
func BenchmarkHelloWorld(b *testing.B) {
|
||||
if err := frankenphp.Init(frankenphp.WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil)))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer frankenphp.Shutdown()
|
||||
require.NoError(b, frankenphp.Init())
|
||||
b.Cleanup(frankenphp.Shutdown)
|
||||
|
||||
cwd, _ := os.Getwd()
|
||||
testDataDir := cwd + "/testdata/"
|
||||
|
||||
opt := frankenphp.WithRequestDocumentRoot(testDataDir, false)
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := frankenphp.NewRequestWithContext(r, frankenphp.WithRequestDocumentRoot(testDataDir, false))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req, err := frankenphp.NewRequestWithContext(r, opt)
|
||||
require.NoError(b, err)
|
||||
|
||||
if err := frankenphp.ServeHTTP(w, req); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
require.NoError(b, frankenphp.ServeHTTP(w, req))
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/index.php", nil)
|
||||
@@ -786,21 +819,18 @@ func BenchmarkHelloWorld(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkEcho(b *testing.B) {
|
||||
if err := frankenphp.Init(frankenphp.WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil)))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer frankenphp.Shutdown()
|
||||
require.NoError(b, frankenphp.Init())
|
||||
b.Cleanup(frankenphp.Shutdown)
|
||||
|
||||
cwd, _ := os.Getwd()
|
||||
testDataDir := cwd + "/testdata/"
|
||||
|
||||
opt := frankenphp.WithRequestDocumentRoot(testDataDir, false)
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := frankenphp.NewRequestWithContext(r, frankenphp.WithRequestDocumentRoot(testDataDir, false))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := frankenphp.ServeHTTP(w, req); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req, err := frankenphp.NewRequestWithContext(r, opt)
|
||||
require.NoError(b, err)
|
||||
|
||||
require.NoError(b, frankenphp.ServeHTTP(w, req))
|
||||
}
|
||||
|
||||
const body = `{
|
||||
@@ -852,10 +882,9 @@ func BenchmarkEcho(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkServerSuperGlobal(b *testing.B) {
|
||||
if err := frankenphp.Init(frankenphp.WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil)))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer frankenphp.Shutdown()
|
||||
require.NoError(b, frankenphp.Init())
|
||||
b.Cleanup(frankenphp.Shutdown)
|
||||
|
||||
cwd, _ := os.Getwd()
|
||||
testDataDir := cwd + "/testdata/"
|
||||
|
||||
@@ -900,16 +929,62 @@ func BenchmarkServerSuperGlobal(b *testing.B) {
|
||||
|
||||
preparedEnv := frankenphp.PrepareEnv(env)
|
||||
|
||||
opts := []frankenphp.RequestOption{frankenphp.WithRequestDocumentRoot(testDataDir, false), frankenphp.WithRequestPreparedEnv(preparedEnv)}
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := frankenphp.NewRequestWithContext(r, frankenphp.WithRequestDocumentRoot(testDataDir, false), frankenphp.WithRequestPreparedEnv(preparedEnv))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req, err := frankenphp.NewRequestWithContext(r, opts...)
|
||||
require.NoError(b, err)
|
||||
|
||||
r.Header = headers
|
||||
if err := frankenphp.ServeHTTP(w, req); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
require.NoError(b, frankenphp.ServeHTTP(w, req))
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/server-variable.php", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
for b.Loop() {
|
||||
handler(w, req)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUncommonHeaders(b *testing.B) {
|
||||
require.NoError(b, frankenphp.Init())
|
||||
b.Cleanup(frankenphp.Shutdown)
|
||||
|
||||
cwd, _ := os.Getwd()
|
||||
testDataDir := cwd + "/testdata/"
|
||||
|
||||
// Mimics headers of a request sent by Firefox to GitHub
|
||||
headers := http.Header{}
|
||||
headers.Add(strings.Clone("Accept"), strings.Clone("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"))
|
||||
headers.Add(strings.Clone("Accept-Encoding"), strings.Clone("gzip, deflate, br"))
|
||||
headers.Add(strings.Clone("Accept-Language"), strings.Clone("fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3"))
|
||||
headers.Add(strings.Clone("Cache-Control"), strings.Clone("no-cache"))
|
||||
headers.Add(strings.Clone("Connection"), strings.Clone("keep-alive"))
|
||||
headers.Add(strings.Clone("Cookie"), strings.Clone("user_session=myrandomuuid; __Host-user_session_same_site=myotherrandomuuid; dotcom_user=dunglas; logged_in=yes; _foo=barbarbarbarbarbar; _device_id=anotherrandomuuid; color_mode=foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar; preferred_color_mode=light; tz=Europe%2FParis; has_recent_activity=1"))
|
||||
headers.Add(strings.Clone("DNT"), strings.Clone("1"))
|
||||
headers.Add(strings.Clone("Host"), strings.Clone("example.com"))
|
||||
headers.Add(strings.Clone("Pragma"), strings.Clone("no-cache"))
|
||||
headers.Add(strings.Clone("Sec-Fetch-Dest"), strings.Clone("document"))
|
||||
headers.Add(strings.Clone("Sec-Fetch-Mode"), strings.Clone("navigate"))
|
||||
headers.Add(strings.Clone("Sec-Fetch-Site"), strings.Clone("cross-site"))
|
||||
headers.Add(strings.Clone("Sec-GPC"), strings.Clone("1"))
|
||||
headers.Add(strings.Clone("Upgrade-Insecure-Requests"), strings.Clone("1"))
|
||||
headers.Add(strings.Clone("User-Agent"), strings.Clone("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:122.0) Gecko/20100101 Firefox/122.0"))
|
||||
// Some uncommon headers
|
||||
headers.Add(strings.Clone("X-Super-Custom"), strings.Clone("Foo"))
|
||||
headers.Add(strings.Clone("Super-Super-Custom"), strings.Clone("Foo"))
|
||||
headers.Add(strings.Clone("Super-Super-Custom"), strings.Clone("Bar"))
|
||||
headers.Add(strings.Clone("Very-Custom"), strings.Clone("1"))
|
||||
|
||||
opt := frankenphp.WithRequestDocumentRoot(testDataDir, false)
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := frankenphp.NewRequestWithContext(r, opt)
|
||||
require.NoError(b, err)
|
||||
|
||||
r.Header = headers
|
||||
|
||||
require.NoError(b, frankenphp.ServeHTTP(w, req))
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/server-variable.php", nil)
|
||||
@@ -942,7 +1017,7 @@ func testRejectInvalidHeaders(t *testing.T, opts *testOptions) {
|
||||
}
|
||||
|
||||
func TestFlushEmptyResponse_module(t *testing.T) { testFlushEmptyResponse(t, &testOptions{}) }
|
||||
func TestFlushEmptyRespnse_worker(t *testing.T) {
|
||||
func TestFlushEmptyResponse_worker(t *testing.T) {
|
||||
testFlushEmptyResponse(t, &testOptions{workerScript: "only-headers.php"})
|
||||
}
|
||||
|
||||
@@ -988,7 +1063,8 @@ func FuzzRequest(f *testing.F) {
|
||||
// The response status must be 400 if the request path contains null bytes
|
||||
if strings.Contains(req.URL.Path, "\x00") {
|
||||
assert.Equal(t, 400, resp.StatusCode)
|
||||
assert.Contains(t, body, "Invalid request path")
|
||||
assert.Contains(t, body, "invalid request path")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -999,7 +1075,6 @@ func FuzzRequest(f *testing.F) {
|
||||
// Headers should always be present even if empty
|
||||
assert.Contains(t, body, fmt.Sprintf("[CONTENT_TYPE] => %s", fuzzedString))
|
||||
assert.Contains(t, body, fmt.Sprintf("[HTTP_FUZZED] => %s", fuzzedString))
|
||||
|
||||
}, &testOptions{workerScript: "request-headers.php"})
|
||||
})
|
||||
}
|
||||
|
||||
53
go.mod
53
go.mod
@@ -1,45 +1,66 @@
|
||||
module github.com/dunglas/frankenphp
|
||||
|
||||
go 1.25.0
|
||||
go 1.25.4
|
||||
|
||||
retract v1.0.0-rc.1 // Human error
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/maypok86/otter v1.2.4
|
||||
github.com/prometheus/client_golang v1.23.0
|
||||
github.com/dunglas/mercure v0.21.4
|
||||
github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146
|
||||
github.com/maypok86/otter/v2 v2.2.1
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.uber.org/zap v1.27.0
|
||||
go.uber.org/zap/exp v0.3.0
|
||||
golang.org/x/net v0.43.0
|
||||
golang.org/x/net v0.48.0
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/gammazero/deque v1.1.0 // indirect
|
||||
github.com/dunglas/skipfilter v1.0.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.4.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // 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/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // 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.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/rs/cors v1.11.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spf13/viper v1.21.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/unrolled/secure v1.17.0 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
106
go.sum
106
go.sum
@@ -6,74 +6,120 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 h1:1yw6O62BReQ+uA1oyk9XaQTvLhcoHWmoQAgXmDFXpIY=
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145/go.mod h1:877WBceefKn14QwVVn4xRFUsHsZb9clICgdeTj4XsUg=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4 h1:4aKySrrg9G/5oRtJ3TrZLObVqxgQ9f1znCRBwEwjuVw=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
||||
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dunglas/mercure v0.21.4 h1:mXPXHfB+4cYfFFCRRDY198mfef5+MQcdCpUnAHBUW2M=
|
||||
github.com/dunglas/mercure v0.21.4/go.mod h1:l/dglCjp/OQx8/quRyceRPx2hqZQ3CNviwoLMRQiJ/k=
|
||||
github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4=
|
||||
github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w=
|
||||
github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 h1:h3vVM6X45PK0mAk8NqiYNQGXTyhvXy1HQ5GhuQN4eeA=
|
||||
github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/gammazero/deque v1.1.0 h1:OyiyReBbnEG2PP0Bnv1AASLIYvyKqIFN5xfl1t8oGLo=
|
||||
github.com/gammazero/deque v1.1.0/go.mod h1:JVrR+Bj1NMQbPnYclvDlvSX0nVGReLrQZ0aUMuWLctg=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
||||
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/maypok86/otter v1.2.4 h1:HhW1Pq6VdJkmWwcZZq19BlEQkHtI8xgsQzBVXJU0nfc=
|
||||
github.com/maypok86/otter v1.2.4/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4=
|
||||
github.com/maypok86/otter/v2 v2.2.1 h1:hnGssisMFkdisYcvQ8L019zpYQcdtPse+g0ps2i7cfI=
|
||||
github.com/maypok86/otter/v2 v2.2.1/go.mod h1:1NKY9bY+kB5jwCXBJfE59u+zAwOt6C7ni1FTlFFMqVs=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
|
||||
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=
|
||||
github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
||||
44
hotreload.go
Normal file
44
hotreload.go
Normal file
@@ -0,0 +1,44 @@
|
||||
//go:build !nomercure && !nowatcher
|
||||
|
||||
package frankenphp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
|
||||
"github.com/dunglas/frankenphp/internal/watcher"
|
||||
"github.com/dunglas/mercure"
|
||||
watcherGo "github.com/e-dant/watcher/watcher-go"
|
||||
)
|
||||
|
||||
// WithHotReload sets files to watch for file changes to trigger a hot reload update.
|
||||
func WithHotReload(topic string, hub *mercure.Hub, patterns []string) Option {
|
||||
return func(o *opt) error {
|
||||
o.hotReload = append(o.hotReload, &watcher.PatternGroup{
|
||||
Patterns: patterns,
|
||||
Callback: func(events []*watcherGo.Event) {
|
||||
// Wait for workers to restart before sending the update
|
||||
go func() {
|
||||
data, err := json.Marshal(events)
|
||||
if err != nil {
|
||||
if globalLogger.Enabled(globalCtx, slog.LevelError) {
|
||||
globalLogger.LogAttrs(globalCtx, slog.LevelError, "error marshaling watcher events", slog.Any("error", err))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err := hub.Publish(globalCtx, &mercure.Update{
|
||||
Topics: []string{topic},
|
||||
Event: mercure.Event{Data: string(data)},
|
||||
Debug: globalLogger.Enabled(globalCtx, slog.LevelDebug),
|
||||
}); err != nil && globalLogger.Enabled(globalCtx, slog.LevelError) {
|
||||
globalLogger.LogAttrs(globalCtx, slog.LevelError, "error publishing hot reloading Mercure update", slog.Any("error", err))
|
||||
}
|
||||
}()
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user