2017-04-01 09:19:00 +00:00
|
|
|
#include <IO/HTTPCommon.h>
|
2016-12-30 20:52:56 +00:00
|
|
|
|
2019-02-10 17:40:52 +00:00
|
|
|
#include <Common/config.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>
|
|
|
|
|
|
|
|
#include <Poco/Version.h>
|
2018-11-18 22:15:42 +00:00
|
|
|
|
2018-05-14 18:36:01 +00:00
|
|
|
#if USE_POCO_NETSSL
|
2017-02-27 21:07:57 +00:00
|
|
|
#include <Poco/Net/AcceptCertificateHandler.h>
|
|
|
|
#include <Poco/Net/Context.h>
|
2018-06-14 21:20:39 +00:00
|
|
|
#include <Poco/Net/HTTPSClientSession.h>
|
2017-02-27 21:07:57 +00:00
|
|
|
#include <Poco/Net/InvalidCertificateHandler.h>
|
|
|
|
#include <Poco/Net/PrivateKeyPassphraseHandler.h>
|
|
|
|
#include <Poco/Net/RejectCertificateHandler.h>
|
|
|
|
#include <Poco/Net/SSLManager.h>
|
2017-03-28 20:30:57 +00:00
|
|
|
#endif
|
2018-11-18 22:15:42 +00:00
|
|
|
|
2017-03-28 20:30:57 +00:00
|
|
|
#include <Poco/Net/HTTPServerResponse.h>
|
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
|
|
|
{
|
2019-02-15 11:46:07 +00:00
|
|
|
#if defined(POCO_CLICKHOUSE_PATCH) || POCO_VERSION >= 0x02000000
|
2018-11-16 13:15:17 +00:00
|
|
|
session.setTimeout(timeouts.connection_timeout, timeouts.send_timeout, timeouts.receive_timeout);
|
|
|
|
#else
|
|
|
|
session.setTimeout(std::max({timeouts.connection_timeout, timeouts.send_timeout, timeouts.receive_timeout}));
|
|
|
|
#endif
|
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
|
|
|
|
throw Exception("Unsupported scheme in URI '" + uri.toString() + "'", ErrorCodes::UNSUPPORTED_URI_SCHEME);
|
|
|
|
}
|
|
|
|
|
2018-11-20 13:15:44 +00:00
|
|
|
HTTPSessionPtr makeHTTPSessionImpl(const std::string & host, UInt16 port, bool https, bool keep_alive)
|
2018-11-16 13:15:17 +00:00
|
|
|
{
|
|
|
|
HTTPSessionPtr session;
|
|
|
|
|
|
|
|
if (https)
|
|
|
|
#if USE_POCO_NETSSL
|
2018-11-20 13:15:44 +00:00
|
|
|
session = std::make_shared<Poco::Net::HTTPSClientSession>();
|
2018-11-16 13:15:17 +00:00
|
|
|
#else
|
|
|
|
throw Exception("ClickHouse was built without HTTPS support", ErrorCodes::FEATURE_IS_NOT_ENABLED_AT_BUILD_TIME);
|
|
|
|
#endif
|
|
|
|
else
|
2018-11-20 13:15:44 +00:00
|
|
|
session = std::make_shared<Poco::Net::HTTPClientSession>();
|
2018-11-16 13:15:17 +00:00
|
|
|
|
|
|
|
ProfileEvents::increment(ProfileEvents::CreatedHTTPConnections);
|
|
|
|
|
|
|
|
session->setHost(DNSResolver::instance().resolveHost(host).toString());
|
|
|
|
session->setPort(port);
|
2018-11-23 09:24:59 +00:00
|
|
|
|
|
|
|
/// doesn't work properly without patch
|
2019-02-15 11:46:07 +00:00
|
|
|
#if defined(POCO_CLICKHOUSE_PATCH)
|
2018-11-20 13:15:44 +00:00
|
|
|
session->setKeepAlive(keep_alive);
|
2018-11-28 11:37:12 +00:00
|
|
|
#else
|
|
|
|
(void)keep_alive; // Avoid warning: unused parameter
|
2018-11-23 09:24:59 +00:00
|
|
|
#endif
|
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;
|
|
|
|
bool https;
|
|
|
|
using Base = PoolBase<Poco::Net::HTTPClientSession>;
|
|
|
|
ObjectPtr allocObject() override
|
|
|
|
{
|
2018-11-20 13:15:44 +00:00
|
|
|
return makeHTTPSessionImpl(host, port, https, true);
|
2018-11-16 13:15:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
|
|
SingleEndpointHTTPSessionPool(const std::string & host_, UInt16 port_, bool https_, size_t max_pool_size_)
|
2018-11-18 22:20:36 +00:00
|
|
|
: Base(max_pool_size_, &Poco::Logger::get("HTTPSessionPool")), host(host_), port(port_), https(https_)
|
2018-11-16 13:15:17 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class HTTPSessionPool : public ext::singleton<HTTPSessionPool>
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
using Key = std::tuple<std::string, UInt16, bool>;
|
|
|
|
using PoolPtr = std::shared_ptr<SingleEndpointHTTPSessionPool>;
|
|
|
|
using Entry = SingleEndpointHTTPSessionPool::Entry;
|
|
|
|
|
|
|
|
friend class ext::singleton<HTTPSessionPool>;
|
|
|
|
|
|
|
|
struct Hasher
|
|
|
|
{
|
|
|
|
size_t operator()(const Key & k) const
|
|
|
|
{
|
|
|
|
SipHash s;
|
|
|
|
s.update(std::get<0>(k));
|
|
|
|
s.update(std::get<1>(k));
|
|
|
|
s.update(std::get<2>(k));
|
|
|
|
return s.get64();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
std::mutex mutex;
|
|
|
|
std::unordered_map<Key, PoolPtr, Hasher> endpoints_pool;
|
|
|
|
|
|
|
|
protected:
|
|
|
|
HTTPSessionPool() = default;
|
|
|
|
|
|
|
|
public:
|
2019-03-29 18:10:03 +00:00
|
|
|
Entry getSession(
|
|
|
|
const Poco::URI & uri,
|
|
|
|
const ConnectionTimeouts & timeouts,
|
|
|
|
size_t max_connections_per_endpoint)
|
2018-11-16 13:15:17 +00:00
|
|
|
{
|
2019-01-02 06:44:36 +00:00
|
|
|
std::unique_lock 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);
|
2018-11-16 13:15:17 +00:00
|
|
|
auto key = std::make_tuple(host, port, https);
|
|
|
|
auto pool_ptr = endpoints_pool.find(key);
|
|
|
|
if (pool_ptr == endpoints_pool.end())
|
|
|
|
std::tie(pool_ptr, std::ignore) = endpoints_pool.emplace(
|
|
|
|
key, std::make_shared<SingleEndpointHTTPSessionPool>(host, port, https, max_connections_per_endpoint));
|
|
|
|
|
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.
|
|
|
|
const auto & sessionData = session->sessionData();
|
|
|
|
if (!sessionData.empty())
|
|
|
|
{
|
|
|
|
auto msg = Poco::AnyCast<std::string>(sessionData);
|
|
|
|
if (!msg.empty())
|
|
|
|
{
|
|
|
|
LOG_TRACE((&Logger::get("HTTPCommon")), "Failed communicating with " << host << " with error '" << msg << "' will try to reconnect session");
|
|
|
|
/// Host can change IP
|
|
|
|
const auto ip = DNSResolver::instance().resolveHost(host).toString();
|
|
|
|
if (ip != session->getHost())
|
|
|
|
{
|
|
|
|
session->reset();
|
|
|
|
session->setHost(ip);
|
|
|
|
session->attachSessionData({});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-09-08 16:41:35 +00:00
|
|
|
void setResponseDefaultHeaders(Poco::Net::HTTPServerResponse & response, unsigned keep_alive_timeout)
|
2016-12-31 02:05:37 +00:00
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
if (!response.getKeepAlive())
|
|
|
|
return;
|
2016-12-30 20:52:56 +00:00
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2018-11-16 13:15:17 +00:00
|
|
|
HTTPSessionPtr makeHTTPSession(const Poco::URI & uri, const ConnectionTimeouts & timeouts)
|
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
|
|
|
|
2018-11-20 13:15:44 +00:00
|
|
|
auto session = makeHTTPSessionImpl(host, port, https, false);
|
2018-11-16 13:15:17 +00:00
|
|
|
setTimeouts(*session, timeouts);
|
|
|
|
return session;
|
|
|
|
}
|
2018-06-14 21:20:39 +00:00
|
|
|
|
|
|
|
|
2018-11-16 13:15:17 +00:00
|
|
|
PooledHTTPSessionPtr makePooledHTTPSession(const Poco::URI & uri, const ConnectionTimeouts & timeouts, size_t per_endpoint_pool_size)
|
|
|
|
{
|
|
|
|
return HTTPSessionPool::instance().getSession(uri, timeouts, per_endpoint_pool_size);
|
2018-06-14 21:20:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-06-16 05:54:06 +00:00
|
|
|
std::istream * receiveResponse(
|
2018-06-14 21:20:39 +00:00
|
|
|
Poco::Net::HTTPClientSession & session, const Poco::Net::HTTPRequest & request, Poco::Net::HTTPResponse & response)
|
|
|
|
{
|
2019-06-17 17:32:57 +00:00
|
|
|
auto & istr = session.receiveResponse(response);
|
2019-05-31 10:58:43 +00:00
|
|
|
assertResponseIsOk(request, response, istr);
|
2019-06-17 17:32:57 +00:00
|
|
|
return &istr;
|
2019-05-31 10:58:43 +00:00
|
|
|
}
|
|
|
|
|
2019-06-17 17:32:57 +00:00
|
|
|
void assertResponseIsOk(const Poco::Net::HTTPRequest & request, Poco::Net::HTTPResponse & response, std::istream & istr) {
|
2018-06-14 21:20:39 +00:00
|
|
|
auto status = response.getStatus();
|
|
|
|
|
|
|
|
if (status != Poco::Net::HTTPResponse::HTTP_OK)
|
|
|
|
{
|
|
|
|
std::stringstream error_message;
|
|
|
|
error_message << "Received error from remote server " << request.getURI() << ". HTTP status code: " << status << " "
|
2019-06-17 17:32:57 +00:00
|
|
|
<< response.getReason() << ", body: " << istr.rdbuf();
|
2018-06-14 21:20:39 +00:00
|
|
|
|
|
|
|
throw Exception(error_message.str(),
|
|
|
|
status == HTTP_TOO_MANY_REQUESTS ? ErrorCodes::RECEIVED_ERROR_TOO_MANY_REQUESTS
|
|
|
|
: ErrorCodes::RECEIVED_ERROR_FROM_REMOTE_IO_SERVER);
|
|
|
|
}
|
|
|
|
}
|
2018-06-16 05:54:06 +00:00
|
|
|
|
2016-12-30 20:52:56 +00:00
|
|
|
}
|