diff options
author | Thomas Markwalder <tmark@isc.org> | 2021-03-10 15:18:21 +0100 |
---|---|---|
committer | Thomas Markwalder <tmark@isc.org> | 2021-03-22 18:51:50 +0100 |
commit | 684d013224185515fc461dad4499dd2f7776053d (patch) | |
tree | 04c72cbca8370c395099e9cdfcbe8ef036e9ef53 /src/lib/http | |
parent | [#1730] CmdHttpListener - initial implementation (diff) | |
download | kea-684d013224185515fc461dad4499dd2f7776053d.tar.xz kea-684d013224185515fc461dad4499dd2f7776053d.zip |
[#1730] Added CmdHttpListener listen-and-respond unit tests
src/lib/config/cmd_http_listener.cc
CmdHttpListener::start() - added check for multi-threading enabled
src/lib/config/tests/cmd_http_listener_unittests.cc
Added CmdHttpListenerTest fixture and new listener/client
interatcion testing
src/lib/http/tests/server_client_unittests.cc
Extracted TestHttpClient to its own header file
src/lib/http/tests/test_http_client.h - new file
Diffstat (limited to 'src/lib/http')
-rw-r--r-- | src/lib/http/tests/server_client_unittests.cc | 226 | ||||
-rw-r--r-- | src/lib/http/tests/test_http_client.h | 266 |
2 files changed, 267 insertions, 225 deletions
diff --git a/src/lib/http/tests/server_client_unittests.cc b/src/lib/http/tests/server_client_unittests.cc index bfcf28d856..f6cf94869b 100644 --- a/src/lib/http/tests/server_client_unittests.cc +++ b/src/lib/http/tests/server_client_unittests.cc @@ -8,6 +8,7 @@ #include <asiolink/asio_wrapper.h> #include <asiolink/interval_timer.h> #include <cc/data.h> +#include <test_http_client.h> #include <http/client.h> #include <http/http_types.h> #include <http/listener.h> @@ -385,231 +386,6 @@ public: } }; - -/// @brief Entity which can connect to the HTTP server endpoint. -class TestHttpClient : public boost::noncopyable { -public: - - /// @brief Constructor. - /// - /// This constructor creates new socket instance. It doesn't connect. Call - /// connect() to connect to the server. - /// - /// @param io_service IO service to be stopped on error. - explicit TestHttpClient(IOService& io_service) - : io_service_(io_service.get_io_service()), socket_(io_service_), - buf_(), response_() { - } - - /// @brief Destructor. - /// - /// Closes the underlying socket if it is open. - ~TestHttpClient() { - close(); - } - - /// @brief Send HTTP request specified in textual format. - /// - /// @param request HTTP request in the textual format. - void startRequest(const std::string& request) { - tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS), - SERVER_PORT); - socket_.async_connect(endpoint, - [this, request](const boost::system::error_code& ec) { - if (ec) { - // One would expect that async_connect wouldn't return - // EINPROGRESS error code, but simply wait for the connection - // to get established before the handler is invoked. It turns out, - // however, that on some OSes the connect handler may receive this - // error code which doesn't necessarily indicate a problem. - // Making an attempt to write and read from this socket will - // typically succeed. So, we ignore this error. - if (ec.value() != boost::asio::error::in_progress) { - ADD_FAILURE() << "error occurred while connecting: " - << ec.message(); - io_service_.stop(); - return; - } - } - sendRequest(request); - }); - } - - /// @brief Send HTTP request. - /// - /// @param request HTTP request in the textual format. - void sendRequest(const std::string& request) { - sendPartialRequest(request); - } - - /// @brief Send part of the HTTP request. - /// - /// @param request part of the HTTP request to be sent. - void sendPartialRequest(std::string request) { - socket_.async_send(boost::asio::buffer(request.data(), request.size()), - [this, request](const boost::system::error_code& ec, - std::size_t bytes_transferred) mutable { - if (ec) { - if (ec.value() == boost::asio::error::operation_aborted) { - return; - - } else if ((ec.value() == boost::asio::error::try_again) || - (ec.value() == boost::asio::error::would_block)) { - // If we should try again make sure there is no garbage in the - // bytes_transferred. - bytes_transferred = 0; - - } else { - ADD_FAILURE() << "error occurred while connecting: " - << ec.message(); - io_service_.stop(); - return; - } - } - - // Remove the part of the request which has been sent. - if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) { - request.erase(0, bytes_transferred); - } - - // Continue sending request data if there are still some data to be - // sent. - if (!request.empty()) { - sendPartialRequest(request); - - } else { - // Request has been sent. Start receiving response. - response_.clear(); - receivePartialResponse(); - } - }); - } - - /// @brief Receive response from the server. - void receivePartialResponse() { - socket_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()), - [this](const boost::system::error_code& ec, - std::size_t bytes_transferred) { - if (ec) { - // IO service stopped so simply return. - if (ec.value() == boost::asio::error::operation_aborted) { - return; - - } else if ((ec.value() == boost::asio::error::try_again) || - (ec.value() == boost::asio::error::would_block)) { - // If we should try again, make sure that there is no garbage - // in the bytes_transferred. - bytes_transferred = 0; - - } else { - // Error occurred, bail... - ADD_FAILURE() << "error occurred while receiving HTTP" - " response from the server: " << ec.message(); - io_service_.stop(); - } - } - - if (bytes_transferred > 0) { - response_.insert(response_.end(), buf_.data(), - buf_.data() + bytes_transferred); - } - - // Two consecutive new lines end the part of the response we're - // expecting. - if (response_.find("\r\n\r\n", 0) != std::string::npos) { - io_service_.stop(); - - } else { - receivePartialResponse(); - } - - }); - } - - /// @brief Checks if the TCP connection is still open. - /// - /// Tests the TCP connection by trying to read from the socket. - /// Unfortunately expected failure depends on a race between the read - /// and the other side close so to check if the connection is closed - /// please use @c isConnectionClosed instead. - /// - /// @return true if the TCP connection is open. - bool isConnectionAlive() { - // Remember the current non blocking setting. - const bool non_blocking_orig = socket_.non_blocking(); - // Set the socket to non blocking mode. We're going to test if the socket - // returns would_block status on the attempt to read from it. - socket_.non_blocking(true); - - // We need to provide a buffer for a call to read. - char data[2]; - boost::system::error_code ec; - boost::asio::read(socket_, boost::asio::buffer(data, sizeof(data)), ec); - - // Revert the original non_blocking flag on the socket. - socket_.non_blocking(non_blocking_orig); - - // If the connection is alive we'd typically get would_block status code. - // If there are any data that haven't been read we may also get success - // status. We're guessing that try_again may also be returned by some - // implementations in some situations. Any other error code indicates a - // problem with the connection so we assume that the connection has been - // closed. - return (!ec || (ec.value() == boost::asio::error::try_again) || - (ec.value() == boost::asio::error::would_block)); - } - - /// @brief Checks if the TCP connection is already closed. - /// - /// Tests the TCP connection by trying to read from the socket. - /// The read can block so this must be used to check if a connection - /// is alive so to check if the connection is alive please always - /// use @c isConnectionAlive. - /// - /// @return true if the TCP connection is closed. - bool isConnectionClosed() { - // Remember the current non blocking setting. - const bool non_blocking_orig = socket_.non_blocking(); - // Set the socket to blocking mode. We're going to test if the socket - // returns eof status on the attempt to read from it. - socket_.non_blocking(false); - - // We need to provide a buffer for a call to read. - char data[2]; - boost::system::error_code ec; - boost::asio::read(socket_, boost::asio::buffer(data, sizeof(data)), ec); - - // Revert the original non_blocking flag on the socket. - socket_.non_blocking(non_blocking_orig); - - // If the connection is closed we'd typically get eof status code. - return (ec.value() == boost::asio::error::eof); - } - - /// @brief Close connection. - void close() { - socket_.close(); - } - - std::string getResponse() const { - return (response_); - } - -private: - - /// @brief Holds reference to the IO service. - boost::asio::io_service& io_service_; - - /// @brief A socket used for the connection. - boost::asio::ip::tcp::socket socket_; - - /// @brief Buffer into which response is written. - std::array<char, 8192> buf_; - - /// @brief Response in the textual format. - std::string response_; -}; - /// @brief Pointer to the TestHttpClient. typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr; diff --git a/src/lib/http/tests/test_http_client.h b/src/lib/http/tests/test_http_client.h new file mode 100644 index 0000000000..c5ba0eb1ac --- /dev/null +++ b/src/lib/http/tests/test_http_client.h @@ -0,0 +1,266 @@ +// Copyright (C) 2017-2021 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 TEST_HTTP_CLIENT_H +#define TEST_HTTP_CLIENT_H + +#include <cc/data.h> +#include <http/client.h> +#include <http/http_types.h> + +#include <boost/asio/read.hpp> +#include <boost/asio/buffer.hpp> +#include <boost/asio/ip/tcp.hpp> +#include <gtest/gtest.h> + +using namespace boost::asio::ip; +using namespace isc::asiolink; + +/// @brief Entity which can connect to the HTTP server endpoint. +class TestHttpClient : public boost::noncopyable { +public: + + /// @brief Constructor. + /// + /// This constructor creates new socket instance. It doesn't connect. Call + /// connect() to connect to the server. + /// + /// @param io_service IO service to be stopped on error or completion. + explicit TestHttpClient(IOService& io_service, + const std::string& server_address = "127.0.0.1", + uint16_t port = 18123) + : io_service_(io_service.get_io_service()), socket_(io_service_), + buf_(), response_(), server_address_(server_address), + server_port_(port), receive_done_(false) { + } + + /// @brief Destructor. + /// + /// Closes the underlying socket if it is open. + ~TestHttpClient() { + close(); + } + + /// @brief Send HTTP request specified in textual format. + /// + /// @param request HTTP request in the textual format. + void startRequest(const std::string& request) { + tcp::endpoint endpoint(address::from_string(server_address_), server_port_); + socket_.async_connect(endpoint, + [this, request](const boost::system::error_code& ec) { + receive_done_ = false; + if (ec) { + // One would expect that async_connect wouldn't return + // EINPROGRESS error code, but simply wait for the connection + // to get established before the handler is invoked. It turns out, + // however, that on some OSes the connect handler may receive this + // error code which doesn't necessarily indicate a problem. + // Making an attempt to write and read from this socket will + // typically succeed. So, we ignore this error. + if (ec.value() != boost::asio::error::in_progress) { + ADD_FAILURE() << "error occurred while connecting: " + << ec.message(); + io_service_.stop(); + return; + } + } + sendRequest(request); + }); + } + + /// @brief Send HTTP request. + /// + /// @param request HTTP request in the textual format. + void sendRequest(const std::string& request) { + sendPartialRequest(request); + } + + /// @brief Send part of the HTTP request. + /// + /// @param request part of the HTTP request to be sent. + void sendPartialRequest(std::string request) { + socket_.async_send(boost::asio::buffer(request.data(), request.size()), + [this, request](const boost::system::error_code& ec, + std::size_t bytes_transferred) mutable { + if (ec) { + if (ec.value() == boost::asio::error::operation_aborted) { + return; + + } else if ((ec.value() == boost::asio::error::try_again) || + (ec.value() == boost::asio::error::would_block)) { + // If we should try again make sure there is no garbage in the + // bytes_transferred. + bytes_transferred = 0; + + } else { + ADD_FAILURE() << "error occurred while connecting: " + << ec.message(); + io_service_.stop(); + return; + } + } + + // Remove the part of the request which has been sent. + if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) { + request.erase(0, bytes_transferred); + } + + // Continue sending request data if there are still some data to be + // sent. + if (!request.empty()) { + sendPartialRequest(request); + + } else { + // Request has been sent. Start receiving response. + response_.clear(); + receivePartialResponse(); + } + }); + } + + /// @brief Receive response from the server. + void receivePartialResponse() { + socket_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()), + [this](const boost::system::error_code& ec, + std::size_t bytes_transferred) { + if (ec) { + // IO service stopped so simply return. + if (ec.value() == boost::asio::error::operation_aborted) { + std::cout << "this: " << this << "IO service stopped" << std::endl; + return; + + } else if ((ec.value() == boost::asio::error::try_again) || + (ec.value() == boost::asio::error::would_block)) { + // If we should try again, make sure that there is no garbage + // in the bytes_transferred. + bytes_transferred = 0; + + } else { + // Error occurred, bail... + ADD_FAILURE() << "error occurred while receiving HTTP" + " response from the server: " << ec.message(); + io_service_.stop(); + } + } + + if (bytes_transferred > 0) { + response_.insert(response_.end(), buf_.data(), + buf_.data() + bytes_transferred); + } + + // Two consecutive new lines end the part of the response we're + // expecting. + if (response_.find("\r\n\r\n", 0) != std::string::npos) { + receive_done_ = true; + io_service_.stop(); + } else { + receivePartialResponse(); + } + }); + } + + /// @brief Checks if the TCP connection is still open. + /// + /// Tests the TCP connection by trying to read from the socket. + /// Unfortunately expected failure depends on a race between the read + /// and the other side close so to check if the connection is closed + /// please use @c isConnectionClosed instead. + /// + /// @return true if the TCP connection is open. + bool isConnectionAlive() { + // Remember the current non blocking setting. + const bool non_blocking_orig = socket_.non_blocking(); + // Set the socket to non blocking mode. We're going to test if the socket + // returns would_block status on the attempt to read from it. + socket_.non_blocking(true); + + // We need to provide a buffer for a call to read. + char data[2]; + boost::system::error_code ec; + boost::asio::read(socket_, boost::asio::buffer(data, sizeof(data)), ec); + + // Revert the original non_blocking flag on the socket. + socket_.non_blocking(non_blocking_orig); + + // If the connection is alive we'd typically get would_block status code. + // If there are any data that haven't been read we may also get success + // status. We're guessing that try_again may also be returned by some + // implementations in some situations. Any other error code indicates a + // problem with the connection so we assume that the connection has been + // closed. + return (!ec || (ec.value() == boost::asio::error::try_again) || + (ec.value() == boost::asio::error::would_block)); + } + + /// @brief Checks if the TCP connection is already closed. + /// + /// Tests the TCP connection by trying to read from the socket. + /// The read can block so this must be used to check if a connection + /// is alive so to check if the connection is alive please always + /// use @c isConnectionAlive. + /// + /// @return true if the TCP connection is closed. + bool isConnectionClosed() { + // Remember the current non blocking setting. + const bool non_blocking_orig = socket_.non_blocking(); + // Set the socket to blocking mode. We're going to test if the socket + // returns eof status on the attempt to read from it. + socket_.non_blocking(false); + + // We need to provide a buffer for a call to read. + char data[2]; + boost::system::error_code ec; + boost::asio::read(socket_, boost::asio::buffer(data, sizeof(data)), ec); + + // Revert the original non_blocking flag on the socket. + socket_.non_blocking(non_blocking_orig); + + // If the connection is closed we'd typically get eof status code. + return (ec.value() == boost::asio::error::eof); + } + + /// @brief Close connection. + void close() { + socket_.close(); + } + + std::string getResponse() const { + return (response_); + } + + /// @brief Returns true if the receive completed without error. + bool receiveDone() { + return (receive_done_); + } + +private: + + /// @brief Holds reference to the IO service. + boost::asio::io_service& io_service_; + + /// @brief A socket used for the connection. + boost::asio::ip::tcp::socket socket_; + + /// @brief Buffer into which response is written. + std::array<char, 8192> buf_; + + /// @brief Response in the textual format. + std::string response_; + + /// @brief IP address of the server. + std::string server_address_; + + /// @brief IP port of the server. + uint16_t server_port_; + + /// @brief Set to true when the receive as completed successfully. + bool receive_done_; +}; + +/// @brief Pointer to the TestHttpClient. +typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr; + +#endif |