Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add headers_sent #1185

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
PetrShumilov marked this conversation as resolved.
Show resolved Hide resolved
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;
PetrShumilov marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -2373,6 +2394,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 @@ -47,6 +47,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):
PetrShumilov marked this conversation as resolved.
Show resolved Hide resolved
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)
PetrShumilov marked this conversation as resolved.
Show resolved Hide resolved

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)

Loading