diff options
-rw-r--r-- | src/lib/asiolink/tcp_socket.h | 22 | ||||
-rw-r--r-- | src/lib/http/Makefile.am | 7 | ||||
-rw-r--r-- | src/lib/http/connection.cc | 125 | ||||
-rw-r--r-- | src/lib/http/connection.h | 102 | ||||
-rw-r--r-- | src/lib/http/connection_pool.cc | 27 | ||||
-rw-r--r-- | src/lib/http/connection_pool.h | 33 | ||||
-rw-r--r-- | src/lib/http/http_acceptor.h | 27 | ||||
-rw-r--r-- | src/lib/http/http_types.h | 6 | ||||
-rw-r--r-- | src/lib/http/listener.cc | 54 | ||||
-rw-r--r-- | src/lib/http/listener.h | 48 | ||||
-rw-r--r-- | src/lib/http/request_parser.h | 8 | ||||
-rw-r--r-- | src/lib/http/response_creator.h | 17 | ||||
-rw-r--r-- | src/lib/http/response_creator_factory.h | 33 | ||||
-rw-r--r-- | src/lib/http/tests/Makefile.am | 1 | ||||
-rw-r--r-- | src/lib/http/tests/listener_unittests.cc | 268 | ||||
-rw-r--r-- | src/lib/http/tests/response_creator_unittests.cc | 10 |
16 files changed, 773 insertions, 15 deletions
diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h index 132ba117e7..e47e222318 100644 --- a/src/lib/asiolink/tcp_socket.h +++ b/src/lib/asiolink/tcp_socket.h @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2017 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 @@ -166,7 +166,6 @@ private: // construction, or where it is asked to manage its own socket. boost::asio::ip::tcp::socket* socket_ptr_; ///< Pointer to own socket boost::asio::ip::tcp::socket& socket_; ///< Socket - bool isopen_; ///< true when socket is open // TODO: Remove temporary buffer // The current implementation copies the buffer passed to asyncSend() into @@ -188,7 +187,7 @@ private: template <typename C> TCPSocket<C>::TCPSocket(boost::asio::ip::tcp::socket& socket) : - socket_ptr_(NULL), socket_(socket), isopen_(true), send_buffer_() + socket_ptr_(NULL), socket_(socket), send_buffer_() { } @@ -197,7 +196,7 @@ TCPSocket<C>::TCPSocket(boost::asio::ip::tcp::socket& socket) : template <typename C> TCPSocket<C>::TCPSocket(IOService& service) : socket_ptr_(new boost::asio::ip::tcp::socket(service.get_io_service())), - socket_(*socket_ptr_), isopen_(false) + socket_(*socket_ptr_) { } @@ -217,14 +216,13 @@ TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) { // Ignore opens on already-open socket. Don't throw a failure because // of uncertainties as to what precedes whan when using asynchronous I/O. // At also allows us a treat a passed-in socket as a self-managed socket. - if (!isopen_) { + if (!socket_.is_open()) { if (endpoint->getFamily() == AF_INET) { socket_.open(boost::asio::ip::tcp::v4()); } else { socket_.open(boost::asio::ip::tcp::v6()); } - isopen_ = true; // Set options on the socket: @@ -254,7 +252,7 @@ template <typename C> void TCPSocket<C>::asyncSend(const void* data, size_t length, const IOEndpoint*, C& callback) { - if (isopen_) { + if (socket_.is_open()) { // Need to copy the data into a temporary buffer and precede it with // a two-byte count field. @@ -264,8 +262,7 @@ TCPSocket<C>::asyncSend(const void* data, size_t length, uint16_t count = boost::numeric_cast<uint16_t>(length); // Copy data into a buffer preceded by the count field. - send_buffer_.reset(new isc::util::OutputBuffer(length + 2)); - send_buffer_->writeUint16(count); + send_buffer_.reset(new isc::util::OutputBuffer(length)); send_buffer_->writeData(data, length); // ... and send it @@ -289,7 +286,7 @@ template <typename C> void TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset, IOEndpoint* endpoint, C& callback) { - if (isopen_) { + if (socket_.is_open()) { // Upconvert to a TCPEndpoint. We need to do this because although // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it // does not contain a method for getting at the underlying endpoint @@ -391,7 +388,7 @@ TCPSocket<C>::processReceivedData(const void* staging, size_t length, template <typename C> void TCPSocket<C>::cancel() { - if (isopen_) { + if (socket_.is_open()) { socket_.cancel(); } } @@ -401,9 +398,8 @@ TCPSocket<C>::cancel() { template <typename C> void TCPSocket<C>::close() { - if (isopen_ && socket_ptr_) { + if (socket_.is_open() && socket_ptr_) { socket_.close(); - isopen_ = false; } } diff --git a/src/lib/http/Makefile.am b/src/lib/http/Makefile.am index 9cf7d4156d..a8c04a8685 100644 --- a/src/lib/http/Makefile.am +++ b/src/lib/http/Makefile.am @@ -22,10 +22,14 @@ EXTRA_DIST = http_messages.mes CLEANFILES = *.gcno *.gcda http_messages.h http_messages.cc s-messages lib_LTLIBRARIES = libkea-http.la -libkea_http_la_SOURCES = date_time.cc date_time.h +libkea_http_la_SOURCES = connection.cc connection.h +libkea_http_la_SOURCES += connection_pool.cc connection_pool.h +libkea_http_la_SOURCES += date_time.cc date_time.h libkea_http_la_SOURCES += http_log.cc http_log.h libkea_http_la_SOURCES += header_context.h +libkea_http_la_SOURCES += http_acceptor.h libkea_http_la_SOURCES += http_types.h +libkea_http_la_SOURCES += listener.cc listener.h libkea_http_la_SOURCES += post_request.cc post_request.h libkea_http_la_SOURCES += post_request_json.cc post_request_json.h libkea_http_la_SOURCES += request.cc request.h @@ -33,6 +37,7 @@ libkea_http_la_SOURCES += request_context.h libkea_http_la_SOURCES += request_parser.cc request_parser.h libkea_http_la_SOURCES += response.cc response.h libkea_http_la_SOURCES += response_creator.cc response_creator.h +libkea_http_la_SOURCES += response_creator_factory.h libkea_http_la_SOURCES += response_json.cc response_json.h nodist_libkea_http_la_SOURCES = http_messages.cc http_messages.h diff --git a/src/lib/http/connection.cc b/src/lib/http/connection.cc new file mode 100644 index 0000000000..3fca6d9954 --- /dev/null +++ b/src/lib/http/connection.cc @@ -0,0 +1,125 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <asiolink/asio_wrapper.h> +#include <http/connection.h> +#include <http/connection_pool.h> +#include <boost/bind.hpp> +#include <iostream> + +using namespace isc::asiolink; + +namespace isc { +namespace http { + +void +HttpConnection:: +SocketCallback::operator()(boost::system::error_code ec, size_t length) { + callback_(ec, length); +} + +HttpConnection:: HttpConnection(asiolink::IOService& io_service, + HttpAcceptor& acceptor, + HttpConnectionPool& connection_pool, + const HttpResponseCreatorPtr& response_creator, + const HttpAcceptorCallback& callback) + : socket_(io_service), + socket_callback_(boost::bind(&HttpConnection::socketReadCallback, this, + _1, _2)), + socket_write_callback_(boost::bind(&HttpConnection::socketWriteCallback, + this, _1, _2)), + acceptor_(acceptor), + connection_pool_(connection_pool), + response_creator_(response_creator), + request_(response_creator_->createNewHttpRequest()), + parser_(new HttpRequestParser(*request_)), + acceptor_callback_(callback), + buf_() { + parser_->initModel(); +} + +HttpConnection::~HttpConnection() { + close(); +} + +void +HttpConnection::asyncAccept() { + HttpAcceptorCallback cb = boost::bind(&HttpConnection::acceptorCallback, + this, _1); + acceptor_.asyncAccept(socket_, cb); +} + +void +HttpConnection::close() { + socket_.close(); +} + +void +HttpConnection::doRead() { + TCPEndpoint endpoint; + socket_.asyncReceive(static_cast<void*>(buf_.data()), buf_.size(), 0, &endpoint, + socket_callback_); +} + +void +HttpConnection::doWrite() { + if (!output_buf_.empty()) { + TCPEndpoint endpoint; + socket_.asyncSend(output_buf_.data(), + output_buf_.length(), &endpoint, + socket_write_callback_); + } +} + +void +HttpConnection::acceptorCallback(const boost::system::error_code& ec) { + if (!acceptor_.isOpen()) { + return; + } + + if (ec) { + connection_pool_.stop(shared_from_this()); + } + + acceptor_callback_(ec); + + if (!ec) { + doRead(); + } + +} + +void +HttpConnection::socketReadCallback(boost::system::error_code ec, size_t length) { + std::string s(&buf_[0], buf_[0] + length); + parser_->postBuffer(static_cast<void*>(buf_.data()), length); + parser_->poll(); + if (parser_->needData()) { + doRead(); + + } else { + request_->finalize(); + HttpResponsePtr response = response_creator_->createHttpResponse(request_); + output_buf_ = response->toString(); + doWrite(); + } +} + +void +HttpConnection::socketWriteCallback(boost::system::error_code ec, + size_t length) { + if (length <= output_buf_.size()) { + output_buf_.erase(0, length); + doWrite(); + + } else { + output_buf_.clear(); + } +} + +} // end of namespace isc::http +} // end of namespace isc + diff --git a/src/lib/http/connection.h b/src/lib/http/connection.h new file mode 100644 index 0000000000..5304a1392f --- /dev/null +++ b/src/lib/http/connection.h @@ -0,0 +1,102 @@ +// Copyright (C) 2017 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_CONNECTION_H +#define HTTP_CONNECTION_H + +#include <asiolink/io_service.h> +#include <http/http_acceptor.h> +#include <http/request_parser.h> +#include <http/response_creator_factory.h> +#include <boost/function.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <boost/system/error_code.hpp> +#include <boost/shared_ptr.hpp> +#include <array> + +namespace isc { +namespace http { + +class HttpConnectionPool; + +class HttpConnection; +typedef boost::shared_ptr<HttpConnection> HttpConnectionPtr; + +class HttpConnection : public boost::enable_shared_from_this<HttpConnection> { +private: + + typedef boost::function<void(boost::system::error_code ec, size_t length)> + SocketCallbackFunction; + + class SocketCallback { + public: + + SocketCallback(SocketCallbackFunction socket_callback) + : callback_(socket_callback) { + } + + void operator()(boost::system::error_code ec, size_t length = 0); + + private: + SocketCallbackFunction callback_; + }; + + +public: + + HttpConnection(asiolink::IOService& io_service, + HttpAcceptor& acceptor, + HttpConnectionPool& connection_pool, + const HttpResponseCreatorPtr& response_creator, + const HttpAcceptorCallback& callback); + + ~HttpConnection(); + + void asyncAccept(); + + void close(); + + void doRead(); + + void doWrite(); + + void acceptorCallback(const boost::system::error_code& ec); + + void socketReadCallback(boost::system::error_code ec, + size_t length); + + void socketWriteCallback(boost::system::error_code ec, + size_t length); + +private: + + asiolink::TCPSocket<SocketCallback> socket_; + + SocketCallback socket_callback_; + + SocketCallback socket_write_callback_; + + HttpAcceptor& acceptor_; + + HttpConnectionPool& connection_pool_; + + HttpResponseCreatorPtr response_creator_; + + HttpRequestPtr request_; + + HttpRequestParserPtr parser_; + + HttpAcceptorCallback acceptor_callback_; + + std::array<char, 4096> buf_; + + std::string output_buf_; +}; + +} +} + +#endif diff --git a/src/lib/http/connection_pool.cc b/src/lib/http/connection_pool.cc new file mode 100644 index 0000000000..eb06d3fca5 --- /dev/null +++ b/src/lib/http/connection_pool.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <asiolink/asio_wrapper.h> +#include <http/connection_pool.h> + +namespace isc { +namespace http { + +void +HttpConnectionPool::start(const HttpConnectionPtr& connection) { + connections_.insert(connection); + connection->asyncAccept(); +} + +void +HttpConnectionPool::stop(const HttpConnectionPtr& connection) { + connections_.erase(connection); + connection->close(); +} + + +} +} diff --git a/src/lib/http/connection_pool.h b/src/lib/http/connection_pool.h new file mode 100644 index 0000000000..fa9462b3e5 --- /dev/null +++ b/src/lib/http/connection_pool.h @@ -0,0 +1,33 @@ +// Copyright (C) 2017 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_CONNECTION_POOL_H +#define HTTP_CONNECTION_POOL_H + +#include <http/connection.h> +#include <set> + +namespace isc { +namespace http { + +class HttpConnectionPool { +public: + + void start(const HttpConnectionPtr& connection); + + void stop(const HttpConnectionPtr& connection); + +private: + + std::set<HttpConnectionPtr> connections_; + +}; + +} +} + +#endif + diff --git a/src/lib/http/http_acceptor.h b/src/lib/http/http_acceptor.h new file mode 100644 index 0000000000..4d52f7b181 --- /dev/null +++ b/src/lib/http/http_acceptor.h @@ -0,0 +1,27 @@ +// Copyright (C) 2017 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_ACCEPTOR_H +#define HTTP_ACCEPTOR_H + +#include <asiolink/tcp_acceptor.h> +#include <boost/function.hpp> +#include <boost/system/system_error.hpp> + +namespace isc { +namespace http { + +/// @brief Type of the callback for the TCP acceptor used in this library. +typedef boost::function<void(const boost::system::error_code&)> +HttpAcceptorCallback; + +/// @brief Type of the TCP acceptor used in this library. +typedef asiolink::TCPAcceptor<HttpAcceptorCallback> HttpAcceptor; + +} // end of namespace isc::http +} // end of namespace isc + +#endif diff --git a/src/lib/http/http_types.h b/src/lib/http/http_types.h index b01f73189c..285096643c 100644 --- a/src/lib/http/http_types.h +++ b/src/lib/http/http_types.h @@ -7,6 +7,9 @@ #ifndef HTTP_TYPES_H #define HTTP_TYPES_H +namespace isc { +namespace http { + /// @brief HTTP protocol version. struct HttpVersion { unsigned major_; ///< Major HTTP version. @@ -43,4 +46,7 @@ struct HttpVersion { } }; +} // end of namespace isc::http +} // end of namespace isc + #endif diff --git a/src/lib/http/listener.cc b/src/lib/http/listener.cc new file mode 100644 index 0000000000..4acd59cc37 --- /dev/null +++ b/src/lib/http/listener.cc @@ -0,0 +1,54 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <asiolink/asio_wrapper.h> +#include <http/listener.h> + +using namespace isc::asiolink; + +namespace isc { +namespace http { + +HttpListener::HttpListener(IOService& io_service, + const asiolink::IOAddress& server_address, + const unsigned short server_port, + const HttpResponseCreatorFactoryPtr& creator_factory) + : io_service_(io_service), acceptor_(io_service), + endpoint_(server_address, server_port), + creator_factory_(creator_factory) { +} + +void +HttpListener::start() { + acceptor_.open(endpoint_); + acceptor_.bind(endpoint_); + acceptor_.listen(); + + accept(); +} + +void +HttpListener::accept() { + HttpResponseCreatorPtr response_creator = creator_factory_->create(); + HttpAcceptorCallback acceptor_callback = + boost::bind(&HttpListener::acceptHandler, this, _1); + HttpConnectionPtr conn(new HttpConnection(io_service_, acceptor_, + connections_, + response_creator, + acceptor_callback)); + connections_.start(conn); +} + +void +HttpListener::acceptHandler(const boost::system::error_code& ec) { + if (!ec) { + accept(); + } +} + + +} // end of namespace isc::http +} // end of namespace isc diff --git a/src/lib/http/listener.h b/src/lib/http/listener.h new file mode 100644 index 0000000000..6d8f5a2afe --- /dev/null +++ b/src/lib/http/listener.h @@ -0,0 +1,48 @@ +// Copyright (C) 2017 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_LISTENER_H +#define HTTP_LISTENER_H + +#include <asiolink/io_address.h> +#include <asiolink/io_service.h> +#include <asiolink/tcp_endpoint.h> +#include <http/connection.h> +#include <http/connection_pool.h> +#include <http/http_acceptor.h> +#include <http/response_creator_factory.h> + +namespace isc { +namespace http { + +class HttpListener { +public: + + HttpListener(asiolink::IOService& io_service, + const asiolink::IOAddress& server_address, + const unsigned short server_port, + const HttpResponseCreatorFactoryPtr& creator_factory); + + void start(); + +private: + + void accept(); + + void acceptHandler(const boost::system::error_code& ec); + + asiolink::IOService& io_service_; + HttpAcceptor acceptor_; + asiolink::TCPEndpoint endpoint_; + HttpConnectionPool connections_; + HttpResponseCreatorFactoryPtr creator_factory_; + +}; + +} // end of namespace isc::http +} // end of namespace isc + +#endif diff --git a/src/lib/http/request_parser.h b/src/lib/http/request_parser.h index 0032657129..0cbce1e4be 100644 --- a/src/lib/http/request_parser.h +++ b/src/lib/http/request_parser.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-2017 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 @@ -11,6 +11,7 @@ #include <http/request.h> #include <util/state_model.h> #include <boost/function.hpp> +#include <boost/shared_ptr.hpp> #include <list> #include <stdint.h> #include <string> @@ -28,6 +29,11 @@ public: isc::Exception(file, line, what) { }; }; +class HttpRequestParser; + +/// @brief Pointer to the @ref HttpRequestParser. +typedef boost::shared_ptr<HttpRequestParser> HttpRequestParserPtr; + /// @brief A generic parser for HTTP requests. /// /// This class implements a parser for HTTP requests. The parser derives from diff --git a/src/lib/http/response_creator.h b/src/lib/http/response_creator.h index ac4b6f780d..ffa68dee9b 100644 --- a/src/lib/http/response_creator.h +++ b/src/lib/http/response_creator.h @@ -9,10 +9,16 @@ #include <http/request.h> #include <http/response.h> +#include <boost/shared_ptr.hpp> namespace isc { namespace http { +class HttpResponseCreator; + +/// @brief Pointer to the @ref HttpResponseCreator object. +typedef boost::shared_ptr<HttpResponseCreator> HttpResponseCreatorPtr; + /// @brief Specifies an interface for classes creating HTTP responses /// from HTTP requests. /// @@ -70,6 +76,17 @@ public: virtual HttpResponsePtr createHttpResponse(const ConstHttpRequestPtr& request) final; + /// @brief Create a new request. + /// + /// This method creates an instance of the @ref HttpRequest or derived + /// class. The type of the object is compatible with the instance of + /// the @ref HttpResponseCreator implementation which creates it, i.e. + /// can be used as an argument in the call to @ref createHttpResponse. + /// + /// @return Pointer to the new instance of the @ref HttpRequest. + virtual HttpRequestPtr + createNewHttpRequest() const = 0; + protected: /// @brief Creates implementation specific HTTP 400 response. diff --git a/src/lib/http/response_creator_factory.h b/src/lib/http/response_creator_factory.h new file mode 100644 index 0000000000..1278f30e7d --- /dev/null +++ b/src/lib/http/response_creator_factory.h @@ -0,0 +1,33 @@ +// Copyright (C) 2017 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_RESPONSE_CREATOR_FACTORY_H +#define HTTP_RESPONSE_CREATOR_FACTORY_H + +#include <http/request.h> +#include <http/response.h> +#include <http/response_creator.h> +#include <boost/shared_ptr.hpp> + +namespace isc { +namespace http { + +class HttpResponseCreatorFactory { +public: + + virtual ~HttpResponseCreatorFactory() { } + + virtual HttpResponseCreatorPtr create() const = 0; + +}; + +typedef boost::shared_ptr<HttpResponseCreatorFactory> +HttpResponseCreatorFactoryPtr; + +} // end of namespace isc::http +} // end of namespace isc + +#endif diff --git a/src/lib/http/tests/Makefile.am b/src/lib/http/tests/Makefile.am index 2af55d2dea..d962542f9f 100644 --- a/src/lib/http/tests/Makefile.am +++ b/src/lib/http/tests/Makefile.am @@ -21,6 +21,7 @@ if HAVE_GTEST TESTS += libhttp_unittests libhttp_unittests_SOURCES = date_time_unittests.cc +libhttp_unittests_SOURCES += listener_unittests.cc libhttp_unittests_SOURCES += post_request_json_unittests.cc libhttp_unittests_SOURCES += request_parser_unittests.cc libhttp_unittests_SOURCES += request_test.h diff --git a/src/lib/http/tests/listener_unittests.cc b/src/lib/http/tests/listener_unittests.cc new file mode 100644 index 0000000000..c8f4e6fcd4 --- /dev/null +++ b/src/lib/http/tests/listener_unittests.cc @@ -0,0 +1,268 @@ +// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/asio_wrapper.h> +#include <asiolink/interval_timer.h> +#include <http/listener.h> +#include <http/post_request_json.h> +#include <http/response_creator.h> +#include <http/response_creator_factory.h> +#include <http/response_json.h> +#include <http/tests/response_test.h> +#include <boost/asio/buffer.hpp> +#include <boost/bind.hpp> +#include <gtest/gtest.h> +#include <list> +#include <string> + +using namespace isc::asiolink; +using namespace isc::http; +using namespace isc::http::test; + +namespace { + +const std::string SERVER_ADDRESS = "127.0.0.1"; +const unsigned short SERVER_PORT = 18123; + +/// @brief Test timeout in ms. +const long TEST_TIMEOUT = 10000; + +/// @brief Test HTTP response. +typedef TestHttpResponseBase<HttpResponseJson> Response; + +/// @brief Pointer to test HTTP response. +typedef boost::shared_ptr<Response> ResponsePtr; + +/// @brief Implementation of the @ref HttpResponseCreator. +class TestHttpResponseCreator : public HttpResponseCreator { +public: + + /// @brief Create a new request. + /// + /// @return Pointer to the new instance of the @ref HttpRequest. + virtual HttpRequestPtr + createNewHttpRequest() const { + return (HttpRequestPtr(new PostHttpRequestJson())); + } + +private: + + /// @brief Creates HTTP 400 response. + /// + /// @param request Pointer to the HTTP request. + /// @return Pointer to the generated HTTP 400 response. + virtual HttpResponsePtr + createStockBadRequest(const ConstHttpRequestPtr& request) const { + // The request hasn't been finalized so the request object + // doesn't contain any information about the HTTP version number + // used. But, the context should have this data (assuming the + // HTTP version is parsed ok). + HttpVersion http_version(request->context()->http_version_major_, + request->context()->http_version_minor_); + // This will generate the response holding JSON content. + ResponsePtr response(new Response(http_version, + HttpStatusCode::BAD_REQUEST)); + return (response); + } + + /// @brief Creates HTTP response. + /// + /// @param request Pointer to the HTTP request. + /// @return Pointer to the generated HTTP OK response with no content. + virtual HttpResponsePtr + createDynamicHttpResponse(const ConstHttpRequestPtr& request) { + // The simplest thing is to create a response with no content. + // We don't need content to test our class. + ResponsePtr response(new Response(request->getHttpVersion(), + HttpStatusCode::OK)); + return (response); + } +}; + +class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory { +public: + + virtual HttpResponseCreatorPtr create() const { + HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator()); + return (response_creator); + } +}; + +/// @brief Entity which can connect to the HTTP server endpoint. +class HttpClient : 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 HttpClient(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. + ~HttpClient() { + close(); + } + + void startRequest() { + boost::asio::ip::tcp::endpoint + endpoint(boost::asio::ip::address::from_string(SERVER_ADDRESS), + SERVER_PORT); + socket_.async_connect(endpoint, [this](const boost::system::error_code& ec) { + if (ec) { + ADD_FAILURE() << "error occurred while connecting: " + << ec.message(); + io_service_.stop(); + } + sendRequest(); + }); + } + + void sendRequest() { + const std::string request = "POST /foo/bar HTTP/1.1\r\n" + "Content-Type: application/json\r\n" + "Content-Length: 3\r\n\r\n" + "{ }"; + sendPartialRequest(request); + } + + 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) { + ADD_FAILURE() << "error occurred while connecting: " + << ec.message(); + io_service_.stop(); + return; + } + + if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) { + request.erase(0, bytes_transferred); + } + + if (!request.empty()) { + sendPartialRequest(request); + + } else { + response_.clear(); + receivePartialResponse(); + } + }); + } + + void receivePartialResponse() { + socket_.async_read_some(boost::asio::buffer(buf_), + [this](const boost::system::error_code& ec, + std::size_t bytes_transferred) { + if (ec) { + 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); + } + + if (response_.find("\r\n\r\n", 0) != std::string::npos) { + io_service_.stop(); + } + + }); + } + + /// @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 HttpClient. +typedef boost::shared_ptr<HttpClient> HttpClientPtr; + +class HttpListenerTest : public ::testing::Test { +public: + + HttpListenerTest() + : io_service_(), factory_(new TestHttpResponseCreatorFactory()), + test_timer_(io_service_) { + test_timer_.setup(boost::bind(&HttpListenerTest::timeoutHandler, this), + TEST_TIMEOUT, IntervalTimer::ONE_SHOT); + } + + /// @brief Connect to the endpoint. + /// + /// This method creates HttpClient instance and retains it in the clients_ + /// list. + void startRequest() { + HttpClientPtr client(new HttpClient(io_service_)); + clients_.push_back(client); + clients_.back()->startRequest(); + } + + /// @brief Callback function invoke upon test timeout. + /// + /// It stops the IO service and reports test timeout. + void timeoutHandler() { + ADD_FAILURE() << "Timeout occurred while running the test!"; + io_service_.stop(); + } + + IOService io_service_; + + HttpResponseCreatorFactoryPtr factory_; + + /// @brief Asynchronous timer service to detect timeouts. + IntervalTimer test_timer_; + + /// @brief List of client connections. + std::list<HttpClientPtr> clients_; +}; + +TEST_F(HttpListenerTest, listen) { + HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT, + factory_); + ASSERT_NO_THROW(listener.start()); + ASSERT_NO_THROW(startRequest()); + ASSERT_NO_THROW(io_service_.run()); + ASSERT_EQ(1, clients_.size()); + HttpClientPtr client = *clients_.begin(); + ASSERT_TRUE(client); + EXPECT_EQ("HTTP/1.1 200 OK\r\n" + "Content-Type: application/json\r\n" + "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n" + "\r\n", + client->getResponse()); +} + +} diff --git a/src/lib/http/tests/response_creator_unittests.cc b/src/lib/http/tests/response_creator_unittests.cc index 8f79aa3f48..f1607f0323 100644 --- a/src/lib/http/tests/response_creator_unittests.cc +++ b/src/lib/http/tests/response_creator_unittests.cc @@ -27,6 +27,16 @@ typedef boost::shared_ptr<Response> ResponsePtr; /// @brief Implementation of the @ref HttpResponseCreator. class TestHttpResponseCreator : public HttpResponseCreator { +public: + + /// @brief Create a new request. + /// + /// @return Pointer to the new instance of the @ref HttpRequest. + virtual HttpRequestPtr + createNewHttpRequest() const { + return (HttpRequestPtr(new HttpRequest())); + } + private: /// @brief Creates HTTP 400 response. |