Browse Source

[master] Merge branch 'trac5099'

Marcin Siodelski 8 years ago
parent
commit
7e8df7993f

+ 47 - 11
src/lib/asiolink/tcp_socket.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 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
@@ -110,9 +110,24 @@ public:
     /// \param endpoint Target of the send. (Unused for a TCP socket because
     ///        that was determined when the connection was opened.)
     /// \param callback Callback object.
+    /// \throw BufferTooLarge on attempt to send a buffer larger than 64kB.
     virtual void asyncSend(const void* data, size_t length,
                            const IOEndpoint* endpoint, C& callback);
 
+    /// \brief Send Asynchronously without count.
+    ///
+    /// This variant of the method sends data over the TCP socket without
+    /// preceding the data with a data count. Eventually, we should migrate
+    /// the virtual method to not insert the count but there are existing
+    /// classes using the count. Once this migration is done, the existing
+    /// virtual method should be replaced by this method.
+    ///
+    /// \param data Data to send
+    /// \param length Length of data to send
+    /// \param callback Callback object.
+    /// \throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+    void asyncSend(const void* data, size_t length, C& callback);
+
     /// \brief Receive Asynchronously
     ///
     /// Calls the underlying socket's async_receive() method to read a packet
@@ -166,7 +181,6 @@ private:
     // construction, or where it is asked to manage its own socket.
     boost::asio::ip::tcp::socket*      socket_ptr_;    ///< Pointer to own socket
     boost::asio::ip::tcp::socket&      socket_;        ///< Socket
-    bool                               isopen_;        ///< true when socket is open
 
     // TODO: Remove temporary buffer
     // The current implementation copies the buffer passed to asyncSend() into
@@ -188,7 +202,7 @@ private:
 
 template <typename C>
 TCPSocket<C>::TCPSocket(boost::asio::ip::tcp::socket& socket) :
-    socket_ptr_(NULL), socket_(socket), isopen_(true), send_buffer_()
+    socket_ptr_(NULL), socket_(socket), send_buffer_()
 {
 }
 
@@ -197,7 +211,7 @@ TCPSocket<C>::TCPSocket(boost::asio::ip::tcp::socket& socket) :
 template <typename C>
 TCPSocket<C>::TCPSocket(IOService& service) :
     socket_ptr_(new boost::asio::ip::tcp::socket(service.get_io_service())),
-    socket_(*socket_ptr_), isopen_(false)
+    socket_(*socket_ptr_)
 {
 }
 
@@ -217,14 +231,13 @@ TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
     // Ignore opens on already-open socket.  Don't throw a failure because
     // of uncertainties as to what precedes whan when using asynchronous I/O.
     // At also allows us a treat a passed-in socket as a self-managed socket.
