summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/lib/asiolink/tcp_socket.h22
-rw-r--r--src/lib/http/Makefile.am7
-rw-r--r--src/lib/http/connection.cc125
-rw-r--r--src/lib/http/connection.h102
-rw-r--r--src/lib/http/connection_pool.cc27
-rw-r--r--src/lib/http/connection_pool.h33
-rw-r--r--src/lib/http/http_acceptor.h27
-rw-r--r--src/lib/http/http_types.h6
-rw-r--r--src/lib/http/listener.cc54
-rw-r--r--src/lib/http/listener.h48
-rw-r--r--src/lib/http/request_parser.h8
-rw-r--r--src/lib/http/response_creator.h17
-rw-r--r--src/lib/http/response_creator_factory.h33
-rw-r--r--src/lib/http/tests/Makefile.am1
-rw-r--r--src/lib/http/tests/listener_unittests.cc268
-rw-r--r--src/lib/http/tests/response_creator_unittests.cc10
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.