/// @param request_timeout Request timeout in milliseconds.
/// @param callback Pointer to the callback function to be invoked when the
/// transaction completes.
+ /// @param connect_callback Pointer to the callback function to be invoked when
+ /// the client connects to the server.
void doTransaction(const HttpRequestPtr& request, const HttpResponsePtr& response,
- const long request_timeout, const HttpClient::RequestHandler& callback);
+ const long request_timeout, const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback);
/// @brief Closes the socket and cancels the request timer.
void close();
/// If the connection is successfully established, this callback will start
/// to asynchronously send the request over the socket.
///
- /// @param request_timeout Request timeout specified for this transaction.
+ /// @param Pointer to the callback to be invoked when client connects to
+ /// the server.
/// @param ec Error code being a result of the connection attempt.
- void connectCallback(const long request_timeout,
+ void connectCallback(HttpClient::ConnectHandler connect_callback,
const boost::system::error_code& ec);
/// @brief Local callback invoked when an attempt to send a portion of data
/// be stored.
/// @param request_timeout Requested timeout for the transaction.
/// @param callback Pointer to the user callback for this request.
+ /// @param connect_callback Pointer to the user callback invoked when
+ /// the client connects to the server.
///
/// @return true if the request for the given URL has been retrieved,
/// false if there are no more requests queued for this URL.
HttpRequestPtr& request,
HttpResponsePtr& response,
long& request_timeout,
- HttpClient::RequestHandler& callback) {
+ HttpClient::RequestHandler& callback,
+ HttpClient::ConnectHandler& connect_callback) {
// Check if there is a queue for this URL. If there is no queue, there
// is no request queued either.
auto it = queue_.find(url);
response = desc.response_;
request_timeout = desc.request_timeout_,
callback = desc.callback_;
+ connect_callback = desc.connect_callback_;
return (true);
}
}
/// stored.
/// @param request_timeout Requested timeout for the transaction in
/// milliseconds.
- /// @param callback Pointer to the user callback to be invoked when the
+ /// @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.
void queueRequest(const Url& url,
const HttpRequestPtr& request,
const HttpResponsePtr& response,
const long request_timeout,
- const HttpClient::RequestHandler& callback) {
+ const HttpClient::RequestHandler& request_callback,
+ const HttpClient::ConnectHandler& connect_callback) {
auto it = conns_.find(url);
if (it != conns_.end()) {
ConnectionPtr conn = it->second;
// Connection is busy, so let's queue the request.
queue_[url].push(RequestDescriptor(request, response,
request_timeout,
- callback));
+ request_callback,
+ connect_callback));
} else {
// Connection is idle, so we can start the transaction.
conn->doTransaction(request, response, request_timeout,
- callback);
+ request_callback, connect_callback);
}
} else {
// it and start the transaction.
ConnectionPtr conn(new Connection(io_service_, shared_from_this(),
url));
- conn->doTransaction(request, response, request_timeout, callback);
+ conn->doTransaction(request, response, request_timeout, request_callback,
+ connect_callback);
conns_[url] = conn;
}
}
/// 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.
RequestDescriptor(const HttpRequestPtr& request,
const HttpResponsePtr& response,
const long request_timeout,
- const HttpClient::RequestHandler& callback)
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback)
: request_(request), response_(response),
request_timeout_(request_timeout),
- callback_(callback) {
+ callback_(callback),
+ connect_callback_(connect_callback) {
}
/// @brief Holds pointer to the request.
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 the queue of requests for different URLs.
Connection::doTransaction(const HttpRequestPtr& request,
const HttpResponsePtr& response,
const long request_timeout,
- const HttpClient::RequestHandler& callback) {
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback) {
try {
current_request_ = request;
current_response_ = response;
.arg(HttpMessageParserBase::logFormatHttpMessage(request->toString(),
MAX_LOGGED_MESSAGE_SIZE));
+ // Setup request timer.
+ scheduleTimer(request_timeout);
+
/// @todo We're getting a hostname but in fact it is expected to be an IP address.
/// We should extend the TCPEndpoint to also accept names. Currently, it will fall
/// over for names.
TCPEndpoint endpoint(url_.getStrippedHostname(),
static_cast<unsigned short>(url_.getPort()));
SocketCallback socket_cb(boost::bind(&Connection::connectCallback, shared_from_this(),
- request_timeout, _1));
+ connect_callback, _1));
// Establish new connection or use existing connection.
socket_.open(&endpoint, socket_cb);
HttpRequestPtr request;
long request_timeout;
HttpClient::RequestHandler callback;
+ HttpClient::ConnectHandler connect_callback;
ConnectionPoolPtr conn_pool = conn_pool_.lock();
if (conn_pool && conn_pool->getNextRequest(url_, request, response, request_timeout,
- callback)) {
- doTransaction(request, response, request_timeout, callback);
+ callback, connect_callback)) {
+ doTransaction(request, response, request_timeout, callback, connect_callback);
}
}
}
void
-Connection::connectCallback(const long request_timeout, const boost::system::error_code& ec) {
+Connection::connectCallback(HttpClient::ConnectHandler connect_callback,
+ const boost::system::error_code& ec) {
+ // Run user defined connect callback if specified.
+ if (connect_callback) {
+ // If the user defined callback indicates that the connection
+ // should not be continued.
+ if (!connect_callback(ec)) {
+ return;
+ }
+ }
+
// In some cases the "in progress" status code may be returned. It doesn't
// indicate an error. Sending the request over the socket is expected to
// be successful. Getting such status appears to be highly dependent on
terminate(ec);
} else {
- // Setup request timer.
- scheduleTimer(request_timeout);
-
// Start sending the request asynchronously.
doSend();
}
void
HttpClient::asyncSendRequest(const Url& url, const HttpRequestPtr& request,
const HttpResponsePtr& response,
- const HttpClient::RequestHandler& callback,
- const HttpClient::RequestTimeout& request_timeout) {
+ const HttpClient::RequestHandler& request_callback,
+ const HttpClient::RequestTimeout& request_timeout,
+ const HttpClient::ConnectHandler& connect_callback) {
if (!url.isValid()) {
isc_throw(HttpClientError, "invalid URL specified for the HTTP client");
}
isc_throw(HttpClientError, "HTTP response must not be null");
}
- if (!callback) {
+ if (!request_callback) {
isc_throw(HttpClientError, "callback for HTTP transaction must not be null");
}
impl_->conn_pool_->queueRequest(url, request, response, request_timeout.value_,
- callback);
+ request_callback, connect_callback);
}
void
ASSERT_NO_THROW(runIOService());
}
+// Test that client times out when connection takes too long.
+TEST_F(HttpClientTest, clientConnectTimeout) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ unsigned cb_num = 0;
+
+ PostHttpRequestJsonPtr request = createRequest("sequence", 1);
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, request, response,
+ [this, &cb_num](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+
+ }, HttpClient::RequestTimeout(100),
+
+ // This callback is invoked upon an attempt to connect to the
+ // server. The false value indicates to the HttpClient to not
+ // try to send a request to the server. This simulates the
+ // case of connect() taking very long and should eventually
+ // cause the transaction to time out.
+ [](const boost::system::error_code& ec) {
+ return (false);
+ }));
+
+ // Create another request after the timeout. It should be handled ok.
+ ASSERT_NO_THROW(client.asyncSendRequest(url, request, response,
+ [this, &cb_num](const boost::system::error_code& /*ec*/, const HttpResponsePtr&,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+}
+
}