Kea 2.7.1
client.cc
Go to the documentation of this file.
1// Copyright (C) 2018-2024 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>
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
35using namespace isc;
36using namespace isc::asiolink;
37using namespace isc::http;
38using namespace isc::util;
39using namespace boost::posix_time;
40
41namespace ph = std::placeholders;
42
43namespace {
44
48constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
49
51typedef std::function<void(boost::system::error_code ec, size_t length)>
52SocketCallbackFunction;
53
59class SocketCallback {
60public:
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
84private:
85
87 SocketCallbackFunction callback_;
88
89};
90
91class ConnectionPool;
92
94typedef boost::shared_ptr<ConnectionPool> ConnectionPoolPtr;
95
111class Connection : public boost::enable_shared_from_this<Connection> {
112public:
113
121 explicit Connection(const IOServicePtr& 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
202private:
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
406 IOServicePtr io_service_;
407
412 boost::weak_ptr<ConnectionPool> conn_pool_;
413
415 Url url_;
416
418 TlsContextPtr tls_context_;
419
421 std::shared_ptr<TCPSocket<SocketCallback>> tcp_socket_;
422
424 std::shared_ptr<TLSSocket<SocketCallback>> tls_socket_;
425
427 IntervalTimerPtr timer_;
428
430 HttpRequestPtr current_request_;
431
433 HttpResponsePtr current_response_;
434
436 HttpResponseParserPtr parser_;
437
439 HttpClient::RequestHandler current_callback_;
440
442 std::string buf_;
443
445 std::array<char, 32768> input_buf_;
446
448 uint64_t current_transid_;
449
451 HttpClient::HandshakeHandler handshake_callback_;
452
454 HttpClient::CloseHandler close_callback_;
455
457 std::atomic<bool> started_;
458
460 std::atomic<bool> need_handshake_;
461
463 std::atomic<bool> closed_;
464
466 std::mutex mutex_;
467};
468
470typedef boost::shared_ptr<Connection> ConnectionPtr;
471
479class ConnectionPool : public boost::enable_shared_from_this<ConnectionPool> {
480public:
481
488 explicit ConnectionPool(const IOServicePtr& io_service, size_t max_url_connections)
489 : io_service_(io_service), destinations_(), pool_mutex_(),
490 max_url_connections_(max_url_connections) {
491 }
492
496 ~ConnectionPool() {
497 closeAll();
498 }
499
505 void processNextRequest(const Url& url, const TlsContextPtr& tls_context) {
506 if (MultiThreadingMgr::instance().getMode()) {
507 std::lock_guard<std::mutex> lk(pool_mutex_);
508 return (processNextRequestInternal(url, tls_context));
509 } else {
510 return (processNextRequestInternal(url, tls_context));
511 }
512 }
513
519 void postProcessNextRequest(const Url& url,
520 const TlsContextPtr& tls_context) {
521 io_service_->post(std::bind(&ConnectionPool::processNextRequest,
522 shared_from_this(), url, tls_context));
523 }
524
545 void queueRequest(const Url& url,
546 const TlsContextPtr& tls_context,
547 const HttpRequestPtr& request,
548 const HttpResponsePtr& response,
549 const long request_timeout,
550 const HttpClient::RequestHandler& request_callback,
551 const HttpClient::ConnectHandler& connect_callback,
552 const HttpClient::HandshakeHandler& handshake_callback,
553 const HttpClient::CloseHandler& close_callback) {
554 if (MultiThreadingMgr::instance().getMode()) {
555 std::lock_guard<std::mutex> lk(pool_mutex_);
556 return (queueRequestInternal(url, tls_context, request, response,
557 request_timeout, request_callback,
558 connect_callback, handshake_callback,
559 close_callback));
560 } else {
561 return (queueRequestInternal(url, tls_context, request, response,
562 request_timeout, request_callback,
563 connect_callback, handshake_callback,
564 close_callback));
565 }
566 }
567
570 void closeAll() {
571 if (MultiThreadingMgr::instance().getMode()) {
572 std::lock_guard<std::mutex> lk(pool_mutex_);
573 closeAllInternal();
574 } else {
575 closeAllInternal();
576 }
577 }
578
591 void closeIfOutOfBand(int socket_fd) {
592 if (MultiThreadingMgr::instance().getMode()) {
593 std::lock_guard<std::mutex> lk(pool_mutex_);
594 closeIfOutOfBandInternal(socket_fd);
595 } else {
596 closeIfOutOfBandInternal(socket_fd);
597 }
598 }
599
600private:
601
609 void processNextRequestInternal(const Url& url,
610 const TlsContextPtr& tls_context) {
611 // Check if there is a queue for this URL. If there is no queue, there
612 // is no request queued either.
613 DestinationPtr destination = findDestination(url, tls_context);
614 if (destination) {
615 // Remove closed connections.
616 destination->garbageCollectConnections();
617 if (!destination->queueEmpty()) {
618 // We have at least one queued request. Do we have an
619 // idle connection?
620 ConnectionPtr connection = destination->getIdleConnection();
621 if (!connection) {
622 // No idle connections.
623 if (destination->connectionsFull()) {
624 return;
625 }
626 // Room to make another connection with this destination,
627 // so make one.
628 connection.reset(new Connection(io_service_, tls_context,
629 shared_from_this(), url));
630 destination->addConnection(connection);
631 }
632
633 // Dequeue the oldest request and start a transaction for it using
634 // the idle connection.
635 RequestDescriptor desc = destination->popNextRequest();
636 connection->doTransaction(desc.request_, desc.response_,
637 desc.request_timeout_, desc.callback_,
638 desc.connect_callback_,
639 desc.handshake_callback_,
640 desc.close_callback_);
641 }
642 }
643 }
644
667 void queueRequestInternal(const Url& url,
668 const TlsContextPtr& tls_context,
669 const HttpRequestPtr& request,
670 const HttpResponsePtr& response,
671 const long request_timeout,
672 const HttpClient::RequestHandler& request_callback,
673 const HttpClient::ConnectHandler& connect_callback,
674 const HttpClient::HandshakeHandler& handshake_callback,
675 const HttpClient::CloseHandler& close_callback) {
676 ConnectionPtr connection;
677 // Find the destination for the requested URL.
678 DestinationPtr destination = findDestination(url, tls_context);
679 if (destination) {
680 // Remove closed connections.
681 destination->garbageCollectConnections();
682 // Found it, look for an idle connection.
683 connection = destination->getIdleConnection();
684 } else {
685 // Doesn't exist yet so it's a new destination.
686 destination = addDestination(url, tls_context);
687 }
688
689 if (!connection) {
690 if (destination->connectionsFull()) {
691 // All connections busy, queue it.
692 destination->pushRequest(RequestDescriptor(request, response,
693 request_timeout,
694 request_callback,
695 connect_callback,
696 handshake_callback,
697 close_callback));
698 return;
699 }
700
701 // Room to make another connection with this destination, so make one.
702 connection.reset(new Connection(io_service_, tls_context,
703 shared_from_this(), url));
704 destination->addConnection(connection);
705 }
706
707 // Use the connection to start the transaction.
708 connection->doTransaction(request, response, request_timeout, request_callback,
709 connect_callback, handshake_callback, close_callback);
710 }
711
716 void closeAllInternal() {
717 for (auto const& destination : destinations_) {
718 destination.second->closeAllConnections();
719 }
720
721 destinations_.clear();
722 }
723
738 void closeIfOutOfBandInternal(int socket_fd) {
739 for (auto const& destination : destinations_) {
740 // First we look for a connection with the socket.
741 ConnectionPtr connection = destination.second->findBySocketFd(socket_fd);
742 if (connection) {
743 if (!connection->isTransactionOngoing()) {
744 // Socket has no transaction, so any ready event is
745 // out-of-band (other end probably closed), so
746 // let's close it. Note we do not remove any queued
747 // requests, as this might somehow be occurring in
748 // between them.
749 destination.second->closeConnection(connection);
750 }
751
752 return;
753 }
754 }
755 }
756
759 struct RequestDescriptor {
773 RequestDescriptor(const HttpRequestPtr& request,
774 const HttpResponsePtr& response,
775 const long& request_timeout,
776 const HttpClient::RequestHandler& callback,
777 const HttpClient::ConnectHandler& connect_callback,
778 const HttpClient::HandshakeHandler& handshake_callback,
779 const HttpClient::CloseHandler& close_callback)
780 : request_(request), response_(response),
781 request_timeout_(request_timeout), callback_(callback),
782 connect_callback_(connect_callback),
783 handshake_callback_(handshake_callback),
784 close_callback_(close_callback) {
785 }
786
788 HttpRequestPtr request_;
789
791 HttpResponsePtr response_;
792
794 long request_timeout_;
795
798
800 HttpClient::ConnectHandler connect_callback_;
801
803 HttpClient::HandshakeHandler handshake_callback_;
804
806 HttpClient::CloseHandler close_callback_;
807 };
808
810 typedef std::pair<Url, TlsContextPtr> DestinationDescriptor;
811
813 class Destination {
814 public:
816 const size_t QUEUE_SIZE_THRESHOLD = 2048;
818 const int QUEUE_WARN_SECS = 5;
819
826 Destination(Url const& url, TlsContextPtr tls_context, size_t max_connections)
827 : url_(url), tls_context_(tls_context),
828 max_connections_(max_connections), connections_(), queue_(),
829 last_queue_warn_time_(min_date_time), last_queue_size_(0) {
830 }
831
833 ~Destination() {
834 closeAllConnections();
835 }
836
844 void addConnection(ConnectionPtr connection) {
845 if (connectionsFull()) {
846 isc_throw(BadValue, "URL: " << url_.toText()
847 << ", already at maximum connections: "
848 << max_connections_);
849 }
850
851 connections_.push_back(connection);
852 }
853
858 void closeConnection(ConnectionPtr connection) {
859 for (auto it = connections_.begin(); it != connections_.end(); ++it) {
860 if (*it == connection) {
861 (*it)->close();
862 connections_.erase(it);
863 break;
864 }
865 }
866 }
867
870 void closeAllConnections() {
871 // Flush the queue.
872 while (!queue_.empty()) {
873 queue_.pop();
874 }
875
876 for (auto const& connection : connections_) {
877 connection->close();
878 }
879
880 connections_.clear();
881 }
882
906 void garbageCollectConnections() {
907 for (auto it = connections_.begin(); it != connections_.end();) {
908 (*it)->isClosedByPeer();
909 if (!(*it)->isClosed()) {
910 ++it;
911 } else {
912 it = connections_.erase(it);
913 }
914 }
915 }
916
928 ConnectionPtr getIdleConnection() {
929 for (auto const& connection : connections_) {
930 if (!connection->isTransactionOngoing() &&
931 !connection->isClosed()) {
932 return (connection);
933 }
934 }
935
936 return (ConnectionPtr());
937 }
938
945 ConnectionPtr findBySocketFd(int socket_fd) {
946 for (auto const& connection : connections_) {
947 if (connection->isMySocket(socket_fd)) {
948 return (connection);
949 }
950 }
951
952 return (ConnectionPtr());
953 }
954
958 bool connectionsEmpty() {
959 return (connections_.empty());
960 }
961
965 bool connectionsFull() {
966 return (connections_.size() >= max_connections_);
967 }
968
972 size_t connectionCount() {
973 return (connections_.size());
974 }
975
979 size_t getMaxConnections() const {
980 return (max_connections_);
981 }
982
986 bool queueEmpty() const {
987 return (queue_.empty());
988 }
989
996 void pushRequest(RequestDescriptor const& desc) {
997 queue_.push(desc);
998 size_t size = queue_.size();
999 // If the queue size is larger than the threshold and growing, issue a
1000 // periodic warning.
1001 if ((size > QUEUE_SIZE_THRESHOLD) && (size > last_queue_size_)) {
1002 ptime now = microsec_clock::universal_time();
1003 if ((now - last_queue_warn_time_) > seconds(QUEUE_WARN_SECS)) {
1005 .arg(url_.toText())
1006 .arg(size);
1007 // Remember the last time we warned.
1008 last_queue_warn_time_ = now;
1009 }
1010 }
1011
1012 // Remember the previous size.
1013 last_queue_size_ = size;
1014 }
1015
1019 RequestDescriptor popNextRequest() {
1020 if (queue_.empty()) {
1021 isc_throw(InvalidOperation, "cannot pop, queue is empty");
1022 }
1023
1024 RequestDescriptor desc = queue_.front();
1025 queue_.pop();
1026 return (desc);
1027 }
1028
1029 private:
1031 Url url_;
1032
1034 TlsContextPtr tls_context_;
1035
1037 size_t max_connections_;
1038
1040 std::list<ConnectionPtr> connections_;
1041
1043 std::queue<RequestDescriptor> queue_;
1044
1046 ptime last_queue_warn_time_;
1047
1049 size_t last_queue_size_;
1050 };
1051
1053 typedef boost::shared_ptr<Destination> DestinationPtr;
1054
1062 DestinationPtr addDestination(const Url& url,
1063 const TlsContextPtr& tls_context) {
1064 const DestinationDescriptor& desc = std::make_pair(url, tls_context);
1065 DestinationPtr destination(new Destination(url, tls_context,
1066 max_url_connections_));
1067 destinations_[desc] = destination;
1068 return (destination);
1069 }
1070
1079 DestinationPtr findDestination(const Url& url,
1080 const TlsContextPtr& tls_context) const {
1081 const DestinationDescriptor& desc = std::make_pair(url, tls_context);
1082 auto it = destinations_.find(desc);
1083 if (it != destinations_.end()) {
1084 return (it->second);
1085 }
1086
1087 return (DestinationPtr());
1088 }
1089
1101 void removeDestination(const Url& url,
1102 const TlsContextPtr& tls_context) {
1103 const DestinationDescriptor& desc = std::make_pair(url, tls_context);
1104 auto it = destinations_.find(desc);
1105 if (it != destinations_.end()) {
1106 it->second->closeAllConnections();
1107 destinations_.erase(it);
1108 }
1109 }
1110
1112 IOServicePtr io_service_;
1113
1115 std::map<DestinationDescriptor, DestinationPtr> destinations_;
1116
1118 std::mutex pool_mutex_;
1119
1121 size_t max_url_connections_;
1122};
1123
1124Connection::Connection(const IOServicePtr& io_service,
1125 const TlsContextPtr& tls_context,
1126 const ConnectionPoolPtr& conn_pool,
1127 const Url& url)
1128 : io_service_(io_service), conn_pool_(conn_pool), url_(url),
1129 tls_context_(tls_context), tcp_socket_(), tls_socket_(),
1130 timer_(new IntervalTimer(io_service)), current_request_(),
1131 current_response_(), parser_(), current_callback_(), buf_(), input_buf_(),
1132 current_transid_(0), close_callback_(), started_(false),
1133 need_handshake_(false), closed_(false) {
1134 if (!tls_context) {
1135 tcp_socket_.reset(new asiolink::TCPSocket<SocketCallback>(io_service));
1136 } else {
1137 tls_socket_.reset(new asiolink::TLSSocket<SocketCallback>(io_service,
1138 tls_context));
1139 need_handshake_ = true;
1140 }
1141}
1142
1143Connection::~Connection() {
1144 close();
1145}
1146
1147void
1148Connection::resetState() {
1149 started_ = false;
1150 current_request_.reset();
1151 current_response_.reset();
1152 parser_.reset();
1153 current_callback_ = HttpClient::RequestHandler();
1154}
1155
1156void
1157Connection::closeCallback(const bool clear) {
1158 if (close_callback_) {
1159 try {
1160 if (tcp_socket_) {
1161 close_callback_(tcp_socket_->getNative());
1162 } else if (tls_socket_) {
1163 close_callback_(tls_socket_->getNative());
1164 } else {
1166 "internal error: can't find a socket to close");
1167 }
1168 } catch (...) {
1170 }
1171 }
1172
1173 if (clear) {
1174 close_callback_ = HttpClient::CloseHandler();
1175 }
1176}
1177
1178void
1179Connection::isClosedByPeer() {
1180 // This method applies only to idle connections.
1181 if (started_ || closed_) {
1182 return;
1183 }
1184 // This code was guarded by a lock so keep this.
1185 if (MultiThreadingMgr::instance().getMode()) {
1186 std::lock_guard<std::mutex> lk(mutex_);
1187 isClosedByPeerInternal();
1188 } else {
1189 isClosedByPeerInternal();
1190 }
1191}
1192
1193void
1194Connection::isClosedByPeerInternal() {
1195 // If the socket is open we check if it is possible to transmit
1196 // the data over this socket by reading from it with message
1197 // peeking. If the socket is not usable, we close it and then
1198 // re-open it. There is a narrow window of time between checking
1199 // the socket usability and actually transmitting the data over
1200 // this socket, when the peer may close the connection. In this
1201 // case we'll need to re-transmit but we don't handle it here.
1202 if (tcp_socket_) {
1203 if (tcp_socket_->getASIOSocket().is_open() &&
1204 !tcp_socket_->isUsable()) {
1205 closeCallback();
1206 closed_ = true;
1207 tcp_socket_->close();
1208 }
1209 } else if (tls_socket_) {
1210 if (tls_socket_->getASIOSocket().is_open() &&
1211 !tls_socket_->isUsable()) {
1212 closeCallback();
1213 closed_ = true;
1214 tls_socket_->close();
1215 }
1216 } else {
1217 isc_throw(Unexpected, "internal error: can't find the sending socket");
1218 }
1219}
1220
1221void
1222Connection::doTransaction(const HttpRequestPtr& request,
1223 const HttpResponsePtr& response,
1224 const long request_timeout,
1225 const HttpClient::RequestHandler& callback,
1226 const HttpClient::ConnectHandler& connect_callback,
1227 const HttpClient::HandshakeHandler& handshake_callback,
1228 const HttpClient::CloseHandler& close_callback) {
1229 if (MultiThreadingMgr::instance().getMode()) {
1230 std::lock_guard<std::mutex> lk(mutex_);
1231 doTransactionInternal(request, response, request_timeout,
1232 callback, connect_callback, handshake_callback,
1233 close_callback);
1234 } else {
1235 doTransactionInternal(request, response, request_timeout,
1236 callback, connect_callback, handshake_callback,
1237 close_callback);
1238 }
1239}
1240
1241void
1242Connection::doTransactionInternal(const HttpRequestPtr& request,
1243 const HttpResponsePtr& response,
1244 const long request_timeout,
1245 const HttpClient::RequestHandler& callback,
1246 const HttpClient::ConnectHandler& connect_callback,
1247 const HttpClient::HandshakeHandler& handshake_callback,
1248 const HttpClient::CloseHandler& close_callback) {
1249 try {
1250 started_ = true;
1251 current_request_ = request;
1252 current_response_ = response;
1253 parser_.reset(new HttpResponseParser(*current_response_));
1254 parser_->initModel();
1255 current_callback_ = callback;
1256 handshake_callback_ = handshake_callback;
1257 close_callback_ = close_callback;
1258
1259 // Starting new transaction. Generate new transaction id.
1260 ++current_transid_;
1261
1262 buf_ = request->toString();
1263
1266 .arg(request->toBriefString())
1267 .arg(url_.toText());
1268
1271 .arg(url_.toText())
1272 .arg(HttpMessageParserBase::logFormatHttpMessage(request->toString(),
1273 MAX_LOGGED_MESSAGE_SIZE));
1274
1275 // Setup request timer.
1276 scheduleTimer(request_timeout);
1277
1281 TCPEndpoint endpoint(url_.getStrippedHostname(),
1282 static_cast<unsigned short>(url_.getPort()));
1283 SocketCallback socket_cb(std::bind(&Connection::connectCallback, shared_from_this(),
1284 connect_callback, current_transid_,
1285 ph::_1));
1286
1287 // Establish new connection or use existing connection.
1288 if (tcp_socket_) {
1289 tcp_socket_->open(&endpoint, socket_cb);
1290 return;
1291 }
1292 if (tls_socket_) {
1293 tls_socket_->open(&endpoint, socket_cb);
1294 return;
1295 }
1296
1297 // Should never reach this point.
1298 isc_throw(Unexpected, "internal error: can't find a socket to open");
1299
1300 } catch (const std::exception& ex) {
1301 // Re-throw with the expected exception type.
1303 }
1304}
1305
1306void
1307Connection::close() {
1308 if (MultiThreadingMgr::instance().getMode()) {
1309 std::lock_guard<std::mutex> lk(mutex_);
1310 return (closeInternal());
1311 } else {
1312 return (closeInternal());
1313 }
1314}
1315
1316void
1317Connection::closeInternal() {
1318 // Pass in true to discard the callback.
1319 closeCallback(true);
1320
1321 closed_ = true;
1322 timer_->cancel();
1323 if (tcp_socket_) {
1324 tcp_socket_->close();
1325 }
1326 if (tls_socket_) {
1327 tls_socket_->close();
1328 }
1329
1330 resetState();
1331}
1332
1333bool
1334Connection::isMySocket(int socket_fd) const {
1335 if (tcp_socket_) {
1336 return (tcp_socket_->getNative() == socket_fd);
1337 } else if (tls_socket_) {
1338 return (tls_socket_->getNative() == socket_fd);
1339 }
1340 // Should never reach this point.
1341 std::cerr << "internal error: can't find my socket\n";
1342 return (false);
1343}
1344
1345bool
1346Connection::checkPrematureTimeout(const uint64_t transid) {
1347 if (MultiThreadingMgr::instance().getMode()) {
1348 std::lock_guard<std::mutex> lk(mutex_);
1349 return (checkPrematureTimeoutInternal(transid));
1350 } else {
1351 return (checkPrematureTimeoutInternal(transid));
1352 }
1353}
1354
1355bool
1356Connection::checkPrematureTimeoutInternal(const uint64_t transid) {
1357 // If there is no transaction but the handlers are invoked it means
1358 // that the last transaction in the queue timed out prematurely.
1359 // Also, if there is a transaction in progress but the ID of that
1360 // transaction doesn't match the one associated with the handler it,
1361 // also means that the transaction timed out prematurely.
1362 if (!isTransactionOngoing() || (transid != current_transid_)) {
1364 .arg(isTransactionOngoing())
1365 .arg(transid)
1366 .arg(current_transid_);
1367 return (true);
1368 }
1369
1370 return (false);
1371}
1372
1373void
1374Connection::terminate(const boost::system::error_code& ec,
1375 const std::string& parsing_error) {
1376 if (MultiThreadingMgr::instance().getMode()) {
1377 std::lock_guard<std::mutex> lk(mutex_);
1378 terminateInternal(ec, parsing_error);
1379 } else {
1380 terminateInternal(ec, parsing_error);
1381 }
1382}
1383
1384void
1385Connection::terminateInternal(const boost::system::error_code& ec,
1386 const std::string& parsing_error) {
1387 HttpResponsePtr response;
1388 if (isTransactionOngoing()) {
1389
1390 timer_->cancel();
1391 if (tcp_socket_) {
1392 tcp_socket_->cancel();
1393 }
1394 if (tls_socket_) {
1395 tls_socket_->cancel();
1396 }
1397
1398 if (!ec && current_response_->isFinalized()) {
1399 response = current_response_;
1400
1403 .arg(url_.toText());
1404
1407 .arg(url_.toText())
1408 .arg(parser_ ?
1409 parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE) :
1410 "[HttpResponseParser is null]");
1411
1412 } else {
1413 std::string err = parsing_error.empty() ? ec.message() :
1414 parsing_error;
1415
1418 .arg(url_.toText())
1419 .arg(err);
1420
1421 // Only log the details if we have received anything and tried
1422 // to parse it.
1423 if (!parsing_error.empty()) {
1426 .arg(url_.toText())
1427 .arg(parser_ ?
1428 parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE) :
1429 "[HttpResponseParser is null]");
1430 }
1431 }
1432
1433 try {
1434 // The callback should take care of its own exceptions but one
1435 // never knows.
1436 if (MultiThreadingMgr::instance().getMode()) {
1437 UnlockGuard<std::mutex> lock(mutex_);
1438 current_callback_(ec, response, parsing_error);
1439 } else {
1440 current_callback_(ec, response, parsing_error);
1441 }
1442 } catch (...) {
1443 }
1444
1445 // If we're not requesting connection persistence or the
1446 // connection has timed out, we should close the socket.
1447 if (!closed_ &&
1448 (!current_request_->isPersistent() ||
1449 (ec == boost::asio::error::timed_out))) {
1450 closeInternal();
1451 }
1452
1453 resetState();
1454 }
1455
1456 // Check if there are any requests queued for this destination and start
1457 // another transaction if there is at least one.
1458 ConnectionPoolPtr conn_pool = conn_pool_.lock();
1459 if (conn_pool) {
1460 conn_pool->postProcessNextRequest(url_, tls_context_);
1461 }
1462}
1463
1464void
1465Connection::scheduleTimer(const long request_timeout) {
1466 if (request_timeout > 0) {
1467 timer_->setup(std::bind(&Connection::timerCallback, this), request_timeout,
1469 }
1470}
1471
1472void
1473Connection::doHandshake(const uint64_t transid) {
1474 // Skip the handshake if it is not needed.
1475 if (!need_handshake_) {
1476 doSend(transid);
1477 return;
1478 }
1479
1480 SocketCallback socket_cb(std::bind(&Connection::handshakeCallback,
1481 shared_from_this(),
1482 handshake_callback_,
1483 transid,
1484 ph::_1));
1485 try {
1486 tls_socket_->handshake(socket_cb);
1487
1488 } catch (...) {
1489 terminate(boost::asio::error::not_connected);
1490 }
1491}
1492
1493void
1494Connection::doSend(const uint64_t transid) {
1495 SocketCallback socket_cb(std::bind(&Connection::sendCallback,
1496 shared_from_this(),
1497 transid,
1498 ph::_1,
1499 ph::_2));
1500 try {
1501 if (tcp_socket_) {
1502 tcp_socket_->asyncSend(&buf_[0], buf_.size(), socket_cb);
1503 return;
1504 }
1505
1506 if (tls_socket_) {
1507 tls_socket_->asyncSend(&buf_[0], buf_.size(), socket_cb);
1508 return;
1509 }
1510
1511 // Should never reach this point.
1512 std::cerr << "internal error: can't find a socket to send to\n";
1514 "internal error: can't find a socket to send to");
1515 } catch (...) {
1516 terminate(boost::asio::error::not_connected);
1517 }
1518}
1519
1520void
1521Connection::doReceive(const uint64_t transid) {
1522 TCPEndpoint endpoint;
1523 SocketCallback socket_cb(std::bind(&Connection::receiveCallback,
1524 shared_from_this(),
1525 transid,
1526 ph::_1,
1527 ph::_2));
1528 try {
1529 if (tcp_socket_) {
1530 tcp_socket_->asyncReceive(static_cast<void*>(input_buf_.data()),
1531 input_buf_.size(), 0,
1532 &endpoint, socket_cb);
1533 return;
1534 }
1535 if (tls_socket_) {
1536 tls_socket_->asyncReceive(static_cast<void*>(input_buf_.data()),
1537 input_buf_.size(), 0,
1538 &endpoint, socket_cb);
1539 return;
1540 }
1541 // Should never reach this point.
1542 std::cerr << "internal error: can't find a socket to receive from\n";
1544 "internal error: can't find a socket to receive from");
1545
1546 } catch (...) {
1547 terminate(boost::asio::error::not_connected);
1548 }
1549}
1550
1551void
1552Connection::connectCallback(HttpClient::ConnectHandler connect_callback,
1553 const uint64_t transid,
1554 const boost::system::error_code& ec) {
1555 if (checkPrematureTimeout(transid)) {
1556 return;
1557 }
1558
1559 // Run user defined connect callback if specified.
1560 if (connect_callback) {
1561 // If the user defined callback indicates that the connection
1562 // should not be continued.
1563 if (tcp_socket_) {
1564 if (!connect_callback(ec, tcp_socket_->getNative())) {
1565 return;
1566 }
1567 } else if (tls_socket_) {
1568 if (!connect_callback(ec, tls_socket_->getNative())) {
1569 return;
1570 }
1571 } else {
1572 // Should never reach this point.
1573 std::cerr << "internal error: can't find a socket to connect\n";
1574 }
1575 }
1576
1577 if (ec && (ec.value() == boost::asio::error::operation_aborted)) {
1578 return;
1579
1580 // In some cases the "in progress" status code may be returned. It doesn't
1581 // indicate an error. Sending the request over the socket is expected to
1582 // be successful. Getting such status appears to be highly dependent on
1583 // the operating system.
1584 } else if (ec &&
1585 (ec.value() != boost::asio::error::in_progress) &&
1586 (ec.value() != boost::asio::error::already_connected)) {
1587 terminate(ec);
1588
1589 } else {
1590 // Start the TLS handshake asynchronously.
1591 doHandshake(transid);
1592 }
1593}
1594
1595void
1596Connection::handshakeCallback(HttpClient::ConnectHandler handshake_callback,
1597 const uint64_t transid,
1598 const boost::system::error_code& ec) {
1599 need_handshake_ = false;
1600 if (checkPrematureTimeout(transid)) {
1601 return;
1602 }
1603
1604 // Run user defined handshake callback if specified.
1605 if (handshake_callback) {
1606 // If the user defined callback indicates that the connection
1607 // should not be continued.
1608 if (tls_socket_) {
1609 if (!handshake_callback(ec, tls_socket_->getNative())) {
1610 return;
1611 }
1612 } else {
1613 // Should never reach this point.
1614 std::cerr << "internal error: can't find TLS socket\n";
1615 }
1616 }
1617
1618 if (ec && (ec.value() == boost::asio::error::operation_aborted)) {
1619 return;
1620 } else if (ec) {
1621 terminate(ec);
1622
1623 } else {
1624 // Start sending the request asynchronously.
1625 doSend(transid);
1626 }
1627}
1628
1629void
1630Connection::sendCallback(const uint64_t transid,
1631 const boost::system::error_code& ec,
1632 size_t length) {
1633 if (checkPrematureTimeout(transid)) {
1634 return;
1635 }
1636
1637 if (ec) {
1638 if (ec.value() == boost::asio::error::operation_aborted) {
1639 return;
1640
1641 // EAGAIN and EWOULDBLOCK don't really indicate an error. The length
1642 // should be 0 in this case but let's be sure.
1643 } else if ((ec.value() == boost::asio::error::would_block) ||
1644 (ec.value() == boost::asio::error::try_again)) {
1645 length = 0;
1646
1647 } else {
1648 // Any other error should cause the transaction to terminate.
1649 terminate(ec);
1650 return;
1651 }
1652 }
1653
1654 // Sending is in progress, so push back the timeout.
1655 scheduleTimer(timer_->getInterval());
1656
1657 // If any data have been sent, remove it from the buffer and only leave the
1658 // portion that still has to be sent.
1659 if (length > 0) {
1660 buf_.erase(0, length);
1661 }
1662
1663 // If there is no more data to be sent, start receiving a response. Otherwise,
1664 // continue sending.
1665 if (buf_.empty()) {
1666 doReceive(transid);
1667
1668 } else {
1669 doSend(transid);
1670 }
1671}
1672
1673void
1674Connection::receiveCallback(const uint64_t transid,
1675 const boost::system::error_code& ec,
1676 size_t length) {
1677 if (checkPrematureTimeout(transid)) {
1678 return;
1679 }
1680
1681 if (ec) {
1682 if (ec.value() == boost::asio::error::operation_aborted) {
1683 return;
1684
1685 // EAGAIN and EWOULDBLOCK don't indicate an error in this case. All
1686 // other errors should terminate the transaction.
1687 }
1688 if ((ec.value() != boost::asio::error::try_again) &&
1689 (ec.value() != boost::asio::error::would_block)) {
1690 terminate(ec);
1691 return;
1692
1693 } else {
1694 // For EAGAIN and EWOULDBLOCK the length should be 0 anyway, but let's
1695 // make sure.
1696 length = 0;
1697 }
1698 }
1699
1700 // Receiving is in progress, so push back the timeout.
1701 scheduleTimer(timer_->getInterval());
1702
1703 if (runParser(ec, length)) {
1704 doReceive(transid);
1705 }
1706}
1707
1708bool
1709Connection::runParser(const boost::system::error_code& ec, size_t length) {
1710 if (MultiThreadingMgr::instance().getMode()) {
1711 std::lock_guard<std::mutex> lk(mutex_);
1712 return (runParserInternal(ec, length));
1713 } else {
1714 return (runParserInternal(ec, length));
1715 }
1716}
1717
1718bool
1719Connection::runParserInternal(const boost::system::error_code& ec,
1720 size_t length) {
1721 // If we have received any data, let's feed the parser with it.
1722 if (length != 0) {
1723 parser_->postBuffer(static_cast<void*>(input_buf_.data()), length);
1724 parser_->poll();
1725 }
1726
1727 // If the parser still needs data, let's schedule another receive.
1728 if (parser_->needData()) {
1729 return (true);
1730
1731 } else if (parser_->httpParseOk()) {
1732 // No more data needed and parsing has been successful so far. Let's
1733 // try to finalize the response parsing.
1734 try {
1735 current_response_->finalize();
1736 terminateInternal(ec);
1737
1738 } catch (const std::exception& ex) {
1739 // If there is an error here, we need to return the error message.
1740 terminateInternal(ec, ex.what());
1741 }
1742
1743 } else {
1744 // Parsing was unsuccessful. Let's pass the error message held in the
1745 // parser.
1746 terminateInternal(ec, parser_->getErrorMessage());
1747 }
1748
1749 return (false);
1750}
1751
1752void
1753Connection::timerCallback() {
1754 // Request timeout occurred.
1755 terminate(boost::asio::error::timed_out);
1756}
1757
1758}
1759
1760namespace isc {
1761namespace http {
1762
1765public:
1789 HttpClientImpl(const IOServicePtr& io_service, size_t thread_pool_size = 0,
1790 bool defer_thread_start = false)
1791 : thread_pool_size_(thread_pool_size), thread_pool_() {
1792 if (thread_pool_size_ > 0) {
1793 // Create our own private IOService.
1794 thread_io_service_.reset(new IOService());
1795
1796 // Create the connection pool. Note that we use the thread_pool_size
1797 // as the maximum connections per URL value.
1798 conn_pool_.reset(new ConnectionPool(thread_io_service_, thread_pool_size_));
1799
1800 // Create the thread pool.
1801 thread_pool_.reset(new IoServiceThreadPool(thread_io_service_, thread_pool_size_,
1802 defer_thread_start));
1803
1805 .arg(thread_pool_size_);
1806 } else {
1807 // Single-threaded mode: use the caller's IOService,
1808 // one connection per URL.
1809 conn_pool_.reset(new ConnectionPool(io_service, 1));
1810 }
1811 }
1812
1817 stop();
1818 }
1819
1826 if (thread_pool_) {
1827 thread_pool_->checkPausePermissions();
1828 }
1829 }
1830
1832 void start() {
1833 if (thread_pool_) {
1834 thread_pool_->run();
1835 }
1836 }
1837
1840 void stop() {
1841 // Close all the connections.
1842 conn_pool_->closeAll();
1843
1844 // Stop the thread pool.
1845 if (thread_pool_) {
1846 thread_pool_->stop();
1847 }
1848
1849 if (thread_io_service_) {
1850 thread_io_service_->stopAndPoll();
1851 thread_io_service_->stop();
1852 }
1853 }
1854
1859 void pause() {
1860 if (!thread_pool_) {
1861 isc_throw(InvalidOperation, "HttpClient::pause - no thread pool");
1862 }
1863
1864 // Pause the thread pool.
1865 thread_pool_->pause();
1866 }
1867
1872 void resume() {
1873 if (!thread_pool_) {
1874 isc_throw(InvalidOperation, "HttpClient::resume - no thread pool");
1875 }
1876
1877 // Resume running the thread pool.
1878 thread_pool_->run();
1879 }
1880
1885 bool isRunning() {
1886 if (thread_pool_) {
1887 return (thread_pool_->isRunning());
1888 }
1889
1890 return (false);
1891 }
1892
1897 bool isStopped() {
1898 if (thread_pool_) {
1899 return (thread_pool_->isStopped());
1900 }
1901
1902 return (false);
1903 }
1904
1909 bool isPaused() {
1910 if (thread_pool_) {
1911 return (thread_pool_->isPaused());
1912 }
1913
1914 return (false);
1915 }
1916
1922 return (thread_io_service_);
1923 };
1924
1929 return (thread_pool_size_);
1930 }
1931
1935 uint16_t getThreadCount() {
1936 if (!thread_pool_) {
1937 return (0);
1938 }
1939 return (thread_pool_->getThreadCount());
1940 }
1941
1943 ConnectionPoolPtr conn_pool_;
1944
1945private:
1946
1948 size_t thread_pool_size_;
1949
1951 asiolink::IOServicePtr thread_io_service_;
1952
1955 IoServiceThreadPoolPtr thread_pool_;
1956};
1957
1958HttpClient::HttpClient(const IOServicePtr& io_service, bool multi_threading_enabled,
1959 size_t thread_pool_size, bool defer_thread_start) {
1960 if (!multi_threading_enabled && thread_pool_size) {
1962 "HttpClient thread_pool_size must be zero "
1963 "when Kea core multi-threading is disabled");
1964 }
1965
1966 impl_.reset(new HttpClientImpl(io_service, thread_pool_size,
1967 defer_thread_start));
1968}
1969
1971 impl_->stop();
1972}
1973
1974void
1976 const TlsContextPtr& tls_context,
1977 const HttpRequestPtr& request,
1978 const HttpResponsePtr& response,
1979 const HttpClient::RequestHandler& request_callback,
1980 const HttpClient::RequestTimeout& request_timeout,
1981 const HttpClient::ConnectHandler& connect_callback,
1982 const HttpClient::HandshakeHandler& handshake_callback,
1983 const HttpClient::CloseHandler& close_callback) {
1984 if (!url.isValid()) {
1985 isc_throw(HttpClientError, "invalid URL specified for the HTTP client");
1986 }
1987
1988 if ((url.getScheme() == Url::Scheme::HTTPS) && !tls_context) {
1989 isc_throw(HttpClientError, "HTTPS URL scheme but no TLS context");
1990 }
1991
1992 if (!request) {
1993 isc_throw(HttpClientError, "HTTP request must not be null");
1994 }
1995
1996 if (!response) {
1997 isc_throw(HttpClientError, "HTTP response must not be null");
1998 }
1999
2000 if (!request_callback) {
2001 isc_throw(HttpClientError, "callback for HTTP transaction must not be null");
2002 }
2003
2004 impl_->conn_pool_->queueRequest(url, tls_context, request, response,
2005 request_timeout.value_,
2006 request_callback, connect_callback,
2007 handshake_callback, close_callback);
2008}
2009
2010void
2012 return (impl_->conn_pool_->closeIfOutOfBand(socket_fd));
2013}
2014
2015void
2017 impl_->start();
2018}
2019
2020void
2022 impl_->checkPermissions();
2023}
2024
2025void
2027 impl_->pause();
2028}
2029
2030void
2032 impl_->resume();
2033}
2034
2035void
2037 impl_->stop();
2038}
2039
2040const IOServicePtr
2042 return (impl_->getThreadIOService());
2043}
2044
2045uint16_t
2047 return (impl_->getThreadPoolSize());
2048}
2049
2050uint16_t
2052 return (impl_->getThreadCount());
2053}
2054
2055bool
2057 return (impl_->isRunning());
2058}
2059
2060bool
2062 return (impl_->isStopped());
2063}
2064
2065bool
2067 return (impl_->isPaused());
2068}
2069
2070} // end of namespace isc::http
2071} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown if a function is called in a prohibited way.
A generic exception that is thrown when an unexpected error condition occurs.
A generic error raised by the HttpClient class.
Definition client.h:26
HttpClient implementation.
Definition client.cc:1764
ConnectionPoolPtr conn_pool_
Holds a pointer to the connection pool.
Definition client.cc:1943
uint16_t getThreadCount()
Fetches the number of threads in the pool.
Definition client.cc:1935
~HttpClientImpl()
Destructor.
Definition client.cc:1816
void pause()
Pauses the client's thread pool.
Definition client.cc:1859
uint16_t getThreadPoolSize()
Fetches the maximum size of the thread pool.
Definition client.cc:1928
void start()
Starts running the client's thread pool, if multi-threaded.
Definition client.cc:1832
void stop()
Close all connections, and if multi-threaded, stops the client's thread pool.
Definition client.cc:1840
asiolink::IOServicePtr getThreadIOService()
Fetches the internal IOService used in multi-threaded mode.
Definition client.cc:1921
void checkPermissions()
Check if the current thread can perform thread pool state transition.
Definition client.cc:1825
bool isPaused()
Indicates if the thread pool is paused.
Definition client.cc:1909
void resume()
Resumes running the client's thread pool.
Definition client.cc:1872
HttpClientImpl(const IOServicePtr &io_service, size_t thread_pool_size=0, bool defer_thread_start=false)
Constructor.
Definition client.cc:1789
bool isStopped()
Indicates if the thread pool is stopped.
Definition client.cc:1897
bool isRunning()
Indicates if the thread pool is running.
Definition client.cc:1885
uint16_t getThreadCount() const
Fetches the number of threads in the pool.
Definition client.cc:2051
bool isRunning()
Indicates if the thread pool is running.
Definition client.cc:2056
HttpClient(const asiolink::IOServicePtr &io_service, bool multi_threading_enabled, size_t thread_pool_size=0, bool defer_thread_start=false)
Constructor.
Definition client.cc:1958
void stop()
Halts client-side IO activity.
Definition client.cc:2036
bool isPaused()
Indicates if the thread pool is paused.
Definition client.cc:2066
std::function< bool(const boost::system::error_code &, const int) ConnectHandler)
Optional handler invoked when client connects to the server.
Definition client.h:114
void pause()
Pauses the client's thread pool.
Definition client.cc:2026
std::function< void(const int)> CloseHandler
Optional handler invoked when client closes the connection to the server.
Definition client.h:132
const asiolink::IOServicePtr getThreadIOService() const
Fetches a pointer to the internal IOService used to drive the thread-pool in multi-threaded mode.
Definition client.cc:2041
void start()
Starts running the client's thread pool, if multi-threaded.
Definition client.cc:2016
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
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
uint16_t getThreadPoolSize() const
Fetches the maximum size of the thread pool.
Definition client.cc:2046
void closeIfOutOfBand(int socket_fd)
Closes a connection if it has an out-of-band socket event.
Definition client.cc:2011
~HttpClient()
Destructor.
Definition client.cc:1970
void resume()
Resumes running the client's thread pool.
Definition client.cc:2031
void asyncSendRequest(const Url &url, const asiolink::TlsContextPtr &tls_context, const HttpRequestPtr &request, const HttpResponsePtr &response, const RequestHandler &request_callback, const RequestTimeout &request_timeout=RequestTimeout(10000), const ConnectHandler &connect_callback=ConnectHandler(), const HandshakeHandler &handshake_callback=HandshakeHandler(), const CloseHandler &close_callback=CloseHandler())
Queues new asynchronous HTTP request for a given URL.
Definition client.cc:1975
bool isStopped()
Indicates if the thread pool is stopped.
Definition client.cc:2061
void checkPermissions()
Check if the current thread can perform thread pool state transition.
Definition client.cc:2021
static std::string logFormatHttpMessage(const std::string &message, const size_t limit=0)
Formats provided HTTP message for logging.
A generic parser for HTTP responses.
Represents an URL.
Definition url.h:20
static MultiThreadingMgr & instance()
Returns a single instance of Multi Threading Manager.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition macros.h:26
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
const isc::log::MessageID HTTP_CLIENT_MT_STARTED
const isc::log::MessageID HTTP_CONNECTION_CLOSE_CALLBACK_FAILED
const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED
isc::log::Logger http_logger("http")
Defines the logger used within libkea-http library.
Definition http_log.h:18
const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED
boost::shared_ptr< HttpResponseParser > HttpResponseParserPtr
Pointer to the HttpResponseParser.
const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND
boost::shared_ptr< HttpResponse > HttpResponsePtr
Pointer to the HttpResponse object.
Definition response.h:81
const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS
const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND_DETAILS
const isc::log::MessageID HTTP_CLIENT_QUEUE_SIZE_GROWING
const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED_DETAILS
boost::shared_ptr< HttpRequest > HttpRequestPtr
Pointer to the HttpRequest object.
Definition request.h:30
const isc::log::MessageID HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED
const int DBGLVL_TRACE_BASIC
Trace basic operations.
const int DBGLVL_TRACE_DETAIL_DATA
Trace data associated with detailed operations.
const int DBGLVL_TRACE_BASIC_DATA
Trace data associated with the basic operations.
const int DBGLVL_TRACE_DETAIL
Trace detailed operations.
Defines the logger used by the top-level component of kea-lfc.
HTTP request/response timeout value.
Definition client.h:89