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
--- /dev/null
+// 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<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 TestHttp[s]Client.
+typedef boost::shared_ptr<BaseTestHttpClient> 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<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,
+ 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<TestClientPtr> clients_;
+
+ /// @brief Server TLS context.
+ TlsContextPtr server_context_;
+};
+
+}
+}
+}
+#endif // HTTP_SERVER_TEST_H
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<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) {
- }
+#include <http/tests/http_server_test.h>
- /// @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;
+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<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
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());
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());
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());
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());
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());
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"
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"
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<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) {
- 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<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 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 <http/tests/http_server_test.h>
- /// @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<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,
- 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<TestHttpsClientPtr> clients_;
-
- /// @brief Server TLS context.
- TlsContextPtr server_context_;
-
/// @brief Client TLS context.
TlsContextPtr client_context_;
};
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());
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());
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());
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());
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());
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"
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"