Kea  2.3.4-git
client.cc
Go to the documentation of this file.
1 // Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
12 #include <asiolink/tls_socket.h>
13 #include <http/client.h>
14 #include <http/http_log.h>
15 #include <http/http_messages.h>
16 #include <http/response_json.h>
17 #include <http/response_parser.h>
18 #include <util/boost_time_utils.h>
20 #include <util/unlock_guard.h>
21 
22 #include <boost/enable_shared_from_this.hpp>
23 #include <boost/weak_ptr.hpp>
24 
25 #include <atomic>
26 #include <array>
27 #include <functional>
28 #include <iostream>
29 #include <map>
30 #include <mutex>
31 #include <queue>
32 #include <thread>
33 
34 
35 using namespace isc;
36 using namespace isc::asiolink;
37 using namespace isc::http;
38 using namespace isc::util;
39 using namespace boost::posix_time;
40 
41 namespace ph = std::placeholders;
42 
43 namespace {
44 
48 constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
49 
51 typedef std::function<void(boost::system::error_code ec, size_t length)>
52 SocketCallbackFunction;
53 
59 class SocketCallback {
60 public:
61 
67  SocketCallback(SocketCallbackFunction socket_callback)
68  : callback_(socket_callback) {
69  }
70 
77  void operator()(boost::system::error_code ec, size_t length = 0) {
78  if (ec.value() == boost::asio::error::operation_aborted) {
79  return;
80  }
81  callback_(ec, length);
82  }
83 
84 private:
85 
87  SocketCallbackFunction callback_;
88 
89 };
90 
91 class ConnectionPool;
92 
94 typedef boost::shared_ptr<ConnectionPool> ConnectionPoolPtr;
95 
111 class Connection : public boost::enable_shared_from_this<Connection> {
112 public:
113 
121  explicit Connection(IOService& io_service,
122  const TlsContextPtr& tls_context,
123  const ConnectionPoolPtr& conn_pool,
124  const Url& url);
125 
127  ~Connection();
128 
147  void doTransaction(const HttpRequestPtr& request,
148  const HttpResponsePtr& response,
149  const long request_timeout,
150  const HttpClient::RequestHandler& callback,
151  const HttpClient::ConnectHandler& connect_callback,
152  const HttpClient::HandshakeHandler& handshake_callback,
153  const HttpClient::CloseHandler& close_callback);
154 
156  void close();
157 
161  bool isTransactionOngoing() const {
162  return (started_);
163  }
164 
168  bool isClosed() const {
169  return (closed_);
170  }
171 
176  void isClosedByPeer();
177 
183  bool isMySocket(int socket_fd) const;
184 
200  bool checkPrematureTimeout(const uint64_t transid);
201 
202 private:
203 
224  void doTransactionInternal(const HttpRequestPtr& request,
225  const HttpResponsePtr& response,
226  const long request_timeout,
227  const HttpClient::RequestHandler& callback,
228  const HttpClient::ConnectHandler& connect_callback,
229  const HttpClient::HandshakeHandler& handshake_callback,
230  const HttpClient::CloseHandler& close_callback);
231 
235  void closeInternal();
236 
243  void isClosedByPeerInternal();
244 
262  bool checkPrematureTimeoutInternal(const uint64_t transid);
263 
270  void resetState();
271 
281  void terminate(const boost::system::error_code& ec,
282  const std::string& parsing_error = "");
283 
295  void terminateInternal(const boost::system::error_code& ec,
296  const std::string& parsing_error = "");
297 
304  bool runParser(const boost::system::error_code& ec, size_t length);
305 
314  bool runParserInternal(const boost::system::error_code& ec, size_t length);
315 
319  void scheduleTimer(const long request_timeout);
320 
326  void doHandshake(const uint64_t transid);
327 
333  void doSend(const uint64_t transid);
334 
340  void doReceive(const uint64_t transid);
341 
352  void connectCallback(HttpClient::ConnectHandler connect_callback,
353  const uint64_t transid,
354  const boost::system::error_code& ec);
355 
365  void handshakeCallback(HttpClient::HandshakeHandler handshake_callback,
366  const uint64_t transid,
367  const boost::system::error_code& ec);
368 
379  void sendCallback(const uint64_t transid, const boost::system::error_code& ec,
380  size_t length);
381 
388  void receiveCallback(const uint64_t transid, const boost::system::error_code& ec,
389  size_t length);
390 
392  void timerCallback();
393 
403  void closeCallback(const bool clear = false);
404 
409  boost::weak_ptr<ConnectionPool> conn_pool_;
410 
412  Url url_;
413 
415  TlsContextPtr tls_context_;
416 
418  std::unique_ptr<TCPSocket<SocketCallback> > tcp_socket_;
419 
421  std::unique_ptr<TLSSocket<SocketCallback> > tls_socket_;
422 
424  IntervalTimer timer_;
425 
427  HttpRequestPtr current_request_;
428 
430  HttpResponsePtr current_response_;
431 
433  HttpResponseParserPtr parser_;
434 
436  HttpClient::RequestHandler current_callback_;
437 
439  std::string buf_;
440 
442  std::array<char, 32768> input_buf_;
443 
445  uint64_t current_transid_;
446 
448  HttpClient::HandshakeHandler handshake_callback_;
449 
451  HttpClient::CloseHandler close_callback_;
452 
454  std::atomic<bool> started_;
455 
457  std::atomic<bool> need_handshake_;
458 
460  std::atomic<bool> closed_;
461 
463  std::mutex mutex_;
464 };
465 
467 typedef boost::shared_ptr<Connection> ConnectionPtr;
468 
476 class ConnectionPool : public boost::enable_shared_from_this<ConnectionPool> {
477 public:
478 
485  explicit ConnectionPool(IOService& io_service, size_t max_url_connections)
486  : io_service_(io_service), destinations_(), pool_mutex_(),
487  max_url_connections_(max_url_connections) {
488  }
489 
493  ~ConnectionPool() {
494  closeAll();
495  }
496 
502  void processNextRequest(const Url& url, const TlsContextPtr& tls_context) {
503  if (MultiThreadingMgr::instance().getMode()) {
504  std::lock_guard<std::mutex> lk(pool_mutex_);
505  return (processNextRequestInternal(url, tls_context));
506  } else {
507  return (processNextRequestInternal(url, tls_context));
508  }
509  }
510 
516  void postProcessNextRequest(const Url& url,
517  const TlsContextPtr& tls_context) {
518  io_service_.post(std::bind(&ConnectionPool::processNextRequest,
519  shared_from_this(), url, tls_context));
520  }
521 
542  void queueRequest(const Url& url,
543  const TlsContextPtr& tls_context,
544  const HttpRequestPtr& request,
545  const HttpResponsePtr& response,
546  const long request_timeout,
547  const HttpClient::RequestHandler& request_callback,
548  const HttpClient::ConnectHandler& connect_callback,
549  const HttpClient::HandshakeHandler& handshake_callback,
550  const HttpClient::CloseHandler& close_callback) {
551  if (MultiThreadingMgr::instance().getMode()) {
552  std::lock_guard<std::mutex> lk(pool_mutex_);
553  return (queueRequestInternal(url, tls_context, request, response,
554  request_timeout, request_callback,
555  connect_callback, handshake_callback,
556  close_callback));
557  } else {
558  return (queueRequestInternal(url, tls_context, request, response,
559  request_timeout, request_callback,
560  connect_callback, handshake_callback,
561  close_callback));
562  }
563  }
564 
567  void closeAll() {
568  if (MultiThreadingMgr::instance().getMode()) {
569  std::lock_guard<std::mutex> lk(pool_mutex_);
570  closeAllInternal();
571  } else {
572  closeAllInternal();
573  }
574  }
575 
588  void closeIfOutOfBand(int socket_fd) {
589  if (MultiThreadingMgr::instance().getMode()) {
590  std::lock_guard<std::mutex> lk(pool_mutex_);
591  closeIfOutOfBandInternal(socket_fd);
592  } else {
593  closeIfOutOfBandInternal(socket_fd);
594  }
595  }
596 
597 private:
598 
606  void processNextRequestInternal(const Url& url,
607  const TlsContextPtr& tls_context) {
608  // Check if there is a queue for this URL. If there is no queue, there
609  // is no request queued either.
610  DestinationPtr destination = findDestination(url, tls_context);
611  if (destination) {
612  // Remove closed connections.
613  destination->garbageCollectConnections();
614  if (!destination->queueEmpty()) {
615  // We have at least one queued request. Do we have an
616  // idle connection?
617  ConnectionPtr connection = destination->getIdleConnection();
618  if (!connection) {
619  // No idle connections.
620  if (destination->connectionsFull()) {
621  return;
622  }
623  // Room to make another connection with this destination,
624  // so make one.
625  connection.reset(new Connection(io_service_, tls_context,
626  shared_from_this(), url));
627  destination->addConnection(connection);
628  }
629 
630  // Dequeue the oldest request and start a transaction for it using
631  // the idle connection.
632  RequestDescriptor desc = destination->popNextRequest();
633  connection->doTransaction(desc.request_, desc.response_,
634  desc.request_timeout_, desc.callback_,
635  desc.connect_callback_,
636  desc.handshake_callback_,
637  desc.close_callback_);
638  }
639  }
640  }
641 
664  void queueRequestInternal(const Url& url,
665  const TlsContextPtr& tls_context,
666  const HttpRequestPtr& request,
667  const HttpResponsePtr& response,
668  const long request_timeout,
669  const HttpClient::RequestHandler& request_callback,
670  const HttpClient::ConnectHandler& connect_callback,
671  const HttpClient::HandshakeHandler& handshake_callback,
672  const HttpClient::CloseHandler& close_callback) {
673  ConnectionPtr connection;
674  // Find the destination for the requested URL.
675  DestinationPtr destination = findDestination(url, tls_context);
676  if (destination) {
677  // Remove closed connections.
678  destination->garbageCollectConnections();
679  // Found it, look for an idle connection.
680  connection = destination->getIdleConnection();
681  } else {
682  // Doesn't exist yet so it's a new destination.
683  destination = addDestination(url, tls_context);
684  }
685 
686  if (!connection) {
687  if (destination->connectionsFull()) {
688  // All connections busy, queue it.
689  destination->pushRequest(RequestDescriptor(request, response,
690  request_timeout,
691  request_callback,
692  connect_callback,
693  handshake_callback,
694  close_callback));
695  return;
696  }
697 
698  // Room to make another connection with this destination, so make one.
699  connection.reset(new Connection(io_service_, tls_context,
700  shared_from_this(), url));
701  destination->addConnection(connection);
702  }
703 
704  // Use the connection to start the transaction.
705  connection->doTransaction(request, response, request_timeout, request_callback,
706  connect_callback, handshake_callback, close_callback);
707  }
708 
713  void closeAllInternal() {
714  for (auto const& destination : destinations_) {
715  destination.second->closeAllConnections();
716  }
717 
718  destinations_.clear();
719  }
720 
735  void closeIfOutOfBandInternal(int socket_fd) {
736  for (auto const& destination : destinations_) {
737  // First we look for a connection with the socket.
738  ConnectionPtr connection = destination.second->findBySocketFd(socket_fd);
739  if (connection) {
740  if (!connection->isTransactionOngoing()) {
741  // Socket has no transaction, so any ready event is
742  // out-of-band (other end probably closed), so
743  // let's close it. Note we do not remove any queued
744  // requests, as this might somehow be occurring in
745  // between them.
746  destination.second->closeConnection(connection);
747  }
748 
749  return;
750  }
751  }
752  }
753 
756  struct RequestDescriptor {
770  RequestDescriptor(const HttpRequestPtr& request,
771  const HttpResponsePtr& response,
772  const long& request_timeout,
773  const HttpClient::RequestHandler& callback,
774  const HttpClient::ConnectHandler& connect_callback,
775  const HttpClient::HandshakeHandler& handshake_callback,
776  const HttpClient::CloseHandler& close_callback)
777  : request_(request), response_(response),
778  request_timeout_(request_timeout), callback_(callback),
779  connect_callback_(connect_callback),
780  handshake_callback_(handshake_callback),
781  close_callback_(close_callback) {
782  }
783 
785  HttpRequestPtr request_;
786 
788  HttpResponsePtr response_;
789 
791  long request_timeout_;
792 
794  HttpClient::RequestHandler callback_;
795 
797  HttpClient::ConnectHandler connect_callback_;
798 
800  HttpClient::HandshakeHandler handshake_callback_;
801 
803  HttpClient::CloseHandler close_callback_;
804  };
805 
807  typedef std::pair<Url, TlsContextPtr> DestinationDescriptor;
808 
810  class Destination {
811  public:
813  const size_t QUEUE_SIZE_THRESHOLD = 2048;
815  const int QUEUE_WARN_SECS = 5;
816 
823  Destination(Url url, TlsContextPtr tls_context, size_t max_connections)
824  : url_(url), tls_context_(tls_context),
825  max_connections_(max_connections), connections_(), queue_(),
826  last_queue_warn_time_(min_date_time), last_queue_size_(0) {
827  }
828 
830  ~Destination() {
831  closeAllConnections();
832  }
833 
841  void addConnection(ConnectionPtr connection) {
842  if (connectionsFull()) {
843  isc_throw(BadValue, "URL: " << url_.toText()
844  << ", already at maximum connections: "
845  << max_connections_);
846  }
847 
848  connections_.push_back(connection);
849  }
850 
855  void closeConnection(ConnectionPtr connection) {
856  for (auto it = connections_.begin(); it != connections_.end(); ++it) {
857  if (*it == connection) {
858  (*it)->close();
859  connections_.erase(it);
860  break;
861  }
862  }
863  }
864 
867  void closeAllConnections() {
868  // Flush the queue.
869  while (!queue_.empty()) {
870  queue_.pop();
871  }
872 
873  for (auto const& connection : connections_) {
874  connection->close();
875  }
876 
877  connections_.clear();
878  }
879 
903  void garbageCollectConnections() {
904  for (auto it = connections_.begin(); it != connections_.end();) {
905  (*it)->isClosedByPeer();
906  if (!(*it)->isClosed()) {
907  ++it;
908  } else {
909  it = connections_.erase(it);
910  }
911  }
912  }
913 
925  ConnectionPtr getIdleConnection() {
926  for (auto const& connection : connections_) {
927  if (!connection->isTransactionOngoing() &&
928  !connection->isClosed()) {
929  return (connection);
930  }
931  }
932 
933  return (ConnectionPtr());
934  }
935 
942  ConnectionPtr findBySocketFd(int socket_fd) {
943  for (auto const& connection : connections_) {
944  if (connection->isMySocket(socket_fd)) {
945  return (connection);
946  }
947  }
948 
949  return (ConnectionPtr());
950  }
951 
955  bool connectionsEmpty() {
956  return (connections_.empty());
957  }
958 
962  bool connectionsFull() {
963  return (connections_.size() >= max_connections_);
964  }
965 
969  size_t connectionCount() {
970  return (connections_.size());
971  }
972 
976  size_t getMaxConnections() const {
977  return (max_connections_);
978  }
979 
983  bool queueEmpty() const {
984  return (queue_.empty());
985  }
986 
993  void pushRequest(RequestDescriptor desc) {
994  queue_.push(desc);
995  size_t size = queue_.size();
996  // If the queue size is larger than the threshold and growing, issue a
997  // periodic warning.
998  if ((size > QUEUE_SIZE_THRESHOLD) && (size > last_queue_size_)) {
999  ptime now = microsec_clock::universal_time();
1000  if ((now - last_queue_warn_time_) > seconds(QUEUE_WARN_SECS)) {
1002  .arg(url_.toText())
1003  .arg(size);
1004  // Remember the last time we warned.
1005  last_queue_warn_time_ = now;
1006  }
1007  }
1008 
1009  // Remember the previous size.
1010  last_queue_size_ = size;
1011  }
1012 
1016  RequestDescriptor popNextRequest() {
1017  if (queue_.empty()) {
1018  isc_throw(InvalidOperation, "cannot pop, queue is empty");
1019  }
1020 
1021  RequestDescriptor desc = queue_.front();
1022  queue_.pop();
1023  return (desc);
1024  }
1025 
1026  private:
1028  Url url_;
1029 
1031  TlsContextPtr tls_context_;
1032 
1034  size_t max_connections_;
1035 
1037  std::list<ConnectionPtr> connections_;
1038 
1040  std::queue<RequestDescriptor> queue_;
1041 
1043  ptime last_queue_warn_time_;
1044 
1046  size_t last_queue_size_;
1047  };
1048 
1050  typedef boost::shared_ptr<Destination> DestinationPtr;
1051 
1059  DestinationPtr addDestination(const Url& url,
1060  const TlsContextPtr& tls_context) {
1061  const DestinationDescriptor& desc = std::make_pair(url, tls_context);
1062  DestinationPtr destination(new Destination(url, tls_context,
1063  max_url_connections_));
1064  destinations_[desc] = destination;
1065  return (destination);
1066  }
1067 
1076  DestinationPtr findDestination(const Url& url,
1077  const TlsContextPtr& tls_context) const {
1078  const DestinationDescriptor& desc = std::make_pair(url, tls_context);
1079  auto it = destinations_.find(desc);
1080  if (it != destinations_.end()) {
1081  return (it->second);
1082  }
1083 
1084  return (DestinationPtr());
1085  }
1086 
1098  void removeDestination(const Url& url,
1099  const TlsContextPtr& tls_context) {
1100  const DestinationDescriptor& desc = std::make_pair(url, tls_context);
1101  auto it = destinations_.find(desc);
1102  if (it != destinations_.end()) {
1103  it->second->closeAllConnections();
1104  destinations_.erase(it);
1105  }
1106  }
1107 
1109  IOService& io_service_;
1110 
1112  std::map<DestinationDescriptor, DestinationPtr> destinations_;
1113 
1115  std::mutex pool_mutex_;
1116 
1118  size_t max_url_connections_;
1119 };
1120 
1121 Connection::Connection(IOService& io_service,
1122  const TlsContextPtr& tls_context,
1123  const ConnectionPoolPtr& conn_pool,
1124  const Url& url)
1125  : conn_pool_(conn_pool), url_(url), tls_context_(tls_context),
1126  tcp_socket_(), tls_socket_(), timer_(io_service),
1127  current_request_(), current_response_(), parser_(),
1128  current_callback_(), buf_(), input_buf_(), current_transid_(0),
1129  close_callback_(), started_(false), need_handshake_(false),
1130  closed_(false) {
1131  if (!tls_context) {
1132  tcp_socket_.reset(new asiolink::TCPSocket<SocketCallback>(io_service));
1133  } else {
1134  tls_socket_.reset(new asiolink::TLSSocket<SocketCallback>(io_service,
1135  tls_context));
1136  need_handshake_ = true;
1137  }
1138 }
1139 
1140 Connection::~Connection() {
1141  close();
1142 }
1143 
1144 void
1145 Connection::resetState() {
1146  started_ = false;
1147  current_request_.reset();
1148  current_response_.reset();
1149  parser_.reset();
1150  current_callback_ = HttpClient::RequestHandler();
1151 }
1152 
1153 void
1154 Connection::closeCallback(const bool clear) {
1155  if (close_callback_) {
1156  try {
1157  if (tcp_socket_) {
1158  close_callback_(tcp_socket_->getNative());
1159  } else if (tls_socket_) {
1160  close_callback_(tls_socket_->getNative());
1161  } else {
1163  "internal error: can't find a socket to close");
1164  }
1165  } catch (...) {
1167  }
1168  }
1169 
1170  if (clear) {
1171  close_callback_ = HttpClient::CloseHandler();
1172  }
1173 }
1174 
1175 void
1176 Connection::isClosedByPeer() {
1177  // This method applies only to idle connections.
1178  if (started_ || closed_) {
1179  return;
1180  }
1181  // This code was guarded by a lock so keep this.
1182  if (MultiThreadingMgr::instance().getMode()) {
1183  std::lock_guard<std::mutex> lk(mutex_);
1184  isClosedByPeerInternal();
1185  } else {
1186  isClosedByPeerInternal();
1187  }
1188 }
1189 
1190 void
1191 Connection::isClosedByPeerInternal() {
1192  // If the socket is open we check if it is possible to transmit
1193  // the data over this socket by reading from it with message
1194  // peeking. If the socket is not usable, we close it and then
1195  // re-open it. There is a narrow window of time between checking
1196  // the socket usability and actually transmitting the data over
1197  // this socket, when the peer may close the connection. In this
1198  // case we'll need to re-transmit but we don't handle it here.
1199  if (tcp_socket_) {
1200  if (tcp_socket_->getASIOSocket().is_open() &&
1201  !tcp_socket_->isUsable()) {
1202  closeCallback();
1203  closed_ = true;
1204  tcp_socket_->close();
1205  }
1206  } else if (tls_socket_) {
1207  if (tls_socket_->getASIOSocket().is_open() &&
1208  !tls_socket_->isUsable()) {
1209  closeCallback();
1210  closed_ = true;
1211  tls_socket_->close();
1212  }
1213  } else {
1214  isc_throw(Unexpected, "internal error: can't find the sending socket");
1215  }
1216 }
1217 
1218 void
1219 Connection::doTransaction(const HttpRequestPtr& request,
1220  const HttpResponsePtr& response,
1221  const long request_timeout,
1222  const HttpClient::RequestHandler& callback,
1223  const HttpClient::ConnectHandler& connect_callback,
1224  const HttpClient::HandshakeHandler& handshake_callback,
1225  const HttpClient::CloseHandler& close_callback) {
1226  if (MultiThreadingMgr::instance().getMode()) {
1227  std::lock_guard<std::mutex> lk(mutex_);
1228  doTransactionInternal(request, response, request_timeout,
1229  callback, connect_callback, handshake_callback,
1230  close_callback);
1231  } else {
1232  doTransactionInternal(request, response, request_timeout,
1233  callback, connect_callback, handshake_callback,
1234  close_callback);
1235  }
1236 }
1237 
1238 void
1239 Connection::doTransactionInternal(const HttpRequestPtr& request,
1240  const HttpResponsePtr& response,
1241  const long request_timeout,
1242  const HttpClient::RequestHandler& callback,
1243  const HttpClient::ConnectHandler& connect_callback,
1244  const HttpClient::HandshakeHandler& handshake_callback,
1245  const HttpClient::CloseHandler& close_callback) {
1246  try {
1247  started_ = true;
1248  current_request_ = request;
1249  current_response_ = response;
1250  parser_.reset(new HttpResponseParser(*current_response_));
1251  parser_->initModel();
1252  current_callback_ = callback;
1253  handshake_callback_ = handshake_callback;
1254  close_callback_ = close_callback;
1255 
1256  // Starting new transaction. Generate new transaction id.
1257  ++current_transid_;
1258 
1259  buf_ = request->toString();
1260 
1263  .arg(request->toBriefString())
1264  .arg(url_.toText());
1265 
1268  .arg(url_.toText())
1269  .arg(HttpMessageParserBase::logFormatHttpMessage(request->toString(),
1270  MAX_LOGGED_MESSAGE_SIZE));
1271 
1272  // Setup request timer.
1273  scheduleTimer(request_timeout);
1274 
1278  TCPEndpoint endpoint(url_.getStrippedHostname(),
1279  static_cast<unsigned short>(url_.getPort()));
1280  SocketCallback socket_cb(std::bind(&Connection::connectCallback, shared_from_this(),
1281  connect_callback, current_transid_,
1282  ph::_1));
1283 
1284  // Establish new connection or use existing connection.
1285  if (tcp_socket_) {
1286  tcp_socket_->open(&endpoint, socket_cb);
1287  return;
1288  }
1289  if (tls_socket_) {
1290  tls_socket_->open(&endpoint, socket_cb);
1291  return;
1292  }
1293 
1294  // Should never reach this point.
1295  isc_throw(Unexpected, "internal error: can't find a socket to open");
1296 
1297  } catch (const std::exception& ex) {
1298  // Re-throw with the expected exception type.
1300  }
1301 }
1302 
1303 void
1304 Connection::close() {
1305  if (MultiThreadingMgr::instance().getMode()) {
1306  std::lock_guard<std::mutex> lk(mutex_);
1307  return (closeInternal());
1308  } else {
1309  return (closeInternal());
1310  }
1311 }
1312 
1313 void
1314 Connection::closeInternal() {
1315  // Pass in true to discard the callback.
1316  closeCallback(true);
1317 
1318  closed_ = true;
1319  timer_.cancel();
1320  if (tcp_socket_) {
1321  tcp_socket_->close();
1322  }
1323  if (tls_socket_) {
1324  tls_socket_->close();
1325  }
1326 
1327  resetState();
1328 }
1329 
1330 bool
1331 Connection::isMySocket(int socket_fd) const {
1332  if (tcp_socket_) {
1333  return (tcp_socket_->getNative() == socket_fd);
1334  } else if (tls_socket_) {
1335  return (tls_socket_->getNative() == socket_fd);
1336  }
1337  // Should never reach this point.
1338  std::cerr << "internal error: can't find my socket\n";
1339  return (false);
1340 }
1341 
1342 bool
1343 Connection::checkPrematureTimeout(const uint64_t transid) {
1344  if (MultiThreadingMgr::instance().getMode()) {
1345  std::lock_guard<std::mutex> lk(mutex_);
1346  return (checkPrematureTimeoutInternal(transid));
1347  } else {
1348  return (checkPrematureTimeoutInternal(transid));
1349  }
1350 }
1351 
1352 bool
1353 Connection::checkPrematureTimeoutInternal(const uint64_t transid) {
1354  // If there is no transaction but the handlers are invoked it means
1355  // that the last transaction in the queue timed out prematurely.
1356  // Also, if there is a transaction in progress but the ID of that
1357  // transaction doesn't match the one associated with the handler it,
1358  // also means that the transaction timed out prematurely.
1359  if (!isTransactionOngoing() || (transid != current_transid_)) {
1361  .arg(isTransactionOngoing())
1362  .arg(transid)
1363  .arg(current_transid_);
1364  return (true);
1365  }
1366 
1367  return (false);
1368 }
1369 
1370 void
1371 Connection::terminate(const boost::system::error_code& ec,
1372  const std::string& parsing_error) {
1373  if (MultiThreadingMgr::instance().getMode()) {
1374  std::lock_guard<std::mutex> lk(mutex_);
1375  terminateInternal(ec, parsing_error);
1376  } else {
1377  terminateInternal(ec, parsing_error);
1378  }
1379 }
1380 
1381 void
1382 Connection::terminateInternal(const boost::system::error_code& ec,
1383  const std::string& parsing_error) {
1384  HttpResponsePtr response;
1385  if (isTransactionOngoing()) {
1386 
1387  timer_.cancel();
1388  if (tcp_socket_) {
1389  tcp_socket_->cancel();
1390  }
1391  if (tls_socket_) {
1392  tls_socket_->cancel();
1393  }
1394 
1395  if (!ec && current_response_->isFinalized()) {
1396  response = current_response_;
1397 
1400  .arg(url_.toText());
1401 
1404  .arg(url_.toText())
1405  .arg(parser_ ?
1406  parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE) :
1407  "[HttpResponseParser is null]");
1408 
1409  } else {
1410  std::string err = parsing_error.empty() ? ec.message() :
1411  parsing_error;
1412 
1415  .arg(url_.toText())
1416  .arg(err);
1417 
1418  // Only log the details if we have received anything and tried
1419  // to parse it.
1420  if (!parsing_error.empty()) {
1423  .arg(url_.toText())
1424  .arg(parser_ ?
1425  parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE) :
1426  "[HttpResponseParser is null]");
1427  }
1428  }
1429 
1430  try {
1431  // The callback should take care of its own exceptions but one
1432  // never knows.
1433  if (MultiThreadingMgr::instance().getMode()) {
1434  UnlockGuard<std::mutex> lock(mutex_);
1435  current_callback_(ec, response, parsing_error);
1436  } else {
1437  current_callback_(ec, response, parsing_error);
1438  }
1439  } catch (...) {
1440  }
1441 
1442  // If we're not requesting connection persistence or the
1443  // connection has timed out, we should close the socket.
1444  if (!closed_ &&
1445  (!current_request_->isPersistent() ||
1446  (ec == boost::asio::error::timed_out))) {
1447  closeInternal();
1448  }
1449 
1450  resetState();
1451  }
1452 
1453  // Check if there are any requests queued for this destination and start
1454  // another transaction if there is at least one.
1455  ConnectionPoolPtr conn_pool = conn_pool_.lock();
1456  if (conn_pool) {
1457  conn_pool->postProcessNextRequest(url_, tls_context_);
1458  }
1459 }
1460 
1461 void
1462 Connection::scheduleTimer(const long request_timeout) {
1463  if (request_timeout > 0) {
1464  timer_.setup(std::bind(&Connection::timerCallback, this), request_timeout,
1466  }
1467 }
1468 
1469 void
1470 Connection::doHandshake(const uint64_t transid) {
1471  // Skip the handshake if it is not needed.
1472  if (!need_handshake_) {
1473  doSend(transid);
1474  return;
1475  }
1476 
1477  SocketCallback socket_cb(std::bind(&Connection::handshakeCallback,
1478  shared_from_this(),
1479  handshake_callback_,
1480  transid,
1481  ph::_1));
1482  try {
1483  tls_socket_->handshake(socket_cb);
1484 
1485  } catch (...) {
1486  terminate(boost::asio::error::not_connected);
1487  }
1488 }
1489 
1490 void
1491 Connection::doSend(const uint64_t transid) {
1492  SocketCallback socket_cb(std::bind(&Connection::sendCallback,
1493  shared_from_this(),
1494  transid,
1495  ph::_1,
1496  ph::_2));
1497  try {
1498  if (tcp_socket_) {
1499  tcp_socket_->asyncSend(&buf_[0], buf_.size(), socket_cb);
1500  return;
1501  }
1502 
1503  if (tls_socket_) {
1504  tls_socket_->asyncSend(&buf_[0], buf_.size(), socket_cb);
1505  return;
1506  }
1507 
1508  // Should never reach this point.
1509  std::cerr << "internal error: can't find a socket to send to\n";
1511  "internal error: can't find a socket to send to");
1512  } catch (...) {
1513  terminate(boost::asio::error::not_connected);
1514  }
1515 }
1516 
1517 void
1518 Connection::doReceive(const uint64_t transid) {
1519  TCPEndpoint endpoint;
1520  SocketCallback socket_cb(std::bind(&Connection::receiveCallback,
1521  shared_from_this(),
1522  transid,
1523  ph::_1,
1524  ph::_2));
1525  try {
1526  if (tcp_socket_) {
1527  tcp_socket_->asyncReceive(static_cast<void*>(input_buf_.data()),
1528  input_buf_.size(), 0,
1529  &endpoint, socket_cb);
1530  return;
1531  }
1532  if (tls_socket_) {
1533  tls_socket_->asyncReceive(static_cast<void*>(input_buf_.data()),
1534  input_buf_.size(), 0,
1535  &endpoint, socket_cb);
1536  return;
1537  }
1538  // Should never reach this point.
1539  std::cerr << "internal error: can't find a socket to receive from\n";
1541  "internal error: can't find a socket to receive from");
1542 
1543  } catch (...) {
1544  terminate(boost::asio::error::not_connected);
1545  }
1546 }
1547 
1548 void
1549 Connection::connectCallback(HttpClient::ConnectHandler connect_callback,
1550  const uint64_t transid,
1551  const boost::system::error_code& ec) {
1552  if (checkPrematureTimeout(transid)) {
1553  return;
1554  }
1555 
1556  // Run user defined connect callback if specified.
1557  if (connect_callback) {
1558  // If the user defined callback indicates that the connection
1559  // should not be continued.
1560  if (tcp_socket_) {
1561  if (!connect_callback(ec, tcp_socket_->getNative())) {
1562  return;
1563  }
1564  } else if (tls_socket_) {
1565  if (!connect_callback(ec, tls_socket_->getNative())) {
1566  return;
1567  }
1568  } else {
1569  // Should never reach this point.
1570  std::cerr << "internal error: can't find a socket to connect\n";
1571  }
1572  }
1573 
1574  if (ec && (ec.value() == boost::asio::error::operation_aborted)) {
1575  return;
1576 
1577  // In some cases the "in progress" status code may be returned. It doesn't
1578  // indicate an error. Sending the request over the socket is expected to
1579  // be successful. Getting such status appears to be highly dependent on
1580  // the operating system.
1581  } else if (ec &&
1582  (ec.value() != boost::asio::error::in_progress) &&
1583  (ec.value() != boost::asio::error::already_connected)) {
1584  terminate(ec);
1585 
1586  } else {
1587  // Start the TLS handshake asynchronously.
1588  doHandshake(transid);
1589  }
1590 }
1591 
1592 void
1593 Connection::handshakeCallback(HttpClient::ConnectHandler handshake_callback,
1594  const uint64_t transid,
1595  const boost::system::error_code& ec) {
1596  need_handshake_ = false;
1597  if (checkPrematureTimeout(transid)) {
1598  return;
1599  }
1600 
1601  // Run user defined handshake callback if specified.
1602  if (handshake_callback) {
1603  // If the user defined callback indicates that the connection
1604  // should not be continued.
1605  if (tls_socket_) {
1606  if (!handshake_callback(ec, tls_socket_->getNative())) {
1607  return;
1608  }
1609  } else {
1610  // Should never reach this point.
1611  std::cerr << "internal error: can't find TLS socket\n";
1612  }
1613  }
1614 
1615  if (ec && (ec.value() == boost::asio::error::operation_aborted)) {
1616  return;
1617  } else if (ec) {
1618  terminate(ec);
1619 
1620  } else {
1621  // Start sending the request asynchronously.
1622  doSend(transid);
1623  }
1624 }
1625 
1626 void
1627 Connection::sendCallback(const uint64_t transid,
1628  const boost::system::error_code& ec,
1629  size_t length) {
1630  if (checkPrematureTimeout(transid)) {
1631  return;
1632  }
1633 
1634  if (ec) {
1635  if (ec.value() == boost::asio::error::operation_aborted) {
1636  return;
1637 
1638  // EAGAIN and EWOULDBLOCK don't really indicate an error. The length
1639  // should be 0 in this case but let's be sure.
1640  } else if ((ec.value() == boost::asio::error::would_block) ||
1641  (ec.value() == boost::asio::error::try_again)) {
1642  length = 0;
1643 
1644  } else {
1645  // Any other error should cause the transaction to terminate.
1646  terminate(ec);
1647  return;
1648  }
1649  }
1650 
1651  // Sending is in progress, so push back the timeout.
1652  scheduleTimer(timer_.getInterval());
1653 
1654  // If any data have been sent, remove it from the buffer and only leave the
1655  // portion that still has to be sent.
1656  if (length > 0) {
1657  buf_.erase(0, length);
1658  }
1659 
1660  // If there is no more data to be sent, start receiving a response. Otherwise,
1661  // continue sending.
1662  if (buf_.empty()) {
1663  doReceive(transid);
1664 
1665  } else {
1666  doSend(transid);
1667  }
1668 }
1669 
1670 void
1671 Connection::receiveCallback(const uint64_t transid,
1672  const boost::system::error_code& ec,
1673  size_t length) {
1674  if (checkPrematureTimeout(transid)) {
1675  return;
1676  }
1677 
1678  if (ec) {
1679  if (ec.value() == boost::asio::error::operation_aborted) {
1680  return;
1681 
1682  // EAGAIN and EWOULDBLOCK don't indicate an error in this case. All
1683  // other errors should terminate the transaction.
1684  }
1685  if ((ec.value() != boost::asio::error::try_again) &&
1686  (ec.value() != boost::asio::error::would_block)) {
1687  terminate(ec);
1688  return;
1689 
1690  } else {
1691  // For EAGAIN and EWOULDBLOCK the length should be 0 anyway, but let's
1692  // make sure.
1693  length = 0;
1694  }
1695  }
1696 
1697  // Receiving is in progress, so push back the timeout.
1698  scheduleTimer(timer_.getInterval());
1699 
1700  if (runParser(ec, length)) {
1701  doReceive(transid);
1702  }
1703 }
1704 
1705 bool
1706 Connection::runParser(const boost::system::error_code& ec, size_t length) {
1707  if (MultiThreadingMgr::instance().getMode()) {
1708  std::lock_guard<std::mutex> lk(mutex_);
1709  return (runParserInternal(ec, length));
1710  } else {
1711  return (runParserInternal(ec, length));
1712  }
1713 }
1714 
1715 bool
1716 Connection::runParserInternal(const boost::system::error_code& ec,
1717  size_t length) {
1718  // If we have received any data, let's feed the parser with it.
1719  if (length != 0) {
1720  parser_->postBuffer(static_cast<void*>(input_buf_.data()), length);
1721  parser_->poll();
1722  }
1723 
1724  // If the parser still needs data, let's schedule another receive.
1725  if (parser_->needData()) {
1726  return (true);
1727 
1728  } else if (parser_->httpParseOk()) {
1729  // No more data needed and parsing has been successful so far. Let's
1730  // try to finalize the response parsing.
1731  try {
1732  current_response_->finalize();
1733  terminateInternal(ec);
1734 
1735  } catch (const std::exception& ex) {
1736  // If there is an error here, we need to return the error message.
1737  terminateInternal(ec, ex.what());
1738  }
1739 
1740  } else {
1741  // Parsing was unsuccessful. Let's pass the error message held in the
1742  // parser.
1743  terminateInternal(ec, parser_->getErrorMessage());
1744  }
1745 
1746  return (false);
1747 }
1748 
1749 void
1750 Connection::timerCallback() {
1751  // Request timeout occurred.
1752  terminate(boost::asio::error::timed_out);
1753 }
1754 
1755 }
1756 
1757 namespace isc {
1758 namespace http {
1759 
1762 public:
1786  HttpClientImpl(IOService& io_service, size_t thread_pool_size = 0,
1787  bool defer_thread_start = false)
1788  : thread_pool_size_(thread_pool_size), thread_pool_() {
1789  if (thread_pool_size_ > 0) {
1790  // Create our own private IOService.
1791  thread_io_service_.reset(new IOService());
1792 
1793  // Create the thread pool.
1794  thread_pool_.reset(new IoServiceThreadPool(thread_io_service_, thread_pool_size_,
1795  defer_thread_start));
1796 
1797  // Create the connection pool. Note that we use the thread_pool_size
1798  // as the maximum connections per URL value.
1799  conn_pool_.reset(new ConnectionPool(*thread_io_service_, thread_pool_size_));
1800 
1802  .arg(thread_pool_size_);
1803  } else {
1804  // Single-threaded mode: use the caller's IOService,
1805  // one connection per URL.
1806  conn_pool_.reset(new ConnectionPool(io_service, 1));
1807  }
1808  }
1809 
1814  stop();
1815  }
1816 
1823  if (thread_pool_) {
1824  thread_pool_->checkPausePermissions();
1825  }
1826  }
1827 
1829  void start() {
1830  if (thread_pool_) {
1831  thread_pool_->run();
1832  }
1833  }
1834 
1837  void stop() {
1838  // Close all the connections.
1839  conn_pool_->closeAll();
1840 
1841  // Stop the thread pool.
1842  if (thread_pool_) {
1843  thread_pool_->stop();
1844  }
1845  }
1846 
1851  void pause() {
1852  if (!thread_pool_) {
1853  isc_throw(InvalidOperation, "HttpClient::pause - no thread pool");
1854  }
1855 
1856  // Pause the thread pool.
1857  thread_pool_->pause();
1858  }
1859 
1864  void resume() {
1865  if (!thread_pool_) {
1866  isc_throw(InvalidOperation, "HttpClient::resume - no thread pool");
1867  }
1868 
1869  // Resume running the thread pool.
1870  thread_pool_->run();
1871  }
1872 
1877  bool isRunning() {
1878  if (thread_pool_) {
1879  return (thread_pool_->isRunning());
1880  }
1881 
1882  return (false);
1883  }
1884 
1889  bool isStopped() {
1890  if (thread_pool_) {
1891  return (thread_pool_->isStopped());
1892  }
1893 
1894  return (false);
1895  }
1896 
1901  bool isPaused() {
1902  if (thread_pool_) {
1903  return (thread_pool_->isPaused());
1904  }
1905 
1906  return (false);
1907  }
1908 
1914  return (thread_io_service_);
1915  };
1916 
1920  uint16_t getThreadPoolSize() {
1921  return (thread_pool_size_);
1922  }
1923 
1927  uint16_t getThreadCount() {
1928  if (!thread_pool_) {
1929  return (0);
1930  }
1931  return (thread_pool_->getThreadCount());
1932  }
1933 
1935  ConnectionPoolPtr conn_pool_;
1936 
1937 private:
1938 
1940  size_t thread_pool_size_;
1941 
1943  asiolink::IOServicePtr thread_io_service_;
1944 
1947  IoServiceThreadPoolPtr thread_pool_;
1948 };
1949 
1950 HttpClient::HttpClient(IOService& io_service, size_t thread_pool_size,
1951  bool defer_thread_start /* = false */) {
1952  if (thread_pool_size > 0) {
1953  if (!MultiThreadingMgr::instance().getMode()) {
1955  "HttpClient thread_pool_size must be zero"
1956  "when Kea core multi-threading is disabled");
1957  }
1958  }
1959 
1960  impl_.reset(new HttpClientImpl(io_service, thread_pool_size,
1961  defer_thread_start));
1962 }
1963 
1964 HttpClient::~HttpClient() {
1965 }
1966 
1967 void
1968 HttpClient::asyncSendRequest(const Url& url,
1969  const TlsContextPtr& tls_context,
1970  const HttpRequestPtr& request,
1971  const HttpResponsePtr& response,
1972  const HttpClient::RequestHandler& request_callback,
1973  const HttpClient::RequestTimeout& request_timeout,
1974  const HttpClient::ConnectHandler& connect_callback,
1975  const HttpClient::HandshakeHandler& handshake_callback,
1976  const HttpClient::CloseHandler& close_callback) {
1977  if (!url.isValid()) {
1978  isc_throw(HttpClientError, "invalid URL specified for the HTTP client");
1979  }
1980 
1981  if ((url.getScheme() == Url::Scheme::HTTPS) && !tls_context) {
1982  isc_throw(HttpClientError, "HTTPS URL scheme but no TLS context");
1983  }
1984 
1985  if (!request) {
1986  isc_throw(HttpClientError, "HTTP request must not be null");
1987  }
1988 
1989  if (!response) {
1990  isc_throw(HttpClientError, "HTTP response must not be null");
1991  }
1992 
1993  if (!request_callback) {
1994  isc_throw(HttpClientError, "callback for HTTP transaction must not be null");
1995  }
1996 
1997  impl_->conn_pool_->queueRequest(url, tls_context, request, response,
1998  request_timeout.value_,
1999  request_callback, connect_callback,
2000  handshake_callback, close_callback);
2001 }
2002 
2003 void
2004 HttpClient::closeIfOutOfBand(int socket_fd) {
2005  return (impl_->conn_pool_->closeIfOutOfBand(socket_fd));
2006 }
2007 
2008 void
2009 HttpClient::start() {
2010  impl_->start();
2011 }
2012 
2013 void
2014 HttpClient::checkPermissions() {
2015  impl_->checkPermissions();
2016 }
2017 
2018 void
2019 HttpClient::pause() {
2020  impl_->pause();
2021 }
2022 
2023 void
2024 HttpClient::resume() {
2025  impl_->resume();
2026 }
2027 
2028 void
2029 HttpClient::stop() {
2030  impl_->stop();
2031 }
2032 
2033 const IOServicePtr
2034 HttpClient::getThreadIOService() const {
2035  return (impl_->getThreadIOService());
2036 }
2037 
2038 uint16_t
2039 HttpClient::getThreadPoolSize() const {
2040  return (impl_->getThreadPoolSize());
2041 }
2042 
2043 uint16_t
2044 HttpClient::getThreadCount() const {
2045  return (impl_->getThreadCount());
2046 }
2047 
2048 bool
2049 HttpClient::isRunning() {
2050  return (impl_->isRunning());
2051 }
2052 
2053 bool
2054 HttpClient::isStopped() {
2055  return (impl_->isStopped());
2056 }
2057 
2058 bool
2059 HttpClient::isPaused() {
2060  return (impl_->isPaused());
2061 }
2062 
2063 } // end of namespace isc::http
2064 } // end of namespace isc
const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED
Definition: http_messages.h:14
bool isRunning()
Indicates if the thread pool is running.
Definition: client.cc:1877
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition: macros.h:26
const isc::log::MessageID HTTP_CLIENT_QUEUE_SIZE_GROWING
Definition: http_messages.h:17
const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS
Definition: http_messages.h:15
std::function< bool(const boost::system::error_code &, const int)> ConnectHandler
Optional handler invoked when client connects to the server.
Definition: client.h:114
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:69
void checkPermissions()
Check if the current thread can perform thread pool state transition.
Definition: client.cc:1822
long value_
Timeout value specified.
Definition: client.h:96
std::function< void(const boost::system::error_code &, const HttpResponsePtr &, const std::string &)> RequestHandler
Callback type used in call to HttpClient::asyncSendRequest.
Definition: client.h:102
void pause()
Pauses the client&#39;s thread pool.
Definition: client.cc:1851
HTTP request/response timeout value.
Definition: client.h:89
void start()
Starts running the client&#39;s thread pool, if multi-threaded.
Definition: client.cc:1829
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED_DETAILS
Definition: http_messages.h:35
std::function< void(const int)> CloseHandler
Optional handler invoked when client closes the connection to the server.
Definition: client.h:132
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
const isc::log::MessageID HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED
Definition: http_messages.h:32
const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED
Definition: http_messages.h:34
HttpClientImpl(IOService &io_service, size_t thread_pool_size=0, bool defer_thread_start=false)
Constructor.
Definition: client.cc:1786
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
~HttpClientImpl()
Destructor.
Definition: client.cc:1813
const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND
Definition: http_messages.h:20
ConnectionPoolPtr conn_pool_
Holds a pointer to the connection pool.
Definition: client.cc:1935
Represents an URL.
Definition: url.h:20
A generic parser for HTTP responses.
Definition: edns.h:19
void resume()
Resumes running the client&#39;s thread pool.
Definition: client.cc:1864
boost::shared_ptr< HttpResponse > HttpResponsePtr
Pointer to the HttpResponse object.
Definition: response.h:78
const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND_DETAILS
Definition: http_messages.h:21
bool isValid() const
Checks if the URL is valid.
Definition: url.h:47
A generic exception that is thrown when an unexpected error condition occurs.
const int DBGLVL_TRACE_DETAIL_DATA
Trace data associated with detailed operations.
Definition: log_dbglevels.h:78
void stop()
Close all connections, and if multi-threaded, stops the client&#39;s thread pool.
Definition: client.cc:1837
void clear()
Clears containers.
Definition: classify.h:183
bool isPaused()
Indicates if the thread pool is paused.
Definition: client.cc:1901
const isc::log::MessageID HTTP_CLIENT_MT_STARTED
Definition: http_messages.h:16
Defines the logger used by the top-level component of kea-lfc.
boost::shared_ptr< HttpResponseParser > HttpResponseParserPtr
Pointer to the HttpResponseParser.
std::function< bool(const boost::system::error_code &, const int)> HandshakeHandler
Optional handler invoked when client performs the TLS handshake with the server.
Definition: client.h:127
A generic exception that is thrown if a function is called in a prohibited way.
Unlock Guard.
Definition: unlock_guard.h:21
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
isc::log::Logger http_logger("http")
Defines the logger used within libkea-http library.
Definition: http_log.h:18
const isc::log::MessageID HTTP_CONNECTION_CLOSE_CALLBACK_FAILED
Definition: http_messages.h:23
boost::shared_ptr< HttpRequest > HttpRequestPtr
Pointer to the HttpRequest object.
Definition: request.h:27
uint16_t getThreadPoolSize()
Fetches the maximum size of the thread pool.
Definition: client.cc:1920
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
Definition: log_dbglevels.h:75
uint16_t getThreadCount()
Fetches the number of threads in the pool.
Definition: client.cc:1927
asiolink::IOServicePtr getThreadIOService()
Fetches the internal IOService used in multi-threaded mode.
Definition: client.cc:1913
bool isStopped()
Indicates if the thread pool is stopped.
Definition: client.cc:1889
A generic error raised by the HttpClient class.
Definition: client.h:26
const int DBGLVL_TRACE_BASIC_DATA
Trace data associated with the basic operations.
Definition: log_dbglevels.h:72
HttpClient implementation.
Definition: client.cc:1761
Scheme getScheme() const
Returns parsed scheme.
Definition: url.cc:31