From d8571ded93a0d8f95578d3cf3e126ed425abd194 Mon Sep 17 00:00:00 2001 From: kojidev Date: Tue, 17 Dec 2024 14:07:00 +0300 Subject: [PATCH 1/3] add headers_sent() --- builtin-functions/kphp-full/_functions.txt | 1 + runtime/interface.cpp | 20 ++++++++++++++++---- runtime/interface.h | 2 ++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/builtin-functions/kphp-full/_functions.txt b/builtin-functions/kphp-full/_functions.txt index 8d99acca97..8957e33aff 100644 --- a/builtin-functions/kphp-full/_functions.txt +++ b/builtin-functions/kphp-full/_functions.txt @@ -141,6 +141,7 @@ function ob_get_flush () ::: string | false; function ob_get_length () ::: int | false; function ob_get_level () ::: int; +function headers_sent () ::: bool; function header ($str ::: string, $replace ::: bool = true, $http_response_code ::: int = 0) ::: void; function headers_list () ::: string[]; function send_http_103_early_hints($headers ::: string[]) ::: void; diff --git a/runtime/interface.cpp b/runtime/interface.cpp index 3efbdf1aa7..8273902696 100644 --- a/runtime/interface.cpp +++ b/runtime/interface.cpp @@ -209,6 +209,7 @@ static string http_status_line; static char headers_storage[sizeof(array)]; static array *headers = reinterpret_cast *> (headers_storage); static long long header_last_query_num = -1; +static bool headers_custom_handler_invoked = false; static bool headers_sent = false; static headers_custom_handler_function_type headers_custom_handler_function; @@ -369,6 +370,10 @@ array f$headers_list() { return result; } +bool f$headers_sent() { + return headers_sent; +} + void f$send_http_103_early_hints(const array & headers) { string header("HTTP/1.1 103 Early Hints\r\n"); for (const auto & h : headers) { @@ -568,9 +573,12 @@ static int ob_merge_buffers() { void f$flush() { php_assert(ob_cur_buffer >= 0 && php_worker.has_value()); // Run custom headers handler before body processing - if (headers_custom_handler_function && !headers_sent && query_type == QUERY_TYPE_HTTP) { + if (!headers_custom_handler_invoked && query_type == QUERY_TYPE_HTTP) { + headers_custom_handler_invoked = true; + if (headers_custom_handler_function) { + headers_custom_handler_function(); + } headers_sent = true; - headers_custom_handler_function(); } string_buffer const * http_body = compress_http_query_body(&oub[ob_system_level]); string_buffer const * http_headers = nullptr; @@ -586,9 +594,12 @@ void f$flush() { void f$fastcgi_finish_request(int64_t exit_code) { // Run custom headers handler before body processing - if (headers_custom_handler_function && !headers_sent && query_type == QUERY_TYPE_HTTP) { + if (!headers_custom_handler_invoked && query_type == QUERY_TYPE_HTTP) { + headers_custom_handler_invoked = true; + if (headers_custom_handler_function) { + headers_custom_handler_function(); + } headers_sent = true; - headers_custom_handler_function(); } int ob_total_buffer = ob_merge_buffers(); if (php_worker.has_value() && php_worker->flushed_http_connection) { @@ -2373,6 +2384,7 @@ static void free_header_handler_function() { headers_custom_handler_function.~headers_custom_handler_function_type(); new(&headers_custom_handler_function) headers_custom_handler_function_type{}; headers_sent = false; + headers_custom_handler_invoked = false; } diff --git a/runtime/interface.h b/runtime/interface.h index 1e5a81bcaa..ffa4c121b0 100644 --- a/runtime/interface.h +++ b/runtime/interface.h @@ -47,6 +47,8 @@ int64_t f$ob_get_level(); void f$flush(); +bool f$headers_sent(); + void f$header(const string &str, bool replace = true, int64_t http_response_code = 0); array f$headers_list(); From 755d3eb996c4737e21fa83658550792b47ca430b Mon Sep 17 00:00:00 2001 From: kojidev Date: Tue, 17 Dec 2024 14:07:07 +0300 Subject: [PATCH 2/3] add python tests --- tests/python/tests/http_server/php/index.php | 16 +++++++++++ .../tests/http_server/test_headers_sent.py | 27 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/python/tests/http_server/test_headers_sent.py diff --git a/tests/python/tests/http_server/php/index.php b/tests/python/tests/http_server/php/index.php index c179cd541c..728064f962 100644 --- a/tests/python/tests/http_server/php/index.php +++ b/tests/python/tests/http_server/php/index.php @@ -178,6 +178,22 @@ public function work(string $output) { break; } +} else if ($_SERVER["PHP_SELF"] === "/test_headers_sent") { + switch($_GET["type"]) { + case "flush": + echo (int)headers_sent(); + flush(); + echo (int)headers_sent(); + break; + case "shutdown": + if ((int)$_GET['flush']) { + flush(); + } + register_shutdown_function(function() { + fwrite(STDERR, "headers_sent() after shutdown callback returns " . var_export(headers_sent(), true) . "\n"); + }); + break; + } } else if ($_SERVER["PHP_SELF"] === "/test_ignore_user_abort") { register_shutdown_function('shutdown_function'); /** @var I */ diff --git a/tests/python/tests/http_server/test_headers_sent.py b/tests/python/tests/http_server/test_headers_sent.py new file mode 100644 index 0000000000..83b8b9613c --- /dev/null +++ b/tests/python/tests/http_server/test_headers_sent.py @@ -0,0 +1,27 @@ +import os +import socket +from python.lib.testcase import KphpServerAutoTestCase +from python.lib.http_client import RawResponse + +class TestHeadersSent(KphpServerAutoTestCase): + def test_on_flush(self): + request = b"GET /test_headers_sent?type=flush HTTP/1.1\r\nHost:localhost\r\n\r\n" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect(('127.0.0.1', self.kphp_server.http_port)) + s.send(request) + + body = RawResponse(s.recv(4096)).content + s.recv(20) + self.assertEqual(b"01", body) + + def test_header_sent_is_false_on_fastcgi_finish_request_when_no_flush(self): + self.kphp_server.http_request(uri="/test_headers_sent?type=shutdown&flush=0") + self.kphp_server.assert_log(["headers_sent\\(\\) after shutdown callback returns false"], timeout=10) + + def test_header_sent_is_true_on_fastcgi_finish_request_when_flush(self): + request = b"GET /test_headers_sent?type=shutdown&flush=1 HTTP/1.1\r\nHost:localhost\r\n\r\n" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect(('127.0.0.1', self.kphp_server.http_port)) + s.send(request) + + self.kphp_server.assert_log(["headers_sent\\(\\) after shutdown callback returns true"], timeout=10) + From 04342d458fe370e5d438dc0823576db71f5957de Mon Sep 17 00:00:00 2001 From: kojidev Date: Tue, 17 Dec 2024 21:43:44 +0300 Subject: [PATCH 3/3] ignore args --- builtin-functions/kphp-full/_functions.txt | 2 +- runtime/interface.cpp | 12 +++++++++++- runtime/interface.h | 4 +++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/builtin-functions/kphp-full/_functions.txt b/builtin-functions/kphp-full/_functions.txt index 8957e33aff..630d07f648 100644 --- a/builtin-functions/kphp-full/_functions.txt +++ b/builtin-functions/kphp-full/_functions.txt @@ -141,7 +141,7 @@ function ob_get_flush () ::: string | false; function ob_get_length () ::: int | false; function ob_get_level () ::: int; -function headers_sent () ::: bool; +function headers_sent (?string &$filename = null, ?int &$line = null) ::: bool; function header ($str ::: string, $replace ::: bool = true, $http_response_code ::: int = 0) ::: void; function headers_list () ::: string[]; function send_http_103_early_hints($headers ::: string[]) ::: void; diff --git a/runtime/interface.cpp b/runtime/interface.cpp index 8273902696..490e276154 100644 --- a/runtime/interface.cpp +++ b/runtime/interface.cpp @@ -370,7 +370,17 @@ array f$headers_list() { return result; } -bool f$headers_sent() { +Optional &get_dummy_headers_sent_filename() noexcept { + static Optional filename; + return filename; +} + +Optional &get_dummy_headers_sent_line() noexcept { + static Optional dummy_line; + return dummy_line; +} + +bool f$headers_sent([[maybe_unused]] Optional &filename, [[maybe_unused]] Optional &line) { return headers_sent; } diff --git a/runtime/interface.h b/runtime/interface.h index ffa4c121b0..7d8de552e6 100644 --- a/runtime/interface.h +++ b/runtime/interface.h @@ -47,7 +47,9 @@ int64_t f$ob_get_level(); void f$flush(); -bool f$headers_sent(); +Optional &get_dummy_headers_sent_filename() noexcept; +Optional &get_dummy_headers_sent_line() noexcept; +bool f$headers_sent(Optional &filename = get_dummy_headers_sent_filename(), Optional &line = get_dummy_headers_sent_line()); void f$header(const string &str, bool replace = true, int64_t http_response_code = 0);