]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1764] Shared common setting
authorFrancis Dupont <fdupont@isc.org>
Fri, 2 Aug 2024 15:26:17 +0000 (17:26 +0200)
committerFrancis Dupont <fdupont@isc.org>
Thu, 22 Aug 2024 08:23:02 +0000 (10:23 +0200)
src/lib/http/tests/Makefile.am
src/lib/http/tests/http_client_unittests.cc [moved from src/lib/http/tests/server_client_unittests.cc with 61% similarity]
src/lib/http/tests/http_server_unittests.cc [new file with mode: 0644]
src/lib/http/tests/http_tests.h [new file with mode: 0644]
src/lib/http/tests/tls_client_unittests.cc
src/lib/http/tests/tls_server_unittests.cc

index 92f1bb7c5e4acdb52a707f32b00ccebf4b1f07b6..cb07e41bfc2871dc2eeafee71855807bf18ab21a 100644 (file)
@@ -43,7 +43,9 @@ libhttp_unittests_SOURCES += request_unittests.cc
 libhttp_unittests_SOURCES += response_unittests.cc
 libhttp_unittests_SOURCES += response_json_unittests.cc
 libhttp_unittests_SOURCES += run_unittests.cc
-libhttp_unittests_SOURCES += server_client_unittests.cc
+libhttp_unittests_SOURCES += http_tests.h
+libhttp_unittests_SOURCES += http_client_unittests.cc
+libhttp_unittests_SOURCES += http_server_unittests.cc
 if HAVE_OPENSSL
 libhttp_unittests_SOURCES += tls_server_unittests.cc
 libhttp_unittests_SOURCES += tls_client_unittests.cc
similarity index 61%
rename from src/lib/http/tests/server_client_unittests.cc
rename to src/lib/http/tests/http_client_unittests.cc
index b34e44047775d2526032aa3ade77ec45fbb9c514..355f6f0b841171df79e8d540e251da0ac1cdf49f 100644 (file)
@@ -21,6 +21,7 @@
 #include <http/tests/response_test.h>
 #include <http/url.h>
 #include <util/multi_threading_mgr.h>
+#include <http/tests/http_tests.h>
 
 #include <boost/asio/buffer.hpp>
 #include <boost/asio/ip/tcp.hpp>
@@ -42,40 +43,6 @@ namespace ph = std::placeholders;
 
 namespace {
 
-/// @brief IP address to which HTTP service is bound.
-const std::string SERVER_ADDRESS = "127.0.0.1";
-
-/// @brief IPv6 address to whch HTTP service is bound.
-const std::string IPV6_SERVER_ADDRESS = "::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 Persistent connection idle timeout used in most of the tests (ms).
-const long IDLE_TIMEOUT = 10000;
-
-/// @brief Persistent connection idle timeout used in tests where idle connections
-/// are tested (ms).
-const long SHORT_IDLE_TIMEOUT = 200;
-
-/// @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 Generic test HTTP response.
-typedef TestHttpResponseBase<HttpResponse> GenericResponse;
-
-/// @brief Pointer to generic test HTTP response.
-typedef boost::shared_ptr<GenericResponse> GenericResponsePtr;
-
 /// @brief Implementation of the @ref HttpResponseCreator.
 class TestHttpResponseCreator : public HttpResponseCreator {
 public:
@@ -197,200 +164,6 @@ public:
     }
 };
 
-/// @brief Implementation of the HTTP listener used in tests.
-///
-/// This implementation replaces the @c HttpConnection type with a custom
-/// implementation.
-///
-/// @tparam HttpConnectionType Type of the connection object to be used by
-/// the listener implementation.
-template<typename HttpConnectionType>
-class HttpListenerImplCustom : public HttpListenerImpl {
-public:
-
-    HttpListenerImplCustom(const IOServicePtr& io_service,
-                           const IOAddress& server_address,
-                           const unsigned short server_port,
-                           const TlsContextPtr& tls_context,
-                           const HttpResponseCreatorFactoryPtr& creator_factory,
-                           const long request_timeout,
-                           const long idle_timeout)
-        : HttpListenerImpl(io_service, server_address, server_port,
-                           tls_context, creator_factory, request_timeout,
-                           idle_timeout) {
-    }
-
-protected:
-
-    /// @brief Creates an instance of the @c HttpConnection.
-    ///
-    /// This method is virtual so as it can be overridden when customized
-    /// connections are to be used, e.g. in case of unit testing.
-    ///
-    /// @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.
-    ///
-    /// @return Pointer to the created connection.
-    virtual HttpConnectionPtr createConnection(const HttpResponseCreatorPtr& response_creator,
-                                               const HttpAcceptorCallback& callback) {
-        HttpConnectionPtr
-            conn(new HttpConnectionType(io_service_, acceptor_,
-                                        tls_context_, connections_,
-                                        response_creator, callback,
-                                        request_timeout_, idle_timeout_));
-        return (conn);
-    }
-};
-
-/// @brief Derivation of the @c HttpListener used in tests.
-///
-/// This class replaces the default implementation instance with the
-/// @c HttpListenerImplCustom using the customized connection type.
-///
-/// @tparam HttpConnectionType Type of the connection object to be used by
-/// the listener implementation.
-template<typename HttpConnectionType>
-class HttpListenerCustom : public HttpListener {
-public:
-
-    /// @brief Constructor.
-    ///
-    /// @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 tls_context TLS context.
-    /// @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.
-    HttpListenerCustom(const IOServicePtr& io_service,
-                       const IOAddress& server_address,
-                       const unsigned short server_port,
-                       const TlsContextPtr& tls_context,
-                       const HttpResponseCreatorFactoryPtr& creator_factory,
-                       const HttpListener::RequestTimeout& request_timeout,
-                       const HttpListener::IdleTimeout& idle_timeout)
-        : HttpListener(io_service, server_address, server_port,
-                       tls_context, creator_factory,
-                       request_timeout, idle_timeout) {
-        // Replace the default implementation with the customized version
-        // using the custom derivation of the HttpConnection.
-        impl_.reset(new HttpListenerImplCustom<HttpConnectionType>
-                    (io_service, server_address, server_port,
-                     tls_context, creator_factory, request_timeout.value_,
-                     idle_timeout.value_));
-    }
-};
-
-/// @brief Implementation of the @c HttpConnection which injects greater
-/// length value than the buffer size into the write socket callback.
-class HttpConnectionLongWriteBuffer : public HttpConnection {
-public:
-
-    /// @brief Constructor.
-    ///
-    /// @param io_service IO service to be used by the connection.
-    /// @param acceptor Pointer to the TCP acceptor object used to listen for
-    /// new HTTP connections.
-    /// @param tls_context TLS context.
-    /// @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.
-    HttpConnectionLongWriteBuffer(const IOServicePtr& io_service,
-                                  const HttpAcceptorPtr& acceptor,
-                                  const TlsContextPtr& tls_context,
-                                  HttpConnectionPool& connection_pool,
-                                  const HttpResponseCreatorPtr& response_creator,
-                                  const HttpAcceptorCallback& callback,
-                                  const long request_timeout,
-                                  const long idle_timeout)
-        : HttpConnection(io_service, acceptor, tls_context, connection_pool,
-                         response_creator, callback, request_timeout,
-                         idle_timeout) {
-    }
-
-    /// @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.
-    virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
-                                     boost::system::error_code ec,
-                                     size_t length) {
-        // Pass greater length of the data written. The callback should deal
-        // with this and adjust the data length.
-        HttpConnection::socketWriteCallback(transaction, ec, length + 1);
-    }
-};
-
-/// @brief Implementation of the @c HttpConnection which replaces
-/// transaction instance prior to calling write socket callback.
-class HttpConnectionTransactionChange : public HttpConnection {
-public:
-
-    /// @brief Constructor.
-    ///
-    /// @param io_service IO service to be used by the connection.
-    /// @param acceptor Pointer to the TCP acceptor object used to listen for
-    /// new HTTP connections.
-    /// @param context TLS tls_context.
-    /// @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.
-    HttpConnectionTransactionChange(const IOServicePtr& io_service,
-                                    const HttpAcceptorPtr& acceptor,
-                                    const TlsContextPtr& tls_context,
-                                    HttpConnectionPool& connection_pool,
-                                    const HttpResponseCreatorPtr& response_creator,
-                                    const HttpAcceptorCallback& callback,
-                                    const long request_timeout,
-                                    const long idle_timeout)
-        : HttpConnection(io_service, acceptor, tls_context, connection_pool,
-                         response_creator, callback, request_timeout,
-                         idle_timeout) {
-    }
-
-    /// @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.
-    virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
-                                     boost::system::error_code ec,
-                                     size_t length) {
-        // Replace the transaction. The socket callback should deal with this
-        // gracefully. It should detect that the output buffer is empty. Then
-        // try to see if the connection is persistent. This check should fail,
-        // because the request hasn't been created/finalized. The exception
-        // thrown upon checking the persistence should be caught and the
-        // connection closed.
-        transaction = HttpConnection::Transaction::create(response_creator_);
-        HttpConnection::socketWriteCallback(transaction, ec, length);
-    }
-};
-
-/// @brief Pointer to the TestHttpClient.
-typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr;
-
 /// @brief Test fixture class for @ref HttpListener.
 class HttpListenerTest : public ::testing::Test {
 public:
@@ -400,7 +173,7 @@ public:
     /// Starts test timer which detects timeouts.
     HttpListenerTest()
         : io_service_(new IOService()), factory_(new TestHttpResponseCreatorFactory()),
-          test_timer_(io_service_), run_io_service_timer_(io_service_), clients_() {
+          test_timer_(io_service_), run_io_service_timer_(io_service_) {
         test_timer_.setup(std::bind(&HttpListenerTest::timeoutHandler, this, true),
                           TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
     }
@@ -409,25 +182,10 @@ public:
     ///
     /// Removes active HTTP clients.
     virtual ~HttpListenerTest() {
-        for (auto const& client : clients_) {
-            client->close();
-        }
         test_timer_.cancel();
         io_service_->stopAndPoll();
     }
 
-    /// @brief Connect to the endpoint.
-    ///
-    /// This method creates TestHttpClient instance and retains it in the clients_
-    /// list.
-    ///
-    /// @param request String containing the HTTP request to be sent.
-    void startRequest(const std::string& request) {
-        TestHttpClientPtr client(new TestHttpClient(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.
@@ -457,104 +215,6 @@ public:
         io_service_->stopAndPoll(false);
     }
 
-    /// @brief Returns HTTP OK response expected by unit tests.
-    ///
-    /// @param http_version HTTP version.
-    ///
-    /// @return HTTP OK response expected by unit tests.
-    std::string httpOk(const HttpVersion& http_version) {
-        std::ostringstream s;
-        s << "HTTP/" << http_version.major_ << "." << http_version.minor_ << " 200 OK\r\n"
-            "Content-Length: 33\r\n"
-            "Content-Type: application/json\r\n"
-            "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
-            "\r\n"
-            "{ \"remote-address\": \"127.0.0.1\" }";
-        return (s.str());
-    }
-
-    /// @brief Tests that HTTP request timeout status is returned when the
-    /// server does not receive the entire request.
-    ///
-    /// @param request Partial request for which the parser will be waiting for
-    /// the next chunks of data.
-    /// @param expected_version HTTP version expected in the response.
-    void testRequestTimeout(const std::string& request,
-                            const HttpVersion& expected_version) {
-        // Open the listener with the Request Timeout of 1 sec and post the
-        // partial request.
-        HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
-                              SERVER_PORT, TlsContextPtr(),
-                              factory_, HttpListener::RequestTimeout(1000),
-                              HttpListener::IdleTimeout(IDLE_TIMEOUT));
-        ASSERT_NO_THROW(listener.start());
-        ASSERT_NO_THROW(startRequest(request));
-        ASSERT_NO_THROW(runIOService());
-        ASSERT_EQ(1, clients_.size());
-        TestHttpClientPtr client = *clients_.begin();
-        ASSERT_TRUE(client);
-
-        // Build the reference response.
-        std::ostringstream expected_response;
-        expected_response
-            << "HTTP/" << expected_version.major_ << "." << expected_version.minor_
-            << " 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\" }";
-
-        // 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(expected_response.str(), client->getResponse());
-    }
-
-    /// @brief Tests various cases when unexpected data is passed to the
-    /// socket write handler.
-    ///
-    /// This test uses the custom listener and the test specific derivations of
-    /// the @c HttpConnection class to enforce injection of the unexpected
-    /// data to the socket write callback. The two example applications of
-    /// this test are:
-    /// - injecting greater length value than the output buffer size,
-    /// - replacing the transaction with another transaction.
-    ///
-    /// It is expected that the socket write callback deals gracefully with
-    /// those situations.
-    ///
-    /// @tparam HttpConnectionType Test specific derivation of the
-    /// @c HttpConnection class.
-    template<typename HttpConnectionType>
-    void testWriteBufferIssues() {
-        // The HTTP/1.1 requests are by default persistent.
-        std::string request = "POST /foo/bar HTTP/1.1\r\n"
-            "Content-Type: application/json\r\n"
-            "Content-Length: 3\r\n\r\n"
-            "{ }";
-
-        // Use custom listener and the specialized connection object.
-        HttpListenerCustom<HttpConnectionType>
-            listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
-                     TlsContextPtr(), factory_,
-                     HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                     HttpListener::IdleTimeout(IDLE_TIMEOUT));
-
-        ASSERT_NO_THROW(listener.start());
-
-        // Send the request.
-        ASSERT_NO_THROW(startRequest(request));
-
-        // Injecting unexpected data should not result in an exception.
-        ASSERT_NO_THROW(runIOService());
-
-        ASSERT_EQ(1, clients_.size());
-        TestHttpClientPtr client = *clients_.begin();
-        ASSERT_TRUE(client);
-        EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
-    }
-
     /// @brief IO service used in the tests.
     IOServicePtr io_service_;
 
@@ -567,435 +227,8 @@ public:
     /// @brief Asynchronous timer for running IO service for a specified amount
     /// of time.
     IntervalTimer run_io_service_timer_;
-
-    /// @brief List of client connections.
-    std::list<TestHttpClientPtr> 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,
-                          TlsContextPtr(), factory_,
-                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
-    ASSERT_NO_THROW(listener.start());
-    ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
-    ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
-    ASSERT_NO_THROW(startRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    ASSERT_EQ(1, clients_.size());
-    TestHttpClientPtr client = *clients_.begin();
-    ASSERT_TRUE(client);
-    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
-
-    listener.stop();
-    io_service_->poll();
-}
-
-
-// This test verifies that persistent HTTP connection can be established when
-// "Connection: Keep-Alive" header value is specified.
-TEST_F(HttpListenerTest, keepAlive) {
-
-    // The first request contains the keep-alive header which instructs the server
-    // to maintain the TCP connection after sending a response.
-    std::string request = "POST /foo/bar HTTP/1.0\r\n"
-        "Content-Type: application/json\r\n"
-        "Content-Length: 3\r\n"
-        "Connection: Keep-Alive\r\n\r\n"
-        "{ }";
-
-    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
-                          TlsContextPtr(), factory_,
-                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
-
-    ASSERT_NO_THROW(listener.start());
-
-    // Send the request with the keep-alive header.
-    ASSERT_NO_THROW(startRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    ASSERT_EQ(1, clients_.size());
-    TestHttpClientPtr client = *clients_.begin();
-    ASSERT_TRUE(client);
-    EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
-
-    // We have sent keep-alive header so we expect that the connection with
-    // the server remains active.
-    ASSERT_TRUE(client->isConnectionAlive());
-
-    // Test that we can send another request via the same connection. This time
-    // it lacks the keep-alive header, so the server should close the connection
-    // after sending the response.
-    request = "POST /foo/bar HTTP/1.0\r\n"
-        "Content-Type: application/json\r\n"
-        "Content-Length: 3\r\n\r\n"
-        "{ }";
-
-    // Send request reusing the existing connection.
-    ASSERT_NO_THROW(client->sendRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
-
-    // Connection should have been closed by the server.
-    EXPECT_TRUE(client->isConnectionClosed());
-
-    listener.stop();
-    io_service_->poll();
-}
-
-// This test verifies that persistent HTTP connection is established by default
-// when HTTP/1.1 is in use.
-TEST_F(HttpListenerTest, persistentConnection) {
-
-    // The HTTP/1.1 requests are by default persistent.
-    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,
-                          TlsContextPtr(), factory_,
-                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
-
-    ASSERT_NO_THROW(listener.start());
-
-    // Send the first request.
-    ASSERT_NO_THROW(startRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    ASSERT_EQ(1, clients_.size());
-    TestHttpClientPtr client = *clients_.begin();
-    ASSERT_TRUE(client);
-    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
-
-    // HTTP/1.1 connection is persistent by default.
-    ASSERT_TRUE(client->isConnectionAlive());
-
-    // Test that we can send another request via the same connection. This time
-    // it includes the "Connection: close" header which instructs the server to
-    // close the connection after responding.
-    request = "POST /foo/bar HTTP/1.1\r\n"
-        "Content-Type: application/json\r\n"
-        "Content-Length: 3\r\n"
-        "Connection: close\r\n\r\n"
-        "{ }";
-
-    // Send request reusing the existing connection.
-    ASSERT_NO_THROW(client->sendRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
-
-    // Connection should have been closed by the server.
-    EXPECT_TRUE(client->isConnectionClosed());
-
-    listener.stop();
-    io_service_->poll();
-}
-
-// This test verifies that "keep-alive" connection is closed by the server after
-// an idle time.
-TEST_F(HttpListenerTest, keepAliveTimeout) {
-
-    // The first request contains the keep-alive header which instructs the server
-    // to maintain the TCP connection after sending a response.
-    std::string request = "POST /foo/bar HTTP/1.0\r\n"
-        "Content-Type: application/json\r\n"
-        "Content-Length: 3\r\n"
-        "Connection: Keep-Alive\r\n\r\n"
-        "{ }";
-
-    // Specify the idle timeout of 500ms.
-    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
-                          TlsContextPtr(), factory_,
-                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                          HttpListener::IdleTimeout(500));
-
-    ASSERT_NO_THROW(listener.start());
-
-    // Send the request with the keep-alive header.
-    ASSERT_NO_THROW(startRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    ASSERT_EQ(1, clients_.size());
-    TestHttpClientPtr client = *clients_.begin();
-    ASSERT_TRUE(client);
-    EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
-
-    // We have sent keep-alive header so we expect that the connection with
-    // the server remains active.
-    ASSERT_TRUE(client->isConnectionAlive());
-
-    // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
-    // should be closed by the server while we wait here.
-    runIOService(1000);
-
-    // Make sure the connection has been closed.
-    EXPECT_TRUE(client->isConnectionClosed());
-
-    // Check if we can re-establish the connection and send another request.
-    clients_.clear();
-    request = "POST /foo/bar HTTP/1.0\r\n"
-        "Content-Type: application/json\r\n"
-        "Content-Length: 3\r\n\r\n"
-        "{ }";
-
-    ASSERT_NO_THROW(startRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    ASSERT_EQ(1, clients_.size());
-    client = *clients_.begin();
-    ASSERT_TRUE(client);
-    EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
-
-    EXPECT_TRUE(client->isConnectionClosed());
-
-    listener.stop();
-    io_service_->poll();
-}
-
-// This test verifies that persistent connection is closed by the server after
-// an idle time.
-TEST_F(HttpListenerTest, persistentConnectionTimeout) {
-
-    // The HTTP/1.1 requests are by default persistent.
-    std::string request = "POST /foo/bar HTTP/1.1\r\n"
-        "Content-Type: application/json\r\n"
-        "Content-Length: 3\r\n\r\n"
-        "{ }";
-
-    // Specify the idle timeout of 500ms.
-    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
-                          TlsContextPtr(), factory_,
-                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                          HttpListener::IdleTimeout(500));
-
-    ASSERT_NO_THROW(listener.start());
-
-    // Send the request.
-    ASSERT_NO_THROW(startRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    ASSERT_EQ(1, clients_.size());
-    TestHttpClientPtr client = *clients_.begin();
-    ASSERT_TRUE(client);
-    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
-
-    // The connection should remain active.
-    ASSERT_TRUE(client->isConnectionAlive());
-
-    // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
-    // should be closed by the server while we wait here.
-    runIOService(1000);
-
-    // Make sure the connection has been closed.
-    EXPECT_TRUE(client->isConnectionClosed());
-
-    // Check if we can re-establish the connection and send another request.
-    clients_.clear();
-    request = "POST /foo/bar HTTP/1.1\r\n"
-        "Content-Type: application/json\r\n"
-        "Content-Length: 3\r\n"
-        "Connection: close\r\n\r\n"
-        "{ }";
-
-    ASSERT_NO_THROW(startRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    ASSERT_EQ(1, clients_.size());
-    client = *clients_.begin();
-    ASSERT_TRUE(client);
-    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
-
-    EXPECT_TRUE(client->isConnectionClosed());
-
-    listener.stop();
-    io_service_->poll();
-}
-
-// This test verifies that HTTP/1.1 connection remains open even if there is an
-// error in the message body.
-TEST_F(HttpListenerTest, persistentConnectionBadBody) {
-
-    // The HTTP/1.1 requests are by default persistent.
-    std::string request = "POST /foo/bar HTTP/1.1\r\n"
-        "Content-Type: application/json\r\n"
-        "Content-Length: 12\r\n\r\n"
-        "{ \"a\": abc }";
-
-    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
-                          TlsContextPtr(), factory_,
-                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
-
-    ASSERT_NO_THROW(listener.start());
-
-    // Send the request.
-    ASSERT_NO_THROW(startRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    ASSERT_EQ(1, clients_.size());
-    TestHttpClientPtr 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());
-
-    // The connection should remain active.
-    ASSERT_TRUE(client->isConnectionAlive());
-
-    // Make sure that we can send another request. This time we specify the
-    // "close" connection-token to force the connection to close.
-    request = "POST /foo/bar HTTP/1.1\r\n"
-        "Content-Type: application/json\r\n"
-        "Content-Length: 3\r\n"
-        "Connection: close\r\n\r\n"
-        "{ }";
-
-    // Send request reusing the existing connection.
-    ASSERT_NO_THROW(client->sendRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
-
-    EXPECT_TRUE(client->isConnectionClosed());
-
-    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,
-                          TlsContextPtr(), factory_,
-                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                          HttpListener::IdleTimeout(IDLE_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,
-                          TlsContextPtr(), factory_,
-                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
-    ASSERT_NO_THROW(listener.start());
-    ASSERT_NO_THROW(startRequest(request));
-    ASSERT_NO_THROW(runIOService());
-    ASSERT_EQ(1, clients_.size());
-    TestHttpClientPtr 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, TlsContextPtr(),
-                              HttpResponseCreatorFactoryPtr(),
-                              HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                              HttpListener::IdleTimeout(IDLE_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, TlsContextPtr(), factory_,
-                              HttpListener::RequestTimeout(0),
-                              HttpListener::IdleTimeout(IDLE_TIMEOUT)),
-                 HttpListenerError);
-}
-
-// This test verifies that the timeout of 0 can't be specified for the
-// idle persistent connection timeout.
-TEST_F(HttpListenerTest, invalidIdleTimeout) {
-    EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
-                              SERVER_PORT, TlsContextPtr(), factory_,
-                              HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                              HttpListener::IdleTimeout(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_->getInternalIOService());
-    // 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, TlsContextPtr(), factory_,
-                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
-                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
-    EXPECT_THROW(listener.start(), HttpListenerError);
-}
-
-// This test verifies that HTTP Request Timeout status is returned as
-// expected when the read part of the request contains the HTTP
-// version number. The timeout response should contain the same
-// HTTP version number as the partial request.
-TEST_F(HttpListenerTest, requestTimeoutHttpVersionFound) {
-    // 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:";
-
-    testRequestTimeout(request, HttpVersion::HTTP_11());
-}
-
-// This test verifies that HTTP Request Timeout status is returned as
-// expected when the read part of the request does not contain
-// the HTTP version number. The timeout response should by default
-// contain HTTP/1.0 version number.
-TEST_F(HttpListenerTest, requestTimeoutHttpVersionNotFound) {
-    // The part of the request specified here is correct but it is not
-    // a complete request.
-    const std::string request = "POST /foo/bar HTTP";
-
-    testRequestTimeout(request, HttpVersion::HTTP_10());
-}
-
-// This test verifies that injecting length value greater than the
-// output buffer length to the socket write callback does not cause
-// an exception.
-TEST_F(HttpListenerTest, tooLongWriteBuffer) {
-    testWriteBufferIssues<HttpConnectionLongWriteBuffer>();
-}
-
-// This test verifies that changing the transaction before calling
-// the socket write callback does not cause an exception.
-TEST_F(HttpListenerTest, transactionChangeDuringWrite) {
-    testWriteBufferIssues<HttpConnectionTransactionChange>();
-}
-
 /// @brief Test fixture class for testing HTTP client.
 class HttpClientTest : public HttpListenerTest {
 public:
diff --git a/src/lib/http/tests/http_server_unittests.cc b/src/lib/http/tests/http_server_unittests.cc
new file mode 100644 (file)
index 0000000..60e7fc6
--- /dev/null
@@ -0,0 +1,966 @@
+// Copyright (C) 2017-2024 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 <asiolink/tls_acceptor.h>
+#include <cc/data.h>
+#include <http/testutils/test_http_client.h>
+#include <http/client.h>
+#include <http/http_types.h>
+#include <http/listener.h>
+#include <http/listener_impl.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 <http/url.h>
+#include <util/multi_threading_mgr.h>
+#include <http/tests/http_tests.h>
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <list>
+#include <sstream>
+#include <string>
+
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @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 HttpRequestPtr& 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));
+        response->finalize();
+        return (response);
+    }
+
+    /// @brief Creates HTTP response.
+    ///
+    /// This method generates 3 types of responses:
+    /// - response with a requested content type,
+    /// - partial response with incomplete JSON body,
+    /// - response with JSON body copied from the request.
+    ///
+    /// The first one is useful to test situations when received response can't
+    /// be parsed because of the content type mismatch. The second one is useful
+    /// to test request timeouts. The third type is used by most of the unit tests
+    /// to test successful transactions.
+    ///
+    /// @param request Pointer to the HTTP request.
+    /// @return Pointer to the generated HTTP OK response with no content.
+    virtual HttpResponsePtr
+    createDynamicHttpResponse(HttpRequestPtr request) {
+        // Request must always be JSON.
+        PostHttpRequestJsonPtr request_json =
+            boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+        ConstElementPtr body;
+        if (request_json) {
+            body = request_json->getBodyAsJson();
+            if (body) {
+                // Check if the client requested one of the two first response
+                // types.
+                GenericResponsePtr response;
+                ConstElementPtr content_type = body->get("requested-content-type");
+                ConstElementPtr partial_response = body->get("partial-response");
+                if (content_type || partial_response) {
+                    // The first two response types can only be generated using the
+                    // generic response as we have to explicitly modify some of the
+                    // values.
+                    response.reset(new GenericResponse(request->getHttpVersion(),
+                                                       HttpStatusCode::OK));
+                    HttpResponseContextPtr ctx = response->context();
+
+                    if (content_type) {
+                        // Provide requested content type.
+                        ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+                                                                  content_type->stringValue()));
+                        // It doesn't matter what body is there.
+                        ctx->body_ = "abcd";
+                        response->finalize();
+
+                    } else {
+                        // Generate JSON response.
+                        ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+                                                                  "application/json"));
+                        // The body lacks '}' so the client will be waiting for it and
+                        // eventually should time out.
+                        ctx->body_ = "{";
+                        response->finalize();
+                        // The auto generated Content-Length header would be based on the
+                        // body size (so set to 1 byte). We have to override it to
+                        // account for the missing '}' character.
+                        response->setContentLength(2);
+                    }
+                    return (response);
+                }
+            }
+        }
+
+        // Third type of response is requested.
+        ResponsePtr response(new Response(request->getHttpVersion(),
+                                          HttpStatusCode::OK));
+        // If body was included in the request. Let's copy it.
+        if (body) {
+            response->setBodyAsJson(body);
+        }
+
+        response->finalize();
+        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 Implementation of the HTTP listener used in tests.
+///
+/// This implementation replaces the @c HttpConnection type with a custom
+/// implementation.
+///
+/// @tparam HttpConnectionType Type of the connection object to be used by
+/// the listener implementation.
+template<typename HttpConnectionType>
+class HttpListenerImplCustom : public HttpListenerImpl {
+public:
+
+    HttpListenerImplCustom(const IOServicePtr& io_service,
+                           const IOAddress& server_address,
+                           const unsigned short server_port,
+                           const TlsContextPtr& tls_context,
+                           const HttpResponseCreatorFactoryPtr& creator_factory,
+                           const long request_timeout,
+                           const long idle_timeout)
+        : HttpListenerImpl(io_service, server_address, server_port,
+                           tls_context, creator_factory, request_timeout,
+                           idle_timeout) {
+    }
+
+protected:
+
+    /// @brief Creates an instance of the @c HttpConnection.
+    ///
+    /// This method is virtual so as it can be overridden when customized
+    /// connections are to be used, e.g. in case of unit testing.
+    ///
+    /// @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.
+    ///
+    /// @return Pointer to the created connection.
+    virtual HttpConnectionPtr createConnection(const HttpResponseCreatorPtr& response_creator,
+                                               const HttpAcceptorCallback& callback) {
+        HttpConnectionPtr
+            conn(new HttpConnectionType(io_service_, acceptor_,
+                                        tls_context_, connections_,
+                                        response_creator, callback,
+                                        request_timeout_, idle_timeout_));
+        return (conn);
+    }
+};
+
+/// @brief Derivation of the @c HttpListener used in tests.
+///
+/// This class replaces the default implementation instance with the
+/// @c HttpListenerImplCustom using the customized connection type.
+///
+/// @tparam HttpConnectionType Type of the connection object to be used by
+/// the listener implementation.
+template<typename HttpConnectionType>
+class HttpListenerCustom : public HttpListener {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @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 tls_context TLS context.
+    /// @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.
+    HttpListenerCustom(const IOServicePtr& io_service,
+                       const IOAddress& server_address,
+                       const unsigned short server_port,
+                       const TlsContextPtr& tls_context,
+                       const HttpResponseCreatorFactoryPtr& creator_factory,
+                       const HttpListener::RequestTimeout& request_timeout,
+                       const HttpListener::IdleTimeout& idle_timeout)
+        : HttpListener(io_service, server_address, server_port,
+                       tls_context, creator_factory,
+                       request_timeout, idle_timeout) {
+        // Replace the default implementation with the customized version
+        // using the custom derivation of the HttpConnection.
+        impl_.reset(new HttpListenerImplCustom<HttpConnectionType>
+                    (io_service, server_address, server_port,
+                     tls_context, creator_factory, request_timeout.value_,
+                     idle_timeout.value_));
+    }
+};
+
+/// @brief Implementation of the @c HttpConnection which injects greater
+/// length value than the buffer size into the write socket callback.
+class HttpConnectionLongWriteBuffer : public HttpConnection {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service IO service to be used by the connection.
+    /// @param acceptor Pointer to the TCP acceptor object used to listen for
+    /// new HTTP connections.
+    /// @param tls_context TLS context.
+    /// @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.
+    HttpConnectionLongWriteBuffer(const IOServicePtr& io_service,
+                                  const HttpAcceptorPtr& acceptor,
+                                  const TlsContextPtr& tls_context,
+                                  HttpConnectionPool& connection_pool,
+                                  const HttpResponseCreatorPtr& response_creator,
+                                  const HttpAcceptorCallback& callback,
+                                  const long request_timeout,
+                                  const long idle_timeout)
+        : HttpConnection(io_service, acceptor, tls_context, connection_pool,
+                         response_creator, callback, request_timeout,
+                         idle_timeout) {
+    }
+
+    /// @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.
+    virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
+                                     boost::system::error_code ec,
+                                     size_t length) {
+        // Pass greater length of the data written. The callback should deal
+        // with this and adjust the data length.
+        HttpConnection::socketWriteCallback(transaction, ec, length + 1);
+    }
+};
+
+/// @brief Implementation of the @c HttpConnection which replaces
+/// transaction instance prior to calling write socket callback.
+class HttpConnectionTransactionChange : public HttpConnection {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param io_service IO service to be used by the connection.
+    /// @param acceptor Pointer to the TCP acceptor object used to listen for
+    /// new HTTP connections.
+    /// @param context TLS tls_context.
+    /// @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.
+    HttpConnectionTransactionChange(const IOServicePtr& io_service,
+                                    const HttpAcceptorPtr& acceptor,
+                                    const TlsContextPtr& tls_context,
+                                    HttpConnectionPool& connection_pool,
+                                    const HttpResponseCreatorPtr& response_creator,
+                                    const HttpAcceptorCallback& callback,
+                                    const long request_timeout,
+                                    const long idle_timeout)
+        : HttpConnection(io_service, acceptor, tls_context, connection_pool,
+                         response_creator, callback, request_timeout,
+                         idle_timeout) {
+    }
+
+    /// @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.
+    virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
+                                     boost::system::error_code ec,
+                                     size_t length) {
+        // Replace the transaction. The socket callback should deal with this
+        // gracefully. It should detect that the output buffer is empty. Then
+        // try to see if the connection is persistent. This check should fail,
+        // because the request hasn't been created/finalized. The exception
+        // thrown upon checking the persistence should be caught and the
+        // connection closed.
+        transaction = HttpConnection::Transaction::create(response_creator_);
+        HttpConnection::socketWriteCallback(transaction, ec, length);
+    }
+};
+
+/// @brief Pointer to the TestHttpClient.
+typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr;
+
+/// @brief Test fixture class for @ref HttpListener.
+class HttpListenerTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Starts test timer which detects timeouts.
+    HttpListenerTest()
+        : io_service_(new IOService()), factory_(new TestHttpResponseCreatorFactory()),
+          test_timer_(io_service_), run_io_service_timer_(io_service_), clients_() {
+        test_timer_.setup(std::bind(&HttpListenerTest::timeoutHandler, this, true),
+                          TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Removes active HTTP clients.
+    virtual ~HttpListenerTest() {
+        for (auto const& client : clients_) {
+            client->close();
+        }
+        test_timer_.cancel();
+        io_service_->stopAndPoll();
+    }
+
+    /// @brief Connect to the endpoint.
+    ///
+    /// This method creates TestHttpClient instance and retains it in the clients_
+    /// list.
+    ///
+    /// @param request String containing the HTTP request to be sent.
+    void startRequest(const std::string& request) {
+        TestHttpClientPtr client(new TestHttpClient(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.
+    ///
+    /// @param fail_on_timeout Specifies if test failure should be reported.
+    void timeoutHandler(const bool fail_on_timeout) {
+        if (fail_on_timeout) {
+            ADD_FAILURE() << "Timeout occurred while running the test!";
+        }
+        io_service_->stop();
+    }
+
+    /// @brief Runs IO service with optional timeout.
+    ///
+    /// @param timeout Optional value specifying for how long the io service
+    /// should be ran.
+    void runIOService(long timeout = 0) {
+        io_service_->stop();
+        io_service_->restart();
+
+        if (timeout > 0) {
+            run_io_service_timer_.setup(std::bind(&HttpListenerTest::timeoutHandler,
+                                                  this, false),
+                                        timeout, IntervalTimer::ONE_SHOT);
+        }
+        io_service_->run();
+        io_service_->stopAndPoll(false);
+    }
+
+    /// @brief Returns HTTP OK response expected by unit tests.
+    ///
+    /// @param http_version HTTP version.
+    ///
+    /// @return HTTP OK response expected by unit tests.
+    std::string httpOk(const HttpVersion& http_version) {
+        std::ostringstream s;
+        s << "HTTP/" << http_version.major_ << "." << http_version.minor_ << " 200 OK\r\n"
+            "Content-Length: 33\r\n"
+            "Content-Type: application/json\r\n"
+            "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+            "\r\n"
+            "{ \"remote-address\": \"127.0.0.1\" }";
+        return (s.str());
+    }
+
+    /// @brief Tests that HTTP request timeout status is returned when the
+    /// server does not receive the entire request.
+    ///
+    /// @param request Partial request for which the parser will be waiting for
+    /// the next chunks of data.
+    /// @param expected_version HTTP version expected in the response.
+    void testRequestTimeout(const std::string& request,
+                            const HttpVersion& expected_version) {
+        // Open the listener with the Request Timeout of 1 sec and post the
+        // partial request.
+        HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
+                              SERVER_PORT, TlsContextPtr(),
+                              factory_, HttpListener::RequestTimeout(1000),
+                              HttpListener::IdleTimeout(IDLE_TIMEOUT));
+        ASSERT_NO_THROW(listener.start());
+        ASSERT_NO_THROW(startRequest(request));
+        ASSERT_NO_THROW(runIOService());
+        ASSERT_EQ(1, clients_.size());
+        TestHttpClientPtr client = *clients_.begin();
+        ASSERT_TRUE(client);
+
+        // Build the reference response.
+        std::ostringstream expected_response;
+        expected_response
+            << "HTTP/" << expected_version.major_ << "." << expected_version.minor_
+            << " 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\" }";
+
+        // 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(expected_response.str(), client->getResponse());
+    }
+
+    /// @brief Tests various cases when unexpected data is passed to the
+    /// socket write handler.
+    ///
+    /// This test uses the custom listener and the test specific derivations of
+    /// the @c HttpConnection class to enforce injection of the unexpected
+    /// data to the socket write callback. The two example applications of
+    /// this test are:
+    /// - injecting greater length value than the output buffer size,
+    /// - replacing the transaction with another transaction.
+    ///
+    /// It is expected that the socket write callback deals gracefully with
+    /// those situations.
+    ///
+    /// @tparam HttpConnectionType Test specific derivation of the
+    /// @c HttpConnection class.
+    template<typename HttpConnectionType>
+    void testWriteBufferIssues() {
+        // The HTTP/1.1 requests are by default persistent.
+        std::string request = "POST /foo/bar HTTP/1.1\r\n"
+            "Content-Type: application/json\r\n"
+            "Content-Length: 3\r\n\r\n"
+            "{ }";
+
+        // Use custom listener and the specialized connection object.
+        HttpListenerCustom<HttpConnectionType>
+            listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+                     TlsContextPtr(), factory_,
+                     HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                     HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+        ASSERT_NO_THROW(listener.start());
+
+        // Send the request.
+        ASSERT_NO_THROW(startRequest(request));
+
+        // Injecting unexpected data should not result in an exception.
+        ASSERT_NO_THROW(runIOService());
+
+        ASSERT_EQ(1, clients_.size());
+        TestHttpClientPtr client = *clients_.begin();
+        ASSERT_TRUE(client);
+        EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+    }
+
+    /// @brief IO service used in the tests.
+    IOServicePtr io_service_;
+
+    /// @brief Pointer to the response creator factory.
+    HttpResponseCreatorFactoryPtr factory_;
+
+    /// @brief Asynchronous timer service to detect timeouts.
+    IntervalTimer test_timer_;
+
+    /// @brief Asynchronous timer for running IO service for a specified amount
+    /// of time.
+    IntervalTimer run_io_service_timer_;
+
+    /// @brief List of client connections.
+    std::list<TestHttpClientPtr> 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,
+                          TlsContextPtr(), factory_,
+                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
+    ASSERT_NO_THROW(listener.start());
+    ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+    ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    ASSERT_EQ(1, clients_.size());
+    TestHttpClientPtr client = *clients_.begin();
+    ASSERT_TRUE(client);
+    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+    listener.stop();
+    io_service_->poll();
+}
+
+
+// This test verifies that persistent HTTP connection can be established when
+// "Connection: Keep-Alive" header value is specified.
+TEST_F(HttpListenerTest, keepAlive) {
+
+    // The first request contains the keep-alive header which instructs the server
+    // to maintain the TCP connection after sending a response.
+    std::string request = "POST /foo/bar HTTP/1.0\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length: 3\r\n"
+        "Connection: Keep-Alive\r\n\r\n"
+        "{ }";
+
+    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+                          TlsContextPtr(), factory_,
+                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+    ASSERT_NO_THROW(listener.start());
+
+    // Send the request with the keep-alive header.
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    ASSERT_EQ(1, clients_.size());
+    TestHttpClientPtr client = *clients_.begin();
+    ASSERT_TRUE(client);
+    EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+    // We have sent keep-alive header so we expect that the connection with
+    // the server remains active.
+    ASSERT_TRUE(client->isConnectionAlive());
+
+    // Test that we can send another request via the same connection. This time
+    // it lacks the keep-alive header, so the server should close the connection
+    // after sending the response.
+    request = "POST /foo/bar HTTP/1.0\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length: 3\r\n\r\n"
+        "{ }";
+
+    // Send request reusing the existing connection.
+    ASSERT_NO_THROW(client->sendRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+    // Connection should have been closed by the server.
+    EXPECT_TRUE(client->isConnectionClosed());
+
+    listener.stop();
+    io_service_->poll();
+}
+
+// This test verifies that persistent HTTP connection is established by default
+// when HTTP/1.1 is in use.
+TEST_F(HttpListenerTest, persistentConnection) {
+
+    // The HTTP/1.1 requests are by default persistent.
+    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,
+                          TlsContextPtr(), factory_,
+                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+    ASSERT_NO_THROW(listener.start());
+
+    // Send the first request.
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    ASSERT_EQ(1, clients_.size());
+    TestHttpClientPtr client = *clients_.begin();
+    ASSERT_TRUE(client);
+    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+    // HTTP/1.1 connection is persistent by default.
+    ASSERT_TRUE(client->isConnectionAlive());
+
+    // Test that we can send another request via the same connection. This time
+    // it includes the "Connection: close" header which instructs the server to
+    // close the connection after responding.
+    request = "POST /foo/bar HTTP/1.1\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length: 3\r\n"
+        "Connection: close\r\n\r\n"
+        "{ }";
+
+    // Send request reusing the existing connection.
+    ASSERT_NO_THROW(client->sendRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+    // Connection should have been closed by the server.
+    EXPECT_TRUE(client->isConnectionClosed());
+
+    listener.stop();
+    io_service_->poll();
+}
+
+// This test verifies that "keep-alive" connection is closed by the server after
+// an idle time.
+TEST_F(HttpListenerTest, keepAliveTimeout) {
+
+    // The first request contains the keep-alive header which instructs the server
+    // to maintain the TCP connection after sending a response.
+    std::string request = "POST /foo/bar HTTP/1.0\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length: 3\r\n"
+        "Connection: Keep-Alive\r\n\r\n"
+        "{ }";
+
+    // Specify the idle timeout of 500ms.
+    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+                          TlsContextPtr(), factory_,
+                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                          HttpListener::IdleTimeout(500));
+
+    ASSERT_NO_THROW(listener.start());
+
+    // Send the request with the keep-alive header.
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    ASSERT_EQ(1, clients_.size());
+    TestHttpClientPtr client = *clients_.begin();
+    ASSERT_TRUE(client);
+    EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+    // We have sent keep-alive header so we expect that the connection with
+    // the server remains active.
+    ASSERT_TRUE(client->isConnectionAlive());
+
+    // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
+    // should be closed by the server while we wait here.
+    runIOService(1000);
+
+    // Make sure the connection has been closed.
+    EXPECT_TRUE(client->isConnectionClosed());
+
+    // Check if we can re-establish the connection and send another request.
+    clients_.clear();
+    request = "POST /foo/bar HTTP/1.0\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length: 3\r\n\r\n"
+        "{ }";
+
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    ASSERT_EQ(1, clients_.size());
+    client = *clients_.begin();
+    ASSERT_TRUE(client);
+    EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+    EXPECT_TRUE(client->isConnectionClosed());
+
+    listener.stop();
+    io_service_->poll();
+}
+
+// This test verifies that persistent connection is closed by the server after
+// an idle time.
+TEST_F(HttpListenerTest, persistentConnectionTimeout) {
+
+    // The HTTP/1.1 requests are by default persistent.
+    std::string request = "POST /foo/bar HTTP/1.1\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length: 3\r\n\r\n"
+        "{ }";
+
+    // Specify the idle timeout of 500ms.
+    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+                          TlsContextPtr(), factory_,
+                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                          HttpListener::IdleTimeout(500));
+
+    ASSERT_NO_THROW(listener.start());
+
+    // Send the request.
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    ASSERT_EQ(1, clients_.size());
+    TestHttpClientPtr client = *clients_.begin();
+    ASSERT_TRUE(client);
+    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+    // The connection should remain active.
+    ASSERT_TRUE(client->isConnectionAlive());
+
+    // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
+    // should be closed by the server while we wait here.
+    runIOService(1000);
+
+    // Make sure the connection has been closed.
+    EXPECT_TRUE(client->isConnectionClosed());
+
+    // Check if we can re-establish the connection and send another request.
+    clients_.clear();
+    request = "POST /foo/bar HTTP/1.1\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length: 3\r\n"
+        "Connection: close\r\n\r\n"
+        "{ }";
+
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    ASSERT_EQ(1, clients_.size());
+    client = *clients_.begin();
+    ASSERT_TRUE(client);
+    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+    EXPECT_TRUE(client->isConnectionClosed());
+
+    listener.stop();
+    io_service_->poll();
+}
+
+// This test verifies that HTTP/1.1 connection remains open even if there is an
+// error in the message body.
+TEST_F(HttpListenerTest, persistentConnectionBadBody) {
+
+    // The HTTP/1.1 requests are by default persistent.
+    std::string request = "POST /foo/bar HTTP/1.1\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length: 12\r\n\r\n"
+        "{ \"a\": abc }";
+
+    HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+                          TlsContextPtr(), factory_,
+                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+    ASSERT_NO_THROW(listener.start());
+
+    // Send the request.
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    ASSERT_EQ(1, clients_.size());
+    TestHttpClientPtr 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());
+
+    // The connection should remain active.
+    ASSERT_TRUE(client->isConnectionAlive());
+
+    // Make sure that we can send another request. This time we specify the
+    // "close" connection-token to force the connection to close.
+    request = "POST /foo/bar HTTP/1.1\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length: 3\r\n"
+        "Connection: close\r\n\r\n"
+        "{ }";
+
+    // Send request reusing the existing connection.
+    ASSERT_NO_THROW(client->sendRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+    EXPECT_TRUE(client->isConnectionClosed());
+
+    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,
+                          TlsContextPtr(), factory_,
+                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                          HttpListener::IdleTimeout(IDLE_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,
+                          TlsContextPtr(), factory_,
+                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
+    ASSERT_NO_THROW(listener.start());
+    ASSERT_NO_THROW(startRequest(request));
+    ASSERT_NO_THROW(runIOService());
+    ASSERT_EQ(1, clients_.size());
+    TestHttpClientPtr 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, TlsContextPtr(),
+                              HttpResponseCreatorFactoryPtr(),
+                              HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                              HttpListener::IdleTimeout(IDLE_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, TlsContextPtr(), factory_,
+                              HttpListener::RequestTimeout(0),
+                              HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+                 HttpListenerError);
+}
+
+// This test verifies that the timeout of 0 can't be specified for the
+// idle persistent connection timeout.
+TEST_F(HttpListenerTest, invalidIdleTimeout) {
+    EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+                              SERVER_PORT, TlsContextPtr(), factory_,
+                              HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                              HttpListener::IdleTimeout(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_->getInternalIOService());
+    // 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, TlsContextPtr(), factory_,
+                          HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+                          HttpListener::IdleTimeout(IDLE_TIMEOUT));
+    EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected when the read part of the request contains the HTTP
+// version number. The timeout response should contain the same
+// HTTP version number as the partial request.
+TEST_F(HttpListenerTest, requestTimeoutHttpVersionFound) {
+    // 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:";
+
+    testRequestTimeout(request, HttpVersion::HTTP_11());
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected when the read part of the request does not contain
+// the HTTP version number. The timeout response should by default
+// contain HTTP/1.0 version number.
+TEST_F(HttpListenerTest, requestTimeoutHttpVersionNotFound) {
+    // The part of the request specified here is correct but it is not
+    // a complete request.
+    const std::string request = "POST /foo/bar HTTP";
+
+    testRequestTimeout(request, HttpVersion::HTTP_10());
+}
+
+// This test verifies that injecting length value greater than the
+// output buffer length to the socket write callback does not cause
+// an exception.
+TEST_F(HttpListenerTest, tooLongWriteBuffer) {
+    testWriteBufferIssues<HttpConnectionLongWriteBuffer>();
+}
+
+// This test verifies that changing the transaction before calling
+// the socket write callback does not cause an exception.
+TEST_F(HttpListenerTest, transactionChangeDuringWrite) {
+    testWriteBufferIssues<HttpConnectionTransactionChange>();
+}
+
+}
diff --git a/src/lib/http/tests/http_tests.h b/src/lib/http/tests/http_tests.h
new file mode 100644 (file)
index 0000000..08219a9
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright (C) 2017-2024 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/.
+
+namespace isc {
+namespace http {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief IPv6 address to whch HTTP service is bound.
+const std::string IPV6_SERVER_ADDRESS = "::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 Persistent connection idle timeout used in most of the tests (ms).
+const long IDLE_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in tests where idle connections
+/// are tested (ms).
+const long SHORT_IDLE_TIMEOUT = 200;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test HTTP response.
+typedef test::TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Generic test HTTP response.
+typedef test::TestHttpResponseBase<HttpResponse> GenericResponse;
+
+/// @brief Pointer to generic test HTTP response.
+typedef boost::shared_ptr<GenericResponse> GenericResponsePtr;
+
+}
+}
index 5135d9256c88b2780f0e9bcb6e39d96eea8f2d49..66638ace76a61a28c8fd967f6b741ed0180ef042 100644 (file)
@@ -21,6 +21,7 @@
 #include <http/tests/response_test.h>
 #include <http/url.h>
 #include <util/multi_threading_mgr.h>
+#include <http/tests/http_tests.h>
 
 #include <boost/asio/buffer.hpp>
 #include <boost/asio/ip/tcp.hpp>
@@ -51,44 +52,8 @@ using namespace isc::http::test;
 using namespace isc::util;
 namespace ph = std::placeholders;
 
-/// @todo: put the common part of client and server tests in its own file(s).
-
 namespace {
 
-/// @brief IP address to which HTTP service is bound.
-const std::string SERVER_ADDRESS = "127.0.0.1";
-
-/// @brief IPv6 address to whch HTTP service is bound.
-const std::string IPV6_SERVER_ADDRESS = "::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 Persistent connection idle timeout used in most of the tests (ms).
-const long IDLE_TIMEOUT = 10000;
-
-/// @brief Persistent connection idle timeout used in tests where idle connections
-/// are tested (ms).
-const long SHORT_IDLE_TIMEOUT = 200;
-
-/// @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 Generic test HTTP response.
-typedef TestHttpResponseBase<HttpResponse> GenericResponse;
-
-/// @brief Pointer to generic test HTTP response.
-typedef boost::shared_ptr<GenericResponse> GenericResponsePtr;
-
 /// @brief Implementation of the @ref HttpResponseCreator.
 class TestHttpResponseCreator : public HttpResponseCreator {
 public:
index 10b82641f97d49e8bd9f0dfb6c6b6d828e155cf4..82a77abc3644c9c0b51af86fff48bd98eb0bc7fe 100644 (file)
@@ -22,6 +22,7 @@
 #include <http/testutils/test_http_client.h>
 #include <http/url.h>
 #include <util/multi_threading_mgr.h>
+#include <http/tests/http_tests.h>
 
 #include <boost/asio/buffer.hpp>
 #include <boost/asio/ip/tcp.hpp>
@@ -42,37 +43,8 @@ using namespace isc::http;
 using namespace isc::http::test;
 using namespace isc::util;
 
-/// @todo: put the common part of client and server tests in its own file(s).
-
 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 Persistent connection idle timeout used in most of the tests (ms).
-const long IDLE_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 Generic test HTTP response.
-typedef TestHttpResponseBase<HttpResponse> GenericResponse;
-
-/// @brief Pointer to generic test HTTP response.
-typedef boost::shared_ptr<GenericResponse> GenericResponsePtr;
-
 /// @brief Implementation of the @ref HttpResponseCreator.
 class TestHttpResponseCreator : public HttpResponseCreator {
 public: