// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace isc::asiolink; using namespace isc::http; using namespace isc::http::test; using namespace isc::util; namespace { /// @brief Test HTTP response. typedef TestHttpResponseBase Response; /// @brief Pointer to test HTTP response. typedef boost::shared_ptr ResponsePtr; /// @brief Request timeout used in tests. const long CONN_REQUEST_TIMEOUT = 1000; /// @brief Idle connection timeout used in tests. const long CONN_IDLE_TIMEOUT = 1000; /// @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)); 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(HttpRequestPtr 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_(new IOService()), acceptor_(new HttpAcceptor(io_service_)), connection_pool_(), response_creator_(new TestHttpResponseCreator()) { MultiThreadingMgr::instance().setMode(false); } /// @brief Destructor. ~HttpConnectionPoolTest() { io_service_->restart(); try { io_service_->poll(); } catch (...) { } MultiThreadingMgr::instance().setMode(false); } /// @brief Verifies that connections can be added to the pool and removed. void startStopTest() { // Create two distinct connections. HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_, TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_, TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); // 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()); } /// @brief Verifies that all connections can be remove with a single call. void stopAllTest() { // Create two distinct connections. HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_, TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_, TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); 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()); } /// @brief Verifies that stopping a non-existing connection is no-op. void stopInvalidTest() { HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_, TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_, TlsContextPtr(), connection_pool_, response_creator_, HttpAcceptorCallback(), CONN_REQUEST_TIMEOUT, CONN_IDLE_TIMEOUT)); 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)); } IOServicePtr io_service_; ///< IO service. HttpAcceptorPtr acceptor_; ///< Test acceptor. HttpConnectionPool connection_pool_; ///< Test connection pool. HttpResponseCreatorPtr response_creator_; ///< Test response creator. }; // Verifies that connections can be added to the pool and removed. // with MultiThreading disabled. TEST_F(HttpConnectionPoolTest, startStopTest) { ASSERT_FALSE(MultiThreadingMgr::instance().getMode()); startStopTest(); } // Verifies that connections can be added to the pool and removed // with MultiThreading enabled. TEST_F(HttpConnectionPoolTest, startStopTestMultiThreading) { MultiThreadingMgr::instance().setMode(true); startStopTest(); } // Check that all connections can be remove with a single call. // with MultiThreading disabled. TEST_F(HttpConnectionPoolTest, stopAll) { ASSERT_FALSE(MultiThreadingMgr::instance().getMode()); stopAllTest(); } // Check that all connections can be remove with a single call // with MultiThreading enabled. TEST_F(HttpConnectionPoolTest, stopAllMultiThreading) { MultiThreadingMgr::instance().setMode(true); ASSERT_TRUE(MultiThreadingMgr::instance().getMode()); stopAllTest(); } // Check that stopping non-existing connection is no-op. // with MultiThreading disabled. TEST_F(HttpConnectionPoolTest, stopInvalid) { ASSERT_FALSE(MultiThreadingMgr::instance().getMode()); stopInvalidTest(); } // Check that stopping non-existing connection is no-op. // with MultiThreading enabled. TEST_F(HttpConnectionPoolTest, stopInvalidMultiThreading) { MultiThreadingMgr::instance().setMode(true); ASSERT_TRUE(MultiThreadingMgr::instance().getMode()); stopInvalidTest(); } }