Skip to content

Commit 935658d

Browse files
authored
Merge pull request #832 from Altinity/backports/24.8.14/79975
24.8.14 Backport of ClickHouse#79975 Allow to add `http_response_headers` in `http_handlers` of any kind
2 parents bf86f0e + 81c082f commit 935658d

17 files changed

+417
-42
lines changed

docs/en/interfaces/http.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,42 @@ $ curl -vv -H 'XXX:xxx' 'http://localhost:8123/get_relative_path_static_handler'
791791
* Connection #0 to host localhost left intact
792792
```
793793
794-
## Valid JSON/XML response on exception during HTTP streaming {valid-output-on-exception-http-streaming}
794+
## HTTP Response Headers {#http-response-headers}
795+
796+
ClickHouse allows you to configure custom HTTP response headers that can be applied to any kind of handler that can be configured. These headers can be set using the `http_response_headers` setting, which accepts key-value pairs representing header names and their values. This feature is particularly useful for implementing custom security headers, CORS policies, or any other HTTP header requirements across your ClickHouse HTTP interface.
797+
798+
For example, you can configure headers for:
799+
- Regular query endpoints
800+
- Web UI
801+
- Health check.
802+
803+
It is also possible to specify `common_http_response_headers`. These will be applied to all http handlers defined in the configuration.
804+
805+
The headers will be included in the HTTP response for every configured handler.
806+
807+
In the example below, every server response will contain two custom headers: `X-My-Common-Header` and `X-My-Custom-Header`.
808+
809+
```xml
810+
<clickhouse>
811+
<http_handlers>
812+
<common_http_response_headers>
813+
<X-My-Common-Header>Common header</X-My-Common-Header>
814+
</common_http_response_headers>
815+
<rule>
816+
<methods>GET</methods>
817+
<url>/ping</url>
818+
<handler>
819+
<type>ping</type>
820+
<http_response_headers>
821+
<X-My-Custom-Header>Custom indeed</X-My-Custom-Header>
822+
</http_response_headers>
823+
</handler>
824+
</rule>
825+
</http_handlers>
826+
</clickhouse>
827+
```
828+
829+
## Valid JSON/XML response on exception during HTTP streaming {#valid-output-on-exception-http-streaming}
795830
796831
While query execution over HTTP an exception can happen when part of the data has already been sent. Usually an exception is sent to the client in plain text
797832
even if some specific data format was used to output data and the output may become invalid in terms of specified data format.

src/Server/HTTPHandler.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -895,11 +895,14 @@ std::string PredefinedQueryHandler::getQuery(HTTPServerRequest & request, HTMLFo
895895

896896
HTTPRequestHandlerFactoryPtr createDynamicHandlerFactory(IServer & server,
897897
const Poco::Util::AbstractConfiguration & config,
898-
const std::string & config_prefix)
898+
const std::string & config_prefix,
899+
std::unordered_map<String, String> & common_headers)
899900
{
900901
auto query_param_name = config.getString(config_prefix + ".handler.query_param_name", "query");
901902

902903
HTTPResponseHeaderSetup http_response_headers_override = parseHTTPResponseHeaders(config, config_prefix);
904+
if (http_response_headers_override.has_value())
905+
http_response_headers_override.value().insert(common_headers.begin(), common_headers.end());
903906

904907
auto creator = [&server, query_param_name, http_response_headers_override]() -> std::unique_ptr<DynamicQueryHandler>
905908
{ return std::make_unique<DynamicQueryHandler>(server, query_param_name, http_response_headers_override); };
@@ -932,7 +935,8 @@ static inline CompiledRegexPtr getCompiledRegex(const std::string & expression)
932935

933936
HTTPRequestHandlerFactoryPtr createPredefinedHandlerFactory(IServer & server,
934937
const Poco::Util::AbstractConfiguration & config,
935-
const std::string & config_prefix)
938+
const std::string & config_prefix,
939+
std::unordered_map<String, String> & common_headers)
936940
{
937941
if (!config.has(config_prefix + ".handler.query"))
938942
throw Exception(ErrorCodes::NO_ELEMENTS_IN_CONFIG, "There is no path '{}.handler.query' in configuration file.", config_prefix);
@@ -958,6 +962,8 @@ HTTPRequestHandlerFactoryPtr createPredefinedHandlerFactory(IServer & server,
958962
}
959963

960964
HTTPResponseHeaderSetup http_response_headers_override = parseHTTPResponseHeaders(config, config_prefix);
965+
if (http_response_headers_override.has_value())
966+
http_response_headers_override.value().insert(common_headers.begin(), common_headers.end());
961967

962968
std::shared_ptr<HandlingRuleHTTPHandlerFactory<PredefinedQueryHandler>> factory;
963969

src/Server/HTTPHandlerFactory.cpp

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "InterserverIOHTTPHandler.h"
1414
#include "WebUIRequestHandler.h"
1515

16+
#include <iostream>
1617

1718
namespace DB
1819
{
@@ -31,27 +32,35 @@ class RedirectRequestHandler : public HTTPRequestHandler
3132
{
3233
private:
3334
std::string url;
35+
std::unordered_map<String, String> http_response_headers_override;
3436

3537
public:
36-
explicit RedirectRequestHandler(std::string url_)
37-
: url(std::move(url_))
38+
explicit RedirectRequestHandler(std::string url_, std::unordered_map<String, String> http_response_headers_override_ = {})
39+
: url(std::move(url_)), http_response_headers_override(http_response_headers_override_)
3840
{
3941
}
4042

4143
void handleRequest(HTTPServerRequest &, HTTPServerResponse & response, const ProfileEvents::Event &) override
4244
{
45+
applyHTTPResponseHeaders(response, http_response_headers_override);
4346
response.redirect(url);
4447
}
4548
};
4649

4750
HTTPRequestHandlerFactoryPtr createRedirectHandlerFactory(
4851
const Poco::Util::AbstractConfiguration & config,
49-
const std::string & config_prefix)
52+
const std::string & config_prefix,
53+
std::unordered_map<String, String> common_headers)
5054
{
5155
std::string url = config.getString(config_prefix + ".handler.location");
5256

57+
auto headers = parseHTTPResponseHeadersWithCommons(config, config_prefix, common_headers);
58+
5359
auto factory = std::make_shared<HandlingRuleHTTPHandlerFactory<RedirectRequestHandler>>(
54-
[my_url = std::move(url)]() { return std::make_unique<RedirectRequestHandler>(my_url); });
60+
[my_url = std::move(url), headers_override = std::move(headers)]()
61+
{
62+
return std::make_unique<RedirectRequestHandler>(my_url, headers_override);
63+
});
5564

5665
factory->addFiltersFromConfig(config, config_prefix);
5766
return factory;
@@ -78,6 +87,33 @@ static auto createPingHandlerFactory(IServer & server)
7887
return std::make_shared<HandlingRuleHTTPHandlerFactory<StaticRequestHandler>>(std::move(creator));
7988
}
8089

90+
static auto createPingHandlerFactory(IServer & server, const Poco::Util::AbstractConfiguration & config, const String & config_prefix,
91+
std::unordered_map<String, String> common_headers)
92+
{
93+
auto creator = [&server,&config,config_prefix,common_headers]() -> std::unique_ptr<StaticRequestHandler>
94+
{
95+
constexpr auto ping_response_expression = "Ok.\n";
96+
97+
auto headers = parseHTTPResponseHeadersWithCommons(config, config_prefix, "text/html; charset=UTF-8", common_headers);
98+
99+
return std::make_unique<StaticRequestHandler>(
100+
server, ping_response_expression, headers);
101+
};
102+
return std::make_shared<HandlingRuleHTTPHandlerFactory<StaticRequestHandler>>(std::move(creator));
103+
}
104+
105+
template <typename UIRequestHandler>
106+
static auto createWebUIHandlerFactory(IServer & server, const Poco::Util::AbstractConfiguration & config, const String & config_prefix,
107+
std::unordered_map<String, String> common_headers)
108+
{
109+
auto creator = [&server,&config,config_prefix,common_headers]() -> std::unique_ptr<UIRequestHandler>
110+
{
111+
auto headers = parseHTTPResponseHeadersWithCommons(config, config_prefix, "text/html; charset=UTF-8", common_headers);
112+
return std::make_unique<UIRequestHandler>(server, headers);
113+
};
114+
return std::make_shared<HandlingRuleHTTPHandlerFactory<UIRequestHandler>>(std::move(creator));
115+
}
116+
81117
static inline auto createHandlersFactoryFromConfig(
82118
IServer & server,
83119
const Poco::Util::AbstractConfiguration & config,
@@ -90,6 +126,19 @@ static inline auto createHandlersFactoryFromConfig(
90126
Poco::Util::AbstractConfiguration::Keys keys;
91127
config.keys(prefix, keys);
92128

129+
std::unordered_map<String, String> common_headers_override;
130+
131+
if (std::find(keys.begin(), keys.end(), "common_http_response_headers") != keys.end())
132+
{
133+
auto common_headers_prefix = prefix + ".common_http_response_headers";
134+
Poco::Util::AbstractConfiguration::Keys headers_keys;
135+
config.keys(common_headers_prefix, headers_keys);
136+
for (const auto & header_key : headers_keys)
137+
{
138+
common_headers_override[header_key] = config.getString(common_headers_prefix + "." + header_key);
139+
}
140+
}
141+
93142
for (const auto & key : keys)
94143
{
95144
if (key == "defaults")
@@ -106,58 +155,75 @@ static inline auto createHandlersFactoryFromConfig(
106155

107156
if (handler_type == "static")
108157
{
109-
main_handler_factory->addHandler(createStaticHandlerFactory(server, config, prefix + "." + key));
158+
main_handler_factory->addHandler(createStaticHandlerFactory(server, config, prefix + "." + key, common_headers_override));
110159
}
111160
else if (handler_type == "redirect")
112161
{
113-
main_handler_factory->addHandler(createRedirectHandlerFactory(config, prefix + "." + key));
162+
main_handler_factory->addHandler(createRedirectHandlerFactory(config, prefix + "." + key, common_headers_override));
114163
}
115164
else if (handler_type == "dynamic_query_handler")
116165
{
117-
main_handler_factory->addHandler(createDynamicHandlerFactory(server, config, prefix + "." + key));
166+
main_handler_factory->addHandler(createDynamicHandlerFactory(server, config, prefix + "." + key, common_headers_override));
118167
}
119168
else if (handler_type == "predefined_query_handler")
120169
{
121-
main_handler_factory->addHandler(createPredefinedHandlerFactory(server, config, prefix + "." + key));
170+
main_handler_factory->addHandler(createPredefinedHandlerFactory(server, config, prefix + "." + key, common_headers_override));
122171
}
123172
else if (handler_type == "prometheus")
124173
{
125174
main_handler_factory->addHandler(
126-
createPrometheusHandlerFactoryForHTTPRule(server, config, prefix + "." + key, async_metrics));
175+
createPrometheusHandlerFactoryForHTTPRule(server, config, prefix + "." + key, async_metrics, common_headers_override));
127176
}
128177
else if (handler_type == "replicas_status")
129178
{
130-
main_handler_factory->addHandler(createReplicasStatusHandlerFactory(server, config, prefix + "." + key));
179+
main_handler_factory->addHandler(createReplicasStatusHandlerFactory(server, config, prefix + "." + key, common_headers_override));
131180
}
132181
else if (handler_type == "ping")
133182
{
134-
auto handler = createPingHandlerFactory(server);
135-
handler->addFiltersFromConfig(config, prefix + "." + key);
183+
const String config_prefix = prefix + "." + key;
184+
auto handler = createPingHandlerFactory(server, config, config_prefix, common_headers_override);
185+
handler->addFiltersFromConfig(config, config_prefix);
136186
main_handler_factory->addHandler(std::move(handler));
137187
}
138188
else if (handler_type == "play")
139189
{
140-
auto handler = std::make_shared<HandlingRuleHTTPHandlerFactory<PlayWebUIRequestHandler>>(server);
190+
auto handler = createWebUIHandlerFactory<PlayWebUIRequestHandler>(server, config, prefix + "." + key, common_headers_override);
141191
handler->addFiltersFromConfig(config, prefix + "." + key);
142192
main_handler_factory->addHandler(std::move(handler));
143193
}
144194
else if (handler_type == "dashboard")
145195
{
146-
auto handler = std::make_shared<HandlingRuleHTTPHandlerFactory<DashboardWebUIRequestHandler>>(server);
196+
auto handler = createWebUIHandlerFactory<DashboardWebUIRequestHandler>(server, config, prefix + "." + key, common_headers_override);
147197
handler->addFiltersFromConfig(config, prefix + "." + key);
148198
main_handler_factory->addHandler(std::move(handler));
149199
}
150200
else if (handler_type == "binary")
151201
{
152-
auto handler = std::make_shared<HandlingRuleHTTPHandlerFactory<BinaryWebUIRequestHandler>>(server);
202+
auto handler = createWebUIHandlerFactory<BinaryWebUIRequestHandler>(server, config, prefix + "." + key, common_headers_override);
203+
handler->addFiltersFromConfig(config, prefix + "." + key);
204+
main_handler_factory->addHandler(std::move(handler));
205+
}
206+
else if (handler_type == "js")
207+
{
208+
// NOTE: JavaScriptWebUIRequestHandler only makes sense for paths other then /js/uplot.js, /js/lz-string.js
209+
// because these paths are hardcoded in dashboard.html
210+
const auto & path = config.getString(prefix + "." + key + ".url", "");
211+
if (path != "/js/uplot.js" && path != "/js/lz-string.js")
212+
{
213+
throw Exception(ErrorCodes::INVALID_CONFIG_PARAMETER,
214+
"Handler type 'js' is only supported for url '/js/'. "
215+
"Configured path here: {}", path);
216+
}
217+
218+
auto handler = createWebUIHandlerFactory<JavaScriptWebUIRequestHandler>(server, config, prefix + "." + key, common_headers_override);
153219
handler->addFiltersFromConfig(config, prefix + "." + key);
154220
main_handler_factory->addHandler(std::move(handler));
155221
}
156222
else
157223
throw Exception(ErrorCodes::INVALID_CONFIG_PARAMETER, "Unknown handler type '{}' in config here: {}.{}.handler.type",
158224
handler_type, prefix, key);
159225
}
160-
else
226+
else if (key != "common_http_response_headers")
161227
throw Exception(ErrorCodes::UNKNOWN_ELEMENT_IN_CONFIG, "Unknown element in config: "
162228
"{}.{}, must be 'rule' or 'defaults'", prefix, key);
163229
}

src/Server/HTTPHandlerFactory.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,19 +110,23 @@ class HandlingRuleHTTPHandlerFactory : public HTTPRequestHandlerFactory
110110

111111
HTTPRequestHandlerFactoryPtr createStaticHandlerFactory(IServer & server,
112112
const Poco::Util::AbstractConfiguration & config,
113-
const std::string & config_prefix);
113+
const std::string & config_prefix,
114+
std::unordered_map<String, String> & common_headers);
114115

115116
HTTPRequestHandlerFactoryPtr createDynamicHandlerFactory(IServer & server,
116117
const Poco::Util::AbstractConfiguration & config,
117-
const std::string & config_prefix);
118+
const std::string & config_prefix,
119+
std::unordered_map<String, String> & common_headers);
118120

119121
HTTPRequestHandlerFactoryPtr createPredefinedHandlerFactory(IServer & server,
120122
const Poco::Util::AbstractConfiguration & config,
121-
const std::string & config_prefix);
123+
const std::string & config_prefix,
124+
std::unordered_map<String, String> & common_headers);
122125

123126
HTTPRequestHandlerFactoryPtr createReplicasStatusHandlerFactory(IServer & server,
124127
const Poco::Util::AbstractConfiguration & config,
125-
const std::string & config_prefix);
128+
const std::string & config_prefix,
129+
std::unordered_map<String, String> & common_headers);
126130

127131
/// @param server - used in handlers to check IServer::isCancelled()
128132
/// @param config - not the same as server.config(), since it can be newer

src/Server/HTTPResponseHeaderWriter.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,25 @@ void applyHTTPResponseHeaders(Poco::Net::HTTPResponse & response, const std::uno
6666
response.set(header_name, header_value);
6767
}
6868

69+
std::unordered_map<String, String> parseHTTPResponseHeadersWithCommons(
70+
const Poco::Util::AbstractConfiguration & config,
71+
const std::string & config_prefix,
72+
const std::string & default_content_type,
73+
const std::unordered_map<String, String> & common_headers)
74+
{
75+
auto headers = parseHTTPResponseHeaders(config, config_prefix, default_content_type);
76+
headers.insert(common_headers.begin(), common_headers.end());
77+
return headers;
78+
}
79+
80+
std::unordered_map<String, String> parseHTTPResponseHeadersWithCommons(
81+
const Poco::Util::AbstractConfiguration & config,
82+
const std::string & config_prefix,
83+
const std::unordered_map<String, String> & common_headers)
84+
{
85+
auto headers = parseHTTPResponseHeaders(config, config_prefix).value_or(std::unordered_map<String, String>{});
86+
headers.insert(common_headers.begin(), common_headers.end());
87+
return headers;
88+
}
89+
6990
}

src/Server/HTTPResponseHeaderWriter.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,16 @@ std::unordered_map<String, String> parseHTTPResponseHeaders(const std::string &
2222
void applyHTTPResponseHeaders(Poco::Net::HTTPResponse & response, const HTTPResponseHeaderSetup & setup);
2323

2424
void applyHTTPResponseHeaders(Poco::Net::HTTPResponse & response, const std::unordered_map<String, String> & setup);
25+
26+
std::unordered_map<String, String> parseHTTPResponseHeadersWithCommons(
27+
const Poco::Util::AbstractConfiguration & config,
28+
const std::string & config_prefix,
29+
const std::unordered_map<String, String> & common_headers);
30+
31+
std::unordered_map<String, String> parseHTTPResponseHeadersWithCommons(
32+
const Poco::Util::AbstractConfiguration & config,
33+
const std::string & config_prefix,
34+
const std::string & default_content_type,
35+
const std::unordered_map<String, String> & common_headers);
36+
2537
}

src/Server/PrometheusRequestHandler.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <Common/logger_useful.h>
44
#include <Common/setThreadName.h>
55
#include <IO/HTTPCommon.h>
6+
#include <Server/HTTPResponseHeaderWriter.h>
67
#include <Server/HTTP/WriteBufferFromHTTPServerResponse.h>
78
#include <Server/HTTP/sendExceptionToHTTPClient.h>
89
#include <Server/IServer.h>
@@ -303,13 +304,15 @@ PrometheusRequestHandler::PrometheusRequestHandler(
303304
IServer & server_,
304305
const PrometheusRequestHandlerConfig & config_,
305306
const AsynchronousMetrics & async_metrics_,
306-
std::shared_ptr<PrometheusMetricsWriter> metrics_writer_)
307+
std::shared_ptr<PrometheusMetricsWriter> metrics_writer_,
308+
std::unordered_map<String, String> response_headers_)
307309
: server(server_)
308310
, config(config_)
309311
, async_metrics(async_metrics_)
310312
, metrics_writer(metrics_writer_)
311313
, log(getLogger("PrometheusRequestHandler"))
312314
{
315+
response_headers = response_headers_;
313316
createImpl();
314317
}
315318

@@ -341,6 +344,7 @@ void PrometheusRequestHandler::createImpl()
341344
void PrometheusRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse & response, const ProfileEvents::Event & write_event_)
342345
{
343346
setThreadName("PrometheusHndlr");
347+
applyHTTPResponseHeaders(response, response_headers);
344348

345349
try
346350
{

src/Server/PrometheusRequestHandler.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ class PrometheusRequestHandler : public HTTPRequestHandler
1919
IServer & server_,
2020
const PrometheusRequestHandlerConfig & config_,
2121
const AsynchronousMetrics & async_metrics_,
22-
std::shared_ptr<PrometheusMetricsWriter> metrics_writer_);
22+
std::shared_ptr<PrometheusMetricsWriter> metrics_writer_,
23+
std::unordered_map<String, String> response_headers_ = {});
2324
~PrometheusRequestHandler() override;
2425

2526
void handleRequest(HTTPServerRequest & request, HTTPServerResponse & response, const ProfileEvents::Event & write_event_) override;
@@ -59,6 +60,7 @@ class PrometheusRequestHandler : public HTTPRequestHandler
5960
std::unique_ptr<WriteBufferFromHTTPServerResponse> write_buffer_from_response;
6061
bool response_finalized = false;
6162
ProfileEvents::Event write_event;
63+
std::unordered_map<String, String> response_headers;
6264
};
6365

6466
}

0 commit comments

Comments
 (0)