[feature] A setting http_response_headers

Implementing https://github.com/ClickHouse/ClickHouse/issues/59620
Deprecating `content_type` setting (still supported).
This commit is contained in:
Grigorii Sokolik 2024-05-09 14:27:43 +03:00
parent 72cc6b2ba0
commit 47686e0c4a
2 changed files with 308 additions and 282 deletions

View File

@ -1,8 +1,8 @@
#include <Server/HTTPHandler.h> #include <Server/HTTPHandler.h>
#include <Access/AccessControl.h>
#include <Access/Authentication.h> #include <Access/Authentication.h>
#include <Access/Credentials.h> #include <Access/Credentials.h>
#include <Access/AccessControl.h>
#include <Access/ExternalAuthenticators.h> #include <Access/ExternalAuthenticators.h>
#include <Access/Role.h> #include <Access/Role.h>
#include <Access/User.h> #include <Access/User.h>
@ -10,6 +10,7 @@
#include <Compression/CompressedWriteBuffer.h> #include <Compression/CompressedWriteBuffer.h>
#include <Core/ExternalTable.h> #include <Core/ExternalTable.h>
#include <Disks/StoragePolicy.h> #include <Disks/StoragePolicy.h>
#include <Formats/FormatFactory.h>
#include <IO/CascadeWriteBuffer.h> #include <IO/CascadeWriteBuffer.h>
#include <IO/ConcatReadBuffer.h> #include <IO/ConcatReadBuffer.h>
#include <IO/MemoryReadWriteBuffer.h> #include <IO/MemoryReadWriteBuffer.h>
@ -17,43 +18,44 @@
#include <IO/WriteHelpers.h> #include <IO/WriteHelpers.h>
#include <IO/copyData.h> #include <IO/copyData.h>
#include <Interpreters/Context.h> #include <Interpreters/Context.h>
#include <Interpreters/TemporaryDataOnDisk.h>
#include <Parsers/QueryParameterVisitor.h>
#include <Interpreters/executeQuery.h>
#include <Interpreters/Session.h> #include <Interpreters/Session.h>
#include <Interpreters/TemporaryDataOnDisk.h>
#include <Interpreters/executeQuery.h>
#include <Parsers/ASTSetQuery.h>
#include <Parsers/QueryParameterVisitor.h>
#include <Processors/Formats/IOutputFormat.h>
#include <Server/HTTPHandlerFactory.h> #include <Server/HTTPHandlerFactory.h>
#include <Server/HTTPHandlerRequestFilter.h> #include <Server/HTTPHandlerRequestFilter.h>
#include <Server/IServer.h> #include <Server/IServer.h>
#include <Common/logger_useful.h>
#include <Common/SettingsChanges.h> #include <Common/SettingsChanges.h>
#include <Common/StringUtils.h> #include <Common/StringUtils.h>
#include <Common/scope_guard_safe.h> #include <Common/scope_guard_safe.h>
#include <Common/setThreadName.h> #include <Common/setThreadName.h>
#include <Common/typeid_cast.h> #include <Common/typeid_cast.h>
#include <Common/re2.h>
#include <Parsers/ASTSetQuery.h>
#include <Processors/Formats/IOutputFormat.h>
#include <Formats/FormatFactory.h>
#include <Server/HTTP/HTTPResponse.h>
#include <base/getFQDNOrHostName.h> #include <base/getFQDNOrHostName.h>
#include <base/scope_guard.h> #include <base/scope_guard.h>
#include <Server/HTTP/HTTPResponse.h>
#include "config.h" #include "config.h"
#include <Poco/Base64Decoder.h> #include <Poco/Base64Decoder.h>
#include <Poco/Base64Encoder.h> #include <Poco/Base64Encoder.h>
#include <Poco/Net/HTTPBasicCredentials.h>
#include <Poco/Net/HTTPStream.h>
#include <Poco/MemoryStream.h> #include <Poco/MemoryStream.h>
#include <Poco/Net/HTTPBasicCredentials.h>
#include <Poco/Net/HTTPMessage.h>
#include <Poco/Net/HTTPStream.h>
#include <Poco/Net/SocketAddress.h>
#include <Poco/StreamCopier.h> #include <Poco/StreamCopier.h>
#include <Poco/String.h> #include <Poco/String.h>
#include <Poco/Net/SocketAddress.h>
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <optional>
#include <sstream> #include <sstream>
#include <unordered_map>
#include <utility>
#if USE_SSL #if USE_SSL
# include <Poco/Net/X509Certificate.h> # include <Poco/Net/X509Certificate.h>
@ -67,8 +69,7 @@ namespace ErrorCodes
{ {
extern const int BAD_ARGUMENTS; extern const int BAD_ARGUMENTS;
extern const int LOGICAL_ERROR; extern const int LOGICAL_ERROR;
extern const int CANNOT_COMPILE_REGEXP; extern const int CANNOT_SCHEDULE_TASK;
extern const int CANNOT_OPEN_FILE;
extern const int CANNOT_PARSE_TEXT; extern const int CANNOT_PARSE_TEXT;
extern const int CANNOT_PARSE_ESCAPE_SEQUENCE; extern const int CANNOT_PARSE_ESCAPE_SEQUENCE;
extern const int CANNOT_PARSE_QUOTED_STRING; extern const int CANNOT_PARSE_QUOTED_STRING;
@ -80,7 +81,8 @@ namespace ErrorCodes
extern const int CANNOT_PARSE_IPV6; extern const int CANNOT_PARSE_IPV6;
extern const int CANNOT_PARSE_UUID; extern const int CANNOT_PARSE_UUID;
extern const int CANNOT_PARSE_INPUT_ASSERTION_FAILED; extern const int CANNOT_PARSE_INPUT_ASSERTION_FAILED;
extern const int CANNOT_SCHEDULE_TASK; extern const int CANNOT_OPEN_FILE;
extern const int CANNOT_COMPILE_REGEXP;
extern const int DUPLICATE_COLUMN; extern const int DUPLICATE_COLUMN;
extern const int ILLEGAL_COLUMN; extern const int ILLEGAL_COLUMN;
extern const int THERE_IS_NO_COLUMN; extern const int THERE_IS_NO_COLUMN;
@ -145,9 +147,9 @@ bool tryAddHTTPOptionHeadersFromConfig(HTTPServerResponse & response, const Poco
if (config.getString("http_options_response." + config_key + ".name", "").empty()) if (config.getString("http_options_response." + config_key + ".name", "").empty())
LOG_WARNING(getLogger("processOptionsRequest"), "Empty header was found in config. It will not be processed."); LOG_WARNING(getLogger("processOptionsRequest"), "Empty header was found in config. It will not be processed.");
else else
response.add(config.getString("http_options_response." + config_key + ".name", ""), response.add(
config.getString("http_options_response." + config_key + ".name", ""),
config.getString("http_options_response." + config_key + ".value", "")); config.getString("http_options_response." + config_key + ".value", ""));
} }
} }
return true; return true;
@ -196,54 +198,37 @@ static Poco::Net::HTTPResponse::HTTPStatus exceptionCodeToHTTPStatus(int excepti
{ {
return HTTPResponse::HTTP_UNAUTHORIZED; return HTTPResponse::HTTP_UNAUTHORIZED;
} }
else if (exception_code == ErrorCodes::UNKNOWN_USER || else if (
exception_code == ErrorCodes::WRONG_PASSWORD || exception_code == ErrorCodes::UNKNOWN_USER || exception_code == ErrorCodes::WRONG_PASSWORD
exception_code == ErrorCodes::AUTHENTICATION_FAILED || || exception_code == ErrorCodes::AUTHENTICATION_FAILED || exception_code == ErrorCodes::SET_NON_GRANTED_ROLE)
exception_code == ErrorCodes::SET_NON_GRANTED_ROLE)
{ {
return HTTPResponse::HTTP_FORBIDDEN; return HTTPResponse::HTTP_FORBIDDEN;
} }
else if (exception_code == ErrorCodes::BAD_ARGUMENTS || else if (
exception_code == ErrorCodes::CANNOT_COMPILE_REGEXP || exception_code == ErrorCodes::BAD_ARGUMENTS || exception_code == ErrorCodes::CANNOT_COMPILE_REGEXP
exception_code == ErrorCodes::CANNOT_PARSE_TEXT || || exception_code == ErrorCodes::CANNOT_PARSE_TEXT || exception_code == ErrorCodes::CANNOT_PARSE_ESCAPE_SEQUENCE
exception_code == ErrorCodes::CANNOT_PARSE_ESCAPE_SEQUENCE || || exception_code == ErrorCodes::CANNOT_PARSE_QUOTED_STRING || exception_code == ErrorCodes::CANNOT_PARSE_DATE
exception_code == ErrorCodes::CANNOT_PARSE_QUOTED_STRING || || exception_code == ErrorCodes::CANNOT_PARSE_DATETIME || exception_code == ErrorCodes::CANNOT_PARSE_NUMBER
exception_code == ErrorCodes::CANNOT_PARSE_DATE || || exception_code == ErrorCodes::CANNOT_PARSE_DOMAIN_VALUE_FROM_STRING || exception_code == ErrorCodes::CANNOT_PARSE_IPV4
exception_code == ErrorCodes::CANNOT_PARSE_DATETIME || || exception_code == ErrorCodes::CANNOT_PARSE_IPV6 || exception_code == ErrorCodes::CANNOT_PARSE_INPUT_ASSERTION_FAILED
exception_code == ErrorCodes::CANNOT_PARSE_NUMBER || || exception_code == ErrorCodes::CANNOT_PARSE_UUID || exception_code == ErrorCodes::DUPLICATE_COLUMN
exception_code == ErrorCodes::CANNOT_PARSE_DOMAIN_VALUE_FROM_STRING || || exception_code == ErrorCodes::ILLEGAL_COLUMN || exception_code == ErrorCodes::UNKNOWN_ELEMENT_IN_AST
exception_code == ErrorCodes::CANNOT_PARSE_IPV4 || || exception_code == ErrorCodes::UNKNOWN_TYPE_OF_AST_NODE || exception_code == ErrorCodes::THERE_IS_NO_COLUMN
exception_code == ErrorCodes::CANNOT_PARSE_IPV6 || || exception_code == ErrorCodes::TOO_DEEP_AST || exception_code == ErrorCodes::TOO_BIG_AST
exception_code == ErrorCodes::CANNOT_PARSE_INPUT_ASSERTION_FAILED || || exception_code == ErrorCodes::UNEXPECTED_AST_STRUCTURE || exception_code == ErrorCodes::SYNTAX_ERROR
exception_code == ErrorCodes::CANNOT_PARSE_UUID || || exception_code == ErrorCodes::INCORRECT_DATA || exception_code == ErrorCodes::TYPE_MISMATCH
exception_code == ErrorCodes::DUPLICATE_COLUMN || || exception_code == ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE)
exception_code == ErrorCodes::ILLEGAL_COLUMN ||
exception_code == ErrorCodes::UNKNOWN_ELEMENT_IN_AST ||
exception_code == ErrorCodes::UNKNOWN_TYPE_OF_AST_NODE ||
exception_code == ErrorCodes::THERE_IS_NO_COLUMN ||
exception_code == ErrorCodes::TOO_DEEP_AST ||
exception_code == ErrorCodes::TOO_BIG_AST ||
exception_code == ErrorCodes::UNEXPECTED_AST_STRUCTURE ||
exception_code == ErrorCodes::SYNTAX_ERROR ||
exception_code == ErrorCodes::INCORRECT_DATA ||
exception_code == ErrorCodes::TYPE_MISMATCH ||
exception_code == ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE)
{ {
return HTTPResponse::HTTP_BAD_REQUEST; return HTTPResponse::HTTP_BAD_REQUEST;
} }
else if (exception_code == ErrorCodes::UNKNOWN_TABLE || else if (
exception_code == ErrorCodes::UNKNOWN_FUNCTION || exception_code == ErrorCodes::UNKNOWN_TABLE || exception_code == ErrorCodes::UNKNOWN_FUNCTION
exception_code == ErrorCodes::UNKNOWN_IDENTIFIER || || exception_code == ErrorCodes::UNKNOWN_IDENTIFIER || exception_code == ErrorCodes::UNKNOWN_TYPE
exception_code == ErrorCodes::UNKNOWN_TYPE || || exception_code == ErrorCodes::UNKNOWN_STORAGE || exception_code == ErrorCodes::UNKNOWN_DATABASE
exception_code == ErrorCodes::UNKNOWN_STORAGE || || exception_code == ErrorCodes::UNKNOWN_SETTING || exception_code == ErrorCodes::UNKNOWN_DIRECTION_OF_SORTING
exception_code == ErrorCodes::UNKNOWN_DATABASE || || exception_code == ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION || exception_code == ErrorCodes::UNKNOWN_FORMAT
exception_code == ErrorCodes::UNKNOWN_SETTING || || exception_code == ErrorCodes::UNKNOWN_DATABASE_ENGINE || exception_code == ErrorCodes::UNKNOWN_TYPE_OF_QUERY
exception_code == ErrorCodes::UNKNOWN_DIRECTION_OF_SORTING || || exception_code == ErrorCodes::UNKNOWN_ROLE)
exception_code == ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION ||
exception_code == ErrorCodes::UNKNOWN_FORMAT ||
exception_code == ErrorCodes::UNKNOWN_DATABASE_ENGINE ||
exception_code == ErrorCodes::UNKNOWN_TYPE_OF_QUERY ||
exception_code == ErrorCodes::UNKNOWN_ROLE)
{ {
return HTTPResponse::HTTP_NOT_FOUND; return HTTPResponse::HTTP_NOT_FOUND;
} }
@ -255,8 +240,7 @@ static Poco::Net::HTTPResponse::HTTPStatus exceptionCodeToHTTPStatus(int excepti
{ {
return HTTPResponse::HTTP_NOT_IMPLEMENTED; return HTTPResponse::HTTP_NOT_IMPLEMENTED;
} }
else if (exception_code == ErrorCodes::SOCKET_TIMEOUT || else if (exception_code == ErrorCodes::SOCKET_TIMEOUT || exception_code == ErrorCodes::CANNOT_OPEN_FILE)
exception_code == ErrorCodes::CANNOT_OPEN_FILE)
{ {
return HTTPResponse::HTTP_SERVICE_UNAVAILABLE; return HTTPResponse::HTTP_SERVICE_UNAVAILABLE;
} }
@ -277,9 +261,7 @@ static Poco::Net::HTTPResponse::HTTPStatus exceptionCodeToHTTPStatus(int excepti
} }
static std::chrono::steady_clock::duration parseSessionTimeout( static std::chrono::steady_clock::duration parseSessionTimeout(const Poco::Util::AbstractConfiguration & config, const HTMLForm & params)
const Poco::Util::AbstractConfiguration & config,
const HTMLForm & params)
{ {
unsigned session_timeout = config.getInt("default_session_timeout", 60); unsigned session_timeout = config.getInt("default_session_timeout", 60);
@ -293,14 +275,19 @@ static std::chrono::steady_clock::duration parseSessionTimeout(
throw Exception(ErrorCodes::INVALID_SESSION_TIMEOUT, "Invalid session timeout: '{}'", session_timeout_str); throw Exception(ErrorCodes::INVALID_SESSION_TIMEOUT, "Invalid session timeout: '{}'", session_timeout_str);
if (session_timeout > max_session_timeout) if (session_timeout > max_session_timeout)
throw Exception(ErrorCodes::INVALID_SESSION_TIMEOUT, "Session timeout '{}' is larger than max_session_timeout: {}. " throw Exception(
ErrorCodes::INVALID_SESSION_TIMEOUT,
"Session timeout '{}' is larger than max_session_timeout: {}. "
"Maximum session timeout could be modified in configuration file.", "Maximum session timeout could be modified in configuration file.",
session_timeout_str, max_session_timeout); session_timeout_str,
max_session_timeout);
} }
return std::chrono::seconds(session_timeout); return std::chrono::seconds(session_timeout);
} }
std::optional<std::unordered_map<String, String>>
parseHttpResponseHeaders(const Poco::Util::AbstractConfiguration & config, const std::string & config_prefix);
void HTTPHandler::pushDelayedResults(Output & used_output) void HTTPHandler::pushDelayedResults(Output & used_output)
{ {
@ -338,11 +325,12 @@ void HTTPHandler::pushDelayedResults(Output & used_output)
} }
HTTPHandler::HTTPHandler(IServer & server_, const std::string & name, const std::optional<String> & content_type_override_) HTTPHandler::HTTPHandler(
IServer & server_, const std::string & name, const std::optional<std::unordered_map<String, String>> & http_response_headers_override_)
: server(server_) : server(server_)
, log(getLogger(name)) , log(getLogger(name))
, default_settings(server.context()->getSettingsRef()) , default_settings(server.context()->getSettingsRef())
, content_type_override(content_type_override_) , http_response_headers_override(http_response_headers_override_)
{ {
server_display_name = server.config().getString("display_name", getFQDNOrHostName()); server_display_name = server.config().getString("display_name", getFQDNOrHostName());
} }
@ -353,10 +341,7 @@ HTTPHandler::HTTPHandler(IServer & server_, const std::string & name, const std:
HTTPHandler::~HTTPHandler() = default; HTTPHandler::~HTTPHandler() = default;
bool HTTPHandler::authenticateUser( bool HTTPHandler::authenticateUser(HTTPServerRequest & request, HTMLForm & params, HTTPServerResponse & response)
HTTPServerRequest & request,
HTMLForm & params,
HTTPServerResponse & response)
{ {
using namespace Poco::Net; using namespace Poco::Net;
@ -383,11 +368,13 @@ bool HTTPHandler::authenticateUser(
{ {
/// It is prohibited to mix different authorization schemes. /// It is prohibited to mix different authorization schemes.
if (has_http_credentials) if (has_http_credentials)
throw Exception(ErrorCodes::AUTHENTICATION_FAILED, throw Exception(
ErrorCodes::AUTHENTICATION_FAILED,
"Invalid authentication: it is not allowed " "Invalid authentication: it is not allowed "
"to use SSL certificate authentication and Authorization HTTP header simultaneously"); "to use SSL certificate authentication and Authorization HTTP header simultaneously");
if (has_credentials_in_query_params) if (has_credentials_in_query_params)
throw Exception(ErrorCodes::AUTHENTICATION_FAILED, throw Exception(
ErrorCodes::AUTHENTICATION_FAILED,
"Invalid authentication: it is not allowed " "Invalid authentication: it is not allowed "
"to use SSL certificate authentication and authentication via parameters simultaneously simultaneously"); "to use SSL certificate authentication and authentication via parameters simultaneously simultaneously");
@ -395,7 +382,8 @@ bool HTTPHandler::authenticateUser(
{ {
#if USE_SSL #if USE_SSL
if (!password.empty()) if (!password.empty())
throw Exception(ErrorCodes::AUTHENTICATION_FAILED, throw Exception(
ErrorCodes::AUTHENTICATION_FAILED,
"Invalid authentication: it is not allowed " "Invalid authentication: it is not allowed "
"to use SSL certificate authentication and authentication via password simultaneously"); "to use SSL certificate authentication and authentication via password simultaneously");
@ -403,10 +391,12 @@ bool HTTPHandler::authenticateUser(
certificate_common_name = request.peerCertificate().commonName(); certificate_common_name = request.peerCertificate().commonName();
if (certificate_common_name.empty()) if (certificate_common_name.empty())
throw Exception(ErrorCodes::AUTHENTICATION_FAILED, throw Exception(
ErrorCodes::AUTHENTICATION_FAILED,
"Invalid authentication: SSL certificate authentication requires nonempty certificate's Common Name"); "Invalid authentication: SSL certificate authentication requires nonempty certificate's Common Name");
#else #else
throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, throw Exception(
ErrorCodes::SUPPORT_IS_DISABLED,
"SSL certificate authentication disabled because ClickHouse was built without SSL library"); "SSL certificate authentication disabled because ClickHouse was built without SSL library");
#endif #endif
} }
@ -415,7 +405,8 @@ bool HTTPHandler::authenticateUser(
{ {
/// It is prohibited to mix different authorization schemes. /// It is prohibited to mix different authorization schemes.
if (has_credentials_in_query_params) if (has_credentials_in_query_params)
throw Exception(ErrorCodes::AUTHENTICATION_FAILED, throw Exception(
ErrorCodes::AUTHENTICATION_FAILED,
"Invalid authentication: it is not allowed " "Invalid authentication: it is not allowed "
"to use Authorization HTTP header and authentication via parameters simultaneously"); "to use Authorization HTTP header and authentication via parameters simultaneously");
@ -438,7 +429,8 @@ bool HTTPHandler::authenticateUser(
} }
else else
{ {
throw Exception(ErrorCodes::AUTHENTICATION_FAILED, "Invalid authentication: '{}' HTTP Authorization scheme is not supported", scheme); throw Exception(
ErrorCodes::AUTHENTICATION_FAILED, "Invalid authentication: '{}' HTTP Authorization scheme is not supported", scheme);
} }
} }
else else
@ -464,7 +456,8 @@ bool HTTPHandler::authenticateUser(
auto * gss_acceptor_context = dynamic_cast<GSSAcceptorContext *>(request_credentials.get()); auto * gss_acceptor_context = dynamic_cast<GSSAcceptorContext *>(request_credentials.get());
if (!gss_acceptor_context) if (!gss_acceptor_context)
throw Exception(ErrorCodes::AUTHENTICATION_FAILED, "Invalid authentication: unexpected 'Negotiate' HTTP Authorization scheme expected"); throw Exception(
ErrorCodes::AUTHENTICATION_FAILED, "Invalid authentication: unexpected 'Negotiate' HTTP Authorization scheme expected");
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code" #pragma clang diagnostic ignored "-Wunreachable-code"
@ -500,7 +493,8 @@ bool HTTPHandler::authenticateUser(
if (params.has("quota_key")) if (params.has("quota_key"))
{ {
if (!quota_key.empty()) if (!quota_key.empty())
throw Exception(ErrorCodes::BAD_ARGUMENTS, throw Exception(
ErrorCodes::BAD_ARGUMENTS,
"Invalid authentication: it is not allowed " "Invalid authentication: it is not allowed "
"to use quota key as HTTP header and as parameter simultaneously"); "to use quota key as HTTP header and as parameter simultaneously");
@ -627,10 +621,10 @@ void HTTPHandler::processQuery(
size_t buffer_size_memory = (buffer_size_total > buffer_size_http) ? buffer_size_total : 0; size_t buffer_size_memory = (buffer_size_total > buffer_size_http) ? buffer_size_total : 0;
bool enable_http_compression = params.getParsed<bool>("enable_http_compression", context->getSettingsRef().enable_http_compression); bool enable_http_compression = params.getParsed<bool>("enable_http_compression", context->getSettingsRef().enable_http_compression);
Int64 http_zlib_compression_level = params.getParsed<Int64>("http_zlib_compression_level", context->getSettingsRef().http_zlib_compression_level); Int64 http_zlib_compression_level
= params.getParsed<Int64>("http_zlib_compression_level", context->getSettingsRef().http_zlib_compression_level);
used_output.out_holder = used_output.out_holder = std::make_shared<WriteBufferFromHTTPServerResponse>(
std::make_shared<WriteBufferFromHTTPServerResponse>(
response, response,
request.getMethod() == HTTPRequest::HTTP_HEAD, request.getMethod() == HTTPRequest::HTTP_HEAD,
context->getServerSettings().keep_alive_timeout.totalSeconds(), context->getServerSettings().keep_alive_timeout.totalSeconds(),
@ -641,12 +635,15 @@ void HTTPHandler::processQuery(
if (client_supports_http_compression && enable_http_compression) if (client_supports_http_compression && enable_http_compression)
{ {
used_output.out_holder->setCompressionMethodHeader(http_response_compression_method); used_output.out_holder->setCompressionMethodHeader(http_response_compression_method);
used_output.wrap_compressed_holder = used_output.wrap_compressed_holder = wrapWriteBufferWithCompressionMethod(
wrapWriteBufferWithCompressionMethod(
used_output.out_holder.get(), used_output.out_holder.get(),
http_response_compression_method, http_response_compression_method,
static_cast<int>(http_zlib_compression_level), static_cast<int>(http_zlib_compression_level),
0, DBMS_DEFAULT_BUFFER_SIZE, nullptr, 0, false); 0,
DBMS_DEFAULT_BUFFER_SIZE,
nullptr,
0,
false);
used_output.out = used_output.wrap_compressed_holder; used_output.out = used_output.wrap_compressed_holder;
} }
@ -670,10 +667,7 @@ void HTTPHandler::processQuery(
{ {
auto tmp_data = std::make_shared<TemporaryDataOnDisk>(server.context()->getTempDataOnDisk()); auto tmp_data = std::make_shared<TemporaryDataOnDisk>(server.context()->getTempDataOnDisk());
auto create_tmp_disk_buffer = [tmp_data] (const WriteBufferPtr &) -> WriteBufferPtr auto create_tmp_disk_buffer = [tmp_data](const WriteBufferPtr &) -> WriteBufferPtr { return tmp_data->createRawStream(); };
{
return tmp_data->createRawStream();
};
cascade_buffer2.emplace_back(std::move(create_tmp_disk_buffer)); cascade_buffer2.emplace_back(std::move(create_tmp_disk_buffer));
} }
@ -694,7 +688,8 @@ void HTTPHandler::processQuery(
cascade_buffer2.emplace_back(push_memory_buffer_and_continue); cascade_buffer2.emplace_back(push_memory_buffer_and_continue);
} }
used_output.out_delayed_and_compressed_holder = std::make_unique<CascadeWriteBuffer>(std::move(cascade_buffer1), std::move(cascade_buffer2)); used_output.out_delayed_and_compressed_holder
= std::make_unique<CascadeWriteBuffer>(std::move(cascade_buffer1), std::move(cascade_buffer2));
used_output.out_maybe_delayed_and_compressed = used_output.out_delayed_and_compressed_holder.get(); used_output.out_maybe_delayed_and_compressed = used_output.out_delayed_and_compressed_holder.get();
} }
else else
@ -707,7 +702,8 @@ void HTTPHandler::processQuery(
int zstd_window_log_max = static_cast<int>(context->getSettingsRef().zstd_window_log_max); int zstd_window_log_max = static_cast<int>(context->getSettingsRef().zstd_window_log_max);
auto in_post = wrapReadBufferWithCompressionMethod( auto in_post = wrapReadBufferWithCompressionMethod(
wrapReadBufferReference(request.getStream()), wrapReadBufferReference(request.getStream()),
chooseCompressionMethod({}, http_request_compression_method_str), zstd_window_log_max); chooseCompressionMethod({}, http_request_compression_method_str),
zstd_window_log_max);
/// The data can also be compressed using incompatible internal algorithm. This is indicated by /// The data can also be compressed using incompatible internal algorithm. This is indicated by
/// 'decompress' query parameter. /// 'decompress' query parameter.
@ -723,8 +719,22 @@ void HTTPHandler::processQuery(
std::unique_ptr<ReadBuffer> in; std::unique_ptr<ReadBuffer> in;
static const NameSet reserved_param_names{"compress", "decompress", "user", "password", "quota_key", "query_id", "stacktrace", "role", static const NameSet reserved_param_names{
"buffer_size", "wait_end_of_query", "session_id", "session_timeout", "session_check", "client_protocol_version", "close_session"}; "compress",
"decompress",
"user",
"password",
"quota_key",
"query_id",
"stacktrace",
"role",
"buffer_size",
"wait_end_of_query",
"session_id",
"session_timeout",
"session_check",
"client_protocol_version",
"close_session"};
Names reserved_param_suffixes; Names reserved_param_suffixes;
@ -738,10 +748,8 @@ void HTTPHandler::processQuery(
return true; return true;
for (const String & suffix : reserved_param_suffixes) for (const String & suffix : reserved_param_suffixes)
{
if (endsWith(name, suffix)) if (endsWith(name, suffix))
return true; return true;
}
return false; return false;
}; };
@ -863,7 +871,8 @@ void HTTPHandler::processQuery(
{ {
auto prev = my_context->getProgressCallback(); auto prev = my_context->getProgressCallback();
my_context->setProgressCallback([prev, callback] (const Progress & progress) my_context->setProgressCallback(
[prev, callback](const Progress & progress)
{ {
if (prev) if (prev)
prev(progress); prev(progress);
@ -874,14 +883,12 @@ void HTTPHandler::processQuery(
/// While still no data has been sent, we will report about query execution progress by sending HTTP headers. /// While still no data has been sent, we will report about query execution progress by sending HTTP headers.
/// Note that we add it unconditionally so the progress is available for `X-ClickHouse-Summary` /// Note that we add it unconditionally so the progress is available for `X-ClickHouse-Summary`
append_callback([&used_output](const Progress & progress) append_callback([&used_output](const Progress & progress) { used_output.out_holder->onProgress(progress); });
{
used_output.out_holder->onProgress(progress);
});
if (settings.readonly > 0 && settings.cancel_http_readonly_queries_on_client_close) if (settings.readonly > 0 && settings.cancel_http_readonly_queries_on_client_close)
{ {
append_callback([&context, &request](const Progress &) append_callback(
[&context, &request](const Progress &)
{ {
/// Assume that at the point this method is called no one is reading data from the socket any more: /// Assume that at the point this method is called no one is reading data from the socket any more:
/// should be true for read-only queries. /// should be true for read-only queries.
@ -896,10 +903,11 @@ void HTTPHandler::processQuery(
auto set_query_result = [&response, this](const QueryResultDetails & details) auto set_query_result = [&response, this](const QueryResultDetails & details)
{ {
response.add("X-ClickHouse-Query-Id", details.query_id); response.add("X-ClickHouse-Query-Id", details.query_id);
if (http_response_headers_override)
for (auto [header_name, header_value] : *http_response_headers_override)
response.add(header_name, header_value);
if (content_type_override) if (response.getContentType() == Poco::Net::HTTPMessage::UNKNOWN_CONTENT_TYPE && details.content_type)
response.setContentType(*content_type_override);
else if (details.content_type)
response.setContentType(*details.content_type); response.setContentType(*details.content_type);
if (details.format) if (details.format)
@ -909,7 +917,10 @@ void HTTPHandler::processQuery(
response.add("X-ClickHouse-Timezone", *details.timezone); response.add("X-ClickHouse-Timezone", *details.timezone);
}; };
auto handle_exception_in_output_format = [&](IOutputFormat & current_output_format, const String & format_name, const ContextPtr & context_, const std::optional<FormatSettings> & format_settings) auto handle_exception_in_output_format = [&](IOutputFormat & current_output_format,
const String & format_name,
const ContextPtr & context_,
const std::optional<FormatSettings> & format_settings)
{ {
if (settings.http_write_exception_in_output_format && current_output_format.supportsWritingException()) if (settings.http_write_exception_in_output_format && current_output_format.supportsWritingException())
{ {
@ -929,7 +940,8 @@ void HTTPHandler::processQuery(
} }
else else
{ {
bool with_stacktrace = (params.getParsed<bool>("stacktrace", false) && server.config().getBool("enable_http_stacktrace", true)); bool with_stacktrace
= (params.getParsed<bool>("stacktrace", false) && server.config().getBool("enable_http_stacktrace", true));
ExecutionStatus status = ExecutionStatus::fromCurrentException("", with_stacktrace); ExecutionStatus status = ExecutionStatus::fromCurrentException("", with_stacktrace);
formatExceptionForClient(status.code, request, response, used_output); formatExceptionForClient(status.code, request, response, used_output);
current_output_format.setException(status.message); current_output_format.setException(status.message);
@ -970,7 +982,8 @@ try
if (!used_output.out_holder && !used_output.exception_is_written) if (!used_output.out_holder && !used_output.exception_is_written)
{ {
/// If nothing was sent yet and we don't even know if we must compress the response. /// If nothing was sent yet and we don't even know if we must compress the response.
WriteBufferFromHTTPServerResponse(response, request.getMethod() == HTTPRequest::HTTP_HEAD, DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT).writeln(s); WriteBufferFromHTTPServerResponse(response, request.getMethod() == HTTPRequest::HTTP_HEAD, DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT)
.writeln(s);
} }
else if (used_output.out_maybe_compressed) else if (used_output.out_maybe_compressed)
{ {
@ -1034,7 +1047,8 @@ catch (...)
} }
} }
void HTTPHandler::formatExceptionForClient(int exception_code, HTTPServerRequest & request, HTTPServerResponse & response, Output & used_output) void HTTPHandler::formatExceptionForClient(
int exception_code, HTTPServerRequest & request, HTTPServerResponse & response, Output & used_output)
{ {
if (used_output.out_holder) if (used_output.out_holder)
used_output.out_holder->setExceptionCode(exception_code); used_output.out_holder->setExceptionCode(exception_code);
@ -1101,9 +1115,7 @@ void HTTPHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse
std::string opentelemetry_traceparent = request.get("traceparent"); std::string opentelemetry_traceparent = request.get("traceparent");
std::string error; std::string error;
if (!client_trace_context.parseTraceparentHeader(opentelemetry_traceparent, error)) if (!client_trace_context.parseTraceparentHeader(opentelemetry_traceparent, error))
{
LOG_DEBUG(log, "Failed to parse OpenTelemetry traceparent header '{}': {}", opentelemetry_traceparent, error); LOG_DEBUG(log, "Failed to parse OpenTelemetry traceparent header '{}': {}", opentelemetry_traceparent, error);
}
client_trace_context.tracestate = request.get("tracestate", ""); client_trace_context.tracestate = request.get("tracestate", "");
} }
@ -1141,7 +1153,8 @@ void HTTPHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse
/// Workaround. Poco does not detect 411 Length Required case. /// Workaround. Poco does not detect 411 Length Required case.
if (request.getMethod() == HTTPRequest::HTTP_POST && !request.getChunkedTransferEncoding() && !request.hasContentLength()) if (request.getMethod() == HTTPRequest::HTTP_POST && !request.getChunkedTransferEncoding() && !request.hasContentLength())
{ {
throw Exception(ErrorCodes::HTTP_LENGTH_REQUIRED, throw Exception(
ErrorCodes::HTTP_LENGTH_REQUIRED,
"The Transfer-Encoding is not chunked and there " "The Transfer-Encoding is not chunked and there "
"is no Content-Length header for POST request"); "is no Content-Length header for POST request");
} }
@ -1155,7 +1168,8 @@ void HTTPHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse
catch (...) catch (...)
{ {
SCOPE_EXIT({ SCOPE_EXIT({
request_credentials.reset(); // ...so that the next requests on the connection have to always start afresh in case of exceptions. request_credentials
.reset(); // ...so that the next requests on the connection have to always start afresh in case of exceptions.
}); });
/// Check if exception was thrown in used_output.finalize(). /// Check if exception was thrown in used_output.finalize().
@ -1185,8 +1199,11 @@ void HTTPHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse
used_output.finalize(); used_output.finalize();
} }
DynamicQueryHandler::DynamicQueryHandler(IServer & server_, const std::string & param_name_, const std::optional<String>& content_type_override_) DynamicQueryHandler::DynamicQueryHandler(
: HTTPHandler(server_, "DynamicQueryHandler", content_type_override_), param_name(param_name_) IServer & server_,
const std::string & param_name_,
const std::optional<std::unordered_map<String, String>> & http_response_headers_override_)
: HTTPHandler(server_, "DynamicQueryHandler", http_response_headers_override_), param_name(param_name_)
{ {
} }
@ -1227,16 +1244,10 @@ std::string DynamicQueryHandler::getQuery(HTTPServerRequest & request, HTMLForm
std::string full_query; std::string full_query;
/// Params are of both form params POST and uri (GET params) /// Params are of both form params POST and uri (GET params)
for (const auto & it : params) for (const auto & it : params)
{
if (it.first == param_name) if (it.first == param_name)
{
full_query += it.second; full_query += it.second;
}
else else
{
customizeQueryParam(context, it.first, it.second); customizeQueryParam(context, it.first, it.second);
}
}
return full_query; return full_query;
} }
@ -1247,8 +1258,8 @@ PredefinedQueryHandler::PredefinedQueryHandler(
const std::string & predefined_query_, const std::string & predefined_query_,
const CompiledRegexPtr & url_regex_, const CompiledRegexPtr & url_regex_,
const std::unordered_map<String, CompiledRegexPtr> & header_name_with_regex_, const std::unordered_map<String, CompiledRegexPtr> & header_name_with_regex_,
const std::optional<String> & content_type_override_) const std::optional<std::unordered_map<String, String>> & http_response_headers_override_)
: HTTPHandler(server_, "PredefinedQueryHandler", content_type_override_) : HTTPHandler(server_, "PredefinedQueryHandler", http_response_headers_override_)
, receive_params(receive_params_) , receive_params(receive_params_)
, predefined_query(predefined_query_) , predefined_query(predefined_query_)
, url_regex(url_regex_) , url_regex(url_regex_)
@ -1334,20 +1345,37 @@ std::string PredefinedQueryHandler::getQuery(HTTPServerRequest & request, HTMLFo
return predefined_query; return predefined_query;
} }
HTTPRequestHandlerFactoryPtr createDynamicHandlerFactory(IServer & server, std::optional<std::unordered_map<String, String>>
const Poco::Util::AbstractConfiguration & config, parseHttpResponseHeaders(const Poco::Util::AbstractConfiguration & config, const std::string & config_prefix)
const std::string & config_prefix) {
std::unordered_map<String, String> http_response_headers_override;
String http_response_headers_key = config_prefix + ".handler.http_response_headers";
String http_response_headers_key_prefix = http_response_headers_key + ".";
if (config.has(http_response_headers_key))
{
Poco::Util::AbstractConfiguration::Keys keys;
config.keys(config_prefix + ".handler.http_response_headers", keys);
for (const auto & key : keys)
http_response_headers_override[key] = config.getString(http_response_headers_key_prefix + key);
}
if (config.has(config_prefix + ".handler.content_type"))
http_response_headers_override[Poco::Net::HTTPMessage::CONTENT_TYPE] = config.getString(config_prefix + ".handler.content_type");
if (http_response_headers_override.empty())
return std::nullopt;
return std::optional(std::move(http_response_headers_override));
}
HTTPRequestHandlerFactoryPtr
createDynamicHandlerFactory(IServer & server, const Poco::Util::AbstractConfiguration & config, const std::string & config_prefix)
{ {
auto query_param_name = config.getString(config_prefix + ".handler.query_param_name", "query"); auto query_param_name = config.getString(config_prefix + ".handler.query_param_name", "query");
std::optional<String> content_type_override; std::optional<std::unordered_map<String, String>> http_response_headers_override = parseHttpResponseHeaders(config, config_prefix);
if (config.has(config_prefix + ".handler.content_type"))
content_type_override = config.getString(config_prefix + ".handler.content_type");
auto creator = [&server, query_param_name, content_type_override] () -> std::unique_ptr<DynamicQueryHandler> auto creator = [&server, query_param_name, http_response_headers_override]() -> std::unique_ptr<DynamicQueryHandler>
{ { return std::make_unique<DynamicQueryHandler>(server, query_param_name, http_response_headers_override); };
return std::make_unique<DynamicQueryHandler>(server, query_param_name, content_type_override);
};
auto factory = std::make_shared<HandlingRuleHTTPHandlerFactory<DynamicQueryHandler>>(std::move(creator)); auto factory = std::make_shared<HandlingRuleHTTPHandlerFactory<DynamicQueryHandler>>(std::move(creator));
factory->addFiltersFromConfig(config, config_prefix); factory->addFiltersFromConfig(config, config_prefix);
@ -1357,10 +1385,13 @@ HTTPRequestHandlerFactoryPtr createDynamicHandlerFactory(IServer & server,
static inline bool capturingNamedQueryParam(NameSet receive_params, const CompiledRegexPtr & compiled_regex) static inline bool capturingNamedQueryParam(NameSet receive_params, const CompiledRegexPtr & compiled_regex)
{ {
const auto & capturing_names = compiled_regex->NamedCapturingGroups(); const auto & capturing_names = compiled_regex->NamedCapturingGroups();
return std::count_if(capturing_names.begin(), capturing_names.end(), [&](const auto & iterator) return std::count_if(
capturing_names.begin(),
capturing_names.end(),
[&](const auto & iterator)
{ {
return std::count_if(receive_params.begin(), receive_params.end(), return std::count_if(
[&](const auto & param_name) { return param_name == iterator.first; }); receive_params.begin(), receive_params.end(), [&](const auto & param_name) { return param_name == iterator.first; });
}); });
} }
@ -1369,15 +1400,18 @@ static inline CompiledRegexPtr getCompiledRegex(const std::string & expression)
auto compiled_regex = std::make_shared<const re2::RE2>(expression); auto compiled_regex = std::make_shared<const re2::RE2>(expression);
if (!compiled_regex->ok()) if (!compiled_regex->ok())
throw Exception(ErrorCodes::CANNOT_COMPILE_REGEXP, "Cannot compile re2: {} for http handling rule, error: {}. " throw Exception(
"Look at https://github.com/google/re2/wiki/Syntax for reference.", expression, compiled_regex->error()); ErrorCodes::CANNOT_COMPILE_REGEXP,
"Cannot compile re2: {} for http handling rule, error: {}. "
"Look at https://github.com/google/re2/wiki/Syntax for reference.",
expression,
compiled_regex->error());
return compiled_regex; return compiled_regex;
} }
HTTPRequestHandlerFactoryPtr createPredefinedHandlerFactory(IServer & server, HTTPRequestHandlerFactoryPtr
const Poco::Util::AbstractConfiguration & config, createPredefinedHandlerFactory(IServer & server, const Poco::Util::AbstractConfiguration & config, const std::string & config_prefix)
const std::string & config_prefix)
{ {
if (!config.has(config_prefix + ".handler.query")) if (!config.has(config_prefix + ".handler.query"))
throw Exception(ErrorCodes::NO_ELEMENTS_IN_CONFIG, "There is no path '{}.handler.query' in configuration file.", config_prefix); throw Exception(ErrorCodes::NO_ELEMENTS_IN_CONFIG, "There is no path '{}.handler.query' in configuration file.", config_prefix);
@ -1402,9 +1436,7 @@ HTTPRequestHandlerFactoryPtr createPredefinedHandlerFactory(IServer & server,
headers_name_with_regex.emplace(std::make_pair(header_name, regex)); headers_name_with_regex.emplace(std::make_pair(header_name, regex));
} }
std::optional<String> content_type_override; std::optional<std::unordered_map<String, String>> http_response_headers_override = parseHttpResponseHeaders(config, config_prefix);
if (config.has(config_prefix + ".handler.content_type"))
content_type_override = config.getString(config_prefix + ".handler.content_type");
std::shared_ptr<HandlingRuleHTTPHandlerFactory<PredefinedQueryHandler>> factory; std::shared_ptr<HandlingRuleHTTPHandlerFactory<PredefinedQueryHandler>> factory;
@ -1418,18 +1450,12 @@ HTTPRequestHandlerFactoryPtr createPredefinedHandlerFactory(IServer & server,
auto regex = getCompiledRegex(url_expression); auto regex = getCompiledRegex(url_expression);
if (capturingNamedQueryParam(analyze_receive_params, regex)) if (capturingNamedQueryParam(analyze_receive_params, regex))
{ {
auto creator = [ auto creator
&server, = [&server, analyze_receive_params, predefined_query, regex, headers_name_with_regex, http_response_headers_override]
analyze_receive_params,
predefined_query,
regex,
headers_name_with_regex,
content_type_override]
-> std::unique_ptr<PredefinedQueryHandler> -> std::unique_ptr<PredefinedQueryHandler>
{ {
return std::make_unique<PredefinedQueryHandler>( return std::make_unique<PredefinedQueryHandler>(
server, analyze_receive_params, predefined_query, regex, server, analyze_receive_params, predefined_query, regex, headers_name_with_regex, http_response_headers_override);
headers_name_with_regex, content_type_override);
}; };
factory = std::make_shared<HandlingRuleHTTPHandlerFactory<PredefinedQueryHandler>>(std::move(creator)); factory = std::make_shared<HandlingRuleHTTPHandlerFactory<PredefinedQueryHandler>>(std::move(creator));
factory->addFiltersFromConfig(config, config_prefix); factory->addFiltersFromConfig(config, config_prefix);
@ -1437,17 +1463,11 @@ HTTPRequestHandlerFactoryPtr createPredefinedHandlerFactory(IServer & server,
} }
} }
auto creator = [ auto creator = [&server, analyze_receive_params, predefined_query, headers_name_with_regex, http_response_headers_override]
&server,
analyze_receive_params,
predefined_query,
headers_name_with_regex,
content_type_override]
-> std::unique_ptr<PredefinedQueryHandler> -> std::unique_ptr<PredefinedQueryHandler>
{ {
return std::make_unique<PredefinedQueryHandler>( return std::make_unique<PredefinedQueryHandler>(
server, analyze_receive_params, predefined_query, CompiledRegexPtr{}, server, analyze_receive_params, predefined_query, CompiledRegexPtr{}, headers_name_with_regex, http_response_headers_override);
headers_name_with_regex, content_type_override);
}; };
factory = std::make_shared<HandlingRuleHTTPHandlerFactory<PredefinedQueryHandler>>(std::move(creator)); factory = std::make_shared<HandlingRuleHTTPHandlerFactory<PredefinedQueryHandler>>(std::move(creator));

View File

@ -1,13 +1,16 @@
#pragma once #pragma once
#include <optional>
#include <string>
#include <unordered_map>
#include <Compression/CompressedWriteBuffer.h>
#include <Core/Names.h> #include <Core/Names.h>
#include <IO/CascadeWriteBuffer.h>
#include <Server/HTTP/HTMLForm.h> #include <Server/HTTP/HTMLForm.h>
#include <Server/HTTP/HTTPRequestHandler.h> #include <Server/HTTP/HTTPRequestHandler.h>
#include <Server/HTTP/WriteBufferFromHTTPServerResponse.h> #include <Server/HTTP/WriteBufferFromHTTPServerResponse.h>
#include <Common/CurrentMetrics.h> #include <Common/CurrentMetrics.h>
#include <Common/CurrentThread.h> #include <Common/CurrentThread.h>
#include <IO/CascadeWriteBuffer.h>
#include <Compression/CompressedWriteBuffer.h>
#include <Common/re2.h> #include <Common/re2.h>
namespace CurrentMetrics namespace CurrentMetrics
@ -15,7 +18,10 @@ namespace CurrentMetrics
extern const Metric HTTPConnection; extern const Metric HTTPConnection;
} }
namespace Poco { class Logger; } namespace Poco
{
class Logger;
}
namespace DB namespace DB
{ {
@ -31,7 +37,10 @@ using CompiledRegexPtr = std::shared_ptr<const re2::RE2>;
class HTTPHandler : public HTTPRequestHandler class HTTPHandler : public HTTPRequestHandler
{ {
public: public:
HTTPHandler(IServer & server_, const std::string & name, const std::optional<String> & content_type_override_); HTTPHandler(
IServer & server_,
const std::string & name,
const std::optional<std::unordered_map<String, String>> & http_response_headers_override_);
~HTTPHandler() override; ~HTTPHandler() override;
void handleRequest(HTTPServerRequest & request, HTTPServerResponse & response, const ProfileEvents::Event & write_event) override; void handleRequest(HTTPServerRequest & request, HTTPServerResponse & response, const ProfileEvents::Event & write_event) override;
@ -113,8 +122,8 @@ private:
/// See settings http_max_fields, http_max_field_name_size, http_max_field_value_size in HTMLForm. /// See settings http_max_fields, http_max_field_name_size, http_max_field_value_size in HTMLForm.
const Settings & default_settings; const Settings & default_settings;
/// Overrides Content-Type provided by the format of the response. /// Overrides for response headers.
std::optional<String> content_type_override; std::optional<std::unordered_map<String, String>> http_response_headers_override;
// session is reset at the end of each request/response. // session is reset at the end of each request/response.
std::unique_ptr<Session> session; std::unique_ptr<Session> session;
@ -128,10 +137,7 @@ private:
// Returns false when the user is not authenticated yet, and the 'Negotiate' response is sent, // Returns false when the user is not authenticated yet, and the 'Negotiate' response is sent,
// the session and request_credentials instances are preserved. // the session and request_credentials instances are preserved.
// Throws an exception if authentication failed. // Throws an exception if authentication failed.
bool authenticateUser( bool authenticateUser(HTTPServerRequest & request, HTMLForm & params, HTTPServerResponse & response);
HTTPServerRequest & request,
HTMLForm & params,
HTTPServerResponse & response);
/// Also initializes 'used_output'. /// Also initializes 'used_output'.
void processQuery( void processQuery(
@ -143,17 +149,9 @@ private:
const ProfileEvents::Event & write_event); const ProfileEvents::Event & write_event);
void trySendExceptionToClient( void trySendExceptionToClient(
const std::string & s, const std::string & s, int exception_code, HTTPServerRequest & request, HTTPServerResponse & response, Output & used_output);
int exception_code,
HTTPServerRequest & request,
HTTPServerResponse & response,
Output & used_output);
void formatExceptionForClient( void formatExceptionForClient(int exception_code, HTTPServerRequest & request, HTTPServerResponse & response, Output & used_output);
int exception_code,
HTTPServerRequest & request,
HTTPServerResponse & response,
Output & used_output);
static void pushDelayedResults(Output & used_output); static void pushDelayedResults(Output & used_output);
}; };
@ -162,8 +160,12 @@ class DynamicQueryHandler : public HTTPHandler
{ {
private: private:
std::string param_name; std::string param_name;
public: public:
explicit DynamicQueryHandler(IServer & server_, const std::string & param_name_ = "query", const std::optional<String>& content_type_override_ = std::nullopt); explicit DynamicQueryHandler(
IServer & server_,
const std::string & param_name_ = "query",
const std::optional<std::unordered_map<String, String>> & http_response_headers_override_ = std::nullopt);
std::string getQuery(HTTPServerRequest & request, HTMLForm & params, ContextMutablePtr context) override; std::string getQuery(HTTPServerRequest & request, HTMLForm & params, ContextMutablePtr context) override;
@ -177,11 +179,15 @@ private:
std::string predefined_query; std::string predefined_query;
CompiledRegexPtr url_regex; CompiledRegexPtr url_regex;
std::unordered_map<String, CompiledRegexPtr> header_name_with_capture_regex; std::unordered_map<String, CompiledRegexPtr> header_name_with_capture_regex;
public: public:
PredefinedQueryHandler( PredefinedQueryHandler(
IServer & server_, const NameSet & receive_params_, const std::string & predefined_query_ IServer & server_,
, const CompiledRegexPtr & url_regex_, const std::unordered_map<String, CompiledRegexPtr> & header_name_with_regex_ const NameSet & receive_params_,
, const std::optional<std::string> & content_type_override_); const std::string & predefined_query_,
const CompiledRegexPtr & url_regex_,
const std::unordered_map<String, CompiledRegexPtr> & header_name_with_regex_,
const std::optional<std::unordered_map<String, String>> & http_response_headers_override_ = std::nullopt);
void customizeContext(HTTPServerRequest & request, ContextMutablePtr context, ReadBuffer & body) override; void customizeContext(HTTPServerRequest & request, ContextMutablePtr context, ReadBuffer & body) override;