mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-12-24 13:38:11 +08:00
Compare commits
6 Commits
feat/apach
...
refactor/b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e118b9f681 | ||
|
|
ab7ce9cb18 | ||
|
|
5afde55ebf | ||
|
|
da63e700b0 | ||
|
|
5a8e5f9518 | ||
|
|
3d9f344a50 |
14
.github/workflows/docker.yaml
vendored
14
.github/workflows/docker.yaml
vendored
@@ -19,6 +19,7 @@ on:
|
||||
- cron: '0 4 * * *'
|
||||
env:
|
||||
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
|
||||
LATEST: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && '0' || '1' }}
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -37,6 +38,8 @@ jobs:
|
||||
-
|
||||
name: Check PHP versions
|
||||
id: check
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PHP_82_LATEST=$(skopeo inspect docker://docker.io/library/php:8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
PHP_83_LATEST=$(skopeo inspect docker://docker.io/library/php:8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
@@ -45,13 +48,13 @@ jobs:
|
||||
echo php82_version="${PHP_82_LATEST//./-}"
|
||||
echo php83_version="${PHP_83_LATEST//./-}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
|
||||
# Check if the Docker images must be rebuilt
|
||||
if [[ "${GITHUB_EVENT_NAME}" != "schedule" ]]; then
|
||||
echo skip=false >> "${GITHUB_OUTPUT}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
FRANKENPHP_82_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:latest-php8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
FRANKENPHP_83_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:latest-php8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
|
||||
@@ -59,7 +62,7 @@ jobs:
|
||||
echo skip=true >> "${GITHUB_OUTPUT}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
{
|
||||
echo ref="$(gh release view --repo dunglas/frankenphp --json tagName --jq '.tagName')"
|
||||
echo skip=false
|
||||
@@ -89,7 +92,6 @@ jobs:
|
||||
SHA: ${{ github.sha }}
|
||||
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || steps.check.outputs.ref || github.sha }}
|
||||
PHP_VERSION: ${{ steps.check.outputs.php_version }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
@@ -229,7 +231,8 @@ jobs:
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
# Temporary fix for https://github.com/docker/buildx/issues/2229
|
||||
version: "https://github.com/jedevc/buildx.git#imagetools-resolver-copy-dupe"
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
@@ -240,6 +243,7 @@ jobs:
|
||||
name: Create manifest list and push
|
||||
working-directory: /tmp/metadata
|
||||
run: |
|
||||
set -x
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools create $(jq -cr '.target."${{ matrix.target }}-${{ matrix.variant }}".tags | map("-t " + .) | join(" ")' <<< ${METADATA}) \
|
||||
$(printf 'dunglas/frankenphp@sha256:%s ' *)
|
||||
|
||||
1
.github/workflows/static.yaml
vendored
1
.github/workflows/static.yaml
vendored
@@ -19,6 +19,7 @@ on:
|
||||
- cron: '0 0 * * *'
|
||||
env:
|
||||
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
|
||||
LATEST: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && '0' || '1' }}
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -144,3 +144,18 @@ RUN adduser -D ${USER} \
|
||||
# Caddy requires write access to /data/caddy and /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.
|
||||
|
||||
63
frankenphp.c
63
frankenphp.c
@@ -243,7 +243,7 @@ PHP_FUNCTION(frankenphp_finish_request) { /* {{{ */
|
||||
} /* }}} */
|
||||
|
||||
/* {{{ Fetch all HTTP request headers */
|
||||
PHP_FUNCTION(apache_request_headers) {
|
||||
PHP_FUNCTION(frankenphp_request_headers) {
|
||||
if (zend_parse_parameters_none() == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
@@ -262,6 +262,59 @@ PHP_FUNCTION(apache_request_headers) {
|
||||
}
|
||||
|
||||
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 +837,12 @@ static char *cli_script;
|
||||
static int cli_argc;
|
||||
static char **cli_argv;
|
||||
|
||||
// Adapted from https://github.com/php/php-src/sapi/cli/php_cli.c (The PHP
|
||||
// Group, The PHP License)
|
||||
// CLI code is adapted from
|
||||
// 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) /* {{{ */
|
||||
{
|
||||
php_stream *s_in, *s_out, *s_err;
|
||||
|
||||
@@ -582,24 +582,41 @@ func go_register_variables(rh C.uintptr_t, trackVarsArray *C.zval) {
|
||||
}
|
||||
|
||||
//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)
|
||||
|
||||
pinner := runtime.Pinner{}
|
||||
pinnerHandle := C.uintptr_t(cgo.NewHandle(pinner))
|
||||
|
||||
rl := len(r.Header)
|
||||
scs := unsafe.Sizeof(C.go_string{})
|
||||
|
||||
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 {
|
||||
*header = C.go_string{C.size_t(len(field)), (*C.char)(unsafe.Pointer(unsafe.StringData(field)))}
|
||||
fd := unsafe.StringData(field)
|
||||
pinner.Pin(fd)
|
||||
|
||||
*header = C.go_string{C.size_t(len(field)), (*C.char)(unsafe.Pointer(fd))}
|
||||
header = (*C.go_string)(unsafe.Add(unsafe.Pointer(header), scs))
|
||||
|
||||
cv := strings.Join(val, ", ")
|
||||
*header = C.go_string{C.size_t(len(cv)), (*C.char)(unsafe.Pointer(unsafe.StringData(cv)))}
|
||||
vd := unsafe.StringData(cv)
|
||||
pinner.Pin(vd)
|
||||
|
||||
*header = C.go_string{C.size_t(len(cv)), (*C.char)(unsafe.Pointer(vd))}
|
||||
header = (*C.go_string)(unsafe.Add(unsafe.Pointer(header), scs))
|
||||
}
|
||||
|
||||
return headers, C.size_t(rl)
|
||||
return headers, C.size_t(rl), 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) {
|
||||
|
||||
@@ -13,9 +13,22 @@ function frankenphp_finish_request(): bool {}
|
||||
*/
|
||||
function fastcgi_finish_request(): bool {}
|
||||
|
||||
function frankenphp_request_headers(): array {}
|
||||
|
||||
/**
|
||||
* @alias frankenphp_request_headers
|
||||
*/
|
||||
function apache_request_headers(): array {}
|
||||
|
||||
/**
|
||||
* @alias apache_request_headers
|
||||
* @alias frankenphp_response_headers
|
||||
*/
|
||||
function getallheaders(): array {}
|
||||
|
||||
function frankenphp_response_headers(): array|bool {}
|
||||
|
||||
/**
|
||||
* @alias frankenphp_response_headers
|
||||
*/
|
||||
function apache_response_headers(): array|bool {}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* 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,
|
||||
_IS_BOOL, 0)
|
||||
@@ -16,16 +16,25 @@ ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_fastcgi_finish_request arginfo_frankenphp_finish_request
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_apache_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_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(headers_send);
|
||||
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[] = {
|
||||
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)
|
||||
ZEND_FALIAS(fastcgi_finish_request, frankenphp_finish_request,
|
||||
arginfo_fastcgi_finish_request)
|
||||
ZEND_FE(apache_request_headers, arginfo_apache_request_headers)
|
||||
ZEND_FALIAS(getallheaders, apache_request_headers,
|
||||
arginfo_getallheaders) ZEND_FE_END};
|
||||
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_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};
|
||||
|
||||
@@ -226,6 +226,27 @@ func testHeaders(t *testing.T, opts *testOptions) {
|
||||
}, 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_worker(t *testing.T) { testInput(t, &testOptions{workerScript: "input.php"}) }
|
||||
func testInput(t *testing.T, opts *testOptions) {
|
||||
@@ -553,15 +574,15 @@ func testFiberNoCgo(t *testing.T, opts *testOptions) {
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func TestApacheRequestHeaders_module(t *testing.T) { testApacheRequestHeaders(t, &testOptions{}) }
|
||||
func TestApacheRequestHeaders_worker(t *testing.T) {
|
||||
testApacheRequestHeaders(t, &testOptions{workerScript: "apache-request-headers.php"})
|
||||
func TestRequestHeaders_module(t *testing.T) { testRequestHeaders(t, &testOptions{}) }
|
||||
func TestRequestHeaders_worker(t *testing.T) {
|
||||
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) {
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/apache-request-headers.php?i=%d", i), nil)
|
||||
req.Header.Add("Content-Type", "text/plain")
|
||||
req.Header.Add("Frankenphp-I", strconv.Itoa(i))
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/request-headers.php?i=%d", i), nil)
|
||||
req.Header.Add(strings.Clone("Content-Type"), strings.Clone("text/plain"))
|
||||
req.Header.Add(strings.Clone("Frankenphp-I"), strings.Clone(strconv.Itoa(i)))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
handler(w, req)
|
||||
|
||||
13
testdata/response-headers.php
vendored
Normal file
13
testdata/response-headers.php
vendored
Normal 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());
|
||||
};
|
||||
Reference in New Issue
Block a user