Browse Source

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

Kévin Dunglas 2 years ago
parent
commit
5af6b10d1f
8 changed files with 87 additions and 7 deletions
  1. 24 5
      frankenphp.c
  2. 12 0
      frankenphp.go
  3. 2 0
      frankenphp.stub.php
  4. 7 1
      frankenphp_arginfo.h
  5. 35 0
      frankenphp_test.go
  6. 2 0
      go.mod
  7. 4 0
      go.sum
  8. 1 1
      testdata/Caddyfile

+ 24 - 5
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",

+ 12 - 0
frankenphp.go

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

+ 2 - 0
frankenphp.stub.php

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

+ 7 - 1
frankenphp_arginfo.h

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

+ 35 - 0
frankenphp_test.go

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

+ 1 - 1
testdata/Caddyfile

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