-    if (!isopen_) {
+    if (!socket_.is_open()) {
         if (endpoint->getFamily() == AF_INET) {
             socket_.open(boost::asio::ip::tcp::v4());
         }
         else {
             socket_.open(boost::asio::ip::tcp::v6());
         }
-        isopen_ = true;
 
         // Set options on the socket:
 
@@ -251,10 +264,34 @@ TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
 // an exception if this is the case.
 
 template <typename C> void
+TCPSocket<C>::asyncSend(const void* data, size_t length, C& callback)
+{
+    if (socket_.is_open()) {
+
+        try {
+            send_buffer_.reset(new isc::util::OutputBuffer(length));
+            send_buffer_->writeData(data, length);
+
+            // Send the data.
+            socket_.async_send(boost::asio::buffer(send_buffer_->getData(),
+                                                   send_buffer_->getLength()),
+                               callback);
+        } catch (boost::numeric::bad_numeric_cast&) {
+            isc_throw(BufferTooLarge,
+                      "attempt to send buffer larger than 64kB");
+        }
+
+    } else {
+        isc_throw(SocketNotOpen,
+            "attempt to send on a TCP socket that is not open");
+    }
+}
+
+template <typename C> void
 TCPSocket<C>::asyncSend(const void* data, size_t length,
     const IOEndpoint*, C& callback)
 {
-    if (isopen_) {
+    if (socket_.is_open()) {
 
         // Need to copy the data into a temporary buffer and precede it with
         // a two-byte count field.
@@ -289,7 +326,7 @@ template <typename C> void
 TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
     IOEndpoint* endpoint, C& callback)
 {
-    if (isopen_) {
+    if (socket_.is_open()) {
         // Upconvert to a TCPEndpoint.  We need to do this because although
         // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
         // does not contain a method for getting at the underlying endpoint
@@ -391,7 +428,7 @@ TCPSocket<C>::processReceivedData(const void* staging, size_t length,
 
 template <typename C> void
 TCPSocket<C>::cancel() {
-    if (isopen_) {
+    if (socket_.is_open()) {
         socket_.cancel();
     }
 }
@@ -401,9 +438,8 @@ TCPSocket<C>::cancel() {
 
 template <typename C> void
 TCPSocket<C>::close() {
-    if (isopen_ && socket_ptr_) {
+    if (socket_.is_open() && socket_ptr_) {
         socket_.close();
-        isopen_ = false;
     }
 }
 

+ 6 - 1
src/lib/http/Makefile.am

@@ -22,10 +22,14 @@ EXTRA_DIST = http_messages.mes
 CLEANFILES = *.gcno *.gcda http_messages.h http_messages.cc s-messages
 
 lib_LTLIBRARIES = libkea-http.la
-libkea_http_la_SOURCES  = date_time.cc date_time.h
+libkea_http_la_SOURCES  = connection.cc connection.h
+libkea_http_la_SOURCES += connection_pool.cc connection_pool.h
+libkea_http_la_SOURCES += date_time.cc date_time.h
 libkea_http_la_SOURCES += http_log.cc http_log.h
 libkea_http_la_SOURCES += header_context.h
+libkea_http_la_SOURCES += http_acceptor.h
 libkea_http_la_SOURCES += http_types.h
+libkea_http_la_SOURCES += listener.cc listener.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
@@ -33,6 +37,7 @@ libkea_http_la_SOURCES += request_context.h
 libkea_http_la_SOURCES += request_parser.cc request_parser.h
 libkea_http_la_SOURCES += response.cc response.h
 libkea_http_la_SOURCES += response_creator.cc response_creator.h
+libkea_http_la_SOURCES += response_creator_factory.h
 libkea_http_la_SOURCES += response_json.cc response_json.h
 
 nodist_libkea_http_la_SOURCES = http_messages.cc http_messages.h

+ 173 - 0
src/lib/http/connection.cc

@@ -0,0 +1,173 @@
+// Copyright (C) 2017 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 <asiolink/asio_wrapper.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <boost/bind.hpp>
+#include <iostream>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace http {
+
+void
+HttpConnection::
+SocketCallback::operator()(boost::system::error_code ec, size_t length) {
+    if (ec.value() == boost::asio::error::operation_aborted) {
+        return;
+    }
+    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)
+    : request_timer_(io_service),
+      request_timeout_(request_timeout),
+      socket_(io_service),
+      socket_callback_(boost::bind(&HttpConnection::socketReadCallback, this,
+                                   _1, _2)),
+      socket_write_callback_(boost::bind(&HttpConnection::socketWriteCallback,
+                                         this, _1, _2)),
+      acceptor_(acceptor),
+      connection_pool_(connection_pool),
+      response_creator_(response_creator),
+      request_(response_creator_->createNewHttpRequest()),
+      parser_(new HttpRequestParser(*request_)),
+      acceptor_callback_(callback),
+      buf_() {
+    parser_->initModel();
+}
+
+HttpConnection::~HttpConnection() {
+    close();
+}
+
+void
+HttpConnection::close() {
+    socket_.close();
+}
+
+void
+HttpConnection::stopThisConnection() {
+    try {
+        connection_pool_.stop(shared_from_this());
+    } catch (...) {
+    }
+}
+
+void
+HttpConnection::asyncAccept() {
+    HttpAcceptorCallback cb = boost::bind(&HttpConnection::acceptorCallback,
+                                          this, _1);
+    try {
+        acceptor_.asyncAccept(socket_, cb);
+
+    } catch (const std::exception& ex) {
+        isc_throw(HttpConnectionError, "unable to start accepting TCP "
+                  "connections: " << ex.what());
+    }
+}
+
+void
+HttpConnection::doRead() {
+    try {
+        TCPEndpoint endpoint;
+        socket_.asyncReceive(static_cast<void*>(buf_.data()), buf_.size(),
+                             0, &endpoint, socket_callback_);
+
+    } catch (const std::exception& ex) {
+        stopThisConnection();
+    }
+}
+
+void
+HttpConnection::doWrite() {
+    try {
+        if (!output_buf_.empty()) {
+            socket_.asyncSend(output_buf_.data(),
+                              output_buf_.length(),
+                              socket_write_callback_);
+        }
+    } catch (const std::exception& ex) {
+        stopThisConnection();
+    }
+}
+
+void
+HttpConnection::asyncSendResponse(const ConstHttpResponsePtr& response) {
+    output_buf_ = response->toString();
+    doWrite();
+}
+
+
+void
+HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
+    if (!acceptor_.isOpen()) {
+        return;
+    }
+
+    if (ec) {
+        stopThisConnection();
+    }
+
+    acceptor_callback_(ec);
+
+    if (!ec) {
+        request_timer_.setup(boost::bind(&HttpConnection::requestTimeoutCallback, this),
+                             request_timeout_, IntervalTimer::ONE_SHOT);
+        doRead();
+    }
+}
+
+void
+HttpConnection::socketReadCallback(boost::system::error_code ec, size_t length) {
+    std::string s(&buf_[0], buf_[0] + length);
+    parser_->postBuffer(static_cast<void*>(buf_.data()), length);
+    parser_->poll();
+    if (parser_->needData()) {
+        doRead();
+
+    } else {
+        try {
+            request_->finalize();
+        } catch (...) {
+        }
+
+        HttpResponsePtr response = response_creator_->createHttpResponse(request_);
+        asyncSendResponse(response);
+    }
+}
+
+void
+HttpConnection::socketWriteCallback(boost::system::error_code ec,
+                                    size_t length) {
+    if (length <= output_buf_.size()) {
+        output_buf_.erase(0, length);
+        doWrite();
+
+    } else {
+        output_buf_.clear();
+    }
+}
+
+void
+HttpConnection::requestTimeoutCallback() {
+    HttpResponsePtr response =
+        response_creator_->createStockHttpResponse(request_,
+                                                   HttpStatusCode::REQUEST_TIMEOUT);
+    asyncSendResponse(response);
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc
+

+ 221 - 0
src/lib/http/connection.h

@@ -0,0 +1,221 @@
+// Copyright (C) 2017 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_CONNECTION_H
+#define HTTP_CONNECTION_H
+
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <http/http_acceptor.h>
+#include <http/request_parser.h>
+#include <http/response_creator_factory.h>
+#include <boost/function.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/system/error_code.hpp>
+#include <boost/shared_ptr.hpp>
+#include <array>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic error reported within @ref HttpConnection class.
+class HttpConnectionError : public Exception {
+public:
+    HttpConnectionError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Forward declaration to the @ref HttpConnectionPool.
+///
+/// This declaration is needed because we don't include the header file
+/// declaring @ref HttpConnectionPool to avoid circular inclusion.
+class HttpConnectionPool;
+
+class HttpConnection;
+/// @brief Pointer to the @ref HttpConnection.
+typedef boost::shared_ptr<HttpConnection> HttpConnectionPtr;
+
+/// @brief Accepts and handles a single HTTP connection.
+class HttpConnection : public boost::enable_shared_from_this<HttpConnection> {
+private:
+
+    /// @brief Type of the function implementing a callback invoked by the
+    /// @c SocketCallback functor.
+    typedef boost::function<void(boost::system::error_code ec, size_t length)>
+    SocketCallbackFunction;
+
+    /// @brief Functor associated with the socket object.
+    ///
+    /// This functor calls a callback function specified in the constructor.
+    class SocketCallback {
+    public:
+
+        /// @brief Constructor.
+        ///
+        /// @param socket_callback Callback to be invoked by the functor upon
+        /// an event associated with the socket.
+        SocketCallback(SocketCallbackFunction socket_callback)
+            : callback_(socket_callback) {
+        }
+
+        /// @brief Operator called when event associated with a socket occurs.
+        ///
+        /// This operator returns immediately when received error code is
+        /// @c boost::system::error_code is equal to
+        /// @c boost::asio::error::operation_aborted, i.e. the callback is not
+        /// invoked.
+        ///
+        /// @param ec Error code.
+        /// @param length Data length.
+        void operator()(boost::system::error_code ec, size_t length = 0);
+
+    private:
+        /// @brief Supplied callback.
+        SocketCallbackFunction callback_;
+    };
+
+
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @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.
+    HttpConnection(asiolink::IOService& io_service,
+                   HttpAcceptor& acceptor,
+                   HttpConnectionPool& connection_pool,
+                   const HttpResponseCreatorPtr& response_creator,
+                   const HttpAcceptorCallback& callback,
+                   const long request_timeout);
+
+    /// @brief Destructor.
+    ///
+    /// Closes current connection.
+    ~HttpConnection();
+
+    /// @brief Asynchronously accepts new connection.
+    ///
+    /// When the connection is established successfully, the timeout timer is
+    /// setup and the asynchronous read from the socket is started.
+    void asyncAccept();
+
+    /// @brief Closes the socket.
+    void close();
+
+    /// @brief Starts asynchronous read from the socket.
+    ///
+    /// The data received over the socket are supplied to the HTTP parser until
+    /// the parser signals that the entire request has been received or until
+    /// the parser signals an error. In the former case the server creates an
+    /// HTTP response using supplied response creator object.
+    ///
+    /// In case of error the connection is stopped.
+    void doRead();
+
+private:
+
+    /// @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();
+
+    /// @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);
+
+    /// @brief Local callback invoked when new connection is accepted.
+    ///
+    /// It invokes external (supplied via constructor) acceptor callback. If
+    /// the acceptor is not opened it returns immediately. If the connection
+    /// is accepted successfully the @ref HttpConnection::doRead is called.
+    ///
+    /// @param ec Error code.
+    void acceptorCallback(const boost::system::error_code& ec);
+
+    /// @brief Callback invoked when new data is received over the socket.
+    ///
+    /// This callback supplies the data to the HTTP parser and continues
+    /// parsing. When the parser signals end of the HTTP request the callback
+    /// prepares a response and starts asynchronous send over the socket.
+    ///
+    /// @param ec Error code.
+    /// @param length Length of the received data.
+    void socketReadCallback(boost::system::error_code ec,
+                            size_t length);
+
+    /// @brief Callback invoked when data is sent over the socket.
+    ///
+    /// @param ec Error code.
+    /// @param length Length of the data sent.
+    void socketWriteCallback(boost::system::error_code ec,
+                             size_t length);
+
+    /// @brief Callback invoked when the HTTP Request Timeout occurs.
+    ///
+    /// This callback creates HTTP response with Request Timeout error code
+    /// and sends it to the client.
+    void requestTimeoutCallback();
+
+    /// @brief Stops current connection.
+    void stopThisConnection();
+
+    /// @brief Timer used to detect Request Timeout.
+    asiolink::IntervalTimer request_timer_;
+
+    /// @brief Configured Request Timeout in milliseconds.
+    long request_timeout_;
+
+    /// @brief Socket used by this connection.
+    asiolink::TCPSocket<SocketCallback> socket_;
+
+    /// @brief Callback invoked when data received over the socket.
+    SocketCallback socket_callback_;
+
+    /// @brief Callback invoked when data sent over the socket.
+    SocketCallback socket_write_callback_;
+
+    /// @brief Reference to the TCP acceptor used to accept new connections.
+    HttpAcceptor& acceptor_;
+
+    /// @brief Connection pool holding this connection.
+    HttpConnectionPool& connection_pool_;
+
+    /// @brief Pointer to the @ref HttpResponseCreator object used to create
+    /// 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, 4096> buf_;
+
+    /// @brief Buffer used for outbound data.
+    std::string output_buf_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif

+ 35 - 0
src/lib/http/connection_pool.cc

@@ -0,0 +1,35 @@
+// Copyright (C) 2017 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 <asiolink/asio_wrapper.h>
+#include <http/connection_pool.h>
+
+namespace isc {
+namespace http {
+
+void
+HttpConnectionPool::start(const HttpConnectionPtr& connection) {
+    connections_.insert(connections_.end(), connection);
+    connection->asyncAccept();
+}
+
+void
+HttpConnectionPool::stop(const HttpConnectionPtr& connection) {
+    connections_.remove(connection);
+}
+
+void
+HttpConnectionPool::stopAll() {
+    for (auto connection = connections_.begin();
+         connection != connections_.end();
+         ++connection) {
+        (*connection)->close();
+    }
+    connections_.clear();
+}
+
+}
+}

+ 60 - 0
src/lib/http/connection_pool.h

@@ -0,0 +1,60 @@
+// Copyright (C) 2017 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_CONNECTION_POOL_H
+#define HTTP_CONNECTION_POOL_H
+
+#include <http/connection.h>
+#include <list>
+
+namespace isc {
+namespace http {
+
+/// @brief Pool of active HTTP connections.
+///
+/// The HTTP server is designed to handle many connections simultaneously.
+/// The communication between the client and the server may take long time
+/// and the server must be able to react on other events while the communication
+/// with the clients is in progress. Thus, the server must track active
+/// connections and gracefully close them when needed. An obvious case when the
+/// connections must be terminated by the server is when the shutdown signal
+/// is received.
+///
+/// This object is a simple container for the server connections which provides
+/// means to terminate them on request.
+class HttpConnectionPool {
+public:
+
+    /// @brief Start new connection.
+    ///
+    /// The connection is inserted to the pool and the
+    /// @ref HttpConnection::asyncAccept is invoked.
+    ///
+    /// @param connection Pointer to the new connection.
+    void start(const HttpConnectionPtr& connection);
+
+    /// @brief Stops a connection and removes it from the pool.
+    ///
+    /// If the connection is not found in the pool, this method is no-op.
+    ///
+    /// @param connection Pointer to the connection.
+    void stop(const HttpConnectionPtr& connection);
+
+    /// @brief Stops all connections and removes them from the pool.
+    void stopAll();
+
+protected:
+
+    /// @brief Set of connections.
+    std::list<HttpConnectionPtr> connections_;
+
+};
+
+}
+}
+
+#endif
+

+ 27 - 0
src/lib/http/http_acceptor.h

@@ -0,0 +1,27 @@
+// Copyright (C) 2017 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_ACCEPTOR_H
+#define HTTP_ACCEPTOR_H
+
+#include <asiolink/tcp_acceptor.h>
+#include <boost/function.hpp>
+#include <boost/system/system_error.hpp>
+
+namespace isc {
+namespace http {
+
+/// @brief Type of the callback for the TCP acceptor used in this library.
+typedef boost::function<void(const boost::system::error_code&)>
+HttpAcceptorCallback;
+
+/// @brief Type of the TCP acceptor used in this library.
+typedef asiolink::TCPAcceptor<HttpAcceptorCallback> HttpAcceptor;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif

+ 6 - 0
src/lib/http/http_types.h

@@ -7,6 +7,9 @@
 #ifndef HTTP_TYPES_H
 #define HTTP_TYPES_H
 
+namespace isc {
+namespace http {
+
 /// @brief HTTP protocol version.
 struct HttpVersion {
     unsigned major_; ///< Major HTTP version.
@@ -43,4 +46,7 @@ struct HttpVersion {
     }
 };
 
+} // end of namespace isc::http
+} // end of namespace isc
+
 #endif

+ 201 - 0
src/lib/http/listener.cc

@@ -0,0 +1,201 @@
+// Copyright (C) 2017 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 <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>
+
+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.
+    ///
+    /// @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);
+
+    /// @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_;
+};
+
+HttpListenerImpl::HttpListenerImpl(IOService& io_service,
+                                   const asiolink::IOAddress& server_address,
+                                   const unsigned short server_port,
+                                   const HttpResponseCreatorFactoryPtr& creator_factory,
+                                   const long request_timeout)
+    : io_service_(io_service), acceptor_(io_service),
+      endpoint_(), creator_factory_(creator_factory),
+      request_timeout_(request_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_);
+    }
+}
+
+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_));
+    // 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,
+                           const HttpResponseCreatorFactoryPtr& creator_factory,
+                           const long request_timeout)
+    : impl_(new HttpListenerImpl(io_service, server_address, server_port,
+                                 creator_factory, request_timeout)) {
+}
+
+HttpListener::~HttpListener() {
+    stop();
+}
+
+void
+HttpListener::start() {
+    impl_->start();
+}
+
+void
+HttpListener::stop() {
+    impl_->stop();
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc

+ 107 - 0
src/lib/http/listener.h

@@ -0,0 +1,107 @@
+// Copyright (C) 2017 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_H
+#define HTTP_LISTENER_H
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <http/response_creator_factory.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+/// @brief A generic error raised by the @ref HttpListener class.
+class HttpListenerError : public Exception {
+public:
+    HttpListenerError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief HttpListener implementation.
+class HttpListenerImpl;
+
+/// @brief HTTP listener.
+///
+/// This class is an entry point to the use of HTTP services in Kea.
+/// It creates a TCP acceptor service on the specified address and
+/// port and listens to the incoming HTTP connections. The constructor
+/// receives a pointer to the implementation of the
+/// @ref HttpResponseCreatorFactory, which is used by the @ref HttpListener
+/// to create/retrieve an instance of the @ref HttpResponseCreator when the
+/// new HTTP response needs to be generated. The @ref HttpResponseCreator
+/// creates an object derived from the @ref HttpResponse class, encapsulating
+/// a HTTP response following some specific rules, e.g. having
+/// "application/json" content type.
+///
+/// When the listener is started it creates an instance of a @ref HttpConnection
+/// and stores them in the pool of active connections. The @ref HttpConnection
+/// is responsible for managing the next connection received and receiving the
+/// HTTP request and sending appropriate response. The listener can handle
+/// many HTTP connections simultaneously.
+///
+/// When the @ref HttpListener::stop is invoked, all active connections are
+/// closed and the listener stops accepting new connections.
+class HttpListener {
+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.
+    ///
+    /// @throw HttpListenerError when any of the specified parameters is
+    /// invalid.
+    HttpListener(asiolink::IOService& io_service,
+                 const asiolink::IOAddress& server_address,
+                 const unsigned short server_port,
+                 const HttpResponseCreatorFactoryPtr& creator_factory,
+                 const long request_timeout);
+
+    /// @brief Destructor.
+    ///
+    /// Stops all active connections and closes TCP acceptor service.
+    ~HttpListener();
+
+    /// @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 Pointer to the implementation of the @ref HttpListener.
+    boost::shared_ptr<HttpListenerImpl> impl_;
+
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif

+ 7 - 1
src/lib/http/request_parser.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 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
@@ -11,6 +11,7 @@
 #include <http/request.h>
 #include <util/state_model.h>
 #include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
 #include <list>
 #include <stdint.h>
 #include <string>
@@ -28,6 +29,11 @@ public:
         isc::Exception(file, line, what) { };
 };
 
+class HttpRequestParser;
+
+/// @brief Pointer to the @ref HttpRequestParser.
+typedef boost::shared_ptr<HttpRequestParser> HttpRequestParserPtr;
+
 /// @brief A generic parser for HTTP requests.
 ///
 /// This class implements a parser for HTTP requests. The parser derives from

+ 1 - 0
src/lib/http/response.cc

@@ -29,6 +29,7 @@ const std::map<HttpStatusCode, std::string> status_code_to_description = {
     { HttpStatusCode::UNAUTHORIZED, "Unauthorized" },
     { HttpStatusCode::FORBIDDEN, "Forbidden" },
     { HttpStatusCode::NOT_FOUND, "Not Found" },
+    { HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout" },
     { HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error" },
     { HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented" },
     { HttpStatusCode::BAD_GATEWAY, "Bad Gateway" },

+ 1 - 0
src/lib/http/response.h

@@ -39,6 +39,7 @@ enum class HttpStatusCode : std::uint16_t {
     UNAUTHORIZED = 401,
     FORBIDDEN = 403,
     NOT_FOUND = 404,
+    REQUEST_TIMEOUT = 408,
     INTERNAL_SERVER_ERROR = 500,
     NOT_IMPLEMENTED = 501,
     BAD_GATEWAY = 502,

+ 1 - 1
src/lib/http/response_creator.cc

@@ -19,7 +19,7 @@ HttpResponseCreator::createHttpResponse(const ConstHttpRequestPtr& request) {
 
     // If not finalized, the request parsing failed. Generate HTTP 400.
     if (!request->isFinalized()) {
-        return (createStockBadRequest(request));
+        return (createStockHttpResponse(request, HttpStatusCode::BAD_REQUEST));
     }
 
     // Message has been successfully parsed. Create implementation specific

+ 23 - 4
src/lib/http/response_creator.h

@@ -9,10 +9,16 @@
 
 #include <http/request.h>
 #include <http/response.h>
+#include <boost/shared_ptr.hpp>
 
 namespace isc {
 namespace http {
 
+class HttpResponseCreator;
+
+/// @brief Pointer to the @ref HttpResponseCreator object.
+typedef boost::shared_ptr<HttpResponseCreator> HttpResponseCreatorPtr;
+
 /// @brief Specifies an interface for classes creating HTTP responses
 /// from HTTP requests.
 ///
@@ -70,14 +76,27 @@ public:
     virtual HttpResponsePtr
     createHttpResponse(const ConstHttpRequestPtr& request) final;
 
-protected:
+    /// @brief Create a new request.
+    ///
+    /// This method creates an instance of the @ref HttpRequest or derived
+    /// class. The type of the object is compatible with the instance of
+    /// the @ref HttpResponseCreator implementation which creates it, i.e.
+    /// can be used as an argument in the call to @ref createHttpResponse.
+    ///
+    /// @return Pointer to the new instance of the @ref HttpRequest.
+    virtual HttpRequestPtr
+    createNewHttpRequest() const = 0;
 
-    /// @brief Creates implementation specific HTTP 400 response.
+    /// @brief Creates implementation specific HTTP response.
     ///
     /// @param request Pointer to an object representing HTTP request.
-    /// @return Pointer to an object representing HTTP 400 response.
+    /// @param status_code Status code of the response.
+    /// @return Pointer to an object representing HTTP response.
     virtual HttpResponsePtr
-    createStockBadRequest(const ConstHttpRequestPtr& request) const = 0;
+    createStockHttpResponse(const ConstHttpRequestPtr& request,
+                            const HttpStatusCode& status_code) const = 0;
+
+protected:
 
     /// @brief Creates implementation specific HTTP response.
     ///

+ 59 - 0
src/lib/http/response_creator_factory.h

@@ -0,0 +1,59 @@
+// Copyright (C) 2017 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_RESPONSE_CREATOR_FACTORY_H
+#define HTTP_RESPONSE_CREATOR_FACTORY_H
+
+#include <http/response_creator.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+/// @brief Specifies the interface for implementing custom factory classes
+/// used to create instances of @ref HttpResponseCreator.
+///
+/// The @ref HttpResponseCreator defines an interface for the classes used
+/// to generate HTTP responses. Such classes are defined outside of this
+/// library and they are specific to the needs of the particular module.
+/// In some cases it may be desired to create new instance of the
+/// @ref HttpResponseCreator implementation for every request processed.
+/// The @ref HttpResponseCreatorFactory is an interface to the "factory"
+/// class which generates canned @ref HttpResponseCreator instances. The
+/// pointer to the factory class is passed to the @ref HttpListener and
+/// the listener propagates it down to other classes. These classes call
+/// @ref HttpResponseCreatorFactory::create to retrieve an instance of the
+/// appropriate @ref HttpResponseCreator, which is in turn used to generate
+/// HTTP response.
+///
+/// Note that an implementation of the @ref HttpResponseCreatorFactory::create
+/// may always return the same instance of the @ref HttpResponseCreator
+/// if creating new instance for each request is not required or undesired.
+class HttpResponseCreatorFactory {
+public:
+
+    /// @brief Virtual destructor.
+    virtual ~HttpResponseCreatorFactory() { }
+
+    /// @brief Returns an instance of the @ref HttpResponseCreator.
+    ///
+    /// The implementation may create new instance every time this method
+    /// is called, or it may always return the same instance.
+    ///
+    /// @return Pointer to the instance of the @ref HttpResponseCreator to
+    /// be used to generate HTTP response.
+    virtual HttpResponseCreatorPtr create() const = 0;
+
+};
+
+/// @brief Pointer to the @ref HttpResponseCreatorFactory.
+typedef boost::shared_ptr<HttpResponseCreatorFactory>
+HttpResponseCreatorFactoryPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif

+ 3 - 1
src/lib/http/tests/Makefile.am

@@ -20,7 +20,9 @@ TESTS =
 if HAVE_GTEST
 TESTS += libhttp_unittests
 
-libhttp_unittests_SOURCES  = date_time_unittests.cc
+libhttp_unittests_SOURCES  = connection_pool_unittests.cc
+libhttp_unittests_SOURCES += date_time_unittests.cc
+libhttp_unittests_SOURCES += listener_unittests.cc
 libhttp_unittests_SOURCES += post_request_json_unittests.cc
 libhttp_unittests_SOURCES += request_parser_unittests.cc
 libhttp_unittests_SOURCES += request_test.h

+ 192 - 0
src/lib/http/tests/connection_pool_unittests.cc

@@ -0,0 +1,192 @@
+// Copyright (C) 2017 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 <asiolink/io_service.h>
+#include <http/http_acceptor.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace isc::asiolink;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+    /// @brief Create a new request.
+    ///
+    /// @return Pointer to the new instance of the @ref HttpRequest.
+    virtual HttpRequestPtr
+    createNewHttpRequest() const {
+        return (HttpRequestPtr(new PostHttpRequestJson()));
+    }
+
+private:
+
+    /// @brief Creates HTTP response.
+    ///
+    /// @param request Pointer to the HTTP request.
+    /// @return Pointer to the generated HTTP response.
+    virtual HttpResponsePtr
+    createStockHttpResponse(const ConstHttpRequestPtr& request,
+                            const HttpStatusCode& status_code) const {
+        // The request hasn't been finalized so the request object
+        // doesn't contain any information about the HTTP version number
+        // used. But, the context should have this data (assuming the
+        // HTTP version is parsed ok).
+        HttpVersion http_version(request->context()->http_version_major_,
+                                 request->context()->http_version_minor_);
+        // This will generate the response holding JSON content.
+        ResponsePtr response(new Response(http_version, status_code));
+        return (response);
+    }
+
+    /// @brief Creates HTTP response.
+    ///
+    /// @param request Pointer to the HTTP request.
+    /// @return Pointer to the generated HTTP OK response with no content.
+    virtual HttpResponsePtr
+    createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+        // The simplest thing is to create a response with no content.
+        // We don't need content to test our class.
+        ResponsePtr response(new Response(request->getHttpVersion(),
+                                          HttpStatusCode::OK));
+        return (response);
+    }
+};
+
+/// @brief Derivation of @ref HttpConnectionPool exposing protected member.
+class TestHttpConnectionPool : public HttpConnectionPool {
+public:
+
+    using HttpConnectionPool::connections_;
+
+    /// @brief Checks if specified connection belongs to the pool.
+    bool hasConnection(const HttpConnectionPtr& conn) const {
+        return (std::find(connections_.begin(), connections_.end(), conn)
+                != connections_.end());
+    }
+
+};
+
+/// @brief Test fixture class for @ref HttpConnectionPool.
+class HttpConnectionPoolTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    HttpConnectionPoolTest()
+        : io_service_(), acceptor_(io_service_), connection_pool_(),
+          response_creator_(new TestHttpResponseCreator()) {
+    }
+
+    IOService io_service_;                      ///< IO service.
+    HttpAcceptor acceptor_;                     ///< Test acceptor.
+    HttpConnectionPool connection_pool_;        ///< Test connection pool.
+    HttpResponseCreatorPtr response_creator_;   ///< Test response creator.
+
+};
+
+// This test verifies that connections can be added to the pool and removed.
+TEST_F(HttpConnectionPoolTest, startStop) {
+    // Create two distinct connections.
+    HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+                                               connection_pool_,
+                                               response_creator_,
+                                               HttpAcceptorCallback(),
+                                               1000));
+    HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+                                               connection_pool_,
+                                               response_creator_,
+                                               HttpAcceptorCallback(),
+                                               1000));
+    // The pool should be initially empty.
+    TestHttpConnectionPool pool;
+    ASSERT_TRUE(pool.connections_.empty());
+
+    // Start first connection and check that it has been added to the pool.
+    ASSERT_NO_THROW(pool.start(conn1));
+    ASSERT_EQ(1, pool.connections_.size());
+    ASSERT_EQ(1, pool.hasConnection(conn1));
+
+    // Start second connection and check that it also has been added.
+    ASSERT_NO_THROW(pool.start(conn2));
+    ASSERT_EQ(2, pool.connections_.size());
+    ASSERT_EQ(1, pool.hasConnection(conn2));
+
+    // Stop first connection.
+    ASSERT_NO_THROW(pool.stop(conn1));
+    ASSERT_EQ(1, pool.connections_.size());
+    // Check that it has been removed but the second connection is still there.
+    ASSERT_EQ(0, pool.hasConnection(conn1));
+    ASSERT_EQ(1, pool.hasConnection(conn2));
+
+    // Remove second connection and verify.
+    ASSERT_NO_THROW(pool.stop(conn2));
+    EXPECT_TRUE(pool.connections_.empty());
+}
+
+// Check that all connections can be remove with a single call.
+TEST_F(HttpConnectionPoolTest, stopAll) {
+    HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+                                               connection_pool_,
+                                               response_creator_,
+                                               HttpAcceptorCallback(),
+                                               1000));
+    HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+                                               connection_pool_,
+                                               response_creator_,
+                                               HttpAcceptorCallback(),
+                                               1000));
+    TestHttpConnectionPool pool;
+    ASSERT_NO_THROW(pool.start(conn1));
+    ASSERT_NO_THROW(pool.start(conn2));
+
+    // There are two distinct connections in the pool.
+    ASSERT_EQ(2, pool.connections_.size());
+
+    // This should remove all connections.
+    ASSERT_NO_THROW(pool.stopAll());
+    EXPECT_TRUE(pool.connections_.empty());
+}
+
+// Check that stopping non-existing connection is no-op.
+TEST_F(HttpConnectionPoolTest, stopInvalid) {
+    HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+                                               connection_pool_,
+                                               response_creator_,
+                                               HttpAcceptorCallback(),
+                                               1000));
+    HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+                                               connection_pool_,
+                                               response_creator_,
+                                               HttpAcceptorCallback(),
+                                               1000));
+    TestHttpConnectionPool pool;
+    ASSERT_NO_THROW(pool.start(conn1));
+    ASSERT_NO_THROW(pool.stop(conn2));
+    ASSERT_EQ(1, pool.connections_.size());
+    ASSERT_EQ(1, pool.hasConnection(conn1));
+}
+
+}

+ 427 - 0
src/lib/http/tests/listener_unittests.cc

@@ -0,0 +1,427 @@
+// Copyright (C) 2017 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 <asiolink/interval_timer.h>
+#include <http/listener.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+#include <list>
+#include <string>
+
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Request Timeout used in most of the tests (ms).
+const long REQUEST_TIMEOUT = 10000;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+    /// @brief Create a new request.
+    ///
+    /// @return Pointer to the new instance of the @ref HttpRequest.
+    virtual HttpRequestPtr
+    createNewHttpRequest() const {
+        return (HttpRequestPtr(new PostHttpRequestJson()));
+    }
+
+private:
+
+    /// @brief Creates HTTP response.
+    ///
+    /// @param request Pointer to the HTTP request.
+    /// @return Pointer to the generated HTTP response.
+    virtual HttpResponsePtr
+    createStockHttpResponse(const ConstHttpRequestPtr& request,
+                            const HttpStatusCode& status_code) const {
+        // The request hasn't been finalized so the request object
+        // doesn't contain any information about the HTTP version number
+        // used. But, the context should have this data (assuming the
+        // HTTP version is parsed ok).
+        HttpVersion http_version(request->context()->http_version_major_,
+                                 request->context()->http_version_minor_);
+        // This will generate the response holding JSON content.
+        ResponsePtr response(new Response(http_version, status_code));
+        return (response);
+    }
+
+    /// @brief Creates HTTP response.
+    ///
+    /// @param request Pointer to the HTTP request.
+    /// @return Pointer to the generated HTTP OK response with no content.
+    virtual HttpResponsePtr
+    createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+        // The simplest thing is to create a response with no content.
+        // We don't need content to test our class.
+        ResponsePtr response(new Response(request->getHttpVersion(),
+                                          HttpStatusCode::OK));
+        return (response);
+    }
+};
+
+/// @brief Implementation of the test @ref HttpResponseCreatorFactory.
+///
+/// This factory class creates @ref TestHttpResponseCreator instances.
+class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
+public:
+
+    /// @brief Creates @ref TestHttpResponseCreator instance.
+    virtual HttpResponseCreatorPtr create() const {
+        HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator());
+        return (response_creator);
+    }
+};
+
+/// @brief Entity which can connect to the HTTP server endpoint.
+class HttpClient : public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor creates new socket instance. It doesn't connect. Call
+    /// connect() to connect to the server.
+    ///
+    /// @param io_service IO service to be stopped on error.
+    explicit HttpClient(IOService& io_service)
+        : io_service_(io_service.get_io_service()), socket_(io_service_),
+          buf_(), response_() {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Closes the underlying socket if it is open.
+    ~HttpClient() {
+        close();
+    }
+
+    /// @brief Send HTTP request specified in textual format.
+    ///
+    /// @param request HTTP request in the textual format.
+    void startRequest(const std::string& request) {
+        tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+                               SERVER_PORT);
+        socket_.async_connect(endpoint,
+                              [this, request](const boost::system::error_code& ec) {
+            if (ec) {
+                ADD_FAILURE() << "error occurred while connecting: "
+                              << ec.message();
+                io_service_.stop();
+            }
+            sendRequest(request);
+        });
+    }
+
+    /// @brief Send HTTP request.
+    ///
+    /// @param request HTTP request in the textual format.
+    void sendRequest(const std::string& request) {
+        sendPartialRequest(request);
+    }
+
+    /// @brief Send part of the HTTP request.
+    ///
+    /// @param request part of the HTTP request to be sent.
+    void sendPartialRequest(std::string request) {
+        socket_.async_send(boost::asio::buffer(request.data(), request.size()),
+                           [this, request](const boost::system::error_code& ec,
+                                           std::size_t bytes_transferred) mutable {
+            if (ec) {
+                ADD_FAILURE() << "error occurred while connecting: "
+                              << ec.message();
+                io_service_.stop();
+                return;
+            }
+
+            // Remove the part of the request which has been sent.
+            if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) {
+                request.erase(0, bytes_transferred);
+            }
+
+            // Continue sending request data if there are still some data to be
+            // sent.
+            if (!request.empty()) {
+                sendPartialRequest(request);
+
+            } else {
+                // Request has been sent. Start receiving response.
+                response_.clear();
+                receivePartialResponse();
+            }
+       });
+    }
+
+    /// @brief Receive response from the server.
+    void receivePartialResponse() {
+        socket_.async_read_some(boost::asio::buffer(buf_),
+                                [this](const boost::system::error_code& ec,
+                                       std::size_t bytes_transferred) {
+            if (ec) {
+                // IO service stopped so simply return.
+                if (ec == boost::asio::error::operation_aborted) {
+                    return;
+                }
+
+                // Error occurred, bail...
+                ADD_FAILURE() << "error occurred while receiving HTTP"
+                    " response from the server: " << ec.message();
+                io_service_.stop();
+            }
+
+            if (bytes_transferred > 0) {
+                response_.insert(response_.end(), buf_.data(),
+                                 buf_.data() + bytes_transferred);
+            }
+
+            // Two consecutive new lines end the part of the response we're
+            // expecting.
+            if (response_.find("\r\n\r\n", 0) != std::string::npos) {
+                io_service_.stop();
+
+            } else {
+                receivePartialResponse();
+            }
+
+        });
+    }
+
+    /// @brief Close connection.
+    void close() {
+        socket_.close();
+    }
+
+    std::string getResponse() const {
+        return (response_);
+    }
+
+private:
+
+    /// @brief Holds reference to the IO service.
+    boost::asio::io_service& io_service_;
+
+    /// @brief A socket used for the connection.
+    boost::asio::ip::tcp::socket socket_;
+
+    /// @brief Buffer into which response is written.
+    std::array<char, 8192> buf_;
+
+    /// @brief Response in the textual format.
+    std::string response_;
+};
+
+/// @brief Pointer to the HttpClient.
+typedef boost::shared_ptr<HttpClient> HttpClientPtr;
+
+/// @brief Test fixture class for @ref HttpListener.
+class HttpListenerTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Starts test timer which detects timeouts.
+    HttpListenerTest()
+        : io_service_(), factory_(new TestHttpResponseCreatorFactory()),
+          test_timer_(io_service_), clients_() {
+        test_timer_.setup(boost::bind(&HttpListenerTest::timeoutHandler, this),
+                          TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Removes active HTTP clients.
+    virtual ~HttpListenerTest() {
+        for (auto client = clients_.begin(); client != clients_.end();
+             ++client) {
+            (*client)->close();
+        }
+    }
+
+    /// @brief Connect to the endpoint.
+    ///
+    /// This method creates HttpClient instance and retains it in the clients_
+    /// list.
+    void startRequest(const std::string& request) {
+        HttpClientPtr client(new HttpClient(io_service_));
+        clients_.push_back(client);
+        clients_.back()->startRequest(request);
+    }
+
+    /// @brief Callback function invoke upon test timeout.
+    ///
+    /// It stops the IO service and reports test timeout.
+    void timeoutHandler() {
+        ADD_FAILURE() << "Timeout occurred while running the test!";
+        io_service_.stop();
+    }
+
+    /// @brief IO service used in the tests.
+    IOService io_service_;
+
+    /// @brief Pointer to the response creator factory.
+    HttpResponseCreatorFactoryPtr factory_;
+
+    /// @brief Asynchronous timer service to detect timeouts.
+    IntervalTimer test_timer_;
+
+    /// @brief List of client connections.
+    std::list<HttpClientPtr> clients_;
+};
+
+// This test verifies that HTTP connection can be established and used to
+// transmit HTTP request and receive a response.
+TEST_F(HttpListenerTest, listen) {
+    const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length: 3\r\n\r\n"
+        "{ }";
+
+    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+                          factory_, REQUEST_TIMEOUT);
+    ASSERT_NO_THROW(listener.start());
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(io_service_.run());
+    ASSERT_EQ(1, clients_.size());
+    HttpClientPtr client = *clients_.begin();
+    ASSERT_TRUE(client);
+    EXPECT_EQ("HTTP/1.1 200 OK\r\n"
+              "Content-Type: application/json\r\n"
+              "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+              "\r\n",
+              client->getResponse());
+    listener.stop();
+    io_service_.poll();
+}
+
+// This test verifies that the HTTP listener can't be started twice.
+TEST_F(HttpListenerTest, startTwice) {
+    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+                          factory_, REQUEST_TIMEOUT);
+    ASSERT_NO_THROW(listener.start());
+    EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that Bad Request status is returned when the request
+// is malformed.
+TEST_F(HttpListenerTest, badRequest) {
+    // Content-Type is wrong. This should result in Bad Request status.
+    const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+        "Content-Type: foo\r\n"
+        "Content-Length: 3\r\n\r\n"
+        "{ }";
+
+    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+                          factory_, REQUEST_TIMEOUT);
+    ASSERT_NO_THROW(listener.start());
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(io_service_.run());
+    ASSERT_EQ(1, clients_.size());
+    HttpClientPtr client = *clients_.begin();
+    ASSERT_TRUE(client);
+    EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n"
+              "Content-Length: 40\r\n"
+              "Content-Type: application/json\r\n"
+              "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+              "\r\n"
+              "{ \"result\": 400, \"text\": \"Bad Request\" }",
+              client->getResponse());
+}
+
+// This test verifies that NULL pointer can't be specified for the
+// HttpResponseCreatorFactory.
+TEST_F(HttpListenerTest, invalidFactory) {
+    EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+                              SERVER_PORT, HttpResponseCreatorFactoryPtr(),
+                              REQUEST_TIMEOUT),
+                 HttpListenerError);
+}
+
+// This test verifies that the timeout of 0 can't be specified for the
+// Request Timeout.
+TEST_F(HttpListenerTest, invalidRequestTimeout) {
+    EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+                              SERVER_PORT, factory_, 0),
+                 HttpListenerError);
+}
+
+// This test verifies that listener can't be bound to the port to which
+// other server is bound.
+TEST_F(HttpListenerTest, addressInUse) {
+    tcp::acceptor acceptor(io_service_.get_io_service());
+    // Use other port than SERVER_PORT to make sure that this TCP connection
+    // doesn't affect subsequent tests.
+    tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+                           SERVER_PORT + 1);
+    acceptor.open(endpoint.protocol());
+    acceptor.bind(endpoint);
+
+    // Listener should report an error when we try to start it because another
+    // acceptor is bound to that port and address.
+    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
+                          SERVER_PORT + 1, factory_, REQUEST_TIMEOUT);
+    EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected.
+TEST_F(HttpListenerTest, requestTimeout) {
+    // The part of the request specified here is correct but it is not
+    // a complete request.
+    const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length:";
+
+    // Open the listener with the Request Timeout of 1 sec and post the
+    // partial request.
+    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+                          factory_, 1000);
+    ASSERT_NO_THROW(listener.start());
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(io_service_.run());
+    ASSERT_EQ(1, clients_.size());
+    HttpClientPtr client = *clients_.begin();
+    ASSERT_TRUE(client);
+
+    // The server should wait for the missing part of the request for 1 second.
+    // The missing part never arrives so the server should respond with the
+    // HTTP Request Timeout status.
+    EXPECT_EQ("HTTP/1.1 408 Request Timeout\r\n"
+              "Content-Length: 44\r\n"
+              "Content-Type: application/json\r\n"
+              "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+              "\r\n"
+              "{ \"result\": 408, \"text\": \"Request Timeout\" }",
+              client->getResponse());
+}
+
+}

