feat: add support for 103 Early Hints (#12)

This commit is contained in:
Kévin Dunglas
2022-10-11 17:40:12 +02:00
committed by GitHub
parent 3712d0d69b
commit 5af6b10d1f
8 changed files with 87 additions and 7 deletions

View File

@@ -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",

View File

@@ -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

View File

@@ -3,3 +3,5 @@
/** @generate-class-entries */
function frankenphp_handle_request(callable $callback): bool {}
function headers_send(int $status = 200): int {}

View File

@@ -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
};

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -1,7 +1,7 @@
{
debug
frankenphp {
worker ./error.php
#worker ./error.php
}
}