Skip to content
Merged
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 runtime-light/server/http/http-server-state.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct HttpServerInstanceState final : private vk::not_copyable {

std::optional<string> opt_raw_post_data;

bool auto_encoding_enabled{};
uint32_t encoding{};
uint64_t status_code{kphp::http::status::NO_STATUS};
kphp::http::method http_method{kphp::http::method::other};
Expand Down
6 changes: 4 additions & 2 deletions runtime-light/server/http/init-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,11 @@ string get_http_response_body(HttpServerInstanceState& http_server_instance_st)
const auto appender{[&body](const auto& buffer) noexcept { body.append(buffer.buffer(), buffer.size()); }};
std::ranges::for_each(user_buffers | std::views::filter([](const auto& buffer) noexcept { return buffer.size() > 0; }), appender);

const bool auto_encoding_enabled{http_server_instance_st.auto_encoding_enabled};
const bool gzip_encoded{static_cast<bool>(http_server_instance_st.encoding & HttpServerInstanceState::ENCODING_GZIP)};
const bool deflate_encoded{static_cast<bool>(http_server_instance_st.encoding & HttpServerInstanceState::ENCODING_DEFLATE)};
// compress body if needed
if (gzip_encoded || deflate_encoded) {
if (auto_encoding_enabled && (gzip_encoded || deflate_encoded)) {
auto encoded_body{kphp::zlib::encode({body.c_str(), static_cast<size_t>(body.size())}, kphp::zlib::DEFAULT_COMPRESSION_LEVEL,
gzip_encoded ? kphp::zlib::ENCODING_GZIP : kphp::zlib::ENCODING_DEFLATE)};
if (encoded_body.has_value()) [[likely]] {
Expand Down Expand Up @@ -389,9 +390,10 @@ kphp::coro::task<> finalize_server() noexcept {
}
[[fallthrough]];
case kphp::http::response_state::sending_headers: {
const bool auto_encoding_enabled{http_server_instance_st.auto_encoding_enabled};
const bool gzip_encoded{static_cast<bool>(http_server_instance_st.encoding & HttpServerInstanceState::ENCODING_GZIP)};
const bool deflate_encoded{static_cast<bool>(http_server_instance_st.encoding & HttpServerInstanceState::ENCODING_DEFLATE)};
if (gzip_encoded || deflate_encoded) {
if (auto_encoding_enabled && (gzip_encoded || deflate_encoded)) {
auto& static_SB{RuntimeContext::get().static_SB};
static_SB.clean() << kphp::http::headers::CONTENT_ENCODING.data() << ": " << (gzip_encoded ? ENCODING_GZIP.data() : ENCODING_DEFLATE.data());
kphp::http::header({static_SB.c_str(), static_SB.size()}, true, kphp::http::status::NO_STATUS);
Expand Down
2 changes: 1 addition & 1 deletion runtime-light/stdlib/output/output-buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ void f$ob_start(const string& callback) noexcept {
if (!callback.empty()) {
if (current_buffering_level == 0 && std::string_view{callback.c_str(), callback.size()} == ob_gzhandler_name) {
auto& http_server_instance_st{HttpServerInstanceState::get()};
http_server_instance_st.encoding |= HttpServerInstanceState::ENCODING_GZIP;
http_server_instance_st.auto_encoding_enabled = true;
} else {
kphp::log::error("unsupported callback {} at buffering level {}", callback.c_str(), output_instance_st.output_buffers.user_level());
}
Expand Down
9 changes: 7 additions & 2 deletions runtime-light/stdlib/output/output-buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ inline bool f$ob_end_clean() noexcept {

output_instance_st.output_buffers.prev_user_buffer();
auto& http_server_instance_st{HttpServerInstanceState::get()};
http_server_instance_st.encoding &= ~HttpServerInstanceState::ENCODING_GZIP;
if (const auto current_buffering_level{output_instance_st.output_buffers.user_level()}; current_buffering_level == 0) {
http_server_instance_st.auto_encoding_enabled = false;
}
return true;
}

Expand All @@ -56,7 +58,10 @@ inline Optional<string> f$ob_get_clean() noexcept {
string result{(*opt_user_buffer).get().str()};
output_instance_st.output_buffers.prev_user_buffer();
auto& http_server_instance_st{HttpServerInstanceState::get()};
http_server_instance_st.encoding &= ~HttpServerInstanceState::ENCODING_GZIP;
if (const auto current_buffering_level{output_instance_st.output_buffers.user_level()}; current_buffering_level == 0) {
http_server_instance_st.auto_encoding_enabled = false;
}

return result;
}

Expand Down
17 changes: 17 additions & 0 deletions tests/python/tests/http_server/php/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,23 @@ public function work(string $output) {
echo 'OK';
ob_end_flush();
break;
case "gzhandler-after-reset":
ob_start("ob_gzhandler");
echo 'OK';
ob_end_flush();

ob_start("ob_gzhandler");
break;
case "gzhandler-with-nested-buffer":
ob_start("ob_gzhandler");
echo 'OK';
ob_start();
ob_end_clean();

break;
case "gzhandler-absent":
echo 'OK';
break;
case "ignore-second-handler":
header('Transfer-Encoding: chunked');
$chunk = "OK";
Expand Down
24 changes: 24 additions & 0 deletions tests/python/tests/http_server/test_gzip_header_reset.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,36 @@ def test_single_gzip_buffer_closed(self):
with self.assertRaises(KeyError):
_ = response.headers["Content-Encoding"]

def test_gzip_handler_after_reset(self):
response = self.gzip_request("gzhandler-after-reset")
self.assertEqual(response.headers["Content-Encoding"], "gzip")

def test_deflate_handler_after_reset(self):
response = self.deflate_request("gzhandler-after-reset")
self.assertEqual(response.headers["Content-Encoding"], "deflate")

def test_gzip_handler_with_nested_buffer(self):
response = self.gzip_request("gzhandler-with-nested-buffer")
self.assertEqual(response.headers["Content-Encoding"], "gzip")

def test_gzip_without_handler(self):
response = self.gzip_request("gzhandler-absent")
with self.assertRaises(KeyError):
_ = response.headers["Content-Encoding"]

@pytest.mark.k2_skip
def test_ignore_second_gzip_handler(self):
response = self.gzip_request("ignore-second-handler")
with self.assertRaises(KeyError):
_ = response.headers["Content-Encoding"]

def deflate_request(self, type):
url = "/test_script_gzip_header?type=" + type
return self.web_server.http_get(url, headers={
"Host": "localhost",
"Accept-Encoding": "deflate"
})

def gzip_request(self, type):
url = "/test_script_gzip_header?type=" + type
return self.web_server.http_get(url, headers={
Expand Down
Loading