Compare commits

...

9 Commits

Author SHA1 Message Date
Kévin Dunglas
34262949d7 ci: fix push or Docker dev images 2024-02-02 11:27:54 +01:00
Kévin Dunglas
e53b1ce891 ci: use master Buildx version 2024-02-01 23:51:08 +01:00
Kévin Dunglas
6dee113a01 perf: add $_SERVER creation benchmark 2024-02-01 11:40:36 +01:00
Kévin Dunglas
fe7e9e7c79 fix: crash when using apache_request_headers() (#536)
* fix: potential crash when using apache_request_headers()

* refactor: better apache_request_headers
2024-02-01 10:00:11 +01:00
Kévin Dunglas
ab7ce9cb18 ci: temporary fix for Buildx crash 2024-01-31 16:29:45 +01:00
Kévin Dunglas
5afde55ebf ci: debug Docker buildx crash 2024-01-31 15:59:54 +01:00
Kévin Dunglas
da63e700b0 ci: fix scheduled Docker images build (ctd) 2024-01-31 15:21:03 +01:00
Kévin Dunglas
5a8e5f9518 feat: add apache_response_headers() function (#530) 2024-01-31 12:34:30 +01:00
Kévin Dunglas
3d9f344a50 docs: Docker image updates and tags 2024-01-30 22:07:33 +01:00
10 changed files with 265 additions and 39 deletions

View File

@@ -19,6 +19,7 @@ on:
- cron: '0 4 * * *' - cron: '0 4 * * *'
env: env:
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }} IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
LATEST: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && '0' || '1' }}
jobs: jobs:
prepare: prepare:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -37,6 +38,8 @@ jobs:
- -
name: Check PHP versions name: Check PHP versions
id: check id: check
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
PHP_82_LATEST=$(skopeo inspect docker://docker.io/library/php:8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') PHP_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_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="; "")')
@@ -45,13 +48,13 @@ jobs:
echo php82_version="${PHP_82_LATEST//./-}" echo php82_version="${PHP_82_LATEST//./-}"
echo php83_version="${PHP_83_LATEST//./-}" echo php83_version="${PHP_83_LATEST//./-}"
} >> "${GITHUB_OUTPUT}" } >> "${GITHUB_OUTPUT}"
# Check if the Docker images must be rebuilt # Check if the Docker images must be rebuilt
if [[ "${GITHUB_EVENT_NAME}" != "schedule" ]]; then if [[ "${GITHUB_EVENT_NAME}" != "schedule" ]]; then
echo skip=false >> "${GITHUB_OUTPUT}" echo skip=false >> "${GITHUB_OUTPUT}"
exit 0 exit 0
fi fi
FRANKENPHP_82_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:latest-php8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') FRANKENPHP_82_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:latest-php8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
FRANKENPHP_83_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:latest-php8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")') FRANKENPHP_83_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:latest-php8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
@@ -59,7 +62,7 @@ jobs:
echo skip=true >> "${GITHUB_OUTPUT}" echo skip=true >> "${GITHUB_OUTPUT}"
exit 0 exit 0
fi fi
{ {
echo ref="$(gh release view --repo dunglas/frankenphp --json tagName --jq '.tagName')" echo ref="$(gh release view --repo dunglas/frankenphp --json tagName --jq '.tagName')"
echo skip=false echo skip=false
@@ -89,7 +92,6 @@ jobs:
SHA: ${{ github.sha }} SHA: ${{ github.sha }}
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || steps.check.outputs.ref || github.sha }} VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || steps.check.outputs.ref || github.sha }}
PHP_VERSION: ${{ steps.check.outputs.php_version }} PHP_VERSION: ${{ steps.check.outputs.php_version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
@@ -160,7 +162,7 @@ jobs:
*.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }} *.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }}
*.cache-from=type=gha,scope=refs/heads/main-${{ matrix.platform }} *.cache-from=type=gha,scope=refs/heads/main-${{ matrix.platform }}
*.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }},ignore-error=true *.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }},ignore-error=true
${{ fromJson(needs.prepare.outputs.push) && '*.output=type=image,name=dunglas/frankenphp,push-by-digest=true,name-canonical=true,push=true' || '' }} ${{ fromJson(needs.prepare.outputs.push) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }}
env: env:
SHA: ${{ github.sha }} SHA: ${{ github.sha }}
VERSION: ${{ github.ref_type == 'tag' && github.ref_name || needs.prepare.outputs.ref || github.sha }} VERSION: ${{ github.ref_type == 'tag' && github.ref_name || needs.prepare.outputs.ref || github.sha }}
@@ -229,7 +231,8 @@ jobs:
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
with: with:
version: latest # Temporary fix for https://github.com/docker/buildx/issues/2229
version: "https://github.com/docker/buildx.git#master"
- -
name: Login to DockerHub name: Login to DockerHub
uses: docker/login-action@v3 uses: docker/login-action@v3
@@ -240,9 +243,10 @@ jobs:
name: Create manifest list and push name: Create manifest list and push
working-directory: /tmp/metadata working-directory: /tmp/metadata
run: | run: |
set -x
# shellcheck disable=SC2046,SC2086 # shellcheck disable=SC2046,SC2086
docker buildx imagetools create $(jq -cr '.target."${{ matrix.target }}-${{ matrix.variant }}".tags | map("-t " + .) | join(" ")' <<< ${METADATA}) \ docker buildx imagetools create $(jq -cr '.target."${{ matrix.target }}-${{ matrix.variant }}".tags | map("-t " + .) | join(" ")' <<< ${METADATA}) \
$(printf 'dunglas/frankenphp@sha256:%s ' *) $(printf "${IMAGE_NAME}@sha256:%s " *)
env: env:
METADATA: ${{ needs.prepare.outputs.metadata }} METADATA: ${{ needs.prepare.outputs.metadata }}
- -

