mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-12-24 13:38:11 +08:00
feat: add fastcgi_finish_request() support (#69)
* Add function to ext
* buggy af, but kinda working
* Get worker and regular mode working
* fix formatting
* add eol
* add eol
* 🤦
* properly generate file
* use sync.Once
This commit is contained in:
34
frankenphp.c
34
frankenphp.c
@@ -55,6 +55,7 @@ typedef struct frankenphp_server_context {
|
||||
uintptr_t current_request;
|
||||
uintptr_t main_request; /* Only available during worker initialization */
|
||||
char *cookie_data;
|
||||
bool finished;
|
||||
} frankenphp_server_context;
|
||||
|
||||
static void frankenphp_request_reset() {
|
||||
@@ -104,7 +105,7 @@ static void frankenphp_worker_request_shutdown(uintptr_t current_request) {
|
||||
sapi_deactivate();
|
||||
} zend_end_try();
|
||||
|
||||
if (current_request != 0) go_frankenphp_worker_handle_request_end(current_request);
|
||||
if (current_request != 0) go_frankenphp_worker_handle_request_end(current_request, true);
|
||||
|
||||
zend_set_memory_limit(PG(memory_limit));
|
||||
|
||||
@@ -154,6 +155,10 @@ static int frankenphp_worker_request_startup() {
|
||||
|
||||
zend_is_auto_global(ZSTR_KNOWN(ZEND_STR_AUTOGLOBAL_SERVER));
|
||||
|
||||
// unfinish the request
|
||||
frankenphp_server_context *ctx = SG(server_context);
|
||||
ctx->finished = false;
|
||||
|
||||
// TODO: store the list of modules to reload in a global module variable
|
||||
const char **module_name;
|
||||
zend_module_entry *module;
|
||||
@@ -171,6 +176,27 @@ static int frankenphp_worker_request_startup() {
|
||||
return retval;
|
||||
}
|
||||
|
||||
PHP_FUNCTION(frankenphp_finish_request) { /* {{{ */
|
||||
if (zend_parse_parameters_none() == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
frankenphp_server_context *ctx = SG(server_context);
|
||||
|
||||
if(!ctx->finished) {
|
||||
php_output_end_all();
|
||||
php_header();
|
||||
|
||||
go_frankenphp_worker_handle_request_end(ctx->current_request, false);
|
||||
ctx->finished = true;
|
||||
|
||||
RETURN_TRUE;
|
||||
}
|
||||
|
||||
RETURN_FALSE;
|
||||
|
||||
} /* }}} */
|
||||
|
||||
PHP_FUNCTION(frankenphp_handle_request) {
|
||||
zend_fcall_info fci;
|
||||
zend_fcall_info_cache fcc;
|
||||
@@ -320,6 +346,7 @@ int frankenphp_create_server_context()
|
||||
ctx->current_request = 0;
|
||||
ctx->main_request = 0;
|
||||
ctx->cookie_data = NULL;
|
||||
ctx->finished = false;
|
||||
|
||||
SG(server_context) = ctx;
|
||||
|
||||
@@ -373,6 +400,11 @@ static size_t frankenphp_ub_write(const char *str, size_t str_length)
|
||||
{
|
||||
frankenphp_server_context* ctx = SG(server_context);
|
||||
|
||||
if(ctx->finished) {
|
||||
// todo: maybe log a warning that we tried to write to a finished request?
|
||||
return 0;
|
||||
}
|
||||
|
||||
return go_ub_write(ctx->current_request ? ctx->current_request : ctx->main_request, (char *) str, str_length);
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,9 @@ type FrankenPHPContext struct {
|
||||
populated bool
|
||||
authPassword string
|
||||
|
||||
// Whether the request is already closed
|
||||
closed sync.Once
|
||||
|
||||
responseWriter http.ResponseWriter
|
||||
done chan interface{}
|
||||
}
|
||||
@@ -359,6 +362,12 @@ func go_fetch_request() C.uintptr_t {
|
||||
return C.uintptr_t(cgo.NewHandle(r))
|
||||
}
|
||||
|
||||
func maybeCloseContext(fc *FrankenPHPContext) {
|
||||
fc.closed.Do(func() {
|
||||
close(fc.done)
|
||||
})
|
||||
}
|
||||
|
||||
//export go_execute_script
|
||||
func go_execute_script(rh unsafe.Pointer) {
|
||||
handle := cgo.Handle(rh)
|
||||
@@ -369,7 +378,7 @@ func go_execute_script(rh unsafe.Pointer) {
|
||||
if !ok {
|
||||
panic(InvalidRequestError)
|
||||
}
|
||||
defer close(fc.done)
|
||||
defer maybeCloseContext(fc)
|
||||
|
||||
if C.frankenphp_create_server_context() < 0 {
|
||||
panic(RequestContextCreationError)
|
||||
|
||||
@@ -5,3 +5,10 @@
|
||||
function frankenphp_handle_request(callable $callback): bool {}
|
||||
|
||||
function headers_send(int $status = 200): int {}
|
||||
|
||||
function frankenphp_finish_request(): bool {}
|
||||
|
||||
/**
|
||||
* @alias frankenphp_finish_request
|
||||
*/
|
||||
function fastcgi_finish_request(): bool {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: f9ead962eae043fa397a4e573e8905876b7b390b */
|
||||
* Stub hash: de4dc4063fafd8c933e3068c8349889a7ece5f03 */
|
||||
|
||||
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)
|
||||
@@ -9,13 +9,21 @@ 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_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_finish_request, 0, 0, _IS_BOOL, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_fastcgi_finish_request arginfo_frankenphp_finish_request
|
||||
|
||||
|
||||
ZEND_FUNCTION(frankenphp_handle_request);
|
||||
ZEND_FUNCTION(headers_send);
|
||||
ZEND_FUNCTION(frankenphp_finish_request);
|
||||
|
||||
|
||||
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(frankenphp_finish_request, arginfo_frankenphp_finish_request)
|
||||
ZEND_FALIAS(fastcgi_finish_request, frankenphp_finish_request, arginfo_fastcgi_finish_request)
|
||||
ZEND_FE_END
|
||||
};
|
||||
|
||||
@@ -100,6 +100,20 @@ func testHelloWorld(t *testing.T, opts *testOptions) {
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func TestFinishRequest_module(t *testing.T) { testFinishRequest(t, nil) }
|
||||
func TestFinishRequest_worker(t *testing.T) { testFinishRequest(t, &testOptions{workerScript: "index.php"}) }
|
||||
func testFinishRequest(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/finish-request.php?i=%d", i), nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
assert.Equal(t, fmt.Sprintf("This is output\n"), string(body))
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func TestServerVariable_module(t *testing.T) { testServerVariable(t, nil) }
|
||||
func TestServerVariable_worker(t *testing.T) {
|
||||
testServerVariable(t, &testOptions{workerScript: "server-variable.php"})
|
||||
|
||||
7
testdata/finish-request.php
vendored
Normal file
7
testdata/finish-request.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
echo "This is output\n";
|
||||
|
||||
frankenphp_finish_request();
|
||||
|
||||
echo "This is not";
|
||||
11
worker.go
11
worker.go
@@ -148,14 +148,19 @@ func go_frankenphp_worker_handle_request_start(rh C.uintptr_t) C.uintptr_t {
|
||||
}
|
||||
|
||||
//export go_frankenphp_worker_handle_request_end
|
||||
func go_frankenphp_worker_handle_request_end(rh C.uintptr_t) {
|
||||
func go_frankenphp_worker_handle_request_end(rh C.uintptr_t, deleteHandle bool) {
|
||||
if rh == 0 {
|
||||
return
|
||||
}
|
||||
rHandle := cgo.Handle(rh)
|
||||
r := rHandle.Value().(*http.Request)
|
||||
fc := r.Context().Value(contextKey).(*FrankenPHPContext)
|
||||
|
||||
cgo.Handle(rh).Delete()
|
||||
if deleteHandle {
|
||||
cgo.Handle(rh).Delete()
|
||||
}
|
||||
|
||||
close(fc.done)
|
||||
maybeCloseContext(fc)
|
||||
|
||||
fc.Logger.Debug("request handling finished", zap.String("worker", fc.Env["SCRIPT_FILENAME"]), zap.String("url", r.RequestURI))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user