mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-09-03 15:35:17 +00:00
[#1732] Refactored internal HttpClient classes
src/lib/http/client.cc Refactored, connections and request queue are now managed together as part of a URL destination. src/lib/http/url.cc Url::operator<(const Url& url) - compares original unparsed string rather then reconstructing a new string for both operands every time
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <unordered_set>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -452,26 +453,357 @@ private:
|
|||||||
/// @brief Shared pointer to the connection.
|
/// @brief Shared pointer to the connection.
|
||||||
typedef boost::shared_ptr<Connection> ConnectionPtr;
|
typedef boost::shared_ptr<Connection> ConnectionPtr;
|
||||||
|
|
||||||
/// @brief Container of Connections for a given URL
|
/// @brief Connection pool for managing multiple connections.
|
||||||
class ConnectionList {
|
///
|
||||||
|
/// Connection pool creates and destroys URL destinations. t manages
|
||||||
|
/// connections to and requests for URLs. Each time a request is
|
||||||
|
/// submitted for a URL, it assigns it to an available idle connection,
|
||||||
|
/// or if no idle connections are available, pushes the request on the queue
|
||||||
|
/// for that URL.
|
||||||
|
class ConnectionPool : public boost::enable_shared_from_this<ConnectionPool> {
|
||||||
public:
|
public:
|
||||||
/// @brief Constructor
|
|
||||||
|
/// @brief Constructor.
|
||||||
///
|
///
|
||||||
/// @param url URL associated with this connection list.
|
/// @param io_service Reference to the IO service to be used by the
|
||||||
/// @param max_connections maximum number of connections
|
/// connections.
|
||||||
/// allowed for in the list URL
|
/// @param max_url_connections maximum number of concurrent
|
||||||
ConnectionList(Url url, size_t max_connections)
|
/// connections allowed per URL.
|
||||||
: url_(url), max_connections_(max_connections) {
|
explicit ConnectionPool(IOService& io_service, size_t max_url_connections)
|
||||||
|
: io_service_(io_service), destinations_(), mutex_(),
|
||||||
|
max_url_connections_(max_url_connections) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Add a new connection to the list
|
/// @brief Destructor.
|
||||||
|
///
|
||||||
|
/// Closes all connections.
|
||||||
|
~ConnectionPool() {
|
||||||
|
closeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Process next queued request for the given URL.
|
||||||
|
///
|
||||||
|
/// @param url URL for which next queued request should be processed.
|
||||||
|
void processNextRequest(const Url& url) {
|
||||||
|
if (MultiThreadingMgr::instance().getMode()) {
|
||||||
|
std::lock_guard<std::mutex> lk(mutex_);
|
||||||
|
return (processNextRequestInternal(url));
|
||||||
|
} else {
|
||||||
|
return (processNextRequestInternal(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Queue next request for sending to the server.
|
||||||
|
///
|
||||||
|
/// A new transaction is started immediately, if there is no other request
|
||||||
|
/// in progress for the given URL. Otherwise, the request is queued.
|
||||||
|
///
|
||||||
|
/// @param url Destination where the request should be sent.
|
||||||
|
/// @param tls_context TLS context to be used for the connection.
|
||||||
|
/// @param request Pointer to the request to be sent to the server.
|
||||||
|
/// @param response Pointer to the object into which the response should be
|
||||||
|
/// stored.
|
||||||
|
/// @param request_timeout Requested timeout for the transaction in
|
||||||
|
/// milliseconds.
|
||||||
|
/// @param request_callback Pointer to the user callback to be invoked when the
|
||||||
|
/// transaction ends.
|
||||||
|
/// @param connect_callback Pointer to the user callback to be invoked when the
|
||||||
|
/// client connects to the server.
|
||||||
|
/// @param handshake_callback Optional callback invoked when the client
|
||||||
|
/// performs the TLS handshake with the server.
|
||||||
|
/// @param close_callback Pointer to the user callback to be invoked when the
|
||||||
|
/// client closes the connection to the server.
|
||||||
|
void queueRequest(const Url& url,
|
||||||
|
const TlsContextPtr& tls_context,
|
||||||
|
const HttpRequestPtr& request,
|
||||||
|
const HttpResponsePtr& response,
|
||||||
|
const long request_timeout,
|
||||||
|
const HttpClient::RequestHandler& request_callback,
|
||||||
|
const HttpClient::ConnectHandler& connect_callback,
|
||||||
|
const HttpClient::HandshakeHandler& handshake_callback,
|
||||||
|
const HttpClient::CloseHandler& close_callback) {
|
||||||
|
if (MultiThreadingMgr::instance().getMode()) {
|
||||||
|
std::lock_guard<std::mutex> lk(mutex_);
|
||||||
|
return (queueRequestInternal(url, tls_context, request, response,
|
||||||
|
request_timeout, request_callback,
|
||||||
|
connect_callback, handshake_callback,
|
||||||
|
close_callback));
|
||||||
|
} else {
|
||||||
|
return (queueRequestInternal(url, tls_context, request, response,
|
||||||
|
request_timeout, request_callback,
|
||||||
|
connect_callback, handshake_callback,
|
||||||
|
close_callback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Closes all URLS and removes associated information from
|
||||||
|
/// the connection pool.
|
||||||
|
void closeAll() {
|
||||||
|
if (MultiThreadingMgr::instance().getMode()) {
|
||||||
|
std::lock_guard<std::mutex> lk(mutex_);
|
||||||
|
closeAllInternal();
|
||||||
|
} else {
|
||||||
|
closeAllInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Closes a connection if it has an out-of-band socket event
|
||||||
|
///
|
||||||
|
/// If the pool contains a connection using the given socket and that
|
||||||
|
/// connection is currently in a transaction the method returns as this
|
||||||
|
/// indicates a normal ready event. If the connection is not in an
|
||||||
|
/// ongoing transaction, then the connection is closed.
|
||||||
|
///
|
||||||
|
/// This is method is intended to be used to detect and clean up then
|
||||||
|
/// sockets that are marked ready outside of transactions. The most common
|
||||||
|
/// case is the other end of the socket being closed.
|
||||||
|
///
|
||||||
|
/// @param socket_fd socket descriptor to check
|
||||||
|
void closeIfOutOfBand(int socket_fd) {
|
||||||
|
if (MultiThreadingMgr::instance().getMode()) {
|
||||||
|
std::lock_guard<std::mutex> lk(mutex_);
|
||||||
|
closeIfOutOfBandInternal(socket_fd);
|
||||||
|
} else {
|
||||||
|
closeIfOutOfBandInternal(socket_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/// @brief Process next queued request for the given URL.
|
||||||
|
///
|
||||||
|
/// This method should be called in a thread safe context.
|
||||||
|
///
|
||||||
|
/// @param url URL for which next queued request should be retrieved.
|
||||||
|
void processNextRequestInternal(const Url& url) {
|
||||||
|
// Check if there is a queue for this URL. If there is no queue, there
|
||||||
|
// is no request queued either.
|
||||||
|
DestinationPtr destination = findDestination(url);
|
||||||
|
if (destination) {
|
||||||
|
if (!destination->queueEmpty()) {
|
||||||
|
// We have at least one queued request. Do we have an
|
||||||
|
// idle connection?
|
||||||
|
ConnectionPtr connection = destination->getIdleConnection();
|
||||||
|
if (!connection) {
|
||||||
|
TOMS_TRACE_LOG("*** No idle connections, don't dequeue?");
|
||||||
|
// @todo Resolve this, throw or just return, possibly log and return
|
||||||
|
//
|
||||||
|
// We shouldn't be in this function w/o an idle connection as it is called
|
||||||
|
// from by terminate() after completion of a transaction? It should not be
|
||||||
|
// possible for the connection that got us here to not be busy.
|
||||||
|
// Do we throw or just not dequeue ther request? It was TSAN tested and
|
||||||
|
// perf tested with just the return.
|
||||||
|
// isc_throw(Unexpected, "no idle connections for :" << url.toText());
|
||||||
|
// Let's leave it on the queue, nothing idle yet?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dequeue the oldest request and start a transaction for it using
|
||||||
|
// the idle connection.
|
||||||
|
RequestDescriptor desc = destination->popNextRequest();
|
||||||
|
connection->doTransaction(desc.request_, desc.response_,
|
||||||
|
desc.request_timeout_, desc.callback_,
|
||||||
|
desc.connect_callback_,
|
||||||
|
desc.handshake_callback_,
|
||||||
|
desc.close_callback_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Queue next request for sending to the server.
|
||||||
|
///
|
||||||
|
/// A new transaction is started immediately, if there is no other request
|
||||||
|
/// in progress for the given URL. Otherwise, the request is queued.
|
||||||
|
///
|
||||||
|
/// This method should be called in a thread safe context.
|
||||||
|
///
|
||||||
|
/// @param url Destination where the request should be sent.
|
||||||
|
/// @param tls_context TLS context to be used for the connection.
|
||||||
|
/// @param request Pointer to the request to be sent to the server.
|
||||||
|
/// @param response Pointer to the object into which the response should be
|
||||||
|
/// stored.
|
||||||
|
/// @param request_timeout Requested timeout for the transaction in
|
||||||
|
/// milliseconds.
|
||||||
|
/// @param request_callback Pointer to the user callback to be invoked when the
|
||||||
|
/// transaction ends.
|
||||||
|
/// @param connect_callback Pointer to the user callback to be invoked when the
|
||||||
|
/// client connects to the server.
|
||||||
|
/// @param handshake_callback Optional callback invoked when the client
|
||||||
|
/// performs the TLS handshake with the server.
|
||||||
|
/// @param close_callback Pointer to the user callback to be invoked when the
|
||||||
|
/// client closes the connection to the server.
|
||||||
|
void queueRequestInternal(const Url& url,
|
||||||
|
const TlsContextPtr& tls_context,
|
||||||
|
const HttpRequestPtr& request,
|
||||||
|
const HttpResponsePtr& response,
|
||||||
|
const long request_timeout,
|
||||||
|
const HttpClient::RequestHandler& request_callback,
|
||||||
|
const HttpClient::ConnectHandler& connect_callback,
|
||||||
|
const HttpClient::HandshakeHandler& handshake_callback,
|
||||||
|
const HttpClient::CloseHandler& close_callback) {
|
||||||
|
ConnectionPtr connection;
|
||||||
|
// Find the destination for the requested URL.
|
||||||
|
DestinationPtr destination = findDestination(url);
|
||||||
|
if (destination) {
|
||||||
|
// Found it, look for an idle connection.
|
||||||
|
connection = destination->getIdleConnection();
|
||||||
|
} else {
|
||||||
|
// Doesn't exist yet so it's a new destination/
|
||||||
|
destination = addDestination(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connection) {
|
||||||
|
if (destination->connectionsFull()) {
|
||||||
|
TOMS_TRACE_LOG("no idle connections, queue request");
|
||||||
|
// All connections busy, queue it.
|
||||||
|
destination->pushRequest(RequestDescriptor(ConnectionPtr(), request, response,
|
||||||
|
request_timeout,
|
||||||
|
request_callback,
|
||||||
|
connect_callback,
|
||||||
|
handshake_callback,
|
||||||
|
close_callback));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Room to make another connection with this destination, so make one.
|
||||||
|
TOMS_TRACE_LOG("creating a new connection");
|
||||||
|
connection.reset(new Connection(io_service_, tls_context,
|
||||||
|
shared_from_this(), url));
|
||||||
|
destination->addConnection(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the connection to start the transaction.
|
||||||
|
TOMS_TRACE_LOG("doTransaction");
|
||||||
|
connection->doTransaction(request, response, request_timeout, request_callback,
|
||||||
|
connect_callback, handshake_callback, close_callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Closes all connections for all URLs and removes associated
|
||||||
|
/// information from the connection pool.
|
||||||
|
///
|
||||||
|
/// This method should be called in a thread safe context.
|
||||||
|
void closeAllInternal() {
|
||||||
|
for (auto const& destination : destinations_) {
|
||||||
|
destination.second->closeAllConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
destinations_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Closes a connection if it has an out-of-band socket event
|
||||||
|
///
|
||||||
|
/// If the pool contains a connection using the given socket and that
|
||||||
|
/// connection is currently in a transaction the method returns as this
|
||||||
|
/// indicates a normal ready event. If the connection is not in an
|
||||||
|
/// ongoing transaction, then the connection is closed.
|
||||||
|
///
|
||||||
|
/// This is method is intended to be used to detect and clean up then
|
||||||
|
/// sockets that are marked ready outside of transactions. The most common
|
||||||
|
/// case is the other end of the socket being closed.
|
||||||
|
///
|
||||||
|
/// This method should be called in a thread safe context.
|
||||||
|
///
|
||||||
|
/// @param socket_fd socket descriptor to check
|
||||||
|
void closeIfOutOfBandInternal(int socket_fd) {
|
||||||
|
for (auto const& destination : destinations_) {
|
||||||
|
// First we look for a connection with the socket.
|
||||||
|
ConnectionPtr connection = destination.second->findBySocketFd(socket_fd);
|
||||||
|
if (connection) {
|
||||||
|
if (!connection->isTransactionOngoing()) {
|
||||||
|
// Socket has no transaction, so any ready event is
|
||||||
|
// out-of-band (other end probably closed), so
|
||||||
|
// let's close it. Note we do not remove any queued
|
||||||
|
// requests, as this might somehow be occurring in
|
||||||
|
// between them.
|
||||||
|
destination.second->closeConnection(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Request descriptor holds parameters associated with the
|
||||||
|
/// particular request.
|
||||||
|
struct RequestDescriptor {
|
||||||
|
/// @brief Constructor.
|
||||||
|
///
|
||||||
|
/// @param conn Pointer to the connection.
|
||||||
|
/// @param request Pointer to the request to be sent.
|
||||||
|
/// @param response Pointer to the object into which the response will
|
||||||
|
/// be stored.
|
||||||
|
/// @param request_timeout Requested timeout for the transaction.
|
||||||
|
/// @param callback Pointer to the user callback.
|
||||||
|
/// @param connect_callback pointer to the user callback to be invoked
|
||||||
|
/// when the client connects to the server.
|
||||||
|
/// @param handshake_callback Optional callback invoked when the client
|
||||||
|
/// performs the TLS handshake with the server.
|
||||||
|
/// @param close_callback pointer to the user callback to be invoked
|
||||||
|
/// when the client closes the connection to the server.
|
||||||
|
RequestDescriptor(const ConnectionPtr& conn,
|
||||||
|
const HttpRequestPtr& request,
|
||||||
|
const HttpResponsePtr& response,
|
||||||
|
const long& request_timeout,
|
||||||
|
const HttpClient::RequestHandler& callback,
|
||||||
|
const HttpClient::ConnectHandler& connect_callback,
|
||||||
|
const HttpClient::HandshakeHandler& handshake_callback,
|
||||||
|
const HttpClient::CloseHandler& close_callback)
|
||||||
|
: conn_(conn), request_(request), response_(response),
|
||||||
|
request_timeout_(request_timeout), callback_(callback),
|
||||||
|
connect_callback_(connect_callback),
|
||||||
|
handshake_callback_(handshake_callback),
|
||||||
|
close_callback_(close_callback) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Holds the connection.
|
||||||
|
ConnectionPtr conn_;
|
||||||
|
|
||||||
|
/// @brief Holds pointer to the request.
|
||||||
|
HttpRequestPtr request_;
|
||||||
|
|
||||||
|
/// @brief Holds pointer to the response.
|
||||||
|
HttpResponsePtr response_;
|
||||||
|
|
||||||
|
/// @brief Holds requested timeout value.
|
||||||
|
long request_timeout_;
|
||||||
|
|
||||||
|
/// @brief Holds pointer to the user callback.
|
||||||
|
HttpClient::RequestHandler callback_;
|
||||||
|
|
||||||
|
/// @brief Holds pointer to the user callback for connect.
|
||||||
|
HttpClient::ConnectHandler connect_callback_;
|
||||||
|
|
||||||
|
/// @brief Holds pointer to the user callback for handshake.
|
||||||
|
HttpClient::HandshakeHandler handshake_callback_;
|
||||||
|
|
||||||
|
/// @brief Holds pointer to the user callback for close.
|
||||||
|
HttpClient::CloseHandler close_callback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Encapsulates connections and requests for a given URL
|
||||||
|
class Destination {
|
||||||
|
public:
|
||||||
|
/// @brief Constructor
|
||||||
|
///
|
||||||
|
/// @param url server URL of this destination
|
||||||
|
/// @param max_connections maximum number of concurrent connections
|
||||||
|
/// allowed for in the list URL
|
||||||
|
Destination(Url url, size_t max_connections)
|
||||||
|
: url_(url), max_connections_(max_connections), connections_(), queue_() { }
|
||||||
|
|
||||||
|
/// @brief Destructor
|
||||||
|
~Destination() {
|
||||||
|
closeAllConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Add a new connection
|
||||||
///
|
///
|
||||||
/// @param connection the connection to add
|
/// @param connection the connection to add
|
||||||
///
|
///
|
||||||
/// @throw BadValue if the maximum number of connections already
|
/// @throw BadValue if the maximum number of connections already
|
||||||
/// exist.
|
/// exist.
|
||||||
void addConnection(ConnectionPtr connection) {
|
void addConnection(ConnectionPtr connection) {
|
||||||
if (full()) {
|
if (connectionsFull()) {
|
||||||
isc_throw(BadValue, "URL: " << url_.toText()
|
isc_throw(BadValue, "URL: " << url_.toText()
|
||||||
<< ", already at maximum connections: "
|
<< ", already at maximum connections: "
|
||||||
<< max_connections_);
|
<< max_connections_);
|
||||||
@@ -538,437 +870,128 @@ public:
|
|||||||
/// @brief Indicates if there are no connections in the list.
|
/// @brief Indicates if there are no connections in the list.
|
||||||
///
|
///
|
||||||
/// @return true if the list is empty.
|
/// @return true if the list is empty.
|
||||||
bool empty() {
|
bool connectionsEmpty() {
|
||||||
return(connections_.empty());
|
return(connections_.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Indicates if list contains the maximum number.
|
/// @brief Indicates if list contains the maximum number.
|
||||||
///
|
///
|
||||||
/// @return true if the list is full.
|
/// @return true if the list is full.
|
||||||
bool full() {
|
bool connectionsFull() {
|
||||||
return(connections_.size() >= max_connections_);
|
return (connections_.size() >= max_connections_);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Fetches the number of connections in the list.
|
/// @brief Fetches the number of connections in the list.
|
||||||
///
|
///
|
||||||
/// @return the number of connections in the list.
|
/// @return the number of connections in the list.
|
||||||
size_t size() {
|
size_t connectionCount() {
|
||||||
return connections_.size();
|
return (connections_.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Fetches the maximum number of connections.
|
/// @brief Fetches the maximum number of connections.
|
||||||
///
|
///
|
||||||
/// @return the maxim number of connections.
|
/// @return the maxim number of connections.
|
||||||
size_t max_connections() const {
|
size_t getMaxConnections() const {
|
||||||
return max_connections_;
|
return (max_connections_);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Fetches the URL.
|
/// @brief Fetches the URL.
|
||||||
///
|
///
|
||||||
/// @return the URL.
|
/// @return the URL.
|
||||||
const Url& getUrl() const {
|
const Url& getUrl() const {
|
||||||
return url_;
|
return (url_);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
/// @brief Indicates if request queue is empty.
|
||||||
|
///
|
||||||
|
/// @return true if there are no requests queued.
|
||||||
|
bool queueEmpty() const {
|
||||||
|
return (queue_.empty());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Adds a request to the end of the request queue.
|
||||||
|
///
|
||||||
|
/// @param desc RequestDescriptor to queue.
|
||||||
|
void pushRequest(RequestDescriptor desc) {
|
||||||
|
queue_.push(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Adds a request to the end of the request queue.
|
||||||
|
///
|
||||||
|
/// @return desc RequestDescriptor to queue.
|
||||||
|
RequestDescriptor popNextRequest() {
|
||||||
|
if (queue_.empty()) {
|
||||||
|
isc_throw(InvalidOperation, "cannot pop, queue is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestDescriptor desc = queue_.front();
|
||||||
|
queue_.pop();
|
||||||
|
return(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
/// @brief URL supported by the list.
|
/// @brief URL supported by the list.
|
||||||
Url url_;
|
Url url_;
|
||||||
|
|
||||||
/// @brief Maximum number of concurrent connections allowed in the list.
|
/// @brief Maximum number of concurrent connections for this destination.
|
||||||
size_t max_connections_;
|
size_t max_connections_;
|
||||||
|
|
||||||
/// @brief List of concurrent connections.
|
/// @brief List of concurrent connections.
|
||||||
std::vector<ConnectionPtr> connections_;
|
std::vector<ConnectionPtr> connections_;
|
||||||
};
|
|
||||||
|
|
||||||
/// @brief Defines a pointer to a ConnectionList instance.
|
/// @brief Holds the queue of request for this destination.
|
||||||
typedef boost::shared_ptr<ConnectionList> ConnectionListPtr;
|
std::queue<RequestDescriptor> queue_;
|
||||||
|
|
||||||
/// @brief Connection pool for managing multiple connections.
|
|
||||||
///
|
|
||||||
/// Connection pool creates and destroys connections. It holds pointers
|
|
||||||
/// to all created connections and can verify whether the particular
|
|
||||||
/// connection is currently busy or idle. If the connection is idle, it
|
|
||||||
/// uses this connection for new requests. If the connection is busy, it
|
|
||||||
/// queues new requests until the connection becomes available.
|
|
||||||
class ConnectionPool : public boost::enable_shared_from_this<ConnectionPool> {
|
|
||||||
public:
|
|
||||||
|
|
||||||
/// @brief Constructor.
|
|
||||||
///
|
|
||||||
/// @param io_service Reference to the IO service to be used by the
|
|
||||||
/// connections.
|
|
||||||
/// @param max_url_connections maximum number of concurrent
|
|
||||||
/// connections allowed per URL.
|
|
||||||
explicit ConnectionPool(IOService& io_service, size_t max_url_connections)
|
|
||||||
: io_service_(io_service), conns_(), queue_(), mutex_(),
|
|
||||||
max_url_connections_(max_url_connections) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Destructor.
|
|
||||||
///
|
|
||||||
/// Closes all connections.
|
|
||||||
~ConnectionPool() {
|
|
||||||
closeAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Process next queued request for the given URL.
|
|
||||||
///
|
|
||||||
/// @param url URL for which next queued request should be processed.
|
|
||||||
void processNextRequest(const Url& url) {
|
|
||||||
if (MultiThreadingMgr::instance().getMode()) {
|
|
||||||
std::lock_guard<std::mutex> lk(mutex_);
|
|
||||||
return (processNextRequestInternal(url));
|
|
||||||
} else {
|
|
||||||
return (processNextRequestInternal(url));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Queue next request for sending to the server.
|
|
||||||
///
|
|
||||||
/// A new transaction is started immediately, if there is no other request
|
|
||||||
/// in progress for the given URL. Otherwise, the request is queued.
|
|
||||||
///
|
|
||||||
/// @param url Destination where the request should be sent.
|
|
||||||
/// @param tls_context TLS context to be used for the connection.
|
|
||||||
/// @param request Pointer to the request to be sent to the server.
|
|
||||||
/// @param response Pointer to the object into which the response should be
|
|
||||||
/// stored.
|
|
||||||
/// @param request_timeout Requested timeout for the transaction in
|
|
||||||
/// milliseconds.
|
|
||||||
/// @param request_callback Pointer to the user callback to be invoked when the
|
|
||||||
/// transaction ends.
|
|
||||||
/// @param connect_callback Pointer to the user callback to be invoked when the
|
|
||||||
/// client connects to the server.
|
|
||||||
/// @param handshake_callback Optional callback invoked when the client
|
|
||||||
/// performs the TLS handshake with the server.
|
|
||||||
/// @param close_callback Pointer to the user callback to be invoked when the
|
|
||||||
/// client closes the connection to the server.
|
|
||||||
void queueRequest(const Url& url,
|
|
||||||
const TlsContextPtr& tls_context,
|
|
||||||
const HttpRequestPtr& request,
|
|
||||||
const HttpResponsePtr& response,
|
|
||||||
const long request_timeout,
|
|
||||||
const HttpClient::RequestHandler& request_callback,
|
|
||||||
const HttpClient::ConnectHandler& connect_callback,
|
|
||||||
const HttpClient::HandshakeHandler& handshake_callback,
|
|
||||||
const HttpClient::CloseHandler& close_callback) {
|
|
||||||
if (MultiThreadingMgr::instance().getMode()) {
|
|
||||||
std::lock_guard<std::mutex> lk(mutex_);
|
|
||||||
return (queueRequestInternal(url, tls_context, request, response,
|
|
||||||
request_timeout, request_callback,
|
|
||||||
connect_callback, handshake_callback,
|
|
||||||
close_callback));
|
|
||||||
} else {
|
|
||||||
return (queueRequestInternal(url, tls_context, request, response,
|
|
||||||
request_timeout, request_callback,
|
|
||||||
connect_callback, handshake_callback,
|
|
||||||
close_callback));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Closes a URL's connections and removes associated information
|
|
||||||
/// from the connection pool.
|
|
||||||
///
|
|
||||||
/// @param url URL for which connection should be closed.
|
|
||||||
void closeUrlConnections(const Url& url) {
|
|
||||||
if (MultiThreadingMgr::instance().getMode()) {
|
|
||||||
std::lock_guard<std::mutex> lk(mutex_);
|
|
||||||
closeUrlConnectionsInternal(url);
|
|
||||||
} else {
|
|
||||||
closeUrlConnectionsInternal(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Closes all URLS and removes associated information from
|
|
||||||
/// the connection pool.
|
|
||||||
void closeAll() {
|
|
||||||
if (MultiThreadingMgr::instance().getMode()) {
|
|
||||||
std::lock_guard<std::mutex> lk(mutex_);
|
|
||||||
closeAllInternal();
|
|
||||||
} else {
|
|
||||||
closeAllInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Closes a connection if it has an out-of-band socket event
|
|
||||||
///
|
|
||||||
/// If the pool contains a connection using the given socket and that
|
|
||||||
/// connection is currently in a transaction the method returns as this
|
|
||||||
/// indicates a normal ready event. If the connection is not in an
|
|
||||||
/// ongoing transaction, then the connection is closed.
|
|
||||||
///
|
|
||||||
/// This is method is intended to be used to detect and clean up then
|
|
||||||
/// sockets that are marked ready outside of transactions. The most common
|
|
||||||
/// case is the other end of the socket being closed.
|
|
||||||
///
|
|
||||||
/// @param socket_fd socket descriptor to check
|
|
||||||
void closeIfOutOfBand(int socket_fd) {
|
|
||||||
if (MultiThreadingMgr::instance().getMode()) {
|
|
||||||
std::lock_guard<std::mutex> lk(mutex_);
|
|
||||||
closeIfOutOfBandInternal(socket_fd);
|
|
||||||
} else {
|
|
||||||
closeIfOutOfBandInternal(socket_fd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
/// @brief Process next queued request for the given URL.
|
|
||||||
///
|
|
||||||
/// This method should be called in a thread safe context.
|
|
||||||
///
|
|
||||||
/// @param url URL for which next queued request should be retrieved.
|
|
||||||
void processNextRequestInternal(const Url& url) {
|
|
||||||
// Check if there is a queue for this URL. If there is no queue, there
|
|
||||||
// is no request queued either.
|
|
||||||
auto queue_it = queue_.find(url);
|
|
||||||
if (queue_it != queue_.end()) {
|
|
||||||
if (!queue_it->second.empty()) {
|
|
||||||
// We have at least one queued request. Do we have an
|
|
||||||
// idle connection?
|
|
||||||
auto conns_it = conns_.find(url);
|
|
||||||
if (conns_it == conns_.end()) {
|
|
||||||
isc_throw(Unexpected, "no connection list for :" << url.toText());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, look for an idle connection.
|
|
||||||
ConnectionPtr connection = conns_it->second->getIdleConnection();
|
|
||||||
if (!connection) {
|
|
||||||
TOMS_TRACE_LOG("*** No idle connections, don't dequeue?");
|
|
||||||
// @todo Resolve this, throw or just return, possibly log and return
|
|
||||||
//
|
|
||||||
// We shouldn't be in this function w/o an idle connection as it is called
|
|
||||||
// from by terminate() after completion of a transaction? It should not be
|
|
||||||
// possible for the connection that got us here to not be busy.
|
|
||||||
// Do we throw or just not dequeue ther request? It was TSAN tested and
|
|
||||||
// perf tested with just the return.
|
|
||||||
// isc_throw(Unexpected, "no idle connections for :" << url.toText());
|
|
||||||
// Let's leave it on the queue, nothing idle yet?
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dequeue the oldest request and start a transaction for it using
|
|
||||||
// the idle connection.
|
|
||||||
RequestDescriptor desc = queue_it->second.front();
|
|
||||||
queue_it->second.pop();
|
|
||||||
desc.conn_ = connection;
|
|
||||||
connection->doTransaction(desc.request_, desc.response_,
|
|
||||||
desc.request_timeout_, desc.callback_,
|
|
||||||
desc.connect_callback_,
|
|
||||||
desc.handshake_callback_,
|
|
||||||
desc.close_callback_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Queue next request for sending to the server.
|
|
||||||
///
|
|
||||||
/// A new transaction is started immediately, if there is no other request
|
|
||||||
/// in progress for the given URL. Otherwise, the request is queued.
|
|
||||||
///
|
|
||||||
/// This method should be called in a thread safe context.
|
|
||||||
///
|
|
||||||
/// @param url Destination where the request should be sent.
|
|
||||||
/// @param tls_context TLS context to be used for the connection.
|
|
||||||
/// @param request Pointer to the request to be sent to the server.
|
|
||||||
/// @param response Pointer to the object into which the response should be
|
|
||||||
/// stored.
|
|
||||||
/// @param request_timeout Requested timeout for the transaction in
|
|
||||||
/// milliseconds.
|
|
||||||
/// @param request_callback Pointer to the user callback to be invoked when the
|
|
||||||
/// transaction ends.
|
|
||||||
/// @param connect_callback Pointer to the user callback to be invoked when the
|
|
||||||
/// client connects to the server.
|
|
||||||
/// @param handshake_callback Optional callback invoked when the client
|
|
||||||
/// performs the TLS handshake with the server.
|
|
||||||
/// @param close_callback Pointer to the user callback to be invoked when the
|
|
||||||
/// client closes the connection to the server.
|
|
||||||
void queueRequestInternal(const Url& url,
|
|
||||||
const TlsContextPtr& tls_context,
|
|
||||||
const HttpRequestPtr& request,
|
|
||||||
const HttpResponsePtr& response,
|
|
||||||
const long request_timeout,
|
|
||||||
const HttpClient::RequestHandler& request_callback,
|
|
||||||
const HttpClient::ConnectHandler& connect_callback,
|
|
||||||
const HttpClient::HandshakeHandler& handshake_callback,
|
|
||||||
const HttpClient::CloseHandler& close_callback) {
|
|
||||||
// Find the connection list for the url
|
|
||||||
ConnectionListPtr url_connections;
|
|
||||||
auto it = conns_.find(url);
|
|
||||||
if (it != conns_.end()) {
|
|
||||||
url_connections = it->second;
|
|
||||||
} else {
|
|
||||||
// Doesn't exist yet, so create it.
|
|
||||||
url_connections.reset(new ConnectionList(url, max_url_connections_));
|
|
||||||
conns_[url] = url_connections;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, look for an idle connection.
|
|
||||||
ConnectionPtr connection = url_connections->getIdleConnection();
|
|
||||||
if (!connection) {
|
|
||||||
if (url_connections->full()) {
|
|
||||||
TOMS_TRACE_LOG("no idle connections, queue request");
|
|
||||||
// All connections busy, queue it.
|
|
||||||
queue_[url].push(RequestDescriptor(ConnectionPtr(), request, response,
|
|
||||||
request_timeout,
|
|
||||||
request_callback,
|
|
||||||
connect_callback,
|
|
||||||
handshake_callback,
|
|
||||||
close_callback));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Room to make another connection with this destination, so make one.
|
|
||||||
TOMS_TRACE_LOG("creating a new connection");
|
|
||||||
connection.reset(new Connection(io_service_, tls_context,
|
|
||||||
shared_from_this(), url));
|
|
||||||
url_connections->addConnection(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the connection to start the transaction.
|
|
||||||
TOMS_TRACE_LOG("doTransaction");
|
|
||||||
connection->doTransaction(request, response, request_timeout, request_callback,
|
|
||||||
connect_callback, handshake_callback, close_callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Closes a URL's connections and clears its queue
|
|
||||||
///
|
|
||||||
/// This method should be called in a thread safe context.
|
|
||||||
///
|
|
||||||
/// @param url URL for which connection should be closed.
|
|
||||||
void closeUrlConnectionsInternal(const Url& url) {
|
|
||||||
// Remove requests from the queue.
|
|
||||||
auto queue_it = queue_.find(url);
|
|
||||||
if (queue_it != queue_.end()) {
|
|
||||||
queue_.erase(queue_it);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close connections for the URL.
|
|
||||||
auto it = conns_.find(url);
|
|
||||||
if (it != conns_.end()) {
|
|
||||||
it->second->closeAllConnections();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Closes all connections for all URLs and removes associated
|
|
||||||
/// information from the connection pool.
|
|
||||||
///
|
|
||||||
/// This method should be called in a thread safe context.
|
|
||||||
void closeAllInternal() {
|
|
||||||
// Remove all requests from the queue.
|
|
||||||
queue_.clear();
|
|
||||||
|
|
||||||
// Close connections for each URL.
|
|
||||||
for (auto it = conns_.begin(); it != conns_.end(); ++it) {
|
|
||||||
it->second->closeAllConnections();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all of the connections.
|
|
||||||
conns_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Closes a connection if it has an out-of-band socket event
|
|
||||||
///
|
|
||||||
/// If the pool contains a connection using the given socket and that
|
|
||||||
/// connection is currently in a transaction the method returns as this
|
|
||||||
/// indicates a normal ready event. If the connection is not in an
|
|
||||||
/// ongoing transaction, then the connection is closed.
|
|
||||||
///
|
|
||||||
/// This is method is intended to be used to detect and clean up then
|
|
||||||
/// sockets that are marked ready outside of transactions. The most common
|
|
||||||
/// case is the other end of the socket being closed.
|
|
||||||
///
|
|
||||||
/// This method should be called in a thread safe context.
|
|
||||||
///
|
|
||||||
/// @param socket_fd socket descriptor to check
|
|
||||||
void closeIfOutOfBandInternal(int socket_fd) {
|
|
||||||
// First we look for a connection with the socket.
|
|
||||||
for (auto conns_it = conns_.begin(); conns_it != conns_.end();
|
|
||||||
++conns_it) {
|
|
||||||
|
|
||||||
ConnectionListPtr url_connections = conns_it->second;
|
|
||||||
ConnectionPtr connection = url_connections->findBySocketFd(socket_fd);
|
|
||||||
if (connection && connection->isTransactionOngoing()) {
|
|
||||||
// Matches but is in a transaction, all is well.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Socket has no transaction, so any ready event is
|
|
||||||
// out-of-band (other end probably closed), so
|
|
||||||
// let's close it. Note we do not remove any queued
|
|
||||||
// requests, as this might somehow be occurring in
|
|
||||||
// between them.
|
|
||||||
url_connections->closeConnection(connection);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Request descriptor holds parameters associated with the
|
|
||||||
/// particular request.
|
|
||||||
struct RequestDescriptor {
|
|
||||||
/// @brief Constructor.
|
|
||||||
///
|
|
||||||
/// @param conn Pointer to the connection.
|
|
||||||
/// @param request Pointer to the request to be sent.
|
|
||||||
/// @param response Pointer to the object into which the response will
|
|
||||||
/// be stored.
|
|
||||||
/// @param request_timeout Requested timeout for the transaction.
|
|
||||||
/// @param callback Pointer to the user callback.
|
|
||||||
/// @param connect_callback pointer to the user callback to be invoked
|
|
||||||
/// when the client connects to the server.
|
|
||||||
/// @param handshake_callback Optional callback invoked when the client
|
|
||||||
/// performs the TLS handshake with the server.
|
|
||||||
/// @param close_callback pointer to the user callback to be invoked
|
|
||||||
/// when the client closes the connection to the server.
|
|
||||||
RequestDescriptor(const ConnectionPtr& conn,
|
|
||||||
const HttpRequestPtr& request,
|
|
||||||
const HttpResponsePtr& response,
|
|
||||||
const long& request_timeout,
|
|
||||||
const HttpClient::RequestHandler& callback,
|
|
||||||
const HttpClient::ConnectHandler& connect_callback,
|
|
||||||
const HttpClient::HandshakeHandler& handshake_callback,
|
|
||||||
const HttpClient::CloseHandler& close_callback)
|
|
||||||
: conn_(conn), request_(request), response_(response),
|
|
||||||
request_timeout_(request_timeout), callback_(callback),
|
|
||||||
connect_callback_(connect_callback),
|
|
||||||
handshake_callback_(handshake_callback),
|
|
||||||
close_callback_(close_callback) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Holds the connection.
|
|
||||||
ConnectionPtr conn_;
|
|
||||||
|
|
||||||
/// @brief Holds pointer to the request.
|
|
||||||
HttpRequestPtr request_;
|
|
||||||
|
|
||||||
/// @brief Holds pointer to the response.
|
|
||||||
HttpResponsePtr response_;
|
|
||||||
|
|
||||||
/// @brief Holds requested timeout value.
|
|
||||||
long request_timeout_;
|
|
||||||
|
|
||||||
/// @brief Holds pointer to the user callback.
|
|
||||||
HttpClient::RequestHandler callback_;
|
|
||||||
|
|
||||||
/// @brief Holds pointer to the user callback for connect.
|
|
||||||
HttpClient::ConnectHandler connect_callback_;
|
|
||||||
|
|
||||||
/// @brief Holds pointer to the user callback for handshake.
|
|
||||||
HttpClient::HandshakeHandler handshake_callback_;
|
|
||||||
|
|
||||||
/// @brief Holds pointer to the user callback for close.
|
|
||||||
HttpClient::CloseHandler close_callback_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// @brief Pointer to a Destination.
|
||||||
|
typedef boost::shared_ptr<Destination> DestinationPtr;
|
||||||
|
|
||||||
|
/// @brief Creates a new destination for the given URL.
|
||||||
|
///
|
||||||
|
/// @param url URL of the new destination.
|
||||||
|
///
|
||||||
|
/// @return Pointer to the newly created destination.
|
||||||
|
/// @note Must be called from within a thread-safe context.
|
||||||
|
DestinationPtr addDestination(const Url& url) {
|
||||||
|
DestinationPtr destination(new Destination(url, max_url_connections_));
|
||||||
|
destinations_[url] = destination;
|
||||||
|
return(destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Fetches a destination by URL
|
||||||
|
///
|
||||||
|
/// @param url Url of the destination desired.
|
||||||
|
///
|
||||||
|
/// @return pointer the desired destination, empty pointer
|
||||||
|
/// if the destination does not exist.
|
||||||
|
DestinationPtr findDestination(const Url& url) const {
|
||||||
|
auto it = destinations_.find(url);
|
||||||
|
if (it != destinations_.end()) {
|
||||||
|
return (it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (DestinationPtr());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Removes a destination by URL
|
||||||
|
///
|
||||||
|
/// @param url Url of the destination to be removed.
|
||||||
|
/// @note Must be called from within a thread-safe context.
|
||||||
|
void removeDestination(const Url& url) {
|
||||||
|
// Remove requests from the queue.
|
||||||
|
auto it = destinations_.find(url);
|
||||||
|
if (it != destinations_.end()) {
|
||||||
|
it->second->closeAllConnections();
|
||||||
|
destinations_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief A reference to the IOService that drives socket IO.
|
/// @brief A reference to the IOService that drives socket IO.
|
||||||
IOService& io_service_;
|
IOService& io_service_;
|
||||||
|
|
||||||
/// @brief Holds lists of connections for each URL.
|
/// @brief Map of Destinations by URL.
|
||||||
std::map<Url, ConnectionListPtr> conns_;
|
std::map<Url, DestinationPtr> destinations_;
|
||||||
|
|
||||||
/// @brief Holds the queue of requests for each URL.
|
|
||||||
std::map<Url, std::queue<RequestDescriptor> > queue_;
|
|
||||||
|
|
||||||
/// @brief Mutex to protect the internal state.
|
/// @brief Mutex to protect the internal state.
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
|
// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
|
||||||
//
|
//
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
@@ -24,7 +24,7 @@ Url::Url(const std::string& url)
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
Url::operator<(const Url& url) const {
|
Url::operator<(const Url& url) const {
|
||||||
return (toText() < url.toText());
|
return (url_ < url.rawUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
Url::Scheme
|
Url::Scheme
|
||||||
|
@@ -83,6 +83,10 @@ public:
|
|||||||
/// @brief Returns textual representation of the URL.
|
/// @brief Returns textual representation of the URL.
|
||||||
std::string toText() const;
|
std::string toText() const;
|
||||||
|
|
||||||
|
const std::string& rawUrl() const {
|
||||||
|
return (url_);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/// @brief Returns boolean value indicating if the URL is valid.
|
/// @brief Returns boolean value indicating if the URL is valid.
|
||||||
|
Reference in New Issue
Block a user