View File

@@ -19,6 +19,7 @@ on:
- cron: '0 0 * * *' - cron: '0 0 * * *'
env: env:
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }} IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
LATEST: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && '0' || '1' }}
jobs: jobs:
prepare: prepare:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -113,7 +114,7 @@ jobs:
*.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder *.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder
*.cache-from=type=gha,scope=refs/heads/main-static-builder *.cache-from=type=gha,scope=refs/heads/main-static-builder
*.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder,ignore-error=true *.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder,ignore-error=true
${{ fromJson(needs.prepare.outputs.push) && '*.output=type=image,name=dunglas/frankenphp,push-by-digest=true,name-canonical=true,push=true' || '' }} ${{ fromJson(needs.prepare.outputs.push) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }}
env: env:
SHA: ${{ github.sha }} SHA: ${{ github.sha }}
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref || github.sha}} VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref || github.sha}}
@@ -188,7 +189,7 @@ jobs:
run: | run: |
# shellcheck disable=SC2046,SC2086 # shellcheck disable=SC2046,SC2086
docker buildx imagetools create $(jq -cr '.target."static-builder".tags | map("-t " + .) | join(" ")' <<< "${METADATA}") \ docker buildx imagetools create $(jq -cr '.target."static-builder".tags | map("-t " + .) | join(" ")' <<< "${METADATA}") \
$(printf 'dunglas/frankenphp@sha256:%s ' *) $(printf "${IMAGE_NAME}@sha256:%s " *)
env: env:
METADATA: ${{ needs.prepare.outputs.metadata }} METADATA: ${{ needs.prepare.outputs.metadata }}
- -

View File

@@ -144,3 +144,18 @@ RUN adduser -D ${USER} \
# Caddy requires write access to /data/caddy and /config/caddy # Caddy requires write access to /data/caddy and /config/caddy
RUN chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy RUN chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
``` ```
## Updates
The Docker images are built:
* when a new release is tagged
* daily at 4am UTC, if new versions of the official PHP images are available
## Development Versions
Development versions are available in the [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev) Docker repository.
A new build is triggered every time a commit is pushed to the main branch of the GitHub repository.
The `latest*` tags point to the head of the `main` branch.
Tags of the form `sha-<git-commit-hash>` are also available.

View File

