Skip to content

Commit

Permalink
add headers_sent (#1185)
Browse files Browse the repository at this point in the history
* add headers_sent()
* add python tests
* ignore args
  • Loading branch information
kojidev authored Jan 21, 2025
1 parent 378807c commit c09f8af
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 4 deletions.
1 change: 1 addition & 0 deletions builtin-functions/kphp-full/_functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ function ob_get_flush () ::: string | false;
function ob_get_length () ::: int | false;
function ob_get_level () ::: int;

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;
Expand Down
30 changes: 26 additions & 4 deletions runtime/interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ static string http_status_line;
static char headers_storage[sizeof(array<string>)];
static array<string> *headers = reinterpret_cast <array<string> *> (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;

Expand Down Expand Up @@ -369,6 +370,20 @@ array<string> f$headers_list() {
return result;
}

Optional<string> &get_dummy_headers_sent_filename() noexcept {
static Optional<string> filename;
return filename;
}

Optional<int64_t> &get_dummy_headers_sent_line() noexcept {
static Optional<int64_t> dummy_line;
return dummy_line;
}

bool f$headers_sent([[maybe_unused]] Optional<string> &filename, [[maybe_unused]] Optional<int64_t> &line) {
return headers_sent;
}

void f$send_http_103_early_hints(const array<string> & headers) {
string header("HTTP/1.1 103 Early Hints\r\n");
for (const auto & h : headers) {
Expand Down Expand Up @@ -568,9 +583,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;
Expand All @@ -586,9 +604,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) {
Expand Down Expand Up @@ -2341,6 +2362,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;
}


Expand Down
4 changes: 4 additions & 0 deletions runtime/interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ int64_t f$ob_get_level();

void f$flush();

Optional<string> &get_dummy_headers_sent_filename() noexcept;
Optional<int64_t> &get_dummy_headers_sent_line() noexcept;
bool f$headers_sent(Optional<string> &filename = get_dummy_headers_sent_filename(), Optional<int64_t> &line = get_dummy_headers_sent_line());

void f$header(const string &str, bool replace = true, int64_t http_response_code = 0);

array<string> f$headers_list();
Expand Down
16 changes: 16 additions & 0 deletions tests/python/tests/http_server/php/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
27 changes: 27 additions & 0 deletions tests/python/tests/http_server/test_headers_sent.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit c09f8af

Please sign in to comment.