mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-12-24 13:38:11 +08:00
feat: add support for 103 Early Hints (#12)
This commit is contained in:
29
frankenphp.c
29
frankenphp.c
@@ -123,9 +123,9 @@ PHP_FUNCTION(frankenphp_handle_request) {
|
||||
zend_fcall_info fci;
|
||||
zend_fcall_info_cache fcc;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "f", &fci, &fcc) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
ZEND_PARSE_PARAMETERS_START(1, 1)
|
||||
Z_PARAM_FUNC(fci, fcc)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
frankenphp_server_context *ctx = SG(server_context);
|
||||
|
||||
@@ -159,8 +159,6 @@ PHP_FUNCTION(frankenphp_handle_request) {
|
||||
/* Call session module's rinit */
|
||||
if (ctx->session_module)
|
||||
ctx->session_module->request_startup_func(ctx->session_module->type, ctx->session_module->module_number);
|
||||
|
||||
|
||||
|
||||
/* Call the PHP func */
|
||||
zval retval = {0};
|
||||
@@ -190,6 +188,27 @@ PHP_FUNCTION(frankenphp_handle_request) {
|
||||
RETURN_TRUE;
|
||||
}
|
||||
|
||||
PHP_FUNCTION(headers_send) {
|
||||
zend_long response_code = 200;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(0, 1)
|
||||
Z_PARAM_OPTIONAL
|
||||
Z_PARAM_LONG(response_code)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
int previous_status_code = SG(sapi_headers).http_response_code;
|
||||
SG(sapi_headers).http_response_code = response_code;
|
||||
|
||||
if (response_code >= 100 && response_code < 200) {
|
||||
int ret = sapi_module.send_headers(&SG(sapi_headers));
|
||||
SG(sapi_headers).http_response_code = previous_status_code;
|
||||
|
||||
RETURN_LONG(ret);
|
||||
}
|
||||
|
||||
RETURN_LONG(sapi_send_headers());
|
||||
}
|
||||
|
||||
static zend_module_entry frankenphp_module = {
|
||||
STANDARD_MODULE_HEADER,
|
||||
"frankenphp",
|
||||
|
||||
@@ -426,7 +426,19 @@ func go_write_header(rh C.uintptr_t, status C.int) {
|
||||
r := cgo.Handle(rh).Value().(*http.Request)
|
||||
fc := r.Context().Value(contextKey).(*FrankenPHPContext)
|
||||
|
||||
if fc.responseWriter == nil {
|
||||
return
|
||||
}
|
||||
|
||||
fc.responseWriter.WriteHeader(int(status))
|
||||
|
||||
if status >= 100 && status < 200 {
|
||||
// Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses
|
||||
h := fc.responseWriter.Header()
|
||||
for k := range h {
|
||||
delete(h, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//export go_read_post
|
||||
|
||||
@@ -3,3 +3,5 @@
|
||||
/** @generate-class-entries */
|
||||
|
||||
function frankenphp_handle_request(callable $callback): bool {}
|
||||
|
||||
function headers_send(int $status = 200): int {}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: f2e8375a3acb3080fc6c329eafc47155f0702c85 */
|
||||
* Stub hash: f9ead962eae043fa397a4e573e8905876b7b390b */
|
||||
|
||||
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_END_ARG_INFO()
|
||||
|
||||
|
||||
ZEND_FUNCTION(frankenphp_handle_request);
|
||||
ZEND_FUNCTION(headers_send);
|
||||
|
||||
|
||||
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_END
|
||||
};
|
||||
|
||||
@@ -7,8 +7,11 @@ import (
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/http/httptest"
|
||||
"net/http/httptrace"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -386,6 +389,38 @@ func testException(t *testing.T, opts *testOptions) {
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func TestEarlyHints_module(t *testing.T) { testEarlyHints(t, &testOptions{}) }
|
||||
func TestEarlyHints_worker(t *testing.T) {
|
||||
testEarlyHints(t, &testOptions{workerScript: "early-hints.php"})
|
||||
}
|
||||
func testEarlyHints(t *testing.T, opts *testOptions) {
|
||||
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
|
||||
var earlyHintReceived bool
|
||||
trace := &httptrace.ClientTrace{
|
||||
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
|
||||
switch code {
|
||||
case http.StatusEarlyHints:
|
||||
assert.Equal(t, "</style.css>; rel=preload; as=style", header.Get("Link"))
|
||||
assert.Equal(t, strconv.Itoa(i), header.Get("Request"))
|
||||
|
||||
earlyHintReceived = true
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/early-hints.php?i=%d", i), nil)
|
||||
w := NewRecorder()
|
||||
w.ClientTrace = trace
|
||||
handler(w, req)
|
||||
|
||||
assert.Equal(t, strconv.Itoa(i), w.Header().Get("Request"))
|
||||
assert.Equal(t, "", w.Header().Get("Link"))
|
||||
|
||||
assert.True(t, earlyHintReceived)
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func ExampleExecuteScript() {
|
||||
if err := frankenphp.Init(); err != nil {
|
||||
panic(err)
|
||||
|
||||
2
go.mod
2
go.mod
@@ -16,6 +16,8 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
4
go.sum
4
go.sum
@@ -32,6 +32,10 @@ go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
|
||||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4=
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
2
testdata/Caddyfile
vendored
2
testdata/Caddyfile
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
debug
|
||||
frankenphp {
|
||||
worker ./error.php
|
||||
#worker ./error.php
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user