@@ -243,7 +243,7 @@ PHP_FUNCTION(frankenphp_finish_request) { /* {{{ */
} /* }}} */ } /* }}} */
/* {{{ Fetch all HTTP request headers */ /* {{{ Fetch all HTTP request headers */
PHP_FUNCTION(apache_request_headers) { PHP_FUNCTION(frankenphp_request_headers) {
if (zend_parse_parameters_none() == FAILURE) { if (zend_parse_parameters_none() == FAILURE) {
RETURN_THROWS(); RETURN_THROWS();
} }
@@ -261,7 +261,59 @@ PHP_FUNCTION(apache_request_headers) {
add_assoc_stringl_ex(return_value, key.data, key.len, val.data, val.len); add_assoc_stringl_ex(return_value, key.data, key.len, val.data, val.len);
} }
free(headers.r0); go_apache_request_cleanup(headers.r2);
}
/* }}} */
// add_response_header and apache_response_headers are copied from
// https://github.com/php/php-src/blob/master/sapi/cli/php_cli_server.c
// Copyright (c) The PHP Group
// Licensed under The PHP License
// Original authors: Moriyoshi Koizumi <moriyoshi@php.net> and Xinchen Hui
// <laruence@php.net>
static void add_response_header(sapi_header_struct *h,
zval *return_value) /* {{{ */
{
if (h->header_len > 0) {
char *s;
size_t len = 0;
ALLOCA_FLAG(use_heap)
char *p = strchr(h->header, ':');
if (NULL != p) {
len = p - h->header;
}
if (len > 0) {
while (len != 0 &&
(h->header[len - 1] == ' ' || h->header[len - 1] == '\t')) {
len--;
}
if (len) {
s = do_alloca(len + 1, use_heap);
memcpy(s, h->header, len);
s[len] = 0;
do {
p++;
} while (*p == ' ' || *p == '\t');
add_assoc_stringl_ex(return_value, s, len, p,
h->header_len - (p - h->header));
free_alloca(s, use_heap);
}
}
}
}
/* }}} */
PHP_FUNCTION(frankenphp_response_headers) /* {{{ */
{
if (zend_parse_parameters_none() == FAILURE) {
RETURN_THROWS();
}
array_init(return_value);
zend_llist_apply_with_argument(
&SG(sapi_headers).headers,
(llist_apply_with_arg_func_t)add_response_header, return_value);
} }
/* }}} */ /* }}} */
@@ -784,8 +836,12 @@ static char *cli_script;
static int cli_argc; static int cli_argc;
static char **cli_argv; static char **cli_argv;
// Adapted from https://github.com/php/php-src/sapi/cli/php_cli.c (The PHP // CLI code is adapted from
// Group, The PHP License) // https://github.com/php/php-src/blob/master/sapi/cli/php_cli.c Copyright (c)
// The PHP Group Licensed under The PHP License Original uthors: Edin Kadribasic
// <edink@php.net>, Marcus Boerger <helly@php.net> and Johannes Schlueter
// <johannes@php.net> Parts based on CGI SAPI Module by Rasmus Lerdorf, Stig
// Bakken and Zeev Suraski
static void cli_register_file_handles(bool no_close) /* {{{ */ static void cli_register_file_handles(bool no_close) /* {{{ */
{ {
php_stream *s_in, *s_out, *s_err; php_stream *s_in, *s_out, *s_err;

View File

@@ -582,24 +582,41 @@ func go_register_variables(rh C.uintptr_t, trackVarsArray *C.zval) {
} }
//export go_apache_request_headers //export go_apache_request_headers
func go_apache_request_headers(rh C.uintptr_t) (*C.go_string, C.size_t) { func go_apache_request_headers(rh C.uintptr_t) (*C.go_string, C.size_t, C.uintptr_t) {
r := cgo.Handle(rh).Value().(*http.Request) r := cgo.Handle(rh).Value().(*http.Request)
rl := len(r.Header) pinner := &runtime.Pinner{}
scs := unsafe.Sizeof(C.go_string{}) pinnerHandle := C.uintptr_t(cgo.NewHandle(pinner))
headers := make([]C.go_string, 0, len(r.Header)*2)
headers := (*C.go_string)(unsafe.Pointer(C.malloc(C.size_t(rl*2) * (C.size_t)(scs))))
header := headers
for field, val := range r.Header { for field, val := range r.Header {
*header = C.go_string{C.size_t(len(field)), (*C.char)(unsafe.Pointer(unsafe.StringData(field)))} fd := unsafe.StringData(field)
header = (*C.go_string)(unsafe.Add(unsafe.Pointer(header), scs)) pinner.Pin(fd)
cv := strings.Join(val, ", ") cv := strings.Join(val, ", ")
*header = C.go_string{C.size_t(len(cv)), (*C.char)(unsafe.Pointer(unsafe.StringData(cv)))} vd := unsafe.StringData(cv)
header = (*C.go_string)(unsafe.Add(unsafe.Pointer(header), scs)) pinner.Pin(vd)
headers = append(
headers,
C.go_string{C.size_t(len(field)), (*C.char)(unsafe.Pointer(fd))},
C.go_string{C.size_t(len(cv)), (*C.char)(unsafe.Pointer(vd))},
)
} }
return headers, C.size_t(rl) sd := unsafe.SliceData(headers)
pinner.Pin(sd)
return sd, C.size_t(len(r.Header)), pinnerHandle
}
//export go_apache_request_cleanup
func go_apache_request_cleanup(rh C.uintptr_t) {
h := cgo.Handle(rh)
p := h.Value().(*runtime.Pinner)
p.Unpin()
h.Delete()
} }
func addHeader(fc *FrankenPHPContext, cString *C.char, length C.int) { func addHeader(fc *FrankenPHPContext, cString *C.char, length C.int) {

View File

@@ -13,9 +13,22 @@ function frankenphp_finish_request(): bool {}
*/ */
function fastcgi_finish_request(): bool {} function fastcgi_finish_request(): bool {}
function frankenphp_request_headers(): array {}
/**
* @alias frankenphp_request_headers
*/
function apache_request_headers(): array {} function apache_request_headers(): array {}
/** /**
* @alias apache_request_headers * @alias frankenphp_response_headers
*/ */
function getallheaders(): array {} function getallheaders(): array {}
function frankenphp_response_headers(): array|bool {}
/**
* @alias frankenphp_response_headers
*/
function apache_response_headers(): array|bool {}

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead. /* This is a generated file, edit the .stub.php file instead.
* Stub hash: f925a1c280fb955eb32d0823cbd4f360b0cbabed */ * Stub hash: 467f1406e17d3b8ca67bba5ea367194e60d8dd27 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_handle_request, 0, 1, ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_handle_request, 0, 1,
_IS_BOOL, 0) _IS_BOOL, 0)
@@ -16,16 +16,25 @@ ZEND_END_ARG_INFO()
#define arginfo_fastcgi_finish_request arginfo_frankenphp_finish_request #define arginfo_fastcgi_finish_request arginfo_frankenphp_finish_request
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_apache_request_headers, 0, 0, ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_request_headers, 0,
IS_ARRAY, 0) 0, IS_ARRAY, 0)
ZEND_END_ARG_INFO() ZEND_END_ARG_INFO()
#define arginfo_getallheaders arginfo_apache_request_headers #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_END_ARG_INFO()
#define arginfo_apache_response_headers arginfo_frankenphp_response_headers
ZEND_FUNCTION(frankenphp_handle_request); ZEND_FUNCTION(frankenphp_handle_request);
ZEND_FUNCTION(headers_send); ZEND_FUNCTION(headers_send);
ZEND_FUNCTION(frankenphp_finish_request); ZEND_FUNCTION(frankenphp_finish_request);
ZEND_FUNCTION(apache_request_headers); ZEND_FUNCTION(frankenphp_request_headers);
ZEND_FUNCTION(frankenphp_response_headers);
static const zend_function_entry ext_functions[] = { static const zend_function_entry ext_functions[] = {
ZEND_FE(frankenphp_handle_request, arginfo_frankenphp_handle_request) ZEND_FE(frankenphp_handle_request, arginfo_frankenphp_handle_request)
@@ -33,6 +42,16 @@ static const zend_function_entry ext_functions[] = {
frankenphp_finish_request, arginfo_frankenphp_finish_request) frankenphp_finish_request, arginfo_frankenphp_finish_request)
ZEND_FALIAS(fastcgi_finish_request, frankenphp_finish_request, ZEND_FALIAS(fastcgi_finish_request, frankenphp_finish_request,
arginfo_fastcgi_finish_request) arginfo_fastcgi_finish_request)
ZEND_FE(apache_request_headers, arginfo_apache_request_headers) ZEND_FE(frankenphp_request_headers,
ZEND_FALIAS(getallheaders, apache_request_headers, arginfo_frankenphp_request_headers)
arginfo_getallheaders) ZEND_FE_END}; ZEND_FALIAS(apache_request_headers,
frankenphp_request_headers,
arginfo_apache_request_headers)
ZEND_FALIAS(getallheaders, frankenphp_response_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};

