2017-04-01 09:19:00 +00:00
|
|
|
#include <IO/HTTPCommon.h>
|
2016-12-30 20:52:56 +00:00
|
|
|
|
2021-02-19 12:51:26 +00:00
|
|
|
#include <Server/HTTP/HTTPServerResponse.h>
|
2018-06-14 21:20:39 +00:00
|
|
|
#include <Common/DNSResolver.h>
|
|
|
|
#include <Common/Exception.h>
|
2019-02-10 17:40:52 +00:00
|
|
|
#include <Common/PoolBase.h>
|
|
|
|
#include <Common/ProfileEvents.h>
|
|
|
|
#include <Common/SipHash.h>
|
|
|
|
|
2022-09-28 08:45:15 +00:00
|
|
|
#include "config.h"
|
2020-04-16 12:31:57 +00:00
|
|
|
|
2020-05-08 14:11:19 +00:00
|
|
|
#if USE_SSL
|
2020-04-16 12:31:57 +00:00
|
|
|
# include <Poco/Net/AcceptCertificateHandler.h>
|
|
|
|
# include <Poco/Net/Context.h>
|
|
|
|
# include <Poco/Net/HTTPSClientSession.h>
|
|
|
|
# include <Poco/Net/InvalidCertificateHandler.h>
|
|
|
|
# include <Poco/Net/PrivateKeyPassphraseHandler.h>
|
|
|
|
# include <Poco/Net/RejectCertificateHandler.h>
|
|
|
|
# include <Poco/Net/SSLManager.h>
|
2020-10-22 02:02:26 +00:00
|
|
|
# include <Poco/Net/SecureStreamSocket.h>
|
2017-03-28 20:30:57 +00:00
|
|
|
#endif
|
2018-11-18 22:15:42 +00:00
|
|
|
|
2017-02-27 21:07:57 +00:00
|
|
|
#include <Poco/Util/Application.h>
|
2017-01-30 05:13:58 +00:00
|
|
|
|
2019-02-10 17:40:52 +00:00
|
|
|
#include <tuple>
|
|
|
|
#include <unordered_map>
|
2018-06-15 07:42:57 +00:00
|
|
|
#include <sstream>
|
2018-11-18 22:15:42 +00:00
|
|
|
|
|
|
|
|
2018-11-16 13:15:17 +00:00
|
|
|
namespace ProfileEvents
|
|
|
|
{
|
|
|
|
extern const Event CreatedHTTPConnections;
|
|
|
|
}
|
2016-12-30 20:52:56 +00:00
|
|
|
|
|
|
|
namespace DB
|
|
|
|
{
|
2018-06-14 21:20:39 +00:00
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
|
|
|
extern const int RECEIVED_ERROR_FROM_REMOTE_IO_SERVER;
|
|
|
|
extern const int RECEIVED_ERROR_TOO_MANY_REQUESTS;
|
2018-06-16 05:54:06 +00:00
|
|
|
extern const int FEATURE_IS_NOT_ENABLED_AT_BUILD_TIME;
|
2018-11-20 17:21:32 +00:00
|
|
|
extern const int UNSUPPORTED_URI_SCHEME;
|
2018-06-14 21:20:39 +00:00
|
|
|
}
|
|
|
|
|
2018-06-16 05:54:06 +00:00
|
|
|
|
2018-11-16 13:15:17 +00:00
|
|
|
namespace
|
|
|
|
{
|
2019-05-31 15:50:21 +00:00
|
|
|
void setTimeouts(Poco::Net::HTTPClientSession & session, const ConnectionTimeouts & timeouts)
|
2018-11-16 13:15:17 +00:00
|
|
|
{
|
|
|
|
session.setTimeout(timeouts.connection_timeout, timeouts.send_timeout, timeouts.receive_timeout);
|
2019-03-29 18:10:03 +00:00
|
|
|
session.setKeepAliveTimeout(timeouts.http_keep_alive_timeout);
|
2018-11-16 13:15:17 +00:00
|
|
|
}
|
|
|
|
|
2018-11-20 17:21:32 +00:00
|
|
|
bool isHTTPS(const Poco::URI & uri)
|
|
|
|
{
|
|
|
|
if (uri.getScheme() == "https")
|
|
|
|
return true;
|
|
|
|
else if (uri.getScheme() == "http")
|
|
|
|
return false;
|
|
|
|
else
|
2023-01-23 21:13:58 +00:00
|
|
|
throw Exception(ErrorCodes::UNSUPPORTED_URI_SCHEME, "Unsupported scheme in URI '{}'", uri.toString());
|
2018-11-20 17:21:32 +00:00
|
|
|
}
|
|
|
|
|
2020-10-22 02:02:26 +00:00
|
|
|
HTTPSessionPtr makeHTTPSessionImpl(const std::string & host, UInt16 port, bool https, bool keep_alive, bool resolve_host = true)
|
2018-11-16 13:15:17 +00:00
|
|
|
{
|
|
|
|
HTTPSessionPtr session;
|
|
|
|
|
|
|
|
if (https)
|
2020-10-22 02:02:26 +00:00
|
|
|
{
|
2020-05-08 14:11:19 +00:00
|
|
|
#if USE_SSL
|
2023-05-25 15:14:16 +00:00
|
|
|
/// Cannot resolve host in advance, otherwise SNI won't work in Poco.
|
|
|
|
/// For more information about SNI, see the https://en.wikipedia.org/wiki/Server_Name_Indication
|
2022-05-12 15:21:29 +00:00
|
|
|
auto https_session = std::make_shared<Poco::Net::HTTPSClientSession>(host, port);
|
|
|
|
if (resolve_host)
|
|
|
|
https_session->setResolvedHost(DNSResolver::instance().resolveHost(host).toString());
|
|
|
|
|
|
|
|
session = std::move(https_session);
|
2018-11-16 13:15:17 +00:00
|
|
|
#else
|
2023-01-23 21:13:58 +00:00
|
|
|
throw Exception(ErrorCodes::FEATURE_IS_NOT_ENABLED_AT_BUILD_TIME, "ClickHouse was built without HTTPS support");
|
2018-11-16 13:15:17 +00:00
|
|
|
#endif
|
2020-10-22 02:02:26 +00:00
|
|
|
}
|
2018-11-16 13:15:17 +00:00
|
|
|
else
|
2020-10-22 02:02:26 +00:00
|
|
|
{
|
|
|
|
String resolved_host = resolve_host ? DNSResolver::instance().resolveHost(host).toString() : host;
|
|
|
|
session = std::make_shared<Poco::Net::HTTPClientSession>(resolved_host, port);
|
|
|
|
}
|
2018-11-16 13:15:17 +00:00
|
|
|
|
|
|
|
ProfileEvents::increment(ProfileEvents::CreatedHTTPConnections);
|
|
|
|
|
2018-11-23 09:24:59 +00:00
|
|
|
/// doesn't work properly without patch
|
2018-11-20 13:15:44 +00:00
|
|
|
session->setKeepAlive(keep_alive);
|
2018-11-16 13:15:17 +00:00
|
|
|
return session;
|
|
|
|
}
|
|
|
|
|
|
|
|
class SingleEndpointHTTPSessionPool : public PoolBase<Poco::Net::HTTPClientSession>
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
const std::string host;
|
|
|
|
const UInt16 port;
|
2023-04-14 10:18:20 +00:00
|
|
|
const bool https;
|
2020-12-03 04:18:12 +00:00
|
|
|
const String proxy_host;
|
|
|
|
const UInt16 proxy_port;
|
2023-04-14 10:18:20 +00:00
|
|
|
const bool proxy_https;
|
|
|
|
const bool resolve_host;
|
|
|
|
|
2018-11-16 13:15:17 +00:00
|
|
|
using Base = PoolBase<Poco::Net::HTTPClientSession>;
|
2023-04-14 10:18:20 +00:00
|
|
|
|
2018-11-16 13:15:17 +00:00
|
|
|
ObjectPtr allocObject() override
|
|
|
|
{
|
2020-12-03 04:18:12 +00:00
|
|
|
auto session = makeHTTPSessionImpl(host, port, https, true, resolve_host);
|
|
|
|
if (!proxy_host.empty())
|
|
|
|
{
|
|
|
|
const String proxy_scheme = proxy_https ? "https" : "http";
|
|
|
|
session->setProxyHost(proxy_host);
|
|
|
|
session->setProxyPort(proxy_port);
|
|
|
|
|
|
|
|
session->setProxyProtocol(proxy_scheme);
|
|
|
|
|
|
|
|
/// Turn on tunnel mode if proxy scheme is HTTP while endpoint scheme is HTTPS.
|
|
|
|
session->setProxyTunnel(!proxy_https && https);
|
|
|
|
}
|
|
|
|
return session;
|
2018-11-16 13:15:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
2020-12-03 04:18:12 +00:00
|
|
|
SingleEndpointHTTPSessionPool(
|
2023-04-14 10:18:20 +00:00
|
|
|
const std::string & host_,
|
|
|
|
UInt16 port_,
|
|
|
|
bool https_,
|
|
|
|
const std::string & proxy_host_,
|
|
|
|
UInt16 proxy_port_,
|
|
|
|
bool proxy_https_,
|
|
|
|
size_t max_pool_size_,
|
|
|
|
bool resolve_host_ = true)
|
2022-10-07 10:46:45 +00:00
|
|
|
: Base(static_cast<unsigned>(max_pool_size_), &Poco::Logger::get("HTTPSessionPool"))
|
2020-12-03 04:18:12 +00:00
|
|
|
, host(host_)
|
|
|
|
, port(port_)
|
|
|
|
, https(https_)
|
|
|
|
, proxy_host(proxy_host_)
|
|
|
|
, proxy_port(proxy_port_)
|
|
|
|
, proxy_https(proxy_https_)
|
|
|
|
, resolve_host(resolve_host_)
|
2018-11-16 13:15:17 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-08-22 03:24:05 +00:00
|
|
|
class HTTPSessionPool : private boost::noncopyable
|
2018-11-16 13:15:17 +00:00
|
|
|
{
|
2020-12-03 04:18:12 +00:00
|
|
|
public:
|
|
|
|
struct Key
|
|
|
|
{
|
|
|
|
String target_host;
|
|
|
|
UInt16 target_port;
|
|
|
|
bool is_target_https;
|
|
|
|
String proxy_host;
|
|
|
|
UInt16 proxy_port;
|
|
|
|
bool is_proxy_https;
|
|
|
|
|
2020-12-21 07:48:26 +00:00
|
|
|
bool operator ==(const Key & rhs) const
|
|
|
|
{
|
|
|
|
return std::tie(target_host, target_port, is_target_https, proxy_host, proxy_port, is_proxy_https)
|
|
|
|
== std::tie(rhs.target_host, rhs.target_port, rhs.is_target_https, rhs.proxy_host, rhs.proxy_port, rhs.is_proxy_https);
|
|
|
|
}
|
2020-12-03 04:18:12 +00:00
|
|
|
};
|
|
|
|
|
2018-11-16 13:15:17 +00:00
|
|
|
private:
|
|
|
|
using PoolPtr = std::shared_ptr<SingleEndpointHTTPSessionPool>;
|
|
|
|
using Entry = SingleEndpointHTTPSessionPool::Entry;
|
|
|
|
|
|
|
|
struct Hasher
|
|
|
|
{
|
|
|
|
size_t operator()(const Key & k) const
|
|
|
|
{
|
|
|
|
SipHash s;
|
2020-12-03 04:18:12 +00:00
|
|
|
s.update(k.target_host);
|
|
|
|
s.update(k.target_port);
|
|
|
|
s.update(k.is_target_https);
|
|
|
|
s.update(k.proxy_host);
|
|
|
|
s.update(k.proxy_port);
|
|
|
|
s.update(k.is_proxy_https);
|
2018-11-16 13:15:17 +00:00
|
|
|
return s.get64();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
std::mutex mutex;
|
|
|
|
std::unordered_map<Key, PoolPtr, Hasher> endpoints_pool;
|
|
|
|
|
2023-05-25 15:14:16 +00:00
|
|
|
void updateHostIfIpChanged(Entry & session, const String & new_ip)
|
|
|
|
{
|
|
|
|
const auto old_ip = session->getResolvedHost().empty() ? session->getHost() : session->getResolvedHost();
|
|
|
|
|
|
|
|
if (new_ip != old_ip)
|
|
|
|
{
|
|
|
|
session->reset();
|
|
|
|
if (session->getResolvedHost().empty())
|
|
|
|
{
|
|
|
|
session->setHost(new_ip);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
session->setResolvedHost(new_ip);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-16 13:15:17 +00:00
|
|
|
protected:
|
|
|
|
HTTPSessionPool() = default;
|
|
|
|
|
|
|
|
public:
|
2019-08-22 03:24:05 +00:00
|
|
|
static auto & instance()
|
|
|
|
{
|
|
|
|
static HTTPSessionPool instance;
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
2019-03-29 18:10:03 +00:00
|
|
|
Entry getSession(
|
|
|
|
const Poco::URI & uri,
|
2020-12-03 04:18:12 +00:00
|
|
|
const Poco::URI & proxy_uri,
|
2019-03-29 18:10:03 +00:00
|
|
|
const ConnectionTimeouts & timeouts,
|
2020-12-03 04:18:12 +00:00
|
|
|
size_t max_connections_per_endpoint,
|
|
|
|
bool resolve_host = true)
|
2018-11-16 13:15:17 +00:00
|
|
|
{
|
2022-06-28 19:19:06 +00:00
|
|
|
std::lock_guard lock(mutex);
|
2018-11-16 13:15:17 +00:00
|
|
|
const std::string & host = uri.getHost();
|
|
|
|
UInt16 port = uri.getPort();
|
2018-11-20 17:21:32 +00:00
|
|
|
bool https = isHTTPS(uri);
|
2020-12-03 04:18:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
String proxy_host;
|
|
|
|
UInt16 proxy_port = 0;
|
|
|
|
bool proxy_https = false;
|
|
|
|
if (!proxy_uri.empty())
|
|
|
|
{
|
|
|
|
proxy_host = proxy_uri.getHost();
|
|
|
|
proxy_port = proxy_uri.getPort();
|
|
|
|
proxy_https = isHTTPS(proxy_uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
HTTPSessionPool::Key key{host, port, https, proxy_host, proxy_port, proxy_https};
|
2018-11-16 13:15:17 +00:00
|
|
|
auto pool_ptr = endpoints_pool.find(key);
|
|
|
|
if (pool_ptr == endpoints_pool.end())
|
|
|
|
std::tie(pool_ptr, std::ignore) = endpoints_pool.emplace(
|
2020-12-03 04:18:12 +00:00
|
|
|
key, std::make_shared<SingleEndpointHTTPSessionPool>(host, port, https, proxy_host, proxy_port, proxy_https, max_connections_per_endpoint, resolve_host));
|
2018-11-16 13:15:17 +00:00
|
|
|
|
2018-11-20 13:15:44 +00:00
|
|
|
auto retry_timeout = timeouts.connection_timeout.totalMicroseconds();
|
|
|
|
auto session = pool_ptr->second->get(retry_timeout);
|
|
|
|
|
2019-06-05 09:23:41 +00:00
|
|
|
/// We store exception messages in session data.
|
|
|
|
/// Poco HTTPSession also stores exception, but it can be removed at any time.
|
2020-03-23 02:12:31 +00:00
|
|
|
const auto & session_data = session->sessionData();
|
|
|
|
if (!session_data.empty())
|
2019-06-05 09:23:41 +00:00
|
|
|
{
|
2020-03-23 02:12:31 +00:00
|
|
|
auto msg = Poco::AnyCast<std::string>(session_data);
|
2019-06-05 09:23:41 +00:00
|
|
|
if (!msg.empty())
|
|
|
|
{
|
2020-05-30 21:57:37 +00:00
|
|
|
LOG_TRACE((&Poco::Logger::get("HTTPCommon")), "Failed communicating with {} with error '{}' will try to reconnect session", host, msg);
|
2020-12-03 04:18:12 +00:00
|
|
|
|
|
|
|
if (resolve_host)
|
2019-06-05 09:23:41 +00:00
|
|
|
{
|
2023-05-25 15:14:16 +00:00
|
|
|
updateHostIfIpChanged(session, DNSResolver::instance().resolveHost(host).toString());
|
2019-06-05 09:23:41 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-14 12:10:14 +00:00
|
|
|
/// Reset the message, once it has been printed,
|
|
|
|
/// otherwise you will get report for failed parts on and on,
|
|
|
|
/// even for different tables (since they uses the same session).
|
|
|
|
session->attachSessionData({});
|
2019-06-05 09:23:41 +00:00
|
|
|
}
|
|
|
|
|
2018-11-16 13:15:17 +00:00
|
|
|
setTimeouts(*session, timeouts);
|
2019-03-29 18:10:03 +00:00
|
|
|
|
2018-11-16 13:15:17 +00:00
|
|
|
return session;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-10-07 10:46:45 +00:00
|
|
|
void setResponseDefaultHeaders(HTTPServerResponse & response, size_t keep_alive_timeout)
|
2016-12-31 02:05:37 +00:00
|
|
|
{
|
2016-12-30 20:52:56 +00:00
|
|
|
if (!response.getKeepAlive())
|
|
|
|
return;
|
|
|
|
|
2017-09-08 11:57:43 +00:00
|
|
|
Poco::Timespan timeout(keep_alive_timeout, 0);
|
|
|
|
if (timeout.totalSeconds())
|
|
|
|
response.set("Keep-Alive", "timeout=" + std::to_string(timeout.totalSeconds()));
|
2016-12-30 20:52:56 +00:00
|
|
|
}
|
|
|
|
|
2020-05-28 16:11:27 +00:00
|
|
|
HTTPSessionPtr makeHTTPSession(const Poco::URI & uri, const ConnectionTimeouts & timeouts, bool resolve_host)
|
2018-06-14 21:20:39 +00:00
|
|
|
{
|
2018-11-16 13:15:17 +00:00
|
|
|
const std::string & host = uri.getHost();
|
|
|
|
UInt16 port = uri.getPort();
|
2018-11-20 17:21:32 +00:00
|
|
|
bool https = isHTTPS(uri);
|
2018-06-14 21:20:39 +00:00
|
|
|
|
2020-05-28 16:11:27 +00:00
|
|
|
auto session = makeHTTPSessionImpl(host, port, https, false, resolve_host);
|
2018-11-16 13:15:17 +00:00
|
|
|
setTimeouts(*session, timeouts);
|
|
|
|
return session;
|
|
|
|
}
|
2018-06-14 21:20:39 +00:00
|
|
|
|
|
|
|
|
2020-12-03 04:18:12 +00:00
|
|
|
PooledHTTPSessionPtr makePooledHTTPSession(const Poco::URI & uri, const ConnectionTimeouts & timeouts, size_t per_endpoint_pool_size, bool resolve_host)
|
|
|
|
{
|
|
|
|
return makePooledHTTPSession(uri, {}, timeouts, per_endpoint_pool_size, resolve_host);
|
|
|
|
}
|
|
|
|
|
|
|
|
PooledHTTPSessionPtr makePooledHTTPSession(const Poco::URI & uri, const Poco::URI & proxy_uri, const ConnectionTimeouts & timeouts, size_t per_endpoint_pool_size, bool resolve_host)
|
2018-11-16 13:15:17 +00:00
|
|
|
{
|
2020-12-03 04:18:12 +00:00
|
|
|
return HTTPSessionPool::instance().getSession(uri, proxy_uri, timeouts, per_endpoint_pool_size, resolve_host);
|
2018-06-14 21:20:39 +00:00
|
|
|
}
|
|
|
|
|
2019-09-16 17:28:41 +00:00
|
|
|
bool isRedirect(const Poco::Net::HTTPResponse::HTTPStatus status) { return status == Poco::Net::HTTPResponse::HTTP_MOVED_PERMANENTLY || status == Poco::Net::HTTPResponse::HTTP_FOUND || status == Poco::Net::HTTPResponse::HTTP_SEE_OTHER || status == Poco::Net::HTTPResponse::HTTP_TEMPORARY_REDIRECT; }
|
2018-06-14 21:20:39 +00:00
|
|
|
|
2018-06-16 05:54:06 +00:00
|
|
|
std::istream * receiveResponse(
|
2019-09-16 17:28:41 +00:00
|
|
|
Poco::Net::HTTPClientSession & session, const Poco::Net::HTTPRequest & request, Poco::Net::HTTPResponse & response, const bool allow_redirects)
|
2018-06-14 21:20:39 +00:00
|
|
|
{
|
2019-06-17 17:32:57 +00:00
|
|
|
auto & istr = session.receiveResponse(response);
|
2019-09-23 08:53:09 +00:00
|
|
|
assertResponseIsOk(request, response, istr, allow_redirects);
|
2019-06-17 17:32:57 +00:00
|
|
|
return &istr;
|
2019-05-31 10:58:43 +00:00
|
|
|
}
|
|
|
|
|
2019-09-23 08:53:09 +00:00
|
|
|
void assertResponseIsOk(const Poco::Net::HTTPRequest & request, Poco::Net::HTTPResponse & response, std::istream & istr, const bool allow_redirects)
|
2019-06-17 18:06:28 +00:00
|
|
|
{
|
2018-06-14 21:20:39 +00:00
|
|
|
auto status = response.getStatus();
|
|
|
|
|
2020-10-29 21:29:10 +00:00
|
|
|
if (!(status == Poco::Net::HTTPResponse::HTTP_OK
|
|
|
|
|| status == Poco::Net::HTTPResponse::HTTP_CREATED
|
|
|
|
|| status == Poco::Net::HTTPResponse::HTTP_ACCEPTED
|
2021-06-19 16:36:39 +00:00
|
|
|
|| status == Poco::Net::HTTPResponse::HTTP_PARTIAL_CONTENT /// Reading with Range header was successful.
|
2020-10-29 21:29:10 +00:00
|
|
|
|| (isRedirect(status) && allow_redirects)))
|
2019-09-11 20:18:22 +00:00
|
|
|
{
|
2022-11-10 21:58:15 +00:00
|
|
|
int code = status == Poco::Net::HTTPResponse::HTTP_TOO_MANY_REQUESTS
|
|
|
|
? ErrorCodes::RECEIVED_ERROR_TOO_MANY_REQUESTS
|
|
|
|
: ErrorCodes::RECEIVED_ERROR_FROM_REMOTE_IO_SERVER;
|
|
|
|
|
|
|
|
std::stringstream body; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
|
|
|
body.exceptions(std::ios::failbit);
|
|
|
|
body << istr.rdbuf();
|
|
|
|
|
|
|
|
throw HTTPException(code, request.getURI(), status, response.getReason(), body.str());
|
2018-06-14 21:20:39 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-16 05:54:06 +00:00
|
|
|
|
2023-01-17 16:39:07 +00:00
|
|
|
Exception HTTPException::makeExceptionMessage(
|
|
|
|
int code,
|
2022-11-10 21:58:15 +00:00
|
|
|
const std::string & uri,
|
|
|
|
Poco::Net::HTTPResponse::HTTPStatus http_status,
|
|
|
|
const std::string & reason,
|
|
|
|
const std::string & body)
|
|
|
|
{
|
2023-01-17 16:39:07 +00:00
|
|
|
return Exception(code,
|
2022-11-10 21:58:15 +00:00
|
|
|
"Received error from remote server {}. "
|
|
|
|
"HTTP status code: {} {}, "
|
|
|
|
"body: {}",
|
2022-12-04 01:23:43 +00:00
|
|
|
uri, static_cast<int>(http_status), reason, body);
|
2022-11-10 21:58:15 +00:00
|
|
|
}
|
|
|
|
|
2016-12-30 20:52:56 +00:00
|
|
|
}
|