]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#491,!363] HTTP listner associates ASIO handlers with transactions.
authorMarcin Siodelski <marcin@isc.org>
Thu, 6 Jun 2019 09:49:35 +0000 (11:49 +0200)
committerMarcin Siodelski <marcin@isc.org>
Tue, 18 Jun 2019 07:17:08 +0000 (03:17 -0400)
src/lib/http/Makefile.am
src/lib/http/connection.cc
src/lib/http/connection.h
src/lib/http/listener.cc
src/lib/http/listener.h
src/lib/http/listener_impl.cc [new file with mode: 0644]
src/lib/http/listener_impl.h [new file with mode: 0644]

index 27316d1e1165ee3af188cbeeb76a3910f00decce..3b3e60a86b3cef45d0c062787ea0506a3b5b45ce 100644 (file)
@@ -23,6 +23,7 @@ libkea_http_la_SOURCES += http_message_parser_base.cc http_message_parser_base.h
 libkea_http_la_SOURCES += http_messages.cc http_messages.h
 libkea_http_la_SOURCES += http_types.h
 libkea_http_la_SOURCES += listener.cc listener.h
+libkea_http_la_SOURCES += listener_impl.cc listener_impl.h
 libkea_http_la_SOURCES += post_request.cc post_request.h
 libkea_http_la_SOURCES += post_request_json.cc post_request_json.h
 libkea_http_la_SOURCES += request.cc request.h
@@ -99,6 +100,7 @@ libkea_http_include_HEADERS = \
        http_messages.h \
        http_types.h \
        listener.h \
+       listener_impl.h \
        post_request.h \
        post_request_json.h \
        request.h \
index ebb6a7ffb66cb1e9c0505efef69b427f5594887c..331106b49e9aeda9e98caf769ee2e615e4f7c99c 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2019 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
@@ -12,6 +12,7 @@
 #include <http/http_log.h>
 #include <http/http_messages.h>
 #include <boost/bind.hpp>
+#include <boost/make_shared.hpp>
 
 using namespace isc::asiolink;
 
@@ -27,6 +28,30 @@ constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
 namespace isc {
 namespace http {
 
+HttpConnection::Transaction::Transaction(const HttpResponseCreatorPtr& response_creator,
+                                         const HttpRequestPtr& request)
+    : request_(request ? request : response_creator->createNewHttpRequest()),
+      parser_(new HttpRequestParser(*request_)),
+      input_buf_(),
+      output_buf_() {
+    parser_->initModel();
+}
+
+HttpConnection::TransactionPtr
+HttpConnection::Transaction::create(const HttpResponseCreatorPtr& response_creator) {
+    return (boost::make_shared<Transaction>(response_creator));
+}
+
+HttpConnection::TransactionPtr
+HttpConnection::Transaction::spawn(const HttpResponseCreatorPtr& response_creator,
+                                   const TransactionPtr& transaction) {
+    if (transaction) {
+        return (boost::make_shared<Transaction>(response_creator,
+                                                transaction->getRequest()));
+    }
+    return (create(response_creator));
+}
+
 void
 HttpConnection::
 SocketCallback::operator()(boost::system::error_code ec, size_t length) {
@@ -36,13 +61,13 @@ SocketCallback::operator()(boost::system::error_code ec, size_t length) {
     callback_(ec, length);
 }
 
-HttpConnection:: HttpConnection(asiolink::IOService& io_service,
-                                HttpAcceptor& acceptor,
-                                HttpConnectionPool& connection_pool,
-                                const HttpResponseCreatorPtr& response_creator,
-                                const HttpAcceptorCallback& callback,
-                                const long request_timeout,
-                                const long idle_timeout)
+HttpConnection::HttpConnection(asiolink::IOService& io_service,
+                               HttpAcceptor& acceptor,
+                               HttpConnectionPool& connection_pool,
+                               const HttpResponseCreatorPtr& response_creator,
+                               const HttpAcceptorCallback& callback,
+                               const long request_timeout,
+                               const long idle_timeout)
     : request_timer_(io_service),
       request_timeout_(request_timeout),
       idle_timeout_(idle_timeout),
@@ -50,12 +75,7 @@ HttpConnection:: HttpConnection(asiolink::IOService& io_service,
       acceptor_(acceptor),
       connection_pool_(connection_pool),
       response_creator_(response_creator),
-      request_(response_creator_->createNewHttpRequest()),
-      parser_(new HttpRequestParser(*request_)),
-      acceptor_callback_(callback),
-      buf_(),
-      output_buf_() {
-    parser_->initModel();
+      acceptor_callback_(callback) {
 }
 
 HttpConnection::~HttpConnection() {
@@ -98,17 +118,26 @@ HttpConnection::asyncAccept() {
 }
 
 void
-HttpConnection::doRead() {
+HttpConnection::doRead(TransactionPtr transaction) {
     try {
         TCPEndpoint endpoint;
+
+        // Transaction is was not created if we are starting to read the
+        // new request.
+        if (!transaction) {
+            transaction = Transaction::create(response_creator_);
+        }
+
         // Create instance of the callback. It is safe to pass the local instance
         // of the callback, because the underlying boost functions make copies
         // as needed.
         SocketCallback cb(boost::bind(&HttpConnection::socketReadCallback,
                                       shared_from_this(),
+                                      transaction,
                                       boost::asio::placeholders::error,
                                       boost::asio::placeholders::bytes_transferred));
-        socket_.asyncReceive(static_cast<void*>(buf_.data()), buf_.size(),
+        socket_.asyncReceive(static_cast<void*>(transaction->getInputBufData()),
+                             transaction->getInputBufSize(),
                              0, &endpoint, cb);
 
     } catch (...) {
@@ -117,25 +146,34 @@ HttpConnection::doRead() {
 }
 
 void
-HttpConnection::doWrite() {
+HttpConnection::doWrite(HttpConnection::TransactionPtr transaction) {
     try {
-        if (!output_buf_.empty()) {
+        if (transaction->outputDataAvail()) {
             // Create instance of the callback. It is safe to pass the local instance
             // of the callback, because the underlying boost functions make copies
             // as needed.
             SocketCallback cb(boost::bind(&HttpConnection::socketWriteCallback,
                                           shared_from_this(),
+                                          transaction,
                                           boost::asio::placeholders::error,
                                           boost::asio::placeholders::bytes_transferred));
-            socket_.asyncSend(output_buf_.data(),
-                              output_buf_.length(),
+            socket_.asyncSend(transaction->getOutputBufData(),
+                              transaction->getOutputBufSize(),
                               cb);
         } else {
-            if (!request_->isPersistent()) {
+            // The isPersistent() function may throw if the request hasn't
+            // been created, i.e. the HTTP headers weren't parsed. We catch
+            // this exception below and close the connection since we're
+            // unable to tell if the connection should remain persistent
+            // or not. The default is to close it.
+            if (!transaction->getRequest()->isPersistent()) {
                 stopThisConnection();
 
             } else {
-                reinitProcessingState();
+                // The connection is persistent and we are done sending
+                // the previous response. Start listening for the next
+                // requests.
+                setupIdleTimer();
                 doRead();
             }
         }
@@ -145,9 +183,10 @@ HttpConnection::doWrite() {
 }
 
 void
-HttpConnection::asyncSendResponse(const ConstHttpResponsePtr& response) {
-    output_buf_ = response->toString();
-    doWrite();
+HttpConnection::asyncSendResponse(const ConstHttpResponsePtr& response,
+                                  TransactionPtr transaction) {
+    transaction->setOutputBuf(response->toString());
+    doWrite(transaction);
 }
 
 
@@ -175,7 +214,8 @@ HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
 }
 
 void
-HttpConnection::socketReadCallback(boost::system::error_code ec, size_t length) {
+HttpConnection::socketReadCallback(HttpConnection::TransactionPtr transaction,
+                                   boost::system::error_code ec, size_t length) {
     if (ec) {
         // IO service has been stopped and the connection is probably
         // going to be shutting down.
@@ -198,7 +238,7 @@ HttpConnection::socketReadCallback(boost::system::error_code ec, size_t length)
     }
 
     // Receiving is in progress, so push back the timeout.
-    setupRequestTimer();
+    setupRequestTimer(transaction);
 
     if (length != 0) {
         LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
@@ -206,17 +246,20 @@ HttpConnection::socketReadCallback(boost::system::error_code ec, size_t length)
             .arg(length)
             .arg(getRemoteEndpointAddressAsText());
 
-        std::string s(&buf_[0], buf_[0] + length);
-        parser_->postBuffer(static_cast<void*>(buf_.data()), length);
-        parser_->poll();
+        transaction->getParser()->postBuffer(static_cast<void*>(transaction->getInputBufData()),
+                                             length);
+        transaction->getParser()->poll();
     }
 
-    if (parser_->needData()) {
-        doRead();
+    if (transaction->getParser()->needData()) {
+        // The parser indicates that the some part of the message being
+        // received is still missing, so continue to read.
+        doRead(transaction);
 
     } else {
         try {
-            request_->finalize();
+            // The whole message has been received, so let's finalize it.
+            transaction->getRequest()->finalize();
 
             LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
                       HTTP_CLIENT_REQUEST_RECEIVED)
@@ -225,7 +268,7 @@ HttpConnection::socketReadCallback(boost::system::error_code ec, size_t length)
             LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
                       HTTP_CLIENT_REQUEST_RECEIVED_DETAILS)
                 .arg(getRemoteEndpointAddressAsText())
-                .arg(parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
+                .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
 
         } catch (const std::exception& ex) {
             LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
@@ -236,13 +279,15 @@ HttpConnection::socketReadCallback(boost::system::error_code ec, size_t length)
             LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
                       HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS)
                 .arg(getRemoteEndpointAddressAsText())
-                .arg(parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
+                .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
         }
 
         // Don't want to timeout if creation of the response takes long.
         request_timer_.cancel();
 
-        HttpResponsePtr response = response_creator_->createHttpResponse(request_);
+        // Create the response from the received request using the custom
+        // response creator.
+        HttpResponsePtr response = response_creator_->createHttpResponse(transaction->getRequest());
         LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
                   HTTP_SERVER_RESPONSE_SEND)
             .arg(response->toBriefString())
@@ -254,15 +299,17 @@ HttpConnection::socketReadCallback(boost::system::error_code ec, size_t length)
             .arg(HttpMessageParserBase::logFormatHttpMessage(response->toString(),
                                                              MAX_LOGGED_MESSAGE_SIZE));
 
-        // Response created. Active timer again.
-        setupRequestTimer();
+        // Response created. Activate the timer again.
+        setupRequestTimer(transaction);
 
-        asyncSendResponse(response);
+        // Start sending the response.
+        asyncSendResponse(response, transaction);
     }
 }
 
 void
-HttpConnection::socketWriteCallback(boost::system::error_code ec, size_t length) {
+HttpConnection::socketWriteCallback(HttpConnection::TransactionPtr transaction,
+                                    boost::system::error_code ec, size_t length) {
     if (ec) {
         // IO service has been stopped and the connection is probably
         // going to be shutting down.
@@ -279,49 +326,41 @@ HttpConnection::socketWriteCallback(boost::system::error_code ec, size_t length)
         // read something from the socket on the next attempt.
         } else {
             // Sending is in progress, so push back the timeout.
-            setupRequestTimer();
+            setupRequestTimer(transaction);
 
-            doWrite();
+            doWrite(transaction);
         }
     }
 
+    // Since each transaction has its own output buffer, it is not really
+    // possible that the number of bytes written is larger than the size
+    // of the buffer. But, let's be safe and set the length to the size
+    // of the buffer if that unexpected condition occurs.
+    if (length > transaction->getOutputBufSize()) {
+        length = transaction->getOutputBufSize();
+    }
 
-    if (length <= output_buf_.size()) {
+    if (length <= transaction->getOutputBufSize()) {
         // Sending is in progress, so push back the timeout.
-        setupRequestTimer();
-
-        output_buf_.erase(0, length);
-        doWrite();
-
-    } else {
-        output_buf_.clear();
-
-        if (!request_->isPersistent()) {
-            stopThisConnection();
-
-        } else {
-            reinitProcessingState();
-            doRead();
-        }
+        setupRequestTimer(transaction);
     }
-}
 
-void
-HttpConnection::reinitProcessingState() {
-    request_ = response_creator_->createNewHttpRequest();
-    parser_.reset(new HttpRequestParser(*request_));
-    parser_->initModel();
-    setupIdleTimer();
+    // Eat the 'length' number of bytes from the output buffer and only
+    // leave the part of the response that hasn't been sent.
+    transaction->consumeOutputBuf(length);
+
+    // Schedule the write of the unsent data.
+    doWrite(transaction);
 }
 
 void
-HttpConnection::setupRequestTimer() {
+HttpConnection::setupRequestTimer(TransactionPtr transaction) {
     // Pass raw pointer rather than shared_ptr to this object,
     // because IntervalTimer already passes shared pointer to the
     // IntervalTimerImpl to make sure that the callback remains
     // valid.
     request_timer_.setup(boost::bind(&HttpConnection::requestTimeoutCallback,
-                                     this),
+                                     this, transaction),
                          request_timeout_, IntervalTimer::ONE_SHOT);
 }
 
@@ -333,14 +372,39 @@ HttpConnection::setupIdleTimer() {
 }
 
 void
-HttpConnection::requestTimeoutCallback() {
+HttpConnection::requestTimeoutCallback(TransactionPtr transaction) {
     LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
               HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED)
         .arg(getRemoteEndpointAddressAsText());
+
+    // We need to differentiate the transactions between a normal response and the
+    // timeout. We create new transaction from the current transaction. It is
+    // to preserve the request we're responding to.
+    transaction = Transaction::spawn(response_creator_, transaction);
+
+    // The new transaction inherits the request from the original transaction
+    // if such transaction exists.
+    auto request = transaction->getRequest();
+
+    // Depending on when the timeout occured, the HTTP version of the request
+    // may or may not be available. Therefore we check if the HTTP version is
+    // set in the request. If it is not available, we need to create a dummy
+    // request with the default HTTP/1.0 version. This version will be used
+    // in the response.
+    if (request->context()->http_version_major_ == 0) {
+        request.reset(new HttpRequest(HttpRequest::Method::HTTP_POST, "/",
+                                      HttpVersion::HTTP_10(),
+                                      HostHttpHeader("dummy")));
+        request->finalize();
+    }
+
+    // Create the timeout response.
     HttpResponsePtr response =
-        response_creator_->createStockHttpResponse(request_,
+        response_creator_->createStockHttpResponse(request,
                                                    HttpStatusCode::REQUEST_TIMEOUT);
-    asyncSendResponse(response);
+
+    // Send the HTTP 408 status.
+    asyncSendResponse(response, transaction);
 }
 
 void
index c8800a13f597656a2fdbf360b94382b9fcafa298..c7ec7224d1de344cc1fb2e5d978217e2720f733f 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2019 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
@@ -78,6 +78,136 @@ private:
         SocketCallbackFunction callback_;
     };
 
+protected:
+
+    class Transaction;
+
+    /// @brief Shared pointer to the @c Transaction.
+    typedef boost::shared_ptr<Transaction> TransactionPtr;
+
+    /// @brief Represents a single exchange of the HTTP messages.
+    ///
+    /// In HTTP/1.1 multiple HTTP message exchanges may be conducted
+    /// over the same persistent connection before the connection is
+    /// closed. Since ASIO handlers for these exchanges may be sometimes
+    /// executed out of order, there is a need to associate the states of
+    /// the exchanges with the appropriate ASIO handlers. This object
+    /// represents such state and includes: received request, request
+    /// parser (being in the particular state of parsing), input buffer
+    /// and the output buffer.
+    ///
+    /// A pointer to the @c Transaction object is passed to the ASIO
+    /// callbacks when the new message exchange begins. It is passed
+    /// between the callbacks until the message exchange is completed.
+    class Transaction {
+    public:
+
+        /// @brief Constructor.
+        ///
+        /// @param response_creator Pointer to the response creator being
+        /// used for generating a response from the request.
+        /// @param request Pointer to the HTTP request. If the request is
+        /// null, the constructor creates new request instance using the
+        /// provided response creator.
+        Transaction(const HttpResponseCreatorPtr& response_creator,
+                    const HttpRequestPtr& request = HttpRequestPtr());
+
+        /// @brief Creates new transaction instance.
+        ///
+        /// It is called when the HTTP server has just scheduled asynchronous
+        /// reading of the HTTP message.
+        ///
+        /// @param response_creator Pointer to the response creator to be passed
+        /// to the transaction's constructor.
+        ///
+        /// @return Pointer to the created transaction instance.
+        static TransactionPtr create(const HttpResponseCreatorPtr& response_creator);
+
+        /// @brief Creates new transaction from the current transaction.
+        ///
+        /// This method creates new transaction and inherits the request
+        /// from the existing transaction. This is used when the timeout
+        /// occurs during the messages exchange. The server creates the new
+        /// transaction to handle the timeout but this new transaction must
+        /// include the request instance so as HTTP version information can
+        /// be inferred from it while sending the timeout response. The
+        /// HTTP version information should match between the request and
+        /// the response.
+        ///
+        /// @param response_creator Pointer to the response creator.
+        /// @param transaction Existing transaction from which the request
+        /// should be inherited. If the transaction is null, the new (dummy)
+        /// request is created for the new transaction.
+        static TransactionPtr spawn(const HttpResponseCreatorPtr& response_creator,
+                                    const TransactionPtr& transaction);
+
+        /// @brief Returns request instance associated with the transaction.
+        HttpRequestPtr getRequest() const {
+            return (request_);
+        }
+
+        /// @brief Returns parser instance associated with the transaction.
+        HttpRequestParserPtr getParser() const {
+            return (parser_);
+        }
+
+        /// @brief Returns pointer to the first byte of the input buffer.
+        char* getInputBufData() {
+            return (input_buf_.data());
+        }
+
+        /// @brief Returns input buffer size.
+        size_t getInputBufSize() const {
+            return (input_buf_.size());
+        }
+
+        /// @brief Checks if the output buffer contains some data to be
+        /// sent.
+        ///
+        /// @return true if the output buffer contains data to be sent,
+        /// false otherwise.
+        bool outputDataAvail() const {
+            return (!output_buf_.empty());
+        }
+
+        /// @brief Returns pointer to the first byte of the output buffer.
+        const char* getOutputBufData() const {
+            return (output_buf_.data());
+        }
+
+        /// @brief Returns size of the output buffer.
+        size_t getOutputBufSize() const {
+            return (output_buf_.size());
+        }
+
+        /// @brief Replaces output buffer contents with new contents.
+        ///
+        /// @param response New contents for the output buffer.
+        void setOutputBuf(const std::string& response) {
+            output_buf_ = response;
+        }
+
+        /// @brief Erases n bytes from the beginning of the output buffer.
+        ///
+        /// @param length Number of bytes to be erased.
+        void consumeOutputBuf(const size_t length) {
+            output_buf_.erase(0, length);
+        }
+
+    private:
+
+        /// @brief Pointer to the request received over this connection.
+        HttpRequestPtr request_;
+
+        /// @brief Pointer to the HTTP request parser.
+        HttpRequestParserPtr parser_;
+
+        /// @brief Buffer for received data.
+        std::array<char, 32768> input_buf_;
+
+        /// @brief Buffer used for outbound data.
+        std::string output_buf_;
+    };
 
 public:
 
@@ -105,7 +235,7 @@ public:
     /// @brief Destructor.
     ///
     /// Closes current connection.
-    ~HttpConnection();
+    virtual ~HttpConnection();
 
     /// @brief Asynchronously accepts new connection.
     ///
@@ -124,23 +254,32 @@ public:
     /// HTTP response using supplied response creator object.
     ///
     /// In case of error the connection is stopped.
-    void doRead();
+    ///
+    /// @param transaction Pointer to the transaction for which the read
+    /// operation should be performed. It defaults to null pointer which
+    /// indicates that this function should create new transaction.
+    void doRead(TransactionPtr transaction = TransactionPtr());
 
-private:
+protected:
 
     /// @brief Starts asynchronous write to the socket.
     ///
     /// The @c output_buf_ must contain the data to be sent.
     ///
     /// In case of error the connection is stopped.
-    void doWrite();
+    ///
+    /// @param transaction Pointer to the transaction for which the write
+    /// operation should be performed.
+    void doWrite(TransactionPtr transaction);
 
     /// @brief Sends HTTP response asynchronously.
     ///
     /// Internally it calls @ref HttpConnection::doWrite to send the data.
     ///
     /// @param response Pointer to the HTTP response to be sent.
-    void asyncSendResponse(const ConstHttpResponsePtr& response);
+    /// @param transaction Pointer to the transaction.
+    void asyncSendResponse(const ConstHttpResponsePtr& response,
+                           TransactionPtr transaction);
 
     /// @brief Local callback invoked when new connection is accepted.
     ///
@@ -157,28 +296,28 @@ private:
     /// parsing. When the parser signals end of the HTTP request the callback
     /// prepares a response and starts asynchronous send over the socket.
     ///
+    /// @param transaction Pointer to the transaction for which the callback
+    /// is invoked.
     /// @param ec Error code.
     /// @param length Length of the received data.
-    void socketReadCallback(boost::system::error_code ec,
+    void socketReadCallback(TransactionPtr transaction,
+                            boost::system::error_code ec,
                             size_t length);
 
     /// @brief Callback invoked when data is sent over the socket.
     ///
+    /// @param transaction Pointer to the transaction for which the callback
+    /// is invoked.
     /// @param ec Error code.
     /// @param length Length of the data sent.
-    void socketWriteCallback(boost::system::error_code ec,
-                             size_t length);
-
-    /// @brief Reinitializes request processing state after sending a response.
-    ///
-    /// This method is only called for persistent connections, when the response
-    /// to a previous command has been sent. It initializes the state machine to
-    /// be able to process the next request. It also sets the persistent connection
-    /// idle timer to monitor the connection timeout.
-    void reinitProcessingState();
+    virtual void socketWriteCallback(TransactionPtr transaction,
+                                     boost::system::error_code ec,
+                                     size_t length);
 
     /// @brief Reset timer for detecting request timeouts.
-    void setupRequestTimer();
+    ///
+    /// @param transaction Pointer to the transaction to be guarded by the timeout.
+    void setupRequestTimer(TransactionPtr transaction = TransactionPtr());
 
     /// @brief Reset timer for detecing idle timeout in persistent connections.
     void setupIdleTimer();
@@ -187,7 +326,9 @@ private:
     ///
     /// This callback creates HTTP response with Request Timeout error code
     /// and sends it to the client.
-    void requestTimeoutCallback();
+    ///
+    /// @param transaction Pointer to the transaction for which timeout occurs.
+    void requestTimeoutCallback(TransactionPtr transaction);
 
     void idleTimeoutCallback();
 
@@ -220,20 +361,8 @@ private:
     /// HTTP responses.
     HttpResponseCreatorPtr response_creator_;
 
-    /// @brief Pointer to the request received over this connection.
-    HttpRequestPtr request_;
-
-    /// @brief Pointer to the HTTP request parser.
-    HttpRequestParserPtr parser_;
-
     /// @brief External TCP acceptor callback.
     HttpAcceptorCallback acceptor_callback_;
-
-    /// @brief Buffer for received data.
-    std::array<char, 32768> buf_;
-
-    /// @brief Buffer used for outbound data.
-    std::string output_buf_;
 };
 
 } // end of namespace isc::http
index e57d0ea449f8e404a8e255eeea80bab44a6a9044..720570a0469399b93d663de6f72e478a1b6addc7 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2019 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
 #include <config.h>
 
 #include <asiolink/asio_wrapper.h>
-#include <asiolink/tcp_endpoint.h>
-#include <http/connection.h>
-#include <http/connection_pool.h>
-#include <http/http_acceptor.h>
 #include <http/listener.h>
-#include <boost/scoped_ptr.hpp>
+#include <http/listener_impl.h>
 
 using namespace isc::asiolink;
 
 namespace isc {
 namespace http {
 
-/// @brief Implementation of the @ref HttpListener.
-class HttpListenerImpl {
-public:
-
-    /// @brief Constructor.
-    ///
-    /// This constructor creates new server endpoint using the specified IP
-    /// address and port. It also validates other specified parameters.
-    ///
-    /// This constructor does not start accepting new connections! To start
-    /// accepting connections run @ref HttpListener::start.
-    ///
-    /// @param io_service IO service to be used by the listener.
-    /// @param server_address Address on which the HTTP service should run.
-    /// @param server_port Port number on which the HTTP service should run.
-    /// @param creator_factory Pointer to the caller-defined
-    /// @ref HttpResponseCreatorFactory derivation which should be used to
-    /// create @ref HttpResponseCreator instances.
-    /// @param request_timeout Timeout after which the HTTP Request Timeout
-    /// is generated.
-    /// @param idle_timeout Timeout after which an idle persistent HTTP
-    /// connection is closed by the server.
-    ///
-    /// @throw HttpListenerError when any of the specified parameters is
-    /// invalid.
-    HttpListenerImpl(asiolink::IOService& io_service,
-                     const asiolink::IOAddress& server_address,
-                     const unsigned short server_port,
-                     const HttpResponseCreatorFactoryPtr& creator_factory,
-                     const long request_timeout,
-                     const long idle_timeout);
-
-    /// @brief Returns reference to the current listener endpoint.
-    const TCPEndpoint& getEndpoint() const;
-
-    /// @brief Starts accepting new connections.
-    ///
-    /// This method starts accepting and handling new HTTP connections on
-    /// the IP address and port number specified in the constructor.
-    ///
-    /// If the method is invoked successfully, it must not be invoked again
-    /// until @ref HttpListener::stop is called.
-    ///
-    /// @throw HttpListenerError if an error occurred.
-    void start();
-
-    /// @brief Stops all active connections and shuts down the service.
-    void stop();
-
-private:
-
-    /// @brief Creates @ref HttpConnection instance and adds it to the
-    /// pool of active connections.
-    ///
-    /// The next accepted connection will be handled by this instance.
-    void accept();
-
-    /// @brief Callback invoked when the new connection is accepted.
-    ///
-    /// It calls @ref HttpListener::accept to create new @ref HttpConnection
-    /// instance.
-    ///
-    /// @param ec Error code passed to the handler. This is currently ignored.
-    void acceptHandler(const boost::system::error_code& ec);
-
-    /// @brief Reference to the IO service.
-    asiolink::IOService& io_service_;
-
-    /// @brief Acceptor instance.
-    HttpAcceptor acceptor_;
-
-    /// @brief Pointer to the endpoint representing IP address and port on
-    /// which the service is running.
-    boost::scoped_ptr<asiolink::TCPEndpoint> endpoint_;
-
-    /// @brief Pool of active connections.
-    HttpConnectionPool connections_;
-
-    /// @brief Pointer to the @ref HttpResponseCreatorFactory.
-    HttpResponseCreatorFactoryPtr creator_factory_;
-
-    /// @brief Timeout for HTTP Request Timeout desired.
-    long request_timeout_;
-
-    /// @brief Timeout after which idle persistent connection is closed by
-    /// the server.
-    long idle_timeout_;
-};
-
-HttpListenerImpl::HttpListenerImpl(IOService& io_service,
-                                   const asiolink::IOAddress& server_address,
-                                   const unsigned short server_port,
-                                   const HttpResponseCreatorFactoryPtr& creator_factory,
-                                   const long request_timeout,
-                                   const long idle_timeout)
-    : io_service_(io_service), acceptor_(io_service),
-      endpoint_(), creator_factory_(creator_factory),
-      request_timeout_(request_timeout), idle_timeout_(idle_timeout) {
-    // Try creating an endpoint. This may cause exceptions.
-    try {
-        endpoint_.reset(new TCPEndpoint(server_address, server_port));
-
-    } catch (...) {
-        isc_throw(HttpListenerError, "unable to create TCP endpoint for "
-                  << server_address << ":" << server_port);
-    }
-
-    // The factory must not be null.
-    if (!creator_factory_) {
-        isc_throw(HttpListenerError, "HttpResponseCreatorFactory must not"
-                  " be null");
-    }
-
-    // Request timeout is signed and must be greater than 0.
-    if (request_timeout_ <= 0) {
-        isc_throw(HttpListenerError, "Invalid desired HTTP request timeout "
-                  << request_timeout_);
-    }
-
-    // Idle persistent connection timeout is signed and must be greater than 0.
-    if (idle_timeout_ <= 0) {
-        isc_throw(HttpListenerError, "Invalid desired HTTP idle persistent connection"
-                  " timeout " << idle_timeout_);
-    }
-}
-
-const TCPEndpoint&
-HttpListenerImpl::getEndpoint() const {
-    return (*endpoint_);
-}
-
-void
-HttpListenerImpl::start() {
-    try {
-        acceptor_.open(*endpoint_);
-        acceptor_.setOption(HttpAcceptor::ReuseAddress(true));
-        acceptor_.bind(*endpoint_);
-        acceptor_.listen();
-
-    } catch (const boost::system::system_error& ex) {
-        stop();
-        isc_throw(HttpListenerError, "unable to setup TCP acceptor for "
-                  "listening to the incoming HTTP requests: " << ex.what());
-    }
-
-    accept();
-}
-
-void
-HttpListenerImpl::stop() {
-    connections_.stopAll();
-    acceptor_.close();
-}
-
-void
-HttpListenerImpl::accept() {
-    // In some cases we may need HttpResponseCreator instance per connection.
-    // But, the factory may also return the same instance each time. It
-    // depends on the use case.
-    HttpResponseCreatorPtr response_creator = creator_factory_->create();
-    HttpAcceptorCallback acceptor_callback =
-        boost::bind(&HttpListenerImpl::acceptHandler, this, _1);
-    HttpConnectionPtr conn(new HttpConnection(io_service_, acceptor_,
-                                              connections_,
-                                              response_creator,
-                                              acceptor_callback,
-                                              request_timeout_,
-                                              idle_timeout_));
-    // Add this new connection to the pool.
-    connections_.start(conn);
-}
-
-void
-HttpListenerImpl::acceptHandler(const boost::system::error_code&) {
-    // The new connection has arrived. Set the acceptor to continue
-    // accepting new connections.
-    accept();
-}
-
 HttpListener::HttpListener(IOService& io_service,
                            const asiolink::IOAddress& server_address,
                            const unsigned short server_port,
@@ -233,6 +50,5 @@ HttpListener::stop() {
     impl_->stop();
 }
 
-
 } // end of namespace isc::http
 } // end of namespace isc
index 7c41bc200774991ec993db66224fd1d699316649..f64d3edd99f3f353df24a65765fdc86c26de1b5f 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2019 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
@@ -12,7 +12,7 @@
 #include <exceptions/exceptions.h>
 #include <http/response_creator_factory.h>
 #include <boost/shared_ptr.hpp>
-#include <stdint.h>
+#include <cstdint>
 
 namespace isc {
 namespace http {
@@ -126,11 +126,10 @@ public:
     /// @brief Stops all active connections and shuts down the service.
     void stop();
 
-private:
+protected:
 
     /// @brief Pointer to the implementation of the @ref HttpListener.
     boost::shared_ptr<HttpListenerImpl> impl_;
-
 };
 
 /// @brief Pointer to the @ref HttpListener.
diff --git a/src/lib/http/listener_impl.cc b/src/lib/http/listener_impl.cc
new file mode 100644 (file)
index 0000000..c10a279
--- /dev/null
@@ -0,0 +1,126 @@
+// Copyright (C) 2019 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/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <http/connection_pool.h>
+#include <http/listener.h>
+#include <http/listener_impl.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace http {
+
+HttpListenerImpl::HttpListenerImpl(IOService& io_service,
+                                   const asiolink::IOAddress& server_address,
+                                   const unsigned short server_port,
+                                   const HttpResponseCreatorFactoryPtr& creator_factory,
+                                   const long request_timeout,
+                                   const long idle_timeout)
+    : io_service_(io_service), acceptor_(io_service),
+      endpoint_(), connections_(),
+      creator_factory_(creator_factory),
+      request_timeout_(request_timeout), idle_timeout_(idle_timeout) {
+    // Try creating an endpoint. This may cause exceptions.
+    try {
+        endpoint_.reset(new TCPEndpoint(server_address, server_port));
+
+    } catch (...) {
+        isc_throw(HttpListenerError, "unable to create TCP endpoint for "
+                  << server_address << ":" << server_port);
+    }
+
+    // The factory must not be null.
+    if (!creator_factory_) {
+        isc_throw(HttpListenerError, "HttpResponseCreatorFactory must not"
+                  " be null");
+    }
+
+    // Request timeout is signed and must be greater than 0.
+    if (request_timeout_ <= 0) {
+        isc_throw(HttpListenerError, "Invalid desired HTTP request timeout "
+                  << request_timeout_);
+    }
+
+    // Idle persistent connection timeout is signed and must be greater than 0.
+    if (idle_timeout_ <= 0) {
+        isc_throw(HttpListenerError, "Invalid desired HTTP idle persistent connection"
+                  " timeout " << idle_timeout_);
+    }
+}
+
+const TCPEndpoint&
+HttpListenerImpl::getEndpoint() const {
+    return (*endpoint_);
+}
+
+void
+HttpListenerImpl::start() {
+    try {
+        acceptor_.open(*endpoint_);
+        acceptor_.setOption(HttpAcceptor::ReuseAddress(true));
+        acceptor_.bind(*endpoint_);
+        acceptor_.listen();
+
+    } catch (const boost::system::system_error& ex) {
+        stop();
+        isc_throw(HttpListenerError, "unable to setup TCP acceptor for "
+                  "listening to the incoming HTTP requests: " << ex.what());
+    }
+
+    accept();
+}
+
+void
+HttpListenerImpl::stop() {
+    connections_.stopAll();
+    acceptor_.close();
+}
+
+void
+HttpListenerImpl::accept() {
+    // In some cases we may need HttpResponseCreator instance per connection.
+    // But, the factory may also return the same instance each time. It
+    // depends on the use case.
+    HttpResponseCreatorPtr response_creator = creator_factory_->create();
+    HttpAcceptorCallback acceptor_callback =
+        boost::bind(&HttpListenerImpl::acceptHandler, this, _1);
+    HttpConnectionPtr conn = createConnection(io_service_,
+                                              acceptor_,
+                                              connections_,
+                                              response_creator,
+                                              acceptor_callback,
+                                              request_timeout_,
+                                              idle_timeout_);
+    // Add this new connection to the pool.
+    connections_.start(conn);
+}
+
+void
+HttpListenerImpl::acceptHandler(const boost::system::error_code&) {
+    // The new connection has arrived. Set the acceptor to continue
+    // accepting new connections.
+    accept();
+}
+
+HttpConnectionPtr
+HttpListenerImpl::createConnection(IOService& io_service,
+                                   HttpAcceptor& acceptor,
+                                   HttpConnectionPool& connection_pool,
+                                   const HttpResponseCreatorPtr& response_creator,
+                                   const HttpAcceptorCallback& callback,
+                                   const long request_timeout,
+                                   const long idle_timeout) {
+    HttpConnectionPtr
+        conn(new HttpConnection(io_service_, acceptor_, connections_,
+                                response_creator, callback,
+                                request_timeout_, idle_timeout_));
+    return (conn);
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/listener_impl.h b/src/lib/http/listener_impl.h
new file mode 100644 (file)
index 0000000..c6a8952
--- /dev/null
@@ -0,0 +1,144 @@
+// Copyright (C) 2019 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_LISTENER_IMPL_H
+#define HTTP_LISTENER_IMPL_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_address.h>
+#include <asiolink/tcp_endpoint.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/response_creator_factory.h>
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+/// @brief Implementation of the @ref HttpListener.
+class HttpListenerImpl {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor creates new server endpoint using the specified IP
+    /// address and port. It also validates other specified parameters.
+    ///
+    /// This constructor does not start accepting new connections! To start
+    /// accepting connections run @ref HttpListener::start.
+    ///
+    /// @param io_service IO service to be used by the listener.
+    /// @param server_address Address on which the HTTP service should run.
+    /// @param server_port Port number on which the HTTP service should run.
+    /// @param creator_factory Pointer to the caller-defined
+    /// @ref HttpResponseCreatorFactory derivation which should be used to
+    /// create @ref HttpResponseCreator instances.
+    /// @param request_timeout Timeout after which the HTTP Request Timeout
+    /// is generated.
+    /// @param idle_timeout Timeout after which an idle persistent HTTP
+    /// connection is closed by the server.
+    ///
+    /// @throw HttpListenerError when any of the specified parameters is
+    /// invalid.
+    HttpListenerImpl(asiolink::IOService& io_service,
+                     const asiolink::IOAddress& server_address,
+                     const unsigned short server_port,
+                     const HttpResponseCreatorFactoryPtr& creator_factory,
+                     const long request_timeout,
+                     const long idle_timeout);
+
+    /// @brief Virtual destructor.
+    virtual ~HttpListenerImpl() {
+    }
+
+    /// @brief Returns reference to the current listener endpoint.
+    const asiolink::TCPEndpoint& getEndpoint() const;
+
+    /// @brief Starts accepting new connections.
+    ///
+    /// This method starts accepting and handling new HTTP connections on
+    /// the IP address and port number specified in the constructor.
+    ///
+    /// If the method is invoked successfully, it must not be invoked again
+    /// until @ref HttpListener::stop is called.
+    ///
+    /// @throw HttpListenerError if an error occurred.
+    void start();
+
+    /// @brief Stops all active connections and shuts down the service.
+    void stop();
+
+protected:
+
+    /// @brief Creates @ref HttpConnection instance and adds it to the
+    /// pool of active connections.
+    ///
+    /// The next accepted connection will be handled by this instance.
+    void accept();
+
+    /// @brief Callback invoked when the new connection is accepted.
+    ///
+    /// It calls @ref HttpListener::accept to create new @ref HttpConnection
+    /// instance.
+    ///
+    /// @param ec Error code passed to the handler. This is currently ignored.
+    void acceptHandler(const boost::system::error_code& ec);
+
+    /// @brief Creates an instance of the @c HttpConnection.
+    ///
+    /// This method is virtual so as it can be overriden when customized
+    /// connections are to be used, e.g. in case of unit testing.
+    ///
+    /// @param io_service IO service to be used by the connection.
+    /// @param acceptor Reference to the TCP acceptor object used to listen for
+    /// new HTTP connections.
+    /// @param connection_pool Connection pool in which this connection is
+    /// stored.
+    /// @param response_creator Pointer to the response creator object used to
+    /// create HTTP response from the HTTP request received.
+    /// @param callback Callback invoked when new connection is accepted.
+    /// @param request_timeout Configured timeout for a HTTP request.
+    /// @param idle_timeout Timeout after which persistent HTTP connection is
+    /// closed by the server.
+    ///
+    /// @return Pointer to the created connection.
+    virtual HttpConnectionPtr createConnection(asiolink::IOService& io_service,
+                                               HttpAcceptor& acceptor,
+                                               HttpConnectionPool& connection_pool,
+                                               const HttpResponseCreatorPtr& response_creator,
+                                               const HttpAcceptorCallback& callback,
+                                               const long request_timeout,
+                                               const long idle_timeout);
+
+    /// @brief Reference to the IO service.
+    asiolink::IOService& io_service_;
+
+    /// @brief Acceptor instance.
+    HttpAcceptor acceptor_;
+
+    /// @brief Pointer to the endpoint representing IP address and port on
+    /// which the service is running.
+    boost::scoped_ptr<asiolink::TCPEndpoint> endpoint_;
+
+    /// @brief Pool of active connections.
+    HttpConnectionPool connections_;
+
+    /// @brief Pointer to the @ref HttpResponseCreatorFactory.
+    HttpResponseCreatorFactoryPtr creator_factory_;
+
+    /// @brief Timeout for HTTP Request Timeout desired.
+    long request_timeout_;
+
+    /// @brief Timeout after which idle persistent connection is closed by
+    /// the server.
+    long idle_timeout_;
+};
+
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // HTTP_LISTENER_IMPL_H