View File

@@ -226,6 +226,27 @@ func testHeaders(t *testing.T, opts *testOptions) {
}, opts) }, opts)
} }
func TestResponseHeaders_module(t *testing.T) { testResponseHeaders(t, nil) }
func TestResponseHeaders_worker(t *testing.T) {
testResponseHeaders(t, &testOptions{workerScript: "response-headers.php"})
}
func testResponseHeaders(t *testing.T, opts *testOptions) {
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/response-headers.php?i=%d", i), nil)
w := httptest.NewRecorder()
handler(w, req)
resp := w.Result()
body, _ := io.ReadAll(resp.Body)
assert.Contains(t, string(body), "'X-Powered-By' => 'PH")
assert.Contains(t, string(body), "'Foo' => 'bar',")
assert.Contains(t, string(body), "'Foo2' => 'bar2',")
assert.Contains(t, string(body), fmt.Sprintf("'I' => '%d',", i))
assert.NotContains(t, string(body), "Invalid")
}, opts)
}
func TestInput_module(t *testing.T) { testInput(t, nil) } func TestInput_module(t *testing.T) { testInput(t, nil) }
func TestInput_worker(t *testing.T) { testInput(t, &testOptions{workerScript: "input.php"}) } func TestInput_worker(t *testing.T) { testInput(t, &testOptions{workerScript: "input.php"}) }
func testInput(t *testing.T, opts *testOptions) { func testInput(t *testing.T, opts *testOptions) {
@@ -553,15 +574,15 @@ func testFiberNoCgo(t *testing.T, opts *testOptions) {
}, opts) }, opts)
} }
func TestApacheRequestHeaders_module(t *testing.T) { testApacheRequestHeaders(t, &testOptions{}) } func TestRequestHeaders_module(t *testing.T) { testRequestHeaders(t, &testOptions{}) }
func TestApacheRequestHeaders_worker(t *testing.T) { func TestRequestHeaders_worker(t *testing.T) {
testApacheRequestHeaders(t, &testOptions{workerScript: "apache-request-headers.php"}) testRequestHeaders(t, &testOptions{workerScript: "request-headers.php"})
} }
func testApacheRequestHeaders(t *testing.T, opts *testOptions) { func testRequestHeaders(t *testing.T, opts *testOptions) {
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/apache-request-headers.php?i=%d", i), nil) req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/request-headers.php?i=%d", i), nil)
req.Header.Add("Content-Type", "text/plain") req.Header.Add(strings.Clone("Content-Type"), strings.Clone("text/plain"))
req.Header.Add("Frankenphp-I", strconv.Itoa(i)) req.Header.Add(strings.Clone("Frankenphp-I"), strings.Clone(strconv.Itoa(i)))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handler(w, req) handler(w, req)
@@ -714,3 +735,70 @@ func BenchmarkEcho(b *testing.B) {
handler(w, req) handler(w, req)
} }
} }
func BenchmarkServerSuperGlobal(b *testing.B) {
if err := frankenphp.Init(frankenphp.WithLogger(zap.NewNop())); err != nil {
panic(err)
}
defer frankenphp.Shutdown()
cwd, _ := os.Getwd()
testDataDir := cwd + "/testdata/"
// Mimicks 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"))
// Env vars available in a typical Docker container
env := map[string]string{
"HOSTNAME": "a88e81aa22e4",
"PHP_INI_DIR": "/usr/local/etc/php",
"HOME": "/root",
"GODEBUG": "cgocheck=0",
"PHP_LDFLAGS": "-Wl,-O1 -pie",
"PHP_CFLAGS": "-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64",
"PHP_VERSION": "8.3.2",
"GPG_KEYS": "1198C0117593497A5EC5C199286AF1F9897469DC C28D937575603EB4ABB725861C0779DC5C0A9DE4 AFD8691FDAEDF03BDF6E460563F15A9B715376CA",
"PHP_CPPFLAGS": "-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64",
"PHP_ASC_URL": "https://www.php.net/distributions/php-8.3.2.tar.xz.asc",
"PHP_URL": "https://www.php.net/distributions/php-8.3.2.tar.xz",
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"XDG_CONFIG_HOME": "/config",
"XDG_DATA_HOME": "/data",
"PHPIZE_DEPS": "autoconf dpkg-dev file g++ gcc libc-dev make pkg-config re2c",
"PWD": "/app",
"PHP_SHA256": "4ffa3e44afc9c590e28dc0d2d31fc61f0139f8b335f11880a121b9f9b9f0634e",
}
handler := func(w http.ResponseWriter, r *http.Request) {
req, err := frankenphp.NewRequestWithContext(r, frankenphp.WithRequestDocumentRoot(testDataDir, false), frankenphp.WithRequestEnv(env))
if err != nil {
panic(err)
}
r.Header = headers
if err := frankenphp.ServeHTTP(w, req); err != nil {
panic(err)
}
}
req := httptest.NewRequest("GET", "http://example.com/server-variable.php", nil)
w := httptest.NewRecorder()
for i := 0; i < b.N; i++ {
handler(w, req)
}
}

13
testdata/response-headers.php vendored Normal file
View File

@@ -0,0 +1,13 @@
<?php
require_once __DIR__.'/_executor.php';
return function () {
header('Foo: bar');
header('Foo2: bar2');
header('Invalid');
header('I: ' . ($_GET['i'] ?? 'i not set'));
http_response_code(201);
var_export(apache_response_headers());
};