+ 15 - 5
src/lib/http/tests/response_creator_unittests.cc

@@ -27,14 +27,25 @@ typedef boost::shared_ptr<Response> ResponsePtr;
 
 /// @brief Implementation of the @ref HttpResponseCreator.
 class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+    /// @brief Create a new request.
+    ///
+    /// @return Pointer to the new instance of the @ref HttpRequest.
+    virtual HttpRequestPtr
+    createNewHttpRequest() const {
+        return (HttpRequestPtr(new HttpRequest()));
+    }
+
 private:
 
-    /// @brief Creates HTTP 400 response.
+    /// @brief Creates HTTP response..
     ///
     /// @param request Pointer to the HTTP request.
-    /// @return Pointer to the generated HTTP 400 response.
+    /// @return Pointer to the generated HTTP response.
     virtual HttpResponsePtr
-    createStockBadRequest(const ConstHttpRequestPtr& request) const {
+    createStockHttpResponse(const ConstHttpRequestPtr& request,
+                            const HttpStatusCode& status_code) const {
         // The request hasn't been finalized so the request object
         // doesn't contain any information about the HTTP version number
         // used. But, the context should have this data (assuming the
@@ -42,8 +53,7 @@ private:
         HttpVersion http_version(request->context()->http_version_major_,
                                  request->context()->http_version_minor_);
         // This will generate the response holding JSON content.
-        ResponsePtr response(new Response(http_version,
-                                          HttpStatusCode::BAD_REQUEST));
+        ResponsePtr response(new Response(http_version, status_code));
         return (response);
     }
 

+ 1 - 0
src/lib/http/tests/response_json_unittests.cc

@@ -135,6 +135,7 @@ TEST_F(HttpResponseJsonTest, genericResponse) {
     testGenericResponse(HttpStatusCode::UNAUTHORIZED, "Unauthorized");
     testGenericResponse(HttpStatusCode::FORBIDDEN, "Forbidden");
     testGenericResponse(HttpStatusCode::NOT_FOUND, "Not Found");
+    testGenericResponse(HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout");
     testGenericResponse(HttpStatusCode::INTERNAL_SERVER_ERROR,
                         "Internal Server Error");
     testGenericResponse(HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented");

+ 3 - 0
src/lib/http/tests/response_unittests.cc

@@ -90,6 +90,7 @@ TEST_F(HttpResponseTest, genericResponse) {
     testResponse(HttpStatusCode::UNAUTHORIZED, "Unauthorized");
     testResponse(HttpStatusCode::FORBIDDEN, "Forbidden");
     testResponse(HttpStatusCode::NOT_FOUND, "Not Found");
+    testResponse(HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout");
     testResponse(HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error");
     testResponse(HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented");
     testResponse(HttpStatusCode::BAD_GATEWAY, "Bad Gateway");
@@ -110,6 +111,7 @@ TEST_F(HttpResponseTest, isClientError) {
     EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::UNAUTHORIZED));
     EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::FORBIDDEN));
     EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::NOT_FOUND));
+    EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::REQUEST_TIMEOUT));
     EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::INTERNAL_SERVER_ERROR));
     EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NOT_IMPLEMENTED));
     EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::BAD_GATEWAY));
@@ -130,6 +132,7 @@ TEST_F(HttpResponseTest, isServerError) {
     EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::UNAUTHORIZED));
     EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::FORBIDDEN));
     EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NOT_FOUND));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::REQUEST_TIMEOUT));
     EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::INTERNAL_SERVER_ERROR));
     EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::NOT_IMPLEMENTED));
     EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::BAD_GATEWAY));