mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-12-04 13:32:13 +00:00
Add http_response_headers
setting
This commit is contained in:
parent
dc4bbc8d73
commit
076a8f8d9e
@ -1816,6 +1816,22 @@ Possible values:
|
|||||||
|
|
||||||
- 0 — Disabled.
|
- 0 — Disabled.
|
||||||
- 1 — Enabled.
|
- 1 — Enabled.
|
||||||
|
)", 0) \
|
||||||
|
DECLARE(Map, http_response_headers, "", R"(
|
||||||
|
Allows to add or override HTTP headers which the server will return it the response with a successful query result.
|
||||||
|
This only affects the HTTP interface.
|
||||||
|
|
||||||
|
If the header is already set by default, the provided value will override it.
|
||||||
|
If the header was not set by default, it will be added to the list of headers.
|
||||||
|
Headers that are set by the server by default and not overridden by this setting, will remain.
|
||||||
|
|
||||||
|
The setting allows you to set a header to a constant value. Currently there is no way to set a header to a dynamically calculated value.
|
||||||
|
|
||||||
|
Neither names or values can contain ASCII control characters.
|
||||||
|
|
||||||
|
If you implement a UI application which allows users to modify settings but at the same time makes decisions based on the returned headers, it is recommended to restrict this setting to readonly.
|
||||||
|
|
||||||
|
Example: `SET http_response_headers = '{"Content-Type": "image/png"}'`
|
||||||
)", 0) \
|
)", 0) \
|
||||||
\
|
\
|
||||||
DECLARE(String, count_distinct_implementation, "uniqExact", R"(
|
DECLARE(String, count_distinct_implementation, "uniqExact", R"(
|
||||||
|
@ -163,6 +163,7 @@ namespace Setting
|
|||||||
extern const SettingsSeconds wait_for_async_insert_timeout;
|
extern const SettingsSeconds wait_for_async_insert_timeout;
|
||||||
extern const SettingsBool implicit_select;
|
extern const SettingsBool implicit_select;
|
||||||
extern const SettingsBool enforce_strict_identifier_format;
|
extern const SettingsBool enforce_strict_identifier_format;
|
||||||
|
extern const SettingsMap http_response_headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ErrorCodes
|
namespace ErrorCodes
|
||||||
@ -1682,6 +1683,32 @@ void executeQuery(
|
|||||||
/// But `session_timezone` setting could be modified in the query itself, so we update the value.
|
/// But `session_timezone` setting could be modified in the query itself, so we update the value.
|
||||||
result_details.timezone = DateLUT::instance().getTimeZone();
|
result_details.timezone = DateLUT::instance().getTimeZone();
|
||||||
|
|
||||||
|
const Map & additional_http_headers = context->getSettingsRef()[Setting::http_response_headers].value;
|
||||||
|
if (!additional_http_headers.empty())
|
||||||
|
{
|
||||||
|
for (const auto & key_value : additional_http_headers)
|
||||||
|
{
|
||||||
|
if (key_value.getType() != Field::Types::Tuple
|
||||||
|
|| key_value.safeGet<Tuple>().size() != 2)
|
||||||
|
throw Exception(ErrorCodes::BAD_ARGUMENTS, "The value of the `additional_http_headers` setting must be a Map");
|
||||||
|
|
||||||
|
if (key_value.safeGet<Tuple>().at(0).getType() != Field::Types::String)
|
||||||
|
throw Exception(ErrorCodes::BAD_ARGUMENTS, "The keys of the `additional_http_headers` setting must be Strings");
|
||||||
|
|
||||||
|
if (key_value.safeGet<Tuple>().at(1).getType() != Field::Types::String)
|
||||||
|
throw Exception(ErrorCodes::BAD_ARGUMENTS, "The values of the `additional_http_headers` setting must be Strings");
|
||||||
|
|
||||||
|
String key = key_value.safeGet<Tuple>().at(0).safeGet<String>();
|
||||||
|
String value = key_value.safeGet<Tuple>().at(1).safeGet<String>();
|
||||||
|
|
||||||
|
if (std::find_if(key.begin(), key.end(), isControlASCII) != key.end()
|
||||||
|
|| std::find_if(value.begin(), value.end(), isControlASCII) != value.end())
|
||||||
|
throw Exception(ErrorCodes::BAD_ARGUMENTS, "The values of the `additional_http_headers` cannot contain ASCII control characters");
|
||||||
|
|
||||||
|
result_details.additional_headers.emplace(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto & pipeline = streams.pipeline;
|
auto & pipeline = streams.pipeline;
|
||||||
|
|
||||||
std::unique_ptr<WriteBuffer> compressed_buffer;
|
std::unique_ptr<WriteBuffer> compressed_buffer;
|
||||||
|
@ -24,6 +24,7 @@ struct QueryResultDetails
|
|||||||
std::optional<String> content_type = {};
|
std::optional<String> content_type = {};
|
||||||
std::optional<String> format = {};
|
std::optional<String> format = {};
|
||||||
std::optional<String> timezone = {};
|
std::optional<String> timezone = {};
|
||||||
|
std::unordered_map<String, String> additional_headers = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
using SetResultDetailsFunc = std::function<void(const QueryResultDetails &)>;
|
using SetResultDetailsFunc = std::function<void(const QueryResultDetails &)>;
|
||||||
@ -42,7 +43,7 @@ void executeQuery(
|
|||||||
WriteBuffer & ostr, /// Where to write query output to.
|
WriteBuffer & ostr, /// Where to write query output to.
|
||||||
bool allow_into_outfile, /// If true and the query contains INTO OUTFILE section, redirect output to that file.
|
bool allow_into_outfile, /// If true and the query contains INTO OUTFILE section, redirect output to that file.
|
||||||
ContextMutablePtr context, /// DB, tables, data types, storage engines, functions, aggregate functions...
|
ContextMutablePtr context, /// DB, tables, data types, storage engines, functions, aggregate functions...
|
||||||
SetResultDetailsFunc set_result_details, /// If a non-empty callback is passed, it will be called with the query id, the content-type, the format, and the timezone.
|
SetResultDetailsFunc set_result_details, /// If a non-empty callback is passed, it will be called with the query id, the content-type, the format, and the timezone, as well as additional headers.
|
||||||
QueryFlags flags = {},
|
QueryFlags flags = {},
|
||||||
const std::optional<FormatSettings> & output_format_settings = std::nullopt, /// Format settings for output format, will be calculated from the context if not set.
|
const std::optional<FormatSettings> & output_format_settings = std::nullopt, /// Format settings for output format, will be calculated from the context if not set.
|
||||||
HandleExceptionInOutputFormatFunc handle_exception_in_output_format = {} /// If a non-empty callback is passed, it will be called on exception with created output format.
|
HandleExceptionInOutputFormatFunc handle_exception_in_output_format = {} /// If a non-empty callback is passed, it will be called on exception with created output format.
|
||||||
|
@ -524,6 +524,9 @@ void HTTPHandler::processQuery(
|
|||||||
|
|
||||||
if (details.timezone)
|
if (details.timezone)
|
||||||
response.add("X-ClickHouse-Timezone", *details.timezone);
|
response.add("X-ClickHouse-Timezone", *details.timezone);
|
||||||
|
|
||||||
|
for (const auto & [name, value] : details.additional_headers)
|
||||||
|
response.set(name, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto handle_exception_in_output_format = [&](IOutputFormat & current_output_format,
|
auto handle_exception_in_output_format = [&](IOutputFormat & current_output_format,
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
We can add a new header:
|
||||||
|
> POST /?http_response_headers={'My-New-Header':'Hello,+world.'} HTTP/1.1
|
||||||
|
< My-New-Header: Hello, world.
|
||||||
|
It works even with the settings clause:
|
||||||
|
< My-New-Header: Hello, world.
|
||||||
|
Check the default header value:
|
||||||
|
> Content-Type: application/x-www-form-urlencoded
|
||||||
|
< Content-Type: text/tab-separated-values; charset=UTF-8
|
||||||
|
Check that we can override it:
|
||||||
|
> POST /?http_response_headers={'Content-Type':'image/png'} HTTP/1.1
|
||||||
|
> Content-Type: application/x-www-form-urlencoded
|
||||||
|
< Content-Type: image/png
|
||||||
|
It does not allow bad characters:
|
||||||
|
BAD_ARGUMENTS
|
||||||
|
BAD_ARGUMENTS
|
21
tests/queries/0_stateless/03277_http_response_headers.sh
Executable file
21
tests/queries/0_stateless/03277_http_response_headers.sh
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
# shellcheck source=../shell_config.sh
|
||||||
|
. "$CUR_DIR"/../shell_config.sh
|
||||||
|
|
||||||
|
echo "We can add a new header:"
|
||||||
|
${CLICKHOUSE_CURL} -sS --globoff -v "http://localhost:8123/?http_response_headers={'My-New-Header':'Hello,+world.'}" -d "SELECT 1" 2>&1 | grep -i 'My-New'
|
||||||
|
|
||||||
|
echo "It works even with the settings clause:"
|
||||||
|
${CLICKHOUSE_CURL} -sS --globoff -v "http://localhost:8123/" -d "SELECT 1 SETTINGS http_response_headers = \$\${'My-New-Header':'Hello, world.'}\$\$" 2>&1 | grep -i 'My-New'
|
||||||
|
|
||||||
|
echo "Check the default header value:"
|
||||||
|
${CLICKHOUSE_CURL} -sS --globoff -v "http://localhost:8123/" -d "SELECT 1" 2>&1 | grep -i 'Content-Type'
|
||||||
|
|
||||||
|
echo "Check that we can override it:"
|
||||||
|
${CLICKHOUSE_CURL} -sS --globoff -v "http://localhost:8123/?http_response_headers={'Content-Type':'image/png'}" -d "SELECT 1" 2>&1 | grep -i 'Content-Type'
|
||||||
|
|
||||||
|
echo "It does not allow bad characters:"
|
||||||
|
${CLICKHOUSE_CURL} -sS --globoff -v "http://localhost:8123/" -d "SELECT 1 SETTINGS http_response_headers = \$\${'My-New-Header':'Hello,\n\nworld.'}\$\$" 2>&1 | grep -o -F 'BAD_ARGUMENTS'
|
||||||
|
${CLICKHOUSE_CURL} -sS --globoff -v "http://localhost:8123/" -d "SELECT 1 SETTINGS http_response_headers = \$\${'My\rNew-Header':'Hello, world.'}\$\$" 2>&1 | grep -o -F 'BAD_ARGUMENTS'
|
Loading…
Reference in New Issue
Block a user