summaryrefslogtreecommitdiffstats
path: root/src/lib/http/client.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/http/client.h')
-rw-r--r--src/lib/http/client.h342
1 files changed, 342 insertions, 0 deletions
diff --git a/src/lib/http/client.h b/src/lib/http/client.h
new file mode 100644
index 0000000..69c441c
--- /dev/null
+++ b/src/lib/http/client.h
@@ -0,0 +1,342 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_CLIENT_H
+#define HTTP_CLIENT_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/tls_socket.h>
+#include <exceptions/exceptions.h>
+#include <http/url.h>
+#include <http/request.h>
+#include <http/response.h>
+#include <http/http_thread_pool.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <string>
+#include <thread>
+#include <vector>
+
+namespace isc {
+namespace http {
+
+/// @brief A generic error raised by the @ref HttpClient class.
+class HttpClientError : public Exception {
+public:
+ HttpClientError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class HttpClientImpl;
+
+/// @brief HTTP client class.
+///
+/// This class implements an asynchronous HTTP client. The caller can schedule
+/// transmission of the HTTP request using @ref HttpClient::asyncSendRequest
+/// method. The caller specifies target URL for each request. The caller also
+/// specifies a pointer to the @ref HttpRequest or derived class, holding a
+/// request that should be transmitted to the destination. Such request must
+/// be finalized, i.e. @ref HttpRequest::finalize method must be called prior
+/// to sending it. The caller must also provide a pointer to the
+/// @ref HttpResponse object or an object derived from it. The type of the
+/// response object must match the expected content type to be returned in the
+/// server's response. The last argument specified in this call is the pointer
+/// to the callback function, which should be launched when the response is
+/// received, an error occurs or when a timeout in the transmission is
+/// signaled.
+///
+/// The HTTP client supports multiple simultaneous and persistent connections
+/// with different destinations. The client determines if the connection is
+/// persistent by looking into the Connection header and HTTP version of the
+/// request. If the connection should be persistent the client doesn't
+/// close the connection after sending a request and receiving a response from
+/// the server. If the client is provided with the request to be sent to the
+/// particular destination, but there is an ongoing communication with this
+/// destination, e.g. as a result of sending previous request, the new
+/// request is queued in the FIFO queue. When the previous request completes,
+/// the next request in the queue for the particular URL will be initiated.
+///
+/// Furthermore, the class supports two modes of operation: single-threaded
+/// and multi-threaded mode. In single-threaded mode, all IO is driven by
+/// an external IOService passed into the class constructor, and ultimately
+/// only a single connection per URL can be open at any given time.
+///
+/// In multi-threaded mode an internal thread pool driven by a private
+/// IOService instance is used to support multiple concurrent connections
+/// per URL. Currently, the number of connections per URL is set to the
+/// number of threads in the thread pool.
+///
+/// The client tests the persistent connection for usability before sending
+/// a request by trying to read from the socket (with message peeking). If
+/// the socket is usable the client uses it to transmit the request.
+///
+/// This classes exposes the underlying transport socket's descriptor for
+/// each connection via connect, handshake and close callbacks.
+/// This is done to permit the sockets to be monitored for IO readiness
+/// by external code that's something other than boost::asio
+/// (e.g.select() or epoll()), and would thus otherwise starve the
+/// client's IOService and cause a backlog of ready event handlers.
+///
+/// All errors are reported to the caller via the callback function supplied
+/// to the @ref HttpClient::asyncSendRequest. The IO errors are communicated
+/// via the @c boost::system::error code value. The response parsing errors
+/// are returned via the 3rd parameter of the callback.
+class HttpClient {
+public:
+ /// @brief HTTP request/response timeout value.
+ struct RequestTimeout {
+ /// @brief Constructor.
+ ///
+ /// @param value Request/response timeout value in milliseconds.
+ explicit RequestTimeout(long value)
+ : value_(value) {
+ }
+ long value_; ///< Timeout value specified.
+ };
+
+ /// @brief Callback type used in call to @ref HttpClient::asyncSendRequest.
+ typedef std::function<void(const boost::system::error_code&,
+ const HttpResponsePtr&,
+ const std::string&)> RequestHandler;
+
+ /// @brief Optional handler invoked when client connects to the server.
+ ///
+ /// Returned boolean value indicates whether the client should continue
+ /// connecting to the server (if true) or not (false).
+ /// It is passed the IO error code along with the native socket handle of
+ /// the connection's TCP socket. The passed socket descriptor may be used
+ /// to monitor the readiness of the events via select() or epoll().
+ ///
+ /// @note Beware that the IO error code can be set to "in progress"
+ /// so a not null error code does not always mean the connect failed.
+ typedef std::function<bool(const boost::system::error_code&, const int)> ConnectHandler;
+
+ /// @brief Optional handler invoked when client performs the TLS handshake
+ /// with the server.
+ ///
+ /// It is called when the TLS handshake completes:
+ /// - if the handshake succeeds it is called with error code 0
+ /// - if the handshake fails it is called with error code != 0
+ /// - if TLS is not enabled, it is not called at all
+ ///
+ /// Returned boolean value indicates whether the client should continue
+ /// connecting to the server (if true) or not (false).
+ /// @note The second argument is not used.
+ typedef std::function<bool(const boost::system::error_code&, const int)> HandshakeHandler;
+
+ /// @brief Optional handler invoked when client closes the connection to the server.
+ ///
+ /// It is passed the native socket handler of the connection's TCP socket.
+ typedef std::function<void(const int)> CloseHandler;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the HTTP client.
+ /// @param thread_pool_size maximum number of threads in the thread pool.
+ /// A value greater than zero enables multi-threaded mode and sets the
+ /// maximum number of concurrent connections per URL. A value of zero
+ /// (default) enables single-threaded mode with one connection per URL.
+ /// @param defer_thread_start When true, starting of the pool threads is
+ /// deferred until a subsequent call to @ref start(). In this case the
+ /// pool's operational state after construction is STOPPED. Otherwise,
+ /// the thread pool threads will be created and started, with the
+ /// operational state being RUNNING. Applicable only when thread-pool size
+ /// is greater than zero.
+ explicit HttpClient(asiolink::IOService& io_service, size_t thread_pool_size = 0,
+ bool defer_thread_start = false);
+
+ /// @brief Destructor.
+ ~HttpClient();
+
+ /// @brief Queues new asynchronous HTTP request for a given URL.
+ ///
+ /// The client maintains an internal connection pool which manages lists
+ /// of connections per URL. In single-threaded mode, each URL is limited
+ /// to a single connection. In multi-threaded mode, each URL may have
+ /// more than one open connection per URL, enabling the client to carry
+ /// on multiple concurrent requests per URL.
+ ///
+ /// The client will search the pool for an open, idle connection for the
+ /// given URL. If there are no idle connections, the client will open
+ /// a new connection up to the maximum number of connections allowed by the
+ /// thread mode. If all possible connections are busy, the request is
+ /// pushed on to back of a URL-specific FIFO queue of pending requests.
+ ///
+ /// If however, there is an idle connection available than a new transaction
+ /// for the request will be initiated immediately upon that connection.
+ ///
+ /// Note that when a connection completes a transaction, and its URL
+ /// queue is not empty, it will pop a pending request from the front of
+ /// the queue and begin a new transaction for that request. The net effect
+ /// is that requests are always pulled from the front of the queue unless
+ /// the queue is empty.
+ ///
+ /// The existing connection is tested before it is used for the new
+ /// transaction by attempting to read (with message peeking) from
+ /// the open transport socket. If the read attempt is successful,
+ /// the client will transmit the HTTP request to the server using
+ /// this connection. It is possible that the server closes the
+ /// connection between the connection test and sending the request.
+ /// In such case, an error will be returned and the caller will
+ /// need to try re-sending the request.
+ ///
+ /// If the connection test fails, the client will close the socket and
+ /// reconnect to the server prior to sending the request.
+ ///
+ /// Pointers to the request and response objects are provided as arguments
+ /// of this method. These pointers should have appropriate types derived
+ /// from the @ref HttpRequest and @ref HttpResponse classes. For example,
+ /// if the request has content type "application/json", a pointer to the
+ /// @ref HttpResponseJson should be passed. In this case, the response type
+ /// should be @ref HttpResponseJson. These types are used to validate both
+ /// the request provided by the caller and the response received from the
+ /// server.
+ ///
+ /// The callback function provided by the caller is invoked when the
+ /// transaction terminates, i.e. when the server has responded or when an
+ /// error occurred. The callback is expected to be exception safe, but the
+ /// client internally guards against exceptions thrown by the callback.
+ ///
+ /// The first argument of the callback indicates an IO error during
+ /// communication with the server. If the communication is successful the
+ /// error code of 0 is returned. However, in this case it is still possible
+ /// that the transaction is unsuccessful due to HTTP response parsing error,
+ /// e.g. invalid content type, malformed response etc. Such errors are
+ /// indicated via third argument.
+ ///
+ /// If message parsing was successful the second argument of the callback
+ /// contains a pointer to the parsed response (the same pointer as provided
+ /// by the caller as the argument). If parsing was unsuccessful, the null
+ /// pointer is returned.
+ ///
+ /// The default timeout for the transaction is set to 10 seconds
+ /// (10 000 ms). If the timeout occurs, the callback is invoked with the
+ /// error code of @c boost::asio::error::timed_out.
+ /// The timeout covers both the connect and the transaction phases
+ /// so when connecting to the server takes too long (e.g. with a
+ /// misconfigured firewall) the timeout is triggered. The connect
+ /// callback can be used to recognize this condition.
+ ///
+ /// @param url URL where the request should be send.
+ /// @param tls_context TLS context.
+ /// @param request Pointer to the object holding a request.
+ /// @param response Pointer to the object where response should be stored.
+ /// @param request_callback Pointer to the user callback function invoked
+ /// when transaction ends.
+ /// @param request_timeout Timeout for the transaction in milliseconds.
+ /// @param connect_callback Optional callback invoked when the client
+ /// connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback Optional callback invoked when the client
+ /// closes the connection to the server.
+ ///
+ /// @throw HttpClientError If invalid arguments were provided.
+ 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());
+
+ /// @brief Check if the current thread can perform thread pool state
+ /// transition.
+ ///
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPermissions();
+
+ /// @brief Starts running the client's thread pool, if multi-threaded.
+ void start();
+
+ /// @brief Pauses the client's thread pool.
+ ///
+ /// Suspends thread pool event processing.
+ /// @throw InvalidOperation if the thread pool does not exist.
+ void pause();
+
+ /// @brief Resumes running the client's thread pool.
+ ///
+ /// Resumes thread pool event processing.
+ /// @throw InvalidOperation if the thread pool does not exist.
+ void resume();
+
+ /// @brief Halts client-side IO activity.
+ ///
+ /// Closes all connections, discards any queued requests, and in
+ /// multi-threaded mode discards the thread-pool and the internal
+ /// IOService.
+ void stop();
+
+ /// @brief Closes a connection if it has an out-of-band socket event
+ ///
+ /// If the client owns a connection using the given socket and that
+ /// connection is currently in a transaction the method returns as this
+ /// indicates a normal ready event. If the connection is not in an
+ /// ongoing transaction, then the connection is closed.
+ ///
+ /// This is method is intended to be used to detect and clean up then
+ /// sockets that are marked ready outside of transactions. The most common
+ /// case is the other end of the socket being closed.
+ ///
+ /// @param socket_fd socket descriptor to check
+ void closeIfOutOfBand(int socket_fd);
+
+ /// @brief Fetches a pointer to the internal IOService used to
+ /// drive the thread-pool in multi-threaded mode.
+ ///
+ /// @return pointer to the IOService instance, or an empty pointer
+ /// in single-threaded mode.
+ const asiolink::IOServicePtr getThreadIOService() const;
+
+ /// @brief Fetches the maximum size of the thread pool.
+ ///
+ /// @return the maximum size of the thread pool.
+ uint16_t getThreadPoolSize() const;
+
+ /// @brief Fetches the number of threads in the pool.
+ ///
+ /// @return the number of running threads.
+ uint16_t getThreadCount() const;
+
+ /// @brief Indicates if the thread pool is running.
+ ///
+ /// @return True if the thread pool exists and it is in the RUNNING state,
+ /// false otherwise.
+ bool isRunning();
+
+ /// @brief Indicates if the thread pool is stopped.
+ ///
+ /// @return True if the thread pool exists and it is in the STOPPED state,
+ /// false otherwise.
+ bool isStopped();
+
+ /// @brief Indicates if the thread pool is paused.
+ ///
+ /// @return True if the thread pool exists and it is in the PAUSED state,
+ /// false otherwise.
+ bool isPaused();
+
+private:
+
+ /// @brief Pointer to the HTTP client implementation.
+ boost::shared_ptr<HttpClientImpl> impl_;
+};
+
+/// @brief Defines a pointer to an HttpClient instance.
+typedef boost::shared_ptr<HttpClient> HttpClientPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif