From: Francis Dupont Date: Sun, 4 Aug 2024 13:39:03 +0000 (+0200) Subject: [#1764] Shared server X-Git-Tag: Kea-2.7.2~35 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=17a642e4dabe7be35757e7d75d2a072f57fbf746;p=thirdparty%2Fkea.git [#1764] Shared server --- diff --git a/src/lib/http/tests/Makefile.am b/src/lib/http/tests/Makefile.am index 9b75632945..c77477c2f0 100644 --- a/src/lib/http/tests/Makefile.am +++ b/src/lib/http/tests/Makefile.am @@ -47,6 +47,7 @@ libhttp_unittests_SOURCES += http_tests.h libhttp_unittests_SOURCES += http_response_creator_test.h libhttp_unittests_SOURCES += http_client_test.h libhttp_unittests_SOURCES += http_client_unittests.cc +libhttp_unittests_SOURCES += http_server_test.h libhttp_unittests_SOURCES += http_server_unittests.cc if HAVE_OPENSSL libhttp_unittests_SOURCES += tls_server_unittests.cc diff --git a/src/lib/http/tests/http_server_test.h b/src/lib/http/tests/http_server_test.h new file mode 100644 index 0000000000..da0b3673f1 --- /dev/null +++ b/src/lib/http/tests/http_server_test.h @@ -0,0 +1,400 @@ +// 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/. + +#ifndef HTTP_SERVER_TEST_H +#define HTTP_SERVER_TEST_H + +namespace isc { +namespace http { +namespace test { + +/// @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 +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 +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 + (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 TestHttp[s]Client. +typedef boost::shared_ptr TestClientPtr; + +/// @brief Test fixture class for @ref HttpListener. +class BaseListenerTest : public ::testing::Test { +public: + + /// @brief Constructor. + /// + /// Starts test timer which detects timeouts. + BaseListenerTest() + : io_service_(new IOService()), factory_(new TestHttpResponseCreatorFactory()), + test_timer_(io_service_), run_io_service_timer_(io_service_), + clients_(), server_context_() { + test_timer_.setup(std::bind(&BaseListenerTest::timeoutHandler, + this, true), + TEST_TIMEOUT, IntervalTimer::ONE_SHOT); + } + + /// @brief Destructor. + /// + /// Removes active HTTP clients. + virtual ~BaseListenerTest() { + for (auto const& client : clients_) { + client->close(); + } + test_timer_.cancel(); + io_service_->stopAndPoll(); + } + + /// brief Create a HTTP test client. + virtual TestClientPtr createClient() = 0; + + /// @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) { + auto client = createClient(); + 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(&BaseListenerTest::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, server_context_, + 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()); + auto 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 + 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 + listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, + server_context_, 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()); + auto 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 clients_; + + /// @brief Server TLS context. + TlsContextPtr server_context_; +}; + +} +} +} +#endif // HTTP_SERVER_TEST_H diff --git a/src/lib/http/tests/http_server_unittests.cc b/src/lib/http/tests/http_server_unittests.cc index 708eb50791..3618fca2db 100644 --- a/src/lib/http/tests/http_server_unittests.cc +++ b/src/lib/http/tests/http_server_unittests.cc @@ -44,381 +44,18 @@ using namespace isc::http::test; using namespace isc::util; namespace ph = std::placeholders; -namespace { - -/// @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 -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 -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 - (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) { - } +#include - /// @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 TestHttpClientPtr; +namespace { /// @brief Test fixture class for @ref HttpListener. -class HttpListenerTest : public ::testing::Test { +class HttpListenerTest : public BaseListenerTest { 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 Create a HTTP test client. + virtual TestClientPtr createClient() override { + return (TestHttpClientPtr(new TestHttpClient(io_service_))); } - - /// @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 - 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 - 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 clients_; }; // This test verifies that HTTP connection can be established and used to @@ -439,7 +76,7 @@ TEST_F(HttpListenerTest, listen) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse()); @@ -471,7 +108,7 @@ TEST_F(HttpListenerTest, keepAlive) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse()); @@ -520,7 +157,7 @@ TEST_F(HttpListenerTest, persistentConnection) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse()); @@ -572,7 +209,7 @@ TEST_F(HttpListenerTest, keepAliveTimeout) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse()); @@ -629,7 +266,7 @@ TEST_F(HttpListenerTest, persistentConnectionTimeout) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse()); @@ -685,7 +322,7 @@ TEST_F(HttpListenerTest, persistentConnectionBadBody) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n" "Content-Length: 40\r\n" @@ -744,7 +381,7 @@ TEST_F(HttpListenerTest, badRequest) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n" "Content-Length: 40\r\n" diff --git a/src/lib/http/tests/tls_server_unittests.cc b/src/lib/http/tests/tls_server_unittests.cc index b11e0d9f71..f281d83c8c 100644 --- a/src/lib/http/tests/tls_server_unittests.cc +++ b/src/lib/http/tests/tls_server_unittests.cc @@ -46,388 +46,28 @@ using namespace isc::http; using namespace isc::http::test; using namespace isc::util; -namespace { - -/// @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 -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) { - TlsContextPtr tls_context; - configClient(tls_context); - 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 -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 - (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 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. - 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) { - } +#include - /// @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); - } -}; +namespace { /// @brief Test fixture class for @ref HttpListener. -class HttpsListenerTest : public ::testing::Test { +class HttpsListenerTest : public BaseListenerTest { public: /// @brief Constructor. /// /// Starts test timer which detects timeouts. - HttpsListenerTest() - : io_service_(new IOService()), factory_(new TestHttpResponseCreatorFactory()), - test_timer_(io_service_), run_io_service_timer_(io_service_), - clients_(), server_context_(), client_context_() { + HttpsListenerTest() : client_context_() { configServer(server_context_); configClient(client_context_); - test_timer_.setup(std::bind(&HttpsListenerTest::timeoutHandler, this, true), - TEST_TIMEOUT, IntervalTimer::ONE_SHOT); - } - - /// @brief Destructor. - /// - /// Removes active HTTP clients. - virtual ~HttpsListenerTest() { - for (auto const& client : clients_) { - client->close(); - } - test_timer_.cancel(); - io_service_->stopAndPoll(); - } - - /// @brief Connect to the endpoint. - /// - /// This method creates TestHttpsClient instance and retains it in the clients_ - /// list. - /// - /// @param request String containing the HTTP request to be sent. - void startRequest(const std::string& request) { - TestHttpsClientPtr client(new TestHttpsClient(io_service_, - client_context_)); - 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 Create a HTTPS test client. + virtual TestClientPtr createClient() override { + return (TestHttpsClientPtr(new TestHttpsClient(io_service_, + client_context_))); } - /// @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(&HttpsListenerTest::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, server_context_, - 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()); - TestHttpsClientPtr 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 - 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 - listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, - server_context_, 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()); - TestHttpsClientPtr 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 clients_; - - /// @brief Server TLS context. - TlsContextPtr server_context_; - /// @brief Client TLS context. TlsContextPtr client_context_; }; @@ -450,7 +90,7 @@ TEST_F(HttpsListenerTest, listen) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpsClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse()); @@ -482,7 +122,7 @@ TEST_F(HttpsListenerTest, keepAlive) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpsClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse()); @@ -531,7 +171,7 @@ TEST_F(HttpsListenerTest, persistentConnection) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpsClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse()); @@ -583,7 +223,7 @@ TEST_F(HttpsListenerTest, keepAliveTimeout) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpsClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse()); @@ -640,7 +280,7 @@ TEST_F(HttpsListenerTest, persistentConnectionTimeout) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpsClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse()); @@ -696,7 +336,7 @@ TEST_F(HttpsListenerTest, persistentConnectionBadBody) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpsClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n" "Content-Length: 40\r\n" @@ -755,7 +395,7 @@ TEST_F(HttpsListenerTest, badRequest) { ASSERT_NO_THROW(startRequest(request)); ASSERT_NO_THROW(runIOService()); ASSERT_EQ(1, clients_.size()); - TestHttpsClientPtr client = *clients_.begin(); + auto client = *clients_.begin(); ASSERT_TRUE(client); EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n" "Content-Length: 40\r\n"