mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-12-24 13:38:11 +08:00
Compare commits
88 Commits
feat/task-
...
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 |
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
|
||||
|
||||
4
.github/workflows/lint.yaml
vendored
4
.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
|
||||
@@ -47,3 +47,5 @@ jobs:
|
||||
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 +1 @@
|
||||
/github/workspace/docs/mercure.md:jwt:65
|
||||
/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
|
||||
|
||||
69
caddy/app.go
69
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,14 +49,16 @@ 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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -254,8 +298,7 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
const (
|
||||
defaultDocumentRoot = "public"
|
||||
defaultWatchPattern = "./**/*.{php,yaml,yml,twig,env}"
|
||||
defaultWatchPattern = "./**/*.{env,php,twig,yaml,yml}"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -29,7 +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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
169
caddy/go.mod
169
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 => ../
|
||||
|
||||
@@ -10,47 +10,47 @@ require (
|
||||
github.com/caddyserver/caddy/v2 v2.10.2
|
||||
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.2
|
||||
github.com/spf13/cobra v1.10.1
|
||||
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.1 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // 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.3 // 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.66.1 // 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/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.20.1 // 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,45 +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
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/crypto v0.42.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.28.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/term v0.35.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.37.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
|
||||
|
||||
566
caddy/go.sum
566
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.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,7 +268,6 @@ 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=
|
||||
@@ -326,27 +275,23 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
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/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
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.3 h1:gUl789rjbJSuM5hYzOFnNaGgWPV1xVfnOs59o0dZEcc=
|
||||
github.com/mholt/acmez/v3 v3.1.3/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.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
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.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
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,30 +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.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
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.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=
|
||||
@@ -509,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=
|
||||
@@ -529,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=
|
||||
@@ -552,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=
|
||||
@@ -595,48 +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=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
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.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
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=
|
||||
@@ -645,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.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
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=
|
||||
@@ -665,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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
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=
|
||||
@@ -689,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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
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=
|
||||
@@ -700,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.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
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=
|
||||
@@ -712,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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
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.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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,6 +77,9 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
|
||||
return fmt.Errorf(`expected ctx.App("frankenphp") to return *FrankenPHPApp, got nil`)
|
||||
}
|
||||
|
||||
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 defined inside php_server
|
||||
@@ -81,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
|
||||
}
|
||||
|
||||
@@ -94,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 {
|
||||
@@ -142,6 +153,10 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.configureHotReload(fapp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -152,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 != "" {
|
||||
@@ -184,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)
|
||||
}
|
||||
|
||||
@@ -202,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() {
|
||||
@@ -244,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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,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 {
|
||||
@@ -565,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)
|
||||
@@ -580,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,
|
||||
|
||||
@@ -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()
|
||||
@@ -63,8 +69,7 @@ func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
30
cgi.go
30
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,11 +212,11 @@ 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()
|
||||
|
||||
if fc.request != nil {
|
||||
addKnownVariablesToServer(thread, fc, trackVarsArray)
|
||||
addHeadersToServer(fc, trackVarsArray)
|
||||
addKnownVariablesToServer(fc, trackVarsArray)
|
||||
addHeadersToServer(thread.context(), fc.request, trackVarsArray)
|
||||
}
|
||||
|
||||
// The Prepared Environment is registered last and can overwrite any previous values
|
||||
@@ -277,13 +277,13 @@ 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 C.bool(fc.worker != nil)
|
||||
return
|
||||
}
|
||||
|
||||
authUser, authPassword, ok := request.BasicAuth()
|
||||
@@ -311,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
|
||||
|
||||
45
context.go
45
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
|
||||
@@ -36,6 +40,11 @@ type frankenPHPContext struct {
|
||||
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)
|
||||
@@ -61,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 == "" {
|
||||
@@ -91,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
|
||||
}
|
||||
@@ -117,23 +126,25 @@ 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 {
|
||||
@@ -149,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()
|
||||
@@ -167,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"]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 @@ 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
|
||||
# 如果你打算在 glibc 系统上运行该二进制文件,请使用 static-builder-gnu
|
||||
|
||||
# 复制应用代码
|
||||
WORKDIR /go/src/app/dist/app
|
||||
|
||||
@@ -270,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 代码之间共享配置值、状态代码和其他常量。
|
||||
|
||||
#### 全局常量
|
||||
|
||||
@@ -292,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
|
||||
```
|
||||
|
||||
@@ -346,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -29,7 +29,7 @@ Depending on your installation method, FrankenPHP and the PHP interpreter will l
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: the main configuration file
|
||||
- `/etc/frankenphp/caddy.d/*.caddy`: additional configuration files that are loaded automatically
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: additional configuration files that are loaded automatically
|
||||
|
||||
PHP:
|
||||
|
||||
@@ -53,13 +53,12 @@ RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
FrankenPHP:
|
||||
|
||||
- `/etc/frankenphp/Caddyfile`: the main configuration file
|
||||
- `/etc/frankenphp/caddy.d/*.caddy`: additional configuration files that are loaded automatically
|
||||
- `/etc/frankenphp/Caddyfile.d/*.caddyfile`: additional configuration files that are loaded automatically
|
||||
|
||||
PHP:
|
||||
|
||||
- `php.ini`: `/etc/frankenphp/php.ini` (a `php.ini` file with production presets is provided by default)
|
||||
- additional configuration files: `/etc/frankenphp/php.d/*.ini`
|
||||
- PHP extensions: `/usr/lib/frankenphp/modules/`
|
||||
- `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
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
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>`
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -88,19 +88,20 @@ While some variable types have the same memory representation between C/PHP and
|
||||
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()` | ✅ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `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]
|
||||
>
|
||||
@@ -132,7 +133,7 @@ import (
|
||||
)
|
||||
|
||||
// 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, err := frankenphp.GoAssociativeArray[any](unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
@@ -157,7 +158,7 @@ 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, err := frankenphp.GoMap[any](unsafe.Pointer(arr))
|
||||
@@ -178,9 +179,9 @@ func process_data_unordered_map(arr *C.zval) unsafe.Pointer {
|
||||
}
|
||||
|
||||
// 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, err := frankenphp.GoPackedArray(unsafe.Pointer(arr), false)
|
||||
goSlice, err := frankenphp.GoPackedArray(unsafe.Pointer(arr))
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
@@ -211,6 +212,43 @@ func process_data_packed(arr *C.zval) unsafe.Pointer {
|
||||
- `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
|
||||
|
||||
@@ -352,7 +390,7 @@ 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
|
||||
|
||||
@@ -376,27 +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
|
||||
package example
|
||||
|
||||
//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
|
||||
```
|
||||
|
||||
@@ -437,10 +475,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -81,18 +86,20 @@ Alors que le premier point parle de lui-même, le second peut être plus diffici
|
||||
|
||||
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() | ✅ |
|
||||
| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ |
|
||||
| `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.
|
||||
@@ -103,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))
|
||||
package example
|
||||
|
||||
result := &frankenphp.Array{}
|
||||
// #include <Zend/zend_types.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
result.SetInt(0, "first")
|
||||
result.SetInt(1, "second")
|
||||
result.Append("third") // Assigne automatiquement la prochaine clé entière
|
||||
"github.com/dunglas/frankenphp"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
// 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 any)` - Définir une valeur avec une clé entière
|
||||
- `SetString(key string, value any)` - Définir une valeur avec une clé chaîne
|
||||
- `Append(value any)` - Ajouter une valeur avec la prochaine clé entière disponible
|
||||
- `Len() uint32` - Obtenir le nombre d'éléments
|
||||
- `At(index uint32) (PHPKey, any)` - 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
|
||||
@@ -182,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
|
||||
@@ -214,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
|
||||
}
|
||||
@@ -270,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
|
||||
|
||||
@@ -292,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
|
||||
```
|
||||
|
||||
@@ -334,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
|
||||
@@ -346,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
|
||||
@@ -398,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 {
|
||||
@@ -500,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"
|
||||
)
|
||||
|
||||
@@ -520,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!")
|
||||
}()
|
||||
}
|
||||
```
|
||||
@@ -697,7 +827,16 @@ 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 {
|
||||
|
||||
@@ -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) :
|
||||
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -218,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コード間で設定値、ステータスコード、その他の定数を共有できます。
|
||||
|
||||
#### グローバル定数
|
||||
|
||||
@@ -240,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
|
||||
```
|
||||
|
||||
@@ -294,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -90,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
|
||||
|
||||
@@ -54,7 +54,30 @@ To subscribe to updates, use the native [`EventSource`](https://developer.mozill
|
||||
|
||||
## Publishing Updates
|
||||
|
||||
### Using `file_put_contents()`
|
||||
### 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:
|
||||
|
||||
@@ -64,7 +87,7 @@ To dispatch an update to connected subscribers, send an authenticated POST reque
|
||||
|
||||
const JWT = 'eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.PXwpfIGng6KObfZlcOXvcnWCJOWTFLtswGI5DZuWSK4';
|
||||
|
||||
$updateID = file_put_contents('https://localhost/.well-known/mercure', context: stream_context_create(['http' => [
|
||||
$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([
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -412,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.
|
||||
@@ -437,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
|
||||
```
|
||||
|
||||
@@ -502,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
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
98
frankenphp.c
98
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;
|
||||
@@ -464,12 +462,16 @@ PHP_FUNCTION(frankenphp_handle_request) {
|
||||
|
||||
/*
|
||||
* 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();
|
||||
@@ -505,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
|
||||
@@ -606,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);
|
||||
@@ -798,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) {
|
||||
|
||||
405
frankenphp.go
405
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.
|
||||
@@ -214,81 +248,105 @@ func Init(options ...Option) error {
|
||||
|
||||
registerExtensions()
|
||||
|
||||
// add registered external workers
|
||||
for _, ew := range extensionWorkers {
|
||||
options = append(options, WithWorkers(ew.Name(), ew.FileName(), ew.GetMinThreads(), WithWorkerEnv(ew.Env())))
|
||||
}
|
||||
|
||||
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
|
||||
@@ -300,7 +358,12 @@ func Shutdown() {
|
||||
return
|
||||
}
|
||||
|
||||
drainWatcher()
|
||||
// call the shutdown hooks (mainly useful for extensions)
|
||||
for _, fn := range onServerShutdown {
|
||||
fn()
|
||||
}
|
||||
|
||||
drainWatchers()
|
||||
drainAutoScaling()
|
||||
drainPHPThreads()
|
||||
|
||||
@@ -312,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)
|
||||
@@ -361,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())
|
||||
@@ -377,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.worker.name))
|
||||
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
|
||||
}
|
||||
@@ -410,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)
|
||||
@@ -452,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)
|
||||
}
|
||||
@@ -471,7 +564,7 @@ func go_write_headers(threadIndex C.uintptr_t, status C.int, headers *C.zend_lli
|
||||
for current != nil {
|
||||
h := (*C.sapi_header_struct)(unsafe.Pointer(&(current.data)))
|
||||
|
||||
addHeader(fc, h.header, C.int(h.header_len))
|
||||
addHeader(thread.context(), fc, h.header, C.int(h.header_len))
|
||||
current = current.next
|
||||
}
|
||||
|
||||
@@ -480,13 +573,18 @@ func go_write_headers(threadIndex C.uintptr_t, status C.int, headers *C.zend_lli
|
||||
// go panics on invalid status code
|
||||
// https://github.com/golang/go/blob/9b8742f2e79438b9442afa4c0a0139d3937ea33f/src/net/http/server.go#L1162
|
||||
if goStatus < 100 || goStatus > 999 {
|
||||
logger.Warn(fmt.Sprintf("Invalid response status code %v", goStatus))
|
||||
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 >= 100 && goStatus < 200 {
|
||||
if goStatus < 200 {
|
||||
// Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses
|
||||
h := fc.responseWriter.Header()
|
||||
for k := range h {
|
||||
@@ -499,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
|
||||
}
|
||||
|
||||
@@ -509,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
|
||||
@@ -517,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
|
||||
@@ -536,7 +643,7 @@ 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 {
|
||||
request := phpThreads[threadIndex].getRequestContext().request
|
||||
request := phpThreads[threadIndex].frankenPHPContext().request
|
||||
if request == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -553,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.
|
||||
@@ -629,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"})
|
||||
})
|
||||
}
|
||||
|
||||
52
go.mod
52
go.mod
@@ -1,46 +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/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.44.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.66.1 // 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
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.29.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
|
||||
)
|
||||
|
||||
104
go.sum
104
go.sum
@@ -6,76 +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.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.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
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=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
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
|
||||
}
|
||||
}
|
||||
86
install.sh
86
install.sh
@@ -2,6 +2,11 @@
|
||||
|
||||
set -e
|
||||
|
||||
SUDO=""
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
SUDO="sudo"
|
||||
fi
|
||||
|
||||
if [ -z "${BIN_DIR}" ]; then
|
||||
BIN_DIR=$(pwd)
|
||||
fi
|
||||
@@ -13,6 +18,11 @@ OS=$(uname -s)
|
||||
ARCH=$(uname -m)
|
||||
GNU=""
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
echo "Please install curl to download FrankenPHP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if type "tput" >/dev/null 2>&1; then
|
||||
bold=$(tput bold || true)
|
||||
italic=$(tput sitm || true)
|
||||
@@ -21,6 +31,50 @@ fi
|
||||
|
||||
case ${OS} in
|
||||
Linux*)
|
||||
if [ "${ARCH}" = "aarch64" ] || [ "${ARCH}" = "x86_64" ]; then
|
||||
if command -v dnf >/dev/null 2>&1; then
|
||||
echo "📦 Detected dnf. Installing FrankenPHP from RPM repository..."
|
||||
if [ -n "${SUDO}" ]; then
|
||||
echo "❗ Enter your password to grant sudo powers for package installation"
|
||||
${SUDO} -v || true
|
||||
fi
|
||||
${SUDO} dnf -y install https://rpm.henderkes.com/static-php-1-0.noarch.rpm
|
||||
${SUDO} dnf -y module enable php-zts:static-8.4 || true
|
||||
${SUDO} dnf -y install frankenphp
|
||||
echo
|
||||
echo "🥳 FrankenPHP installed to ${italic}/usr/bin/frankenphp${normal} successfully."
|
||||
echo "❗ The systemd service uses the Caddyfile in ${italic}/etc/frankenphp/Caddyfile${normal}"
|
||||
echo "❗ Your php.ini is found in ${italic}/etc/php-zts/php.ini${normal}"
|
||||
echo
|
||||
echo "⭐ If you like FrankenPHP, please give it a star on GitHub: ${italic}https://github.com/php/frankenphp${normal}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if command -v apt >/dev/null 2>&1 || command -v apt-get >/dev/null 2>&1; then
|
||||
echo "📦 Detected apt. Installing FrankenPHP from DEB repository..."
|
||||
if [ -n "${SUDO}" ]; then
|
||||
echo "❗ Enter your password to grant sudo powers for package installation"
|
||||
${SUDO} -v || true
|
||||
fi
|
||||
${SUDO} sh -c 'curl -fsSL https://key.henderkes.com/static-php.gpg -o /usr/share/keyrings/static-php.gpg'
|
||||
${SUDO} sh -c 'echo "deb [signed-by=/usr/share/keyrings/static-php.gpg] https://deb.henderkes.com/ stable main" > /etc/apt/sources.list.d/static-php.list'
|
||||
if command -v apt >/dev/null 2>&1; then
|
||||
${SUDO} apt update
|
||||
${SUDO} apt -y install frankenphp
|
||||
else
|
||||
${SUDO} apt-get update
|
||||
${SUDO} apt-get -y install frankenphp
|
||||
fi
|
||||
echo
|
||||
echo "🥳 FrankenPHP installed to ${italic}/usr/bin/frankenphp${normal} successfully."
|
||||
echo "❗ The systemd service uses the Caddyfile in ${italic}/etc/frankenphp/Caddyfile${normal}"
|
||||
echo "❗ Your php.ini is found in ${italic}/etc/php-zts/php.ini${normal}"
|
||||
echo
|
||||
echo "⭐ If you like FrankenPHP, please give it a star on GitHub: ${italic}https://github.com/php/frankenphp${normal}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
case ${ARCH} in
|
||||
aarch64)
|
||||
THE_ARCH_BIN="frankenphp-linux-aarch64"
|
||||
@@ -58,12 +112,11 @@ Windows | MINGW64_NT*)
|
||||
esac
|
||||
|
||||
if [ -z "${THE_ARCH_BIN}" ]; then
|
||||
echo "❗ FrankenPHP is not supported on ${OS} and ${ARCH}"
|
||||
echo "❗ Precompiled binaries are not available for ${ARCH}-${OS}"
|
||||
echo "❗ You can compile from sources by following the documentation at: https://frankenphp.dev/docs/compile/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUDO=""
|
||||
|
||||
echo "📦 Downloading ${bold}FrankenPHP${normal} for ${OS}${GNU} (${ARCH}):"
|
||||
|
||||
# check if $DEST is writable and suppress an error message
|
||||
@@ -75,20 +128,27 @@ if [ $? -eq 1 ]; then
|
||||
SUDO="sudo"
|
||||
fi
|
||||
|
||||
if type "curl" >/dev/null 2>&1; then
|
||||
curl -L --progress-bar "https://github.com/php/frankenphp/releases/latest/download/${THE_ARCH_BIN}" -o "${DEST}"
|
||||
elif type "wget" >/dev/null 2>&1; then
|
||||
${SUDO} wget "https://github.com/php/frankenphp/releases/latest/download/${THE_ARCH_BIN}" -O "${DEST}"
|
||||
else
|
||||
echo "❗ Please install ${italic}curl${normal} or ${italic}wget${normal} to download FrankenPHP"
|
||||
exit 1
|
||||
fi
|
||||
curl -L --progress-bar "https://github.com/php/frankenphp/releases/latest/download/${THE_ARCH_BIN}" -o "${DEST}"
|
||||
|
||||
${SUDO} chmod +x "${DEST}"
|
||||
# Allow binding to ports 80/443 without running as root (if setcap is available)
|
||||
if command -v setcap >/dev/null 2>&1; then
|
||||
${SUDO} setcap 'cap_net_bind_service=+ep' "${DEST}" || true
|
||||
else
|
||||
echo "❗ install setcap (e.g. libcap2-bin) to allow FrankenPHP to bind to ports 80/443 without root:"
|
||||
echo " ${bold}sudo setcap 'cap_net_bind_service=+ep' \"${DEST}\"${normal}"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "🥳 FrankenPHP downloaded successfully to ${italic}${DEST}${normal}"
|
||||
echo "🔧 Move the binary to ${italic}/usr/local/bin/${normal} or another directory in your ${italic}PATH${normal} to use it globally:"
|
||||
echo " ${bold}sudo mv ${DEST} /usr/local/bin/${normal}"
|
||||
echo "❗ It uses ${italic}/etc/frankenphp/php.ini${normal} if found."
|
||||
case ":$PATH:" in
|
||||
*":$DEST:"*) ;;
|
||||
*)
|
||||
echo "🔧 Move the binary to ${italic}/usr/local/bin/${normal} or another directory in your ${italic}PATH${normal} to use it globally:"
|
||||
echo " ${bold}sudo mv ${DEST} /usr/local/bin/${normal}"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo
|
||||
echo "⭐ If you like FrankenPHP, please give it a star on GitHub: ${italic}https://github.com/php/frankenphp${normal}"
|
||||
|
||||
@@ -2,6 +2,7 @@ package extgen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -25,8 +26,10 @@ func (ag *arginfoGenerator) generate() error {
|
||||
stubFile := ag.generator.BaseName + ".stub.php"
|
||||
cmd := exec.Command("php", genStubPath, filepath.Join(ag.generator.BuildDir, stubFile))
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("running gen_stub script: %w", err)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Print("gen_stub.php output:\n", string(output))
|
||||
return fmt.Errorf("running gen_stub script: %w\nOutput: %s", err, string(output))
|
||||
}
|
||||
|
||||
return ag.fixArginfoFile(stubFile)
|
||||
|
||||
@@ -58,6 +58,7 @@ func (cg *cFileGenerator) buildContent() (string, error) {
|
||||
func (cg *cFileGenerator) getTemplateContent() (string, error) {
|
||||
funcMap := sprig.FuncMap()
|
||||
funcMap["namespacedClassName"] = NamespacedName
|
||||
funcMap["cString"] = escapeCString
|
||||
|
||||
tmpl := template.Must(template.New("cfile").Funcs(funcMap).Parse(cFileContent))
|
||||
|
||||
@@ -74,3 +75,8 @@ func (cg *cFileGenerator) getTemplateContent() (string, error) {
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// escapeCString escapes backslashes for C string literals
|
||||
func escapeCString(s string) string {
|
||||
return strings.ReplaceAll(s, `\`, `\\`)
|
||||
}
|
||||
|
||||
@@ -129,3 +129,212 @@ func TestCFileGenerationWithoutNamespace(t *testing.T) {
|
||||
expectedCall := "register_class_MySuperClass()"
|
||||
require.Contains(t, contentResult, expectedCall, "C file should not contain the standard function call")
|
||||
}
|
||||
|
||||
func TestCFileGenerationWithNamespacedConstants(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
constants []phpConstant
|
||||
contains []string
|
||||
}{
|
||||
{
|
||||
name: "integer constant with namespace",
|
||||
namespace: `Go\Extension`,
|
||||
constants: []phpConstant{
|
||||
{Name: "TEST_INT", Value: "42", PhpType: phpInt},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_NS_LONG_CONSTANT("Go\\Extension", "TEST_INT", 42, CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string constant with namespace",
|
||||
namespace: `Go\Extension`,
|
||||
constants: []phpConstant{
|
||||
{Name: "TEST_STRING", Value: `"hello"`, PhpType: phpString},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_NS_STRING_CONSTANT("Go\\Extension", "TEST_STRING", "hello", CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "float constant with namespace",
|
||||
namespace: `Go\Extension`,
|
||||
constants: []phpConstant{
|
||||
{Name: "TEST_FLOAT", Value: "3.14", PhpType: phpFloat},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_NS_DOUBLE_CONSTANT("Go\\Extension", "TEST_FLOAT", 3.14, CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bool constant with namespace",
|
||||
namespace: `Go\Extension`,
|
||||
constants: []phpConstant{
|
||||
{Name: "TEST_BOOL", Value: "true", PhpType: phpBool},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_NS_BOOL_CONSTANT("Go\\Extension", "TEST_BOOL", true, CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "iota constant with namespace",
|
||||
namespace: `Go\Extension`,
|
||||
constants: []phpConstant{
|
||||
{Name: "STATUS_OK", Value: "STATUS_OK", PhpType: phpInt, IsIota: true},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_NS_LONG_CONSTANT("Go\\Extension", "STATUS_OK", STATUS_OK, CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple constants with deep namespace",
|
||||
namespace: `My\Deep\Namespace`,
|
||||
constants: []phpConstant{
|
||||
{Name: "CONST_INT", Value: "100", PhpType: phpInt},
|
||||
{Name: "CONST_STR", Value: `"value"`, PhpType: phpString},
|
||||
{Name: "CONST_FLOAT", Value: "1.5", PhpType: phpFloat},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_NS_LONG_CONSTANT("My\\Deep\\Namespace", "CONST_INT", 100, CONST_CS | CONST_PERSISTENT);`,
|
||||
`REGISTER_NS_STRING_CONSTANT("My\\Deep\\Namespace", "CONST_STR", "value", CONST_CS | CONST_PERSISTENT);`,
|
||||
`REGISTER_NS_DOUBLE_CONSTANT("My\\Deep\\Namespace", "CONST_FLOAT", 1.5, CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single level namespace",
|
||||
namespace: `TestNamespace`,
|
||||
constants: []phpConstant{
|
||||
{Name: "MY_CONST", Value: "999", PhpType: phpInt},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_NS_LONG_CONSTANT("TestNamespace", "MY_CONST", 999, CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace with trailing backslash",
|
||||
namespace: `TestIntegration\Extension`,
|
||||
constants: []phpConstant{
|
||||
{Name: "VERSION", Value: `"1.0.0"`, PhpType: phpString},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_NS_STRING_CONSTANT("TestIntegration\\Extension", "VERSION", "1.0.0", CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
generator := &Generator{
|
||||
BaseName: "test_ext",
|
||||
Namespace: tt.namespace,
|
||||
Constants: tt.constants,
|
||||
}
|
||||
|
||||
cGen := cFileGenerator{generator: generator}
|
||||
content, err := cGen.buildContent()
|
||||
require.NoError(t, err, "Failed to build C file content")
|
||||
|
||||
for _, expected := range tt.contains {
|
||||
assert.Contains(t, content, expected, "Generated C content should contain '%s'", expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCFileGenerationWithoutNamespacedConstants(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
constants []phpConstant
|
||||
contains []string
|
||||
}{
|
||||
{
|
||||
name: "integer constant without namespace",
|
||||
namespace: "",
|
||||
constants: []phpConstant{
|
||||
{Name: "GLOBAL_INT", Value: "42", PhpType: phpInt},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_LONG_CONSTANT("GLOBAL_INT", 42, CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string constant without namespace",
|
||||
namespace: "",
|
||||
constants: []phpConstant{
|
||||
{Name: "GLOBAL_STRING", Value: `"test"`, PhpType: phpString},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_STRING_CONSTANT("GLOBAL_STRING", "test", CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "float constant without namespace",
|
||||
namespace: "",
|
||||
constants: []phpConstant{
|
||||
{Name: "GLOBAL_FLOAT", Value: "2.71", PhpType: phpFloat},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_DOUBLE_CONSTANT("GLOBAL_FLOAT", 2.71, CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bool constant without namespace",
|
||||
namespace: "",
|
||||
constants: []phpConstant{
|
||||
{Name: "GLOBAL_BOOL", Value: "false", PhpType: phpBool},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_BOOL_CONSTANT("GLOBAL_BOOL", false, CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "iota constant without namespace",
|
||||
namespace: "",
|
||||
constants: []phpConstant{
|
||||
{Name: "ERROR_CODE", Value: "ERROR_CODE", PhpType: phpInt, IsIota: true},
|
||||
},
|
||||
contains: []string{
|
||||
`REGISTER_LONG_CONSTANT("ERROR_CODE", ERROR_CODE, CONST_CS | CONST_PERSISTENT);`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
generator := &Generator{
|
||||
BaseName: "test_ext",
|
||||
Namespace: tt.namespace,
|
||||
Constants: tt.constants,
|
||||
}
|
||||
|
||||
cGen := cFileGenerator{generator: generator}
|
||||
content, err := cGen.buildContent()
|
||||
require.NoError(t, err, "Failed to build C file content")
|
||||
|
||||
for _, expected := range tt.contains {
|
||||
assert.Contains(t, content, expected, "Generated C content should contain '%s'", expected)
|
||||
}
|
||||
|
||||
assert.NotContains(t, content, "REGISTER_NS_", "Content should NOT contain namespaced constant macros when namespace is empty")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCFileTemplateFunctionMapCString(t *testing.T) {
|
||||
generator := &Generator{
|
||||
BaseName: "test_ext",
|
||||
Namespace: `My\Namespace\Test`,
|
||||
Constants: []phpConstant{
|
||||
{Name: "MY_CONST", Value: "123", PhpType: phpInt},
|
||||
},
|
||||
}
|
||||
|
||||
cGen := cFileGenerator{generator: generator}
|
||||
content, err := cGen.getTemplateContent()
|
||||
require.NoError(t, err, "Failed to get template content")
|
||||
|
||||
assert.Contains(t, content, `"My\\Namespace\\Test"`, "Template should properly escape namespace backslashes using cString filter")
|
||||
assert.NotContains(t, content, `"My\Namespace\Test"`, "Template should not contain unescaped namespace (single backslashes)")
|
||||
}
|
||||
|
||||
@@ -185,3 +185,32 @@ func TestCFile_PHP_METHOD_Integration(t *testing.T) {
|
||||
require.NotContains(t, fullContent, old, "Did not expect to find old declaration %q in full C file content", old)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCFile_ClassMethodStringReturn(t *testing.T) {
|
||||
generator := &Generator{
|
||||
BaseName: "test_extension",
|
||||
Classes: []phpClass{
|
||||
{
|
||||
Name: "TestClass",
|
||||
GoStruct: "TestClass",
|
||||
Methods: []phpClassMethod{
|
||||
{
|
||||
Name: "getString",
|
||||
PhpName: "getString",
|
||||
ReturnType: "string",
|
||||
ClassName: "TestClass",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
BuildDir: t.TempDir(),
|
||||
}
|
||||
|
||||
cFileGen := cFileGenerator{generator: generator}
|
||||
content, err := cFileGen.getTemplateContent()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, content, "if (result)", "Expected NULL check for string return")
|
||||
require.Contains(t, content, "RETURN_STR(result)", "Expected RETURN_STR macro")
|
||||
require.Contains(t, content, "RETURN_EMPTY_STRING()", "Expected RETURN_EMPTY_STRING fallback")
|
||||
}
|
||||
|
||||
@@ -459,3 +459,74 @@ func TestCFileTemplateErrorHandling(t *testing.T) {
|
||||
_, err := cGen.getTemplateContent()
|
||||
assert.NoError(t, err, "getTemplateContent() should not fail with valid template")
|
||||
}
|
||||
|
||||
func TestEscapeCString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "simple namespace with single backslash",
|
||||
input: `Go\Extension`,
|
||||
expected: `Go\\Extension`,
|
||||
},
|
||||
{
|
||||
name: "namespace with multiple backslashes",
|
||||
input: `My\Deep\Namespace`,
|
||||
expected: `My\\Deep\\Namespace`,
|
||||
},
|
||||
{
|
||||
name: "complex nested namespace",
|
||||
input: `TestIntegration\Extension\Module`,
|
||||
expected: `TestIntegration\\Extension\\Module`,
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "single backslash",
|
||||
input: `\`,
|
||||
expected: `\\`,
|
||||
},
|
||||
{
|
||||
name: "multiple consecutive backslashes",
|
||||
input: `\\\`,
|
||||
expected: `\\\\\\`,
|
||||
},
|
||||
{
|
||||
name: "string without backslashes",
|
||||
input: "TestNamespace",
|
||||
expected: "TestNamespace",
|
||||
},
|
||||
{
|
||||
name: "leading backslash",
|
||||
input: `\Leading`,
|
||||
expected: `\\Leading`,
|
||||
},
|
||||
{
|
||||
name: "trailing backslash",
|
||||
input: `Trailing\`,
|
||||
expected: `Trailing\\`,
|
||||
},
|
||||
{
|
||||
name: "mixed alphanumeric with backslashes",
|
||||
input: `Path123\To456\File789`,
|
||||
expected: `Path123\\To456\\File789`,
|
||||
},
|
||||
{
|
||||
name: "unicode characters with backslashes",
|
||||
input: `Namespace\Über\Test`,
|
||||
expected: `Namespace\\Über\\Test`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := escapeCString(tt.input)
|
||||
assert.Equal(t, tt.expected, result, "escapeCString(%q) should return %q, got %q", tt.input, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,10 +50,6 @@ func (g *Generator) Generate() error {
|
||||
}
|
||||
|
||||
func (g *Generator) setupBuildDirectory() error {
|
||||
if err := os.RemoveAll(g.BuildDir); err != nil {
|
||||
return fmt.Errorf("removing build directory: %w", err)
|
||||
}
|
||||
|
||||
return os.MkdirAll(g.BuildDir, 0755)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
@@ -30,6 +31,16 @@ type goTemplateData struct {
|
||||
|
||||
func (gg *GoFileGenerator) generate() error {
|
||||
filename := filepath.Join(gg.generator.BuildDir, gg.generator.BaseName+".go")
|
||||
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
backupFilename := filename + ".bak"
|
||||
if err := os.Rename(filename, backupFilename); err != nil {
|
||||
return fmt.Errorf("backing up existing Go file: %w", err)
|
||||
}
|
||||
|
||||
gg.generator.SourceFile = backupFilename
|
||||
}
|
||||
|
||||
content, err := gg.buildContent()
|
||||
if err != nil {
|
||||
return fmt.Errorf("building Go file content: %w", err)
|
||||
@@ -47,7 +58,7 @@ func (gg *GoFileGenerator) buildContent() (string, error) {
|
||||
|
||||
filteredImports := make([]string, 0, len(imports))
|
||||
for _, imp := range imports {
|
||||
if imp != `"C"` && imp != `"unsafe"` && imp != `"github.com/dunglas/frankenphp"` {
|
||||
if imp != `"C"` && imp != `"unsafe"` && imp != `"github.com/dunglas/frankenphp"` && imp != `"runtime/cgo"` {
|
||||
filteredImports = append(filteredImports, imp)
|
||||
}
|
||||
}
|
||||
@@ -55,6 +66,19 @@ func (gg *GoFileGenerator) buildContent() (string, error) {
|
||||
classes := make([]phpClass, len(gg.generator.Classes))
|
||||
copy(classes, gg.generator.Classes)
|
||||
|
||||
if len(classes) > 0 {
|
||||
hasCgo := false
|
||||
for _, imp := range imports {
|
||||
if imp == `"runtime/cgo"` {
|
||||
hasCgo = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasCgo {
|
||||
filteredImports = append(filteredImports, `"runtime/cgo"`)
|
||||
}
|
||||
}
|
||||
|
||||
templateContent, err := gg.getTemplateContent(goTemplateData{
|
||||
PackageName: SanitizePackageName(gg.generator.BaseName),
|
||||
BaseName: gg.generator.BaseName,
|
||||
@@ -104,14 +128,15 @@ type GoParameter struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
var phpToGoTypeMap = map[phpType]string{
|
||||
phpString: "string",
|
||||
phpInt: "int64",
|
||||
phpFloat: "float64",
|
||||
phpBool: "bool",
|
||||
phpArray: "*frankenphp.Array",
|
||||
phpMixed: "any",
|
||||
phpVoid: "",
|
||||
var phpToGoTypeMap= map[phpType]string{
|
||||
phpString: "string",
|
||||
phpInt: "int64",
|
||||
phpFloat: "float64",
|
||||
phpBool: "bool",
|
||||
phpArray: "*frankenphp.Array",
|
||||
phpMixed: "any",
|
||||
phpVoid: "",
|
||||
phpCallable: "*C.zval",
|
||||
}
|
||||
|
||||
func (gg *GoFileGenerator) phpTypeToGoType(phpT phpType) string {
|
||||
|
||||
@@ -703,6 +703,125 @@ func testGoFileExportedFunctions(t *testing.T, content string, functions []phpFu
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoFileGenerator_MethodWrapperWithCallableParams(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
sourceContent := `package main
|
||||
|
||||
import "C"
|
||||
|
||||
//export_php:class CallableClass
|
||||
type CallableStruct struct{}
|
||||
|
||||
//export_php:method CallableClass::processCallback(callable $callback): string
|
||||
func (cs *CallableStruct) ProcessCallback(callback *C.zval) string {
|
||||
return "processed"
|
||||
}
|
||||
|
||||
//export_php:method CallableClass::processOptionalCallback(?callable $callback): string
|
||||
func (cs *CallableStruct) ProcessOptionalCallback(callback *C.zval) string {
|
||||
return "processed_optional"
|
||||
}`
|
||||
|
||||
sourceFile := filepath.Join(tmpDir, "test.go")
|
||||
require.NoError(t, os.WriteFile(sourceFile, []byte(sourceContent), 0644))
|
||||
|
||||
methods := []phpClassMethod{
|
||||
{
|
||||
Name: "ProcessCallback",
|
||||
PhpName: "processCallback",
|
||||
ClassName: "CallableClass",
|
||||
Signature: "processCallback(callable $callback): string",
|
||||
ReturnType: phpString,
|
||||
Params: []phpParameter{
|
||||
{Name: "callback", PhpType: phpCallable, IsNullable: false},
|
||||
},
|
||||
GoFunction: `func (cs *CallableStruct) ProcessCallback(callback *C.zval) string {
|
||||
return "processed"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
Name: "ProcessOptionalCallback",
|
||||
PhpName: "processOptionalCallback",
|
||||
ClassName: "CallableClass",
|
||||
Signature: "processOptionalCallback(?callable $callback): string",
|
||||
ReturnType: phpString,
|
||||
Params: []phpParameter{
|
||||
{Name: "callback", PhpType: phpCallable, IsNullable: true},
|
||||
},
|
||||
GoFunction: `func (cs *CallableStruct) ProcessOptionalCallback(callback *C.zval) string {
|
||||
return "processed_optional"
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
classes := []phpClass{
|
||||
{
|
||||
Name: "CallableClass",
|
||||
GoStruct: "CallableStruct",
|
||||
Methods: methods,
|
||||
},
|
||||
}
|
||||
|
||||
generator := &Generator{
|
||||
BaseName: "callable_test",
|
||||
SourceFile: sourceFile,
|
||||
Classes: classes,
|
||||
BuildDir: tmpDir,
|
||||
}
|
||||
|
||||
goGen := GoFileGenerator{generator}
|
||||
content, err := goGen.buildContent()
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedCallableWrapperSignature := "func ProcessCallback_wrapper(handle C.uintptr_t, callback *C.zval) unsafe.Pointer"
|
||||
assert.Contains(t, content, expectedCallableWrapperSignature, "Generated content should contain callable wrapper signature: %s", expectedCallableWrapperSignature)
|
||||
|
||||
expectedOptionalCallableWrapperSignature := "func ProcessOptionalCallback_wrapper(handle C.uintptr_t, callback *C.zval) unsafe.Pointer"
|
||||
assert.Contains(t, content, expectedOptionalCallableWrapperSignature, "Generated content should contain optional callable wrapper signature: %s", expectedOptionalCallableWrapperSignature)
|
||||
|
||||
expectedCallableCall := "structObj.ProcessCallback(callback)"
|
||||
assert.Contains(t, content, expectedCallableCall, "Generated content should contain callable method call: %s", expectedCallableCall)
|
||||
|
||||
expectedOptionalCallableCall := "structObj.ProcessOptionalCallback(callback)"
|
||||
assert.Contains(t, content, expectedOptionalCallableCall, "Generated content should contain optional callable method call: %s", expectedOptionalCallableCall)
|
||||
|
||||
assert.Contains(t, content, "//export ProcessCallback_wrapper", "Generated content should contain ProcessCallback export directive")
|
||||
assert.Contains(t, content, "//export ProcessOptionalCallback_wrapper", "Generated content should contain ProcessOptionalCallback export directive")
|
||||
}
|
||||
|
||||
func TestGoFileGenerator_phpTypeToGoType(t *testing.T) {
|
||||
generator := &Generator{}
|
||||
goGen := GoFileGenerator{generator}
|
||||
|
||||
tests := []struct {
|
||||
phpType phpType
|
||||
expected string
|
||||
}{
|
||||
{phpString, "string"},
|
||||
{phpInt, "int64"},
|
||||
{phpFloat, "float64"},
|
||||
{phpBool, "bool"},
|
||||
{phpArray, "*frankenphp.Array"},
|
||||
{phpMixed, "any"},
|
||||
{phpVoid, ""},
|
||||
{phpCallable, "*C.zval"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.phpType), func(t *testing.T) {
|
||||
result := goGen.phpTypeToGoType(tt.phpType)
|
||||
assert.Equal(t, tt.expected, result, "phpTypeToGoType(%s) should return %s", tt.phpType, tt.expected)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("unknown_type", func(t *testing.T) {
|
||||
unknownType := phpType("unknown")
|
||||
result := goGen.phpTypeToGoType(unknownType)
|
||||
assert.Equal(t, "any", result, "phpTypeToGoType should fallback to interface{} for unknown types")
|
||||
})
|
||||
}
|
||||
|
||||
func testGoFileInternalFunctions(t *testing.T, content string) {
|
||||
internalIndicators := []string{
|
||||
"func internalHelper",
|
||||
|
||||
793
internal/extgen/integration_test.go
Normal file
793
internal/extgen/integration_test.go
Normal file
@@ -0,0 +1,793 @@
|
||||
//go:build integration
|
||||
|
||||
package extgen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testModuleName = "github.com/frankenphp/test-extension"
|
||||
)
|
||||
|
||||
type IntegrationTestSuite struct {
|
||||
tempDir string
|
||||
genStubScript string
|
||||
xcaddyPath string
|
||||
frankenphpPath string
|
||||
phpConfigPath string
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func setupTest(t *testing.T) *IntegrationTestSuite {
|
||||
t.Helper()
|
||||
|
||||
suite := &IntegrationTestSuite{t: t}
|
||||
|
||||
suite.genStubScript = os.Getenv("GEN_STUB_SCRIPT")
|
||||
if suite.genStubScript == "" {
|
||||
suite.genStubScript = "/usr/local/src/php/build/gen_stub.php"
|
||||
}
|
||||
|
||||
if _, err := os.Stat(suite.genStubScript); os.IsNotExist(err) {
|
||||
t.Error("GEN_STUB_SCRIPT not found. Integration tests require PHP sources. Set GEN_STUB_SCRIPT environment variable.")
|
||||
}
|
||||
|
||||
xcaddyPath, err := exec.LookPath("xcaddy")
|
||||
if err != nil {
|
||||
t.Error("xcaddy not found in PATH. Integration tests require xcaddy to build FrankenPHP.")
|
||||
}
|
||||
suite.xcaddyPath = xcaddyPath
|
||||
|
||||
phpConfigPath, err := exec.LookPath("php-config")
|
||||
if err != nil {
|
||||
t.Error("php-config not found in PATH. Integration tests require PHP development headers.")
|
||||
}
|
||||
suite.phpConfigPath = phpConfigPath
|
||||
|
||||
tempDir := t.TempDir()
|
||||
suite.tempDir = tempDir
|
||||
|
||||
return suite
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) createGoModule(sourceFile string) (string, error) {
|
||||
s.t.Helper()
|
||||
|
||||
moduleDir := filepath.Join(s.tempDir, "module")
|
||||
if err := os.MkdirAll(moduleDir, 0o755); err != nil {
|
||||
return "", fmt.Errorf("failed to create module directory: %w", err)
|
||||
}
|
||||
|
||||
// Get project root for replace directive
|
||||
projectRoot, err := filepath.Abs(filepath.Join("..", ".."))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get project root: %w", err)
|
||||
}
|
||||
|
||||
goModContent := fmt.Sprintf(`module %s
|
||||
|
||||
go 1.25
|
||||
|
||||
require github.com/dunglas/frankenphp v0.0.0
|
||||
|
||||
replace github.com/dunglas/frankenphp => %s
|
||||
`, testModuleName, projectRoot)
|
||||
|
||||
if err := os.WriteFile(filepath.Join(moduleDir, "go.mod"), []byte(goModContent), 0o644); err != nil {
|
||||
return "", fmt.Errorf("failed to create go.mod: %w", err)
|
||||
}
|
||||
|
||||
sourceContent, err := os.ReadFile(sourceFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read source file: %w", err)
|
||||
}
|
||||
|
||||
targetFile := filepath.Join(moduleDir, filepath.Base(sourceFile))
|
||||
if err := os.WriteFile(targetFile, sourceContent, 0o644); err != nil {
|
||||
return "", fmt.Errorf("failed to write source file: %w", err)
|
||||
}
|
||||
|
||||
return targetFile, nil
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) runExtensionInit(sourceFile string) error {
|
||||
s.t.Helper()
|
||||
|
||||
os.Setenv("GEN_STUB_SCRIPT", s.genStubScript)
|
||||
|
||||
baseName := SanitizePackageName(strings.TrimSuffix(filepath.Base(sourceFile), ".go"))
|
||||
generator := Generator{
|
||||
BaseName: baseName,
|
||||
SourceFile: sourceFile,
|
||||
BuildDir: filepath.Dir(sourceFile),
|
||||
}
|
||||
|
||||
if err := generator.Generate(); err != nil {
|
||||
return fmt.Errorf("generation failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanupGeneratedFiles removes generated files from the original source directory
|
||||
func (s *IntegrationTestSuite) cleanupGeneratedFiles(originalSourceFile string) {
|
||||
s.t.Helper()
|
||||
|
||||
sourceDir := filepath.Dir(originalSourceFile)
|
||||
baseName := SanitizePackageName(strings.TrimSuffix(filepath.Base(originalSourceFile), ".go"))
|
||||
|
||||
generatedFiles := []string{
|
||||
baseName + ".stub.php",
|
||||
baseName + "_arginfo.h",
|
||||
baseName + ".h",
|
||||
baseName + ".c",
|
||||
baseName + ".go",
|
||||
"README.md",
|
||||
}
|
||||
|
||||
for _, file := range generatedFiles {
|
||||
fullPath := filepath.Join(sourceDir, file)
|
||||
if _, err := os.Stat(fullPath); err == nil {
|
||||
os.Remove(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compileFrankenPHP compiles FrankenPHP with the generated extension
|
||||
func (s *IntegrationTestSuite) compileFrankenPHP(moduleDir string) (string, error) {
|
||||
s.t.Helper()
|
||||
|
||||
projectRoot, err := filepath.Abs(filepath.Join("..", ".."))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get project root: %w", err)
|
||||
}
|
||||
|
||||
cflags, err := exec.Command(s.phpConfigPath, "--includes").Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get PHP includes: %w", err)
|
||||
}
|
||||
|
||||
ldflags, err := exec.Command(s.phpConfigPath, "--ldflags").Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get PHP ldflags: %w", err)
|
||||
}
|
||||
|
||||
libs, err := exec.Command(s.phpConfigPath, "--libs").Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get PHP libs: %w", err)
|
||||
}
|
||||
|
||||
cgoCflags := strings.TrimSpace(string(cflags))
|
||||
cgoLdflags := strings.TrimSpace(string(ldflags)) + " " + strings.TrimSpace(string(libs))
|
||||
|
||||
outputBinary := filepath.Join(s.tempDir, "frankenphp")
|
||||
|
||||
cmd := exec.Command(
|
||||
s.xcaddyPath,
|
||||
"build",
|
||||
"--output", outputBinary,
|
||||
"--with", "github.com/dunglas/frankenphp="+projectRoot,
|
||||
"--with", "github.com/dunglas/frankenphp/caddy="+projectRoot+"/caddy",
|
||||
"--with", testModuleName+"="+moduleDir,
|
||||
)
|
||||
|
||||
cmd.Env = append(os.Environ(),
|
||||
"CGO_ENABLED=1",
|
||||
"CGO_CFLAGS="+cgoCflags,
|
||||
"CGO_LDFLAGS="+cgoLdflags,
|
||||
fmt.Sprintf("XCADDY_GO_BUILD_FLAGS=-ldflags='-w -s' -tags=nobadger,nomysql,nopgx,nowatcher"),
|
||||
)
|
||||
|
||||
cmd.Dir = s.tempDir
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("xcaddy build failed: %w\nOutput: %s", err, string(output))
|
||||
}
|
||||
|
||||
s.frankenphpPath = outputBinary
|
||||
return outputBinary, nil
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) runPHPCode(phpCode string) (string, error) {
|
||||
s.t.Helper()
|
||||
|
||||
if s.frankenphpPath == "" {
|
||||
return "", fmt.Errorf("FrankenPHP not compiled yet")
|
||||
}
|
||||
|
||||
phpFile := filepath.Join(s.tempDir, "test.php")
|
||||
if err := os.WriteFile(phpFile, []byte(phpCode), 0o644); err != nil {
|
||||
return "", fmt.Errorf("failed to create PHP file: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(s.frankenphpPath, "php-cli", phpFile)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("PHP execution failed: %w\nOutput: %s", err, string(output))
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// verifyPHPSymbols checks if PHP can find the exposed functions, classes, and constants
|
||||
func (s *IntegrationTestSuite) verifyPHPSymbols(functions []string, classes []string, constants []string) error {
|
||||
s.t.Helper()
|
||||
|
||||
var checks []string
|
||||
|
||||
for _, fn := range functions {
|
||||
checks = append(checks, fmt.Sprintf("if (!function_exists('%s')) { echo 'MISSING_FUNCTION: %s'; exit(1); }", fn, fn))
|
||||
}
|
||||
|
||||
for _, cls := range classes {
|
||||
checks = append(checks, fmt.Sprintf("if (!class_exists('%s')) { echo 'MISSING_CLASS: %s'; exit(1); }", cls, cls))
|
||||
}
|
||||
|
||||
for _, cnst := range constants {
|
||||
checks = append(checks, fmt.Sprintf("if (!defined('%s')) { echo 'MISSING_CONSTANT: %s'; exit(1); }", cnst, cnst))
|
||||
}
|
||||
|
||||
checks = append(checks, "echo 'OK';")
|
||||
|
||||
phpCode := "<?php\n" + strings.Join(checks, "\n")
|
||||
|
||||
output, err := s.runPHPCode(phpCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.Contains(output, "OK") {
|
||||
return fmt.Errorf("symbol verification failed: %s", output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) verifyFunctionBehavior(phpCode string, expectedOutput string) error {
|
||||
s.t.Helper()
|
||||
|
||||
output, err := s.runPHPCode(phpCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.Contains(output, expectedOutput) {
|
||||
return fmt.Errorf("unexpected output.\nExpected to contain: %q\nGot: %q", expectedOutput, output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestBasicFunction(t *testing.T) {
|
||||
suite := setupTest(t)
|
||||
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "basic_function.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.runExtensionInit(targetFile)
|
||||
require.NoError(t, err, "extension-init should succeed")
|
||||
|
||||
baseDir := filepath.Dir(targetFile)
|
||||
baseName := strings.TrimSuffix(filepath.Base(targetFile), ".go")
|
||||
|
||||
expectedFiles := []string{
|
||||
baseName + ".stub.php",
|
||||
baseName + "_arginfo.h",
|
||||
baseName + ".h",
|
||||
baseName + ".c",
|
||||
baseName + ".go",
|
||||
"README.md",
|
||||
}
|
||||
|
||||
for _, file := range expectedFiles {
|
||||
fullPath := filepath.Join(baseDir, file)
|
||||
assert.FileExists(t, fullPath, "Generated file should exist: %s", file)
|
||||
}
|
||||
|
||||
_, err = suite.compileFrankenPHP(filepath.Dir(targetFile))
|
||||
require.NoError(t, err, "FrankenPHP compilation should succeed")
|
||||
|
||||
err = suite.verifyPHPSymbols(
|
||||
[]string{"test_uppercase", "test_add_numbers", "test_multiply", "test_is_enabled"},
|
||||
[]string{},
|
||||
[]string{},
|
||||
)
|
||||
require.NoError(t, err, "all functions should be accessible from PHP")
|
||||
|
||||
err = suite.verifyFunctionBehavior(`<?php
|
||||
$result = test_uppercase("hello world");
|
||||
if ($result !== "HELLO WORLD") {
|
||||
echo "FAIL: test_uppercase expected 'HELLO WORLD', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = test_uppercase("");
|
||||
if ($result !== "") {
|
||||
echo "FAIL: test_uppercase with empty string expected '', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$sum = test_add_numbers(5, 7);
|
||||
if ($sum !== 12) {
|
||||
echo "FAIL: test_add_numbers(5, 7) expected 12, got $sum";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = test_is_enabled(true);
|
||||
if ($result !== false) {
|
||||
echo "FAIL: test_is_enabled(true) expected false, got " . ($result ? "true" : "false");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = test_is_enabled(false);
|
||||
if ($result !== true) {
|
||||
echo "FAIL: test_is_enabled(false) expected true, got " . ($result ? "true" : "false");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "OK";
|
||||
`, "OK")
|
||||
require.NoError(t, err, "all function calls should work correctly")
|
||||
}
|
||||
|
||||
func TestClassMethodsIntegration(t *testing.T) {
|
||||
suite := setupTest(t)
|
||||
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "class_methods.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.runExtensionInit(targetFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = suite.compileFrankenPHP(filepath.Dir(targetFile))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.verifyPHPSymbols(
|
||||
[]string{},
|
||||
[]string{"Counter", "StringHolder"},
|
||||
[]string{},
|
||||
)
|
||||
require.NoError(t, err, "all classes should be accessible from PHP")
|
||||
|
||||
err = suite.verifyFunctionBehavior(`<?php
|
||||
$counter = new Counter();
|
||||
if ($counter->getValue() !== 0) {
|
||||
echo "FAIL: Counter initial value expected 0, got " . $counter->getValue();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$counter->increment();
|
||||
if ($counter->getValue() !== 1) {
|
||||
echo "FAIL: Counter after increment expected 1, got " . $counter->getValue();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$counter->decrement();
|
||||
if ($counter->getValue() !== 0) {
|
||||
echo "FAIL: Counter after decrement expected 0, got " . $counter->getValue();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$counter->setValue(10);
|
||||
if ($counter->getValue() !== 10) {
|
||||
echo "FAIL: Counter after setValue(10) expected 10, got " . $counter->getValue();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$newValue = $counter->addValue(5);
|
||||
if ($newValue !== 15) {
|
||||
echo "FAIL: Counter addValue(5) expected to return 15, got $newValue";
|
||||
exit(1);
|
||||
}
|
||||
if ($counter->getValue() !== 15) {
|
||||
echo "FAIL: Counter value after addValue(5) expected 15, got " . $counter->getValue();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$counter->updateWithNullable(50);
|
||||
if ($counter->getValue() !== 50) {
|
||||
echo "FAIL: Counter after updateWithNullable(50) expected 50, got " . $counter->getValue();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$counter->updateWithNullable(null);
|
||||
if ($counter->getValue() !== 50) {
|
||||
echo "FAIL: Counter after updateWithNullable(null) expected 50 (unchanged), got " . $counter->getValue();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$counter->reset();
|
||||
if ($counter->getValue() !== 0) {
|
||||
echo "FAIL: Counter after reset expected 0, got " . $counter->getValue();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$counter1 = new Counter();
|
||||
$counter2 = new Counter();
|
||||
$counter1->setValue(100);
|
||||
$counter2->setValue(200);
|
||||
if ($counter1->getValue() !== 100 || $counter2->getValue() !== 200) {
|
||||
echo "FAIL: Multiple Counter instances should be independent";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$holder = new StringHolder();
|
||||
$holder->setData("test string");
|
||||
if ($holder->getData() !== "test string") {
|
||||
echo "FAIL: StringHolder getData expected 'test string', got '" . $holder->getData() . "'";
|
||||
exit(1);
|
||||
}
|
||||
if ($holder->getLength() !== 11) {
|
||||
echo "FAIL: StringHolder getLength expected 11, got " . $holder->getLength();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$holder->setData("");
|
||||
if ($holder->getData() !== "") {
|
||||
echo "FAIL: StringHolder empty string expected '', got '" . $holder->getData() . "'";
|
||||
exit(1);
|
||||
}
|
||||
if ($holder->getLength() !== 0) {
|
||||
echo "FAIL: StringHolder empty string length expected 0, got " . $holder->getLength();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "OK";
|
||||
`, "OK")
|
||||
require.NoError(t, err, "all class methods should work correctly")
|
||||
}
|
||||
|
||||
func TestConstants(t *testing.T) {
|
||||
suite := setupTest(t)
|
||||
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "constants.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.runExtensionInit(targetFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = suite.compileFrankenPHP(filepath.Dir(targetFile))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.verifyPHPSymbols(
|
||||
[]string{"test_with_constants"},
|
||||
[]string{"Config"},
|
||||
[]string{
|
||||
"TEST_MAX_RETRIES", "TEST_API_VERSION", "TEST_ENABLED", "TEST_PI",
|
||||
"STATUS_PENDING", "STATUS_PROCESSING", "STATUS_COMPLETED",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err, "all constants, functions, and classes should be accessible from PHP")
|
||||
|
||||
err = suite.verifyFunctionBehavior(`<?php
|
||||
if (TEST_MAX_RETRIES !== 100) {
|
||||
echo "FAIL: TEST_MAX_RETRIES expected 100, got " . TEST_MAX_RETRIES;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (TEST_API_VERSION !== "2.0.0") {
|
||||
echo "FAIL: TEST_API_VERSION expected '2.0.0', got '" . TEST_API_VERSION . "'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (TEST_ENABLED !== true) {
|
||||
var_dump(TEST_ENABLED);
|
||||
echo "FAIL: TEST_ENABLED expected true, got " . (TEST_ENABLED ? "true" : "false");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (abs(TEST_PI - 3.14159) > 0.00001) {
|
||||
echo "FAIL: TEST_PI expected 3.14159, got " . TEST_PI;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (Config::MODE_DEBUG !== 1) {
|
||||
echo "FAIL: Config::MODE_DEBUG expected 1, got " . Config::MODE_DEBUG;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (Config::MODE_PRODUCTION !== 2) {
|
||||
echo "FAIL: Config::MODE_PRODUCTION expected 2, got " . Config::MODE_PRODUCTION;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (Config::DEFAULT_TIMEOUT !== 30) {
|
||||
echo "FAIL: Config::DEFAULT_TIMEOUT expected 30, got " . Config::DEFAULT_TIMEOUT;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$config = new Config();
|
||||
$config->setMode(Config::MODE_DEBUG);
|
||||
if ($config->getMode() !== Config::MODE_DEBUG) {
|
||||
echo "FAIL: Config getMode expected MODE_DEBUG, got " . $config->getMode();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = test_with_constants(STATUS_PENDING);
|
||||
if ($result !== "pending") {
|
||||
echo "FAIL: test_with_constants(STATUS_PENDING) expected 'pending', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = test_with_constants(STATUS_PROCESSING);
|
||||
if ($result !== "processing") {
|
||||
echo "FAIL: test_with_constants(STATUS_PROCESSING) expected 'processing', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = test_with_constants(STATUS_COMPLETED);
|
||||
if ($result !== "completed") {
|
||||
echo "FAIL: test_with_constants(STATUS_COMPLETED) expected 'completed', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = test_with_constants(999);
|
||||
if ($result !== "unknown") {
|
||||
echo "FAIL: test_with_constants(999) expected 'unknown', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "OK";
|
||||
`, "OK")
|
||||
require.NoError(t, err, "all constants should have correct values and functions should work")
|
||||
}
|
||||
|
||||
func TestNamespace(t *testing.T) {
|
||||
suite := setupTest(t)
|
||||
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "namespace.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.runExtensionInit(targetFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = suite.compileFrankenPHP(filepath.Dir(targetFile))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.verifyPHPSymbols(
|
||||
[]string{`\\TestIntegration\\Extension\\greet`},
|
||||
[]string{`\\TestIntegration\\Extension\\Person`},
|
||||
[]string{`\\TestIntegration\\Extension\\NAMESPACE_VERSION`},
|
||||
)
|
||||
require.NoError(t, err, "all namespaced symbols should be accessible from PHP")
|
||||
|
||||
err = suite.verifyFunctionBehavior(`<?php
|
||||
use TestIntegration\Extension;
|
||||
|
||||
if (Extension\NAMESPACE_VERSION !== "1.0.0") {
|
||||
echo "FAIL: NAMESPACE_VERSION expected '1.0.0', got '" . Extension\NAMESPACE_VERSION . "'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$greeting = Extension\greet("Alice");
|
||||
if ($greeting !== "Hello, Alice!") {
|
||||
echo "FAIL: greet('Alice') expected 'Hello, Alice!', got '$greeting'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$greeting = Extension\greet("");
|
||||
if ($greeting !== "Hello, !") {
|
||||
echo "FAIL: greet('') expected 'Hello, !', got '$greeting'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (Extension\Person::DEFAULT_AGE !== 18) {
|
||||
echo "FAIL: Person::DEFAULT_AGE expected 18, got " . Extension\Person::DEFAULT_AGE;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$person = new Extension\Person();
|
||||
$person->setName("Bob");
|
||||
$person->setAge(25);
|
||||
|
||||
if ($person->getName() !== "Bob") {
|
||||
echo "FAIL: Person getName expected 'Bob', got '" . $person->getName() . "'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ($person->getAge() !== 25) {
|
||||
echo "FAIL: Person getAge expected 25, got " . $person->getAge();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$person->setAge(Extension\Person::DEFAULT_AGE);
|
||||
if ($person->getAge() !== 18) {
|
||||
echo "FAIL: Person setAge(DEFAULT_AGE) expected 18, got " . $person->getAge();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$person1 = new Extension\Person();
|
||||
$person2 = new Extension\Person();
|
||||
$person1->setName("Alice");
|
||||
$person1->setAge(30);
|
||||
$person2->setName("Charlie");
|
||||
$person2->setAge(40);
|
||||
|
||||
if ($person1->getName() !== "Alice" || $person1->getAge() !== 30) {
|
||||
echo "FAIL: person1 should have independent state";
|
||||
exit(1);
|
||||
}
|
||||
if ($person2->getName() !== "Charlie" || $person2->getAge() !== 40) {
|
||||
echo "FAIL: person2 should have independent state";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "OK";
|
||||
`, "OK")
|
||||
require.NoError(t, err, "all namespaced symbols should work correctly")
|
||||
}
|
||||
|
||||
func TestInvalidSignature(t *testing.T) {
|
||||
suite := setupTest(t)
|
||||
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "invalid_signature.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.runExtensionInit(targetFile)
|
||||
assert.Error(t, err, "extension-init should fail for invalid return type")
|
||||
assert.Contains(t, err.Error(), "no PHP functions, classes, or constants found", "invalid functions should be ignored, resulting in no valid exports")
|
||||
}
|
||||
|
||||
func TestTypeMismatch(t *testing.T) {
|
||||
suite := setupTest(t)
|
||||
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "type_mismatch.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.runExtensionInit(targetFile)
|
||||
assert.NoError(t, err, "generation should succeed - class is valid even though function/method have type mismatches")
|
||||
|
||||
baseDir := filepath.Dir(targetFile)
|
||||
baseName := strings.TrimSuffix(filepath.Base(targetFile), ".go")
|
||||
stubFile := filepath.Join(baseDir, baseName+".stub.php")
|
||||
assert.FileExists(t, stubFile, "stub file should be generated for valid class")
|
||||
}
|
||||
|
||||
func TestMissingGenStub(t *testing.T) {
|
||||
// temp override of GEN_STUB_SCRIPT
|
||||
originalValue := os.Getenv("GEN_STUB_SCRIPT")
|
||||
defer os.Setenv("GEN_STUB_SCRIPT", originalValue)
|
||||
|
||||
os.Setenv("GEN_STUB_SCRIPT", "/nonexistent/gen_stub.php")
|
||||
|
||||
tempDir := t.TempDir()
|
||||
sourceFile := filepath.Join(tempDir, "test.go")
|
||||
|
||||
err := os.WriteFile(sourceFile, []byte(`package test
|
||||
|
||||
//export_php:function dummy(): void
|
||||
func dummy() {}
|
||||
`), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
baseName := SanitizePackageName(strings.TrimSuffix(filepath.Base(sourceFile), ".go"))
|
||||
gen := Generator{
|
||||
BaseName: baseName,
|
||||
SourceFile: sourceFile,
|
||||
BuildDir: filepath.Dir(sourceFile),
|
||||
}
|
||||
|
||||
err = gen.Generate()
|
||||
assert.Error(t, err, "should fail when gen_stub.php is missing")
|
||||
assert.Contains(t, err.Error(), "gen_stub.php", "error should mention missing script")
|
||||
}
|
||||
|
||||
func TestCallable(t *testing.T) {
|
||||
suite := setupTest(t)
|
||||
|
||||
sourceFile := filepath.Join("..", "..", "testdata", "integration", "callable.go")
|
||||
sourceFile, err := filepath.Abs(sourceFile)
|
||||
require.NoError(t, err)
|
||||
defer suite.cleanupGeneratedFiles(sourceFile)
|
||||
|
||||
targetFile, err := suite.createGoModule(sourceFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.runExtensionInit(targetFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = suite.compileFrankenPHP(filepath.Dir(targetFile))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = suite.verifyPHPSymbols(
|
||||
[]string{"my_array_map", "my_filter"},
|
||||
[]string{"Processor"},
|
||||
[]string{},
|
||||
)
|
||||
require.NoError(t, err, "all functions and classes should be accessible from PHP")
|
||||
|
||||
err = suite.verifyFunctionBehavior(`<?php
|
||||
|
||||
$result = my_array_map([1, 2, 3], function($x) { return $x * 2; });
|
||||
if ($result !== [2, 4, 6]) {
|
||||
echo "FAIL: my_array_map with closure expected [2, 4, 6], got " . json_encode($result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = my_array_map(['hello', 'world'], 'strtoupper');
|
||||
if ($result !== ['HELLO', 'WORLD']) {
|
||||
echo "FAIL: my_array_map with function name expected ['HELLO', 'WORLD'], got " . json_encode($result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = my_array_map([], function($x) { return $x; });
|
||||
if ($result !== []) {
|
||||
echo "FAIL: my_array_map with empty array expected [], got " . json_encode($result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = my_filter([1, 2, 3, 4, 5, 6], function($x) { return $x % 2 === 0; });
|
||||
if ($result !== [2, 4, 6]) {
|
||||
echo "FAIL: my_filter expected [2, 4, 6], got " . json_encode($result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = my_filter([1, 2, 3, 4], null);
|
||||
if ($result !== [1, 2, 3, 4]) {
|
||||
echo "FAIL: my_filter with null callback expected [1, 2, 3, 4], got " . json_encode($result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$processor = new Processor();
|
||||
$result = $processor->transform('hello', function($s) { return strtoupper($s); });
|
||||
if ($result !== 'HELLO') {
|
||||
echo "FAIL: Processor::transform with closure expected 'HELLO', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = $processor->transform('world', 'strtoupper');
|
||||
if ($result !== 'WORLD') {
|
||||
echo "FAIL: Processor::transform with function name expected 'WORLD', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$result = $processor->transform(' test ', 'trim');
|
||||
if ($result !== 'test') {
|
||||
echo "FAIL: Processor::transform with trim expected 'test', got '$result'";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "OK";
|
||||
`, "OK")
|
||||
require.NoError(t, err, "all callable tests should pass")
|
||||
}
|
||||
@@ -9,17 +9,18 @@ import (
|
||||
type phpType string
|
||||
|
||||
const (
|
||||
phpString phpType = "string"
|
||||
phpInt phpType = "int"
|
||||
phpFloat phpType = "float"
|
||||
phpBool phpType = "bool"
|
||||
phpArray phpType = "array"
|
||||
phpObject phpType = "object"
|
||||
phpMixed phpType = "mixed"
|
||||
phpVoid phpType = "void"
|
||||
phpNull phpType = "null"
|
||||
phpTrue phpType = "true"
|
||||
phpFalse phpType = "false"
|
||||
phpString phpType = "string"
|
||||
phpInt phpType = "int"
|
||||
phpFloat phpType = "float"
|
||||
phpBool phpType = "bool"
|
||||
phpArray phpType = "array"
|
||||
phpObject phpType = "object"
|
||||
phpMixed phpType = "mixed"
|
||||
phpVoid phpType = "void"
|
||||
phpNull phpType = "null"
|
||||
phpTrue phpType = "true"
|
||||
phpFalse phpType = "false"
|
||||
phpCallable phpType = "callable"
|
||||
)
|
||||
|
||||
type phpFunction struct {
|
||||
|
||||
@@ -68,8 +68,12 @@ func (pp *ParameterParser) generateSingleParamDeclaration(param phpParameter) []
|
||||
if param.IsNullable {
|
||||
decls = append(decls, fmt.Sprintf("zend_bool %s_is_null = 0;", param.Name))
|
||||
}
|
||||
case phpArray, phpMixed:
|
||||
case phpArray:
|
||||
decls = append(decls, fmt.Sprintf("zend_array *%s = NULL;", param.Name))
|
||||
case phpMixed:
|
||||
decls = append(decls, fmt.Sprintf("zval *%s = NULL;", param.Name))
|
||||
case "callable":
|
||||
decls = append(decls, fmt.Sprintf("zval *%s_callback;", param.Name))
|
||||
}
|
||||
|
||||
return decls
|
||||
@@ -118,9 +122,11 @@ func (pp *ParameterParser) generateParamParsingMacro(param phpParameter) string
|
||||
case phpBool:
|
||||
return fmt.Sprintf("\n Z_PARAM_BOOL_OR_NULL(%s, %s_is_null)", param.Name, param.Name)
|
||||
case phpArray:
|
||||
return fmt.Sprintf("\n Z_PARAM_ARRAY_OR_NULL(%s)", param.Name)
|
||||
return fmt.Sprintf("\n Z_PARAM_ARRAY_HT_OR_NULL(%s)", param.Name)
|
||||
case phpMixed:
|
||||
return fmt.Sprintf("\n Z_PARAM_ZVAL_OR_NULL(%s)", param.Name)
|
||||
case phpCallable:
|
||||
return fmt.Sprintf("\n Z_PARAM_ZVAL_OR_NULL(%s_callback)", param.Name)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -135,9 +141,11 @@ func (pp *ParameterParser) generateParamParsingMacro(param phpParameter) string
|
||||
case phpBool:
|
||||
return fmt.Sprintf("\n Z_PARAM_BOOL(%s)", param.Name)
|
||||
case phpArray:
|
||||
return fmt.Sprintf("\n Z_PARAM_ARRAY(%s)", param.Name)
|
||||
return fmt.Sprintf("\n Z_PARAM_ARRAY_HT(%s)", param.Name)
|
||||
case phpMixed:
|
||||
return fmt.Sprintf("\n Z_PARAM_ZVAL(%s)", param.Name)
|
||||
case phpCallable:
|
||||
return fmt.Sprintf("\n Z_PARAM_ZVAL(%s_callback)", param.Name)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -168,6 +176,8 @@ func (pp *ParameterParser) generateSingleGoCallParam(param phpParameter) string
|
||||
return fmt.Sprintf("%s_is_null ? NULL : &%s", param.Name, param.Name)
|
||||
case phpBool:
|
||||
return fmt.Sprintf("%s_is_null ? NULL : &%s", param.Name, param.Name)
|
||||
case phpCallable:
|
||||
return fmt.Sprintf("%s_callback", param.Name)
|
||||
default:
|
||||
return param.Name
|
||||
}
|
||||
@@ -180,6 +190,8 @@ func (pp *ParameterParser) generateSingleGoCallParam(param phpParameter) string
|
||||
return fmt.Sprintf("(double) %s", param.Name)
|
||||
case phpBool:
|
||||
return fmt.Sprintf("(int) %s", param.Name)
|
||||
case phpCallable:
|
||||
return fmt.Sprintf("%s_callback", param.Name)
|
||||
default:
|
||||
return param.Name
|
||||
}
|
||||
|
||||
@@ -145,14 +145,14 @@ func TestParameterParser_GenerateParamDeclarations(t *testing.T) {
|
||||
params: []phpParameter{
|
||||
{Name: "items", PhpType: phpArray, HasDefault: false},
|
||||
},
|
||||
expected: " zval *items = NULL;",
|
||||
expected: " zend_array *items = NULL;",
|
||||
},
|
||||
{
|
||||
name: "nullable array parameter",
|
||||
params: []phpParameter{
|
||||
{Name: "items", PhpType: phpArray, HasDefault: false, IsNullable: true},
|
||||
},
|
||||
expected: " zval *items = NULL;",
|
||||
expected: " zend_array *items = NULL;",
|
||||
},
|
||||
{
|
||||
name: "mixed types with array",
|
||||
@@ -161,7 +161,7 @@ func TestParameterParser_GenerateParamDeclarations(t *testing.T) {
|
||||
{Name: "items", PhpType: phpArray, HasDefault: false},
|
||||
{Name: "count", PhpType: phpInt, HasDefault: true, DefaultValue: "5"},
|
||||
},
|
||||
expected: " zend_string *name = NULL;\n zval *items = NULL;\n zend_long count = 5;",
|
||||
expected: " zend_string *name = NULL;\n zend_array *items = NULL;\n zend_long count = 5;",
|
||||
},
|
||||
{
|
||||
name: "mixed parameter",
|
||||
@@ -177,6 +177,29 @@ func TestParameterParser_GenerateParamDeclarations(t *testing.T) {
|
||||
},
|
||||
expected: " zval *m = NULL;",
|
||||
},
|
||||
{
|
||||
name: "callable parameter",
|
||||
params: []phpParameter{
|
||||
{Name: "callback", PhpType: phpCallable, HasDefault: false},
|
||||
},
|
||||
expected: " zval *callback_callback;",
|
||||
},
|
||||
{
|
||||
name: "nullable callable parameter",
|
||||
params: []phpParameter{
|
||||
{Name: "callback", PhpType: phpCallable, HasDefault: false, IsNullable: true},
|
||||
},
|
||||
expected: " zval *callback_callback;",
|
||||
},
|
||||
{
|
||||
name: "mixed types with callable",
|
||||
params: []phpParameter{
|
||||
{Name: "data", PhpType: phpArray, HasDefault: false},
|
||||
{Name: "callback", PhpType: phpCallable, HasDefault: false},
|
||||
{Name: "options", PhpType: phpInt, HasDefault: true, DefaultValue: "0"},
|
||||
},
|
||||
expected: " zend_array *data = NULL;\n zval *callback_callback;\n zend_long options = 0;",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -292,6 +315,29 @@ func TestParameterParser_GenerateGoCallParams(t *testing.T) {
|
||||
},
|
||||
expected: "name, items, (long) count",
|
||||
},
|
||||
{
|
||||
name: "callable parameter",
|
||||
params: []phpParameter{
|
||||
{Name: "callback", PhpType: "callable"},
|
||||
},
|
||||
expected: "callback_callback",
|
||||
},
|
||||
{
|
||||
name: "nullable callable parameter",
|
||||
params: []phpParameter{
|
||||
{Name: "callback", PhpType: "callable", IsNullable: true},
|
||||
},
|
||||
expected: "callback_callback",
|
||||
},
|
||||
{
|
||||
name: "mixed parameters with callable",
|
||||
params: []phpParameter{
|
||||
{Name: "data", PhpType: "array"},
|
||||
{Name: "callback", PhpType: "callable"},
|
||||
{Name: "limit", PhpType: "int"},
|
||||
},
|
||||
expected: "data, callback_callback, (long) limit",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -353,12 +399,12 @@ func TestParameterParser_GenerateParamParsingMacro(t *testing.T) {
|
||||
{
|
||||
name: "array parameter",
|
||||
param: phpParameter{Name: "items", PhpType: phpArray},
|
||||
expected: "\n Z_PARAM_ARRAY(items)",
|
||||
expected: "\n Z_PARAM_ARRAY_HT(items)",
|
||||
},
|
||||
{
|
||||
name: "nullable array parameter",
|
||||
param: phpParameter{Name: "items", PhpType: phpArray, IsNullable: true},
|
||||
expected: "\n Z_PARAM_ARRAY_OR_NULL(items)",
|
||||
expected: "\n Z_PARAM_ARRAY_HT_OR_NULL(items)",
|
||||
},
|
||||
{
|
||||
name: "mixed parameter",
|
||||
@@ -370,6 +416,16 @@ func TestParameterParser_GenerateParamParsingMacro(t *testing.T) {
|
||||
param: phpParameter{Name: "m", PhpType: phpMixed, IsNullable: true},
|
||||
expected: "\n Z_PARAM_ZVAL_OR_NULL(m)",
|
||||
},
|
||||
{
|
||||
name: "callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: phpCallable},
|
||||
expected: "\n Z_PARAM_ZVAL(callback_callback)",
|
||||
},
|
||||
{
|
||||
name: "nullable callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: phpCallable, IsNullable: true},
|
||||
expected: "\n Z_PARAM_ZVAL_OR_NULL(callback_callback)",
|
||||
},
|
||||
{
|
||||
name: "unknown type",
|
||||
param: phpParameter{Name: "unknown", PhpType: phpType("unknown")},
|
||||
@@ -480,6 +536,16 @@ func TestParameterParser_GenerateSingleGoCallParam(t *testing.T) {
|
||||
param: phpParameter{Name: "items", PhpType: phpArray, IsNullable: true},
|
||||
expected: "items",
|
||||
},
|
||||
{
|
||||
name: "callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: "callable"},
|
||||
expected: "callback_callback",
|
||||
},
|
||||
{
|
||||
name: "nullable callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: "callable", IsNullable: true},
|
||||
expected: "callback_callback",
|
||||
},
|
||||
{
|
||||
name: "unknown type",
|
||||
param: phpParameter{Name: "unknown", PhpType: phpType("unknown")},
|
||||
@@ -551,12 +617,22 @@ func TestParameterParser_GenerateSingleParamDeclaration(t *testing.T) {
|
||||
{
|
||||
name: "array parameter",
|
||||
param: phpParameter{Name: "items", PhpType: phpArray, HasDefault: false},
|
||||
expected: []string{"zval *items = NULL;"},
|
||||
expected: []string{"zend_array *items = NULL;"},
|
||||
},
|
||||
{
|
||||
name: "nullable array parameter",
|
||||
param: phpParameter{Name: "items", PhpType: phpArray, HasDefault: false, IsNullable: true},
|
||||
expected: []string{"zval *items = NULL;"},
|
||||
expected: []string{"zend_array *items = NULL;"},
|
||||
},
|
||||
{
|
||||
name: "callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: "callable", HasDefault: false},
|
||||
expected: []string{"zval *callback_callback;"},
|
||||
},
|
||||
{
|
||||
name: "nullable callable parameter",
|
||||
param: phpParameter{Name: "callback", PhpType: "callable", HasDefault: false, IsNullable: true},
|
||||
expected: []string{"zval *callback_callback;"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user