Skip to content

Commit

Permalink
feat: add fastcgi_finish_request() support (#69)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
withinboredom authored Nov 3, 2022
1 parent bfb0b17 commit 9ef3bd7
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 6 deletions.
34 changes: 33 additions & 1 deletion frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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));

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}

Expand Down
11 changes: 10 additions & 1 deletion frankenphp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
}
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions frankenphp.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
10 changes: 9 additions & 1 deletion frankenphp_arginfo.h
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
};
14 changes: 14 additions & 0 deletions frankenphp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"})
Expand Down
7 changes: 7 additions & 0 deletions testdata/finish-request.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

echo "This is output\n";

frankenphp_finish_request();

echo "This is not";
11 changes: 8 additions & 3 deletions worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

0 comments on commit 9ef3bd7

Please sign in to comment.