namespace isc {
namespace http {
+/// @brief Generic error reported within @ref HttpConnection class.
class HttpConnectionError : public Exception {
public:
HttpConnectionError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
-
+/// @brief Forward declaration to the @ref HttpConnectionPool.
+///
+/// This declaration is needed because we don't include the header file
+/// declaring @ref HttpConnectionPool to avoid circular inclusion.
class HttpConnectionPool;
class HttpConnection;
+/// @brief Pointer to the @ref HttpConnection.
typedef boost::shared_ptr<HttpConnection> HttpConnectionPtr;
+/// @brief Accepts and handles a single HTTP connection.
class HttpConnection : public boost::enable_shared_from_this<HttpConnection> {
private:
+ /// @brief Type of the function implementing a callback invoked by the
+ /// @c SocketCallback functor.
typedef boost::function<void(boost::system::error_code ec, size_t length)>
SocketCallbackFunction;
+ /// @brief Functor associated with the socket object.
+ ///
+ /// This functor calls a callback function specified in the constructor.
class SocketCallback {
public:
+ /// @brief Constructor.
+ ///
+ /// @param socket_callback Callback to be invoked by the functor upon
+ /// an event associated with the socket.
SocketCallback(SocketCallbackFunction socket_callback)
: callback_(socket_callback) {
}
+ /// @brief Operator called when event associated with a socket occurs.
+ ///
+ /// This operator returns immediately when received error code is
+ /// @c boost::system::error_code is equal to
+ /// @c boost::asio::error::operation_aborted, i.e. the callback is not
+ /// invoked.
+ ///
+ /// @param ec Error code.
+ /// @param length Data length.
void operator()(boost::system::error_code ec, size_t length = 0);
private:
+ /// @brief Supplied callback.
SocketCallbackFunction callback_;
};
public:
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Reference to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @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.
HttpConnection(asiolink::IOService& io_service,
HttpAcceptor& acceptor,
HttpConnectionPool& connection_pool,
const HttpAcceptorCallback& callback,
const long request_timeout);
+ /// @brief Destructor.
+ ///
+ /// Closes current connection.
~HttpConnection();
+ /// @brief Asynchronously accepts new connection.
+ ///
+ /// When the connection is established successfully, the timeout timer is
+ /// setup and the asynchronous read from the socket is started.
void asyncAccept();
+ /// @brief Closes the socket.
void close();
+ /// @brief Starts asynchronous read from the socket.
+ ///
+ /// The data received over the socket are supplied to the HTTP parser until
+ /// the parser signals that the entire request has been received or until
+ /// the parser signals an error. In the former case the server creates an
+ /// HTTP response using supplied response creator object.
+ ///
+ /// In case of error the connection is stopped.
void doRead();
+private:
+
+ /// @brief Starts asynchronous write to the socket.
+ ///
+ /// The @c output_buf_ must contain the data to be sent.
+ ///
+ /// In case of error the connection is stopped.
void doWrite();
+ /// @brief Sends HTTP response asynchronously.
+ ///
+ /// Internally it calls @ref HttpConnection::doWrite to send the data.
+ ///
+ /// @param response Pointer to the HTTP response to be sent.
+ void asyncSendResponse(const ConstHttpResponsePtr& response);
+
+ /// @brief Local callback invoked when new connection is accepted.
+ ///
+ /// It invokes external (supplied via constructor) acceptor callback. If
+ /// the acceptor is not opened it returns immediately. If the connection
+ /// is accepted successfully the @ref HttpConnection::doRead is called.
+ ///
+ /// @param ec Error code.
void acceptorCallback(const boost::system::error_code& ec);
+ /// @brief Callback invoked when new data is received over the socket.
+ ///
+ /// This callback supplies the data to the HTTP parser and continues
+ /// parsing. When the parser signals end of the HTTP request the callback
+ /// prepares a response and starts asynchronous send over the socket.
+ ///
+ /// @param ec Error code.
+ /// @param length Length of the received data.
void socketReadCallback(boost::system::error_code ec,
size_t length);
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
void socketWriteCallback(boost::system::error_code ec,
size_t length);
+ /// @brief Callback invoked when the HTTP Request Timeout occurs.
+ ///
+ /// This callback creates HTTP response with Request Timeout error code
+ /// and sends it to the client.
void requestTimeoutCallback();
-private:
-
- void asyncSendResponse(const ConstHttpResponsePtr& response);
-
+ /// @brief Stops current connection.
void stopThisConnection();
+ /// @brief Timer used to detect Request Timeout.
asiolink::IntervalTimer request_timer_;
+ /// @brief Configured Request Timeout in milliseconds.
long request_timeout_;
+ /// @brief Socket used by this connection.
asiolink::TCPSocket<SocketCallback> socket_;
+ /// @brief Callback invoked when data received over the socket.
SocketCallback socket_callback_;
+ /// @brief Callback invoked when data sent over the socket.
SocketCallback socket_write_callback_;
+ /// @brief Reference to the TCP acceptor used to accept new connections.
HttpAcceptor& acceptor_;
+ /// @brief Connection pool holding this connection.
HttpConnectionPool& connection_pool_;
+ /// @brief Pointer to the @ref HttpResponseCreator object used to create
+ /// HTTP responses.
HttpResponseCreatorPtr response_creator_;
+ /// @brief Pointer to the request received over this connection.
HttpRequestPtr request_;
+ /// @brief Pointer to the HTTP request parser.
HttpRequestParserPtr parser_;
+ /// @brief External TCP acceptor callback.
HttpAcceptorCallback acceptor_callback_;
+ /// @brief Buffer for received data.
std::array<char, 4096> buf_;
+ /// @brief Buffer used for outbound data.
std::string output_buf_;
};
-}
-}
+} // end of namespace isc::http
+} // end of namespace isc
#endif
void
HttpConnectionPool::stop(const HttpConnectionPtr& connection) {
connections_.erase(connection);
-// connection->close();
}
void
namespace isc {
namespace http {
+/// @brief Pool of active HTTP connections.
+///
+/// The HTTP server is designed to handle many connections simultanously.
+/// The communication between the client and the server may take long time
+/// and the server must be able to react on other events while the communication
+/// with the clients is in progress. Thus, the server must track active
+/// connections and gracefully close them when needed. An obvious case when the
+/// connections must be terminated by the server is when the shutdown signal
+/// is received.
+///
+/// This object is a simple container for the server connections which provides
+/// means to terminate them on request.
class HttpConnectionPool {
public:
+ /// @brief Start new connection.
+ ///
+ /// The connection is inserted to the pool and the
+ /// @ref HttpConnection::asyncAccept is invoked.
+ ///
+ /// @param connection Pointer to the new connection.
void start(const HttpConnectionPtr& connection);
+ /// @brief Stops a connection and removes it from the pool.
+ ///
+ /// If the connection is not found in the pool, this method is no-op.
+ ///
+ /// @param connection Pointer to the connection.
void stop(const HttpConnectionPtr& connection);
+ /// @brief Stops all connections and removes them from the pool.
void stopAll();
private:
+ /// @brief Set of connections.
std::set<HttpConnectionPtr> connections_;
};
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <asiolink/asio_wrapper.h>
+#include <asiolink/tcp_endpoint.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/http_acceptor.h>
#include <http/listener.h>
+#include <boost/scoped_ptr.hpp>
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,
- const long request_timeout)
+/// @brief Implementation of the @ref HttpListener.
+class HttpListenerImpl {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new server endpoint using the specified IP
+ /// address and port. It also validates other specified parameters.
+ ///
+ /// This constructor does not start accepting new connections! To start
+ /// accepting connections run @ref HttpListener::start.
+ ///
+ /// @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 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.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
+ HttpListenerImpl(asiolink::IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout);
+
+ /// @brief Starts accepting new connections.
+ ///
+ /// This method starts accepting and handling new HTTP connections on
+ /// the IP address and port number specified in the constructor.
+ ///
+ /// If the method is invoked successfully, it must not be invoked again
+ /// until @ref HttpListener::stop is called.
+ ///
+ /// @throw HttpListenerError if an error occurred.
+ void start();
+
+ /// @brief Stops all active connections and shuts down the service.
+ void stop();
+
+private:
+
+ /// @brief Creates @ref HttpConnection instance and adds it to the
+ /// pool of active connections.
+ ///
+ /// The next accepted connection will be handled by this instance.
+ void accept();
+
+ /// @brief Callback invoked when the new connection is accepted.
+ ///
+ /// It calls @ref HttpListener::accept to create new @ref HttpConnection
+ /// instance.
+ ///
+ /// @param ec Error code passed to the handler. This is currently ignored.
+ void acceptHandler(const boost::system::error_code& ec);
+
+ /// @brief Reference to the IO service.
+ asiolink::IOService& io_service_;
+
+ /// @brief Acceptor instance.
+ HttpAcceptor acceptor_;
+
+ /// @brief Pointer to the endpoint representing IP address and port on
+ /// which the service is running.
+ boost::scoped_ptr<asiolink::TCPEndpoint> endpoint_;
+
+ /// @brief Pool of active connections.
+ HttpConnectionPool connections_;
+
+ /// @brief Pointer to the @ref HttpResponseCreatorFactory.
+ HttpResponseCreatorFactoryPtr creator_factory_;
+
+ /// @brief Timeout for HTTP Request Timeout desired.
+ long request_timeout_;
+};
+
+HttpListenerImpl::HttpListenerImpl(IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout)
: io_service_(io_service), acceptor_(io_service),
endpoint_(), creator_factory_(creator_factory),
request_timeout_(request_timeout) {
+ // Try creating an endpoint. This may cause exceptions.
try {
endpoint_.reset(new TCPEndpoint(server_address, server_port));
isc_throw(HttpListenerError, "unable to create TCP endpoint for "
<< server_address << ":" << server_port);
}
+
+ // The factory must not be null.
if (!creator_factory_) {
isc_throw(HttpListenerError, "HttpResponseCreatorFactory must not"
" be null");
}
+ // Request timeout is signed and must be greater than 0.
if (request_timeout_ <= 0) {
isc_throw(HttpListenerError, "Invalid desired HTTP request timeout "
<< request_timeout_);
}
}
-HttpListener::~HttpListener() {
- stop();
-}
-
void
-HttpListener::start() {
+HttpListenerImpl::start() {
try {
acceptor_.open(*endpoint_);
acceptor_.setOption(HttpAcceptor::ReuseAddress(true));
acceptor_.listen();
} catch (const boost::system::system_error& ex) {
+ stop();
isc_throw(HttpListenerError, "unable to setup TCP acceptor for "
"listening to the incoming HTTP requests: " << ex.what());
- }
+ }
accept();
}
void
-HttpListener::stop() {
+HttpListenerImpl::stop() {
connections_.stopAll();
acceptor_.close();
}
void
-HttpListener::accept() {
+HttpListenerImpl::accept() {
+ // In some cases we may need HttpResponseCreator instance per connection.
+ // But, the factory may also return the same instance each time. It
+ // depends on the use case.
HttpResponseCreatorPtr response_creator = creator_factory_->create();
HttpAcceptorCallback acceptor_callback =
- boost::bind(&HttpListener::acceptHandler, this, _1);
+ boost::bind(&HttpListenerImpl::acceptHandler, this, _1);
HttpConnectionPtr conn(new HttpConnection(io_service_, acceptor_,
connections_,
response_creator,
acceptor_callback,
request_timeout_));
+ // Add this new connection to the pool.
connections_.start(conn);
}
void
-HttpListener::acceptHandler(const boost::system::error_code& ec) {
+HttpListenerImpl::acceptHandler(const boost::system::error_code&) {
+ // The new connection has arrived. Set the acceptor to continue
+ // accepting new connections.
accept();
}
+HttpListener::HttpListener(IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout)
+ : impl_(new HttpListenerImpl(io_service, server_address, server_port,
+ creator_factory, request_timeout)) {
+}
+
+HttpListener::~HttpListener() {
+ stop();
+}
+
+void
+HttpListener::start() {
+ impl_->start();
+}
+
+void
+HttpListener::stop() {
+ impl_->stop();
+}
+
} // end of namespace isc::http
} // end of namespace isc
#include <asiolink/io_address.h>
#include <asiolink/io_service.h>
-#include <asiolink/tcp_endpoint.h>
#include <exceptions/exceptions.h>
-#include <http/connection.h>
-#include <http/connection_pool.h>
-#include <http/http_acceptor.h>
#include <http/response_creator_factory.h>
-#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
namespace isc {
namespace http {
+/// @brief A generic error raised by the @ref HttpListener class.
class HttpListenerError : public Exception {
public:
HttpListenerError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
+/// @brief HttpListener implementation.
+class HttpListenerImpl;
+
+/// @brief HTTP listener.
+///
+/// This class is an entry point to the use of HTTP services in Kea.
+/// It creates a TCP acceptor service on the specified address and
+/// port and listens to the incoming HTTP connections. The constructor
+/// receives a pointer to the implementation of the
+/// @ref HttpResponseCreatorFactory, which is used by the @ref HttpListener
+/// to create/retrieve an instance of the @ref HttpResponseCreator when the
+/// new HTTP response needs to be generated. The @ref HttpResponseCreator
+/// creates an object derived from the @ref HttpResponse class, encapsulating
+/// a HTTP response following some specific rules, e.g. having
+/// "application/json" content type.
+///
+/// When the listener is started it creates an instance of a @ref HttpConnection
+/// and stores them in the pool of active connections. The @ref HttpConnection
+/// is responsible for managing the next connection received and receiving the
+/// HTTP request and sending appropriate response. The listener can handle
+/// many HTTP connections simultaneously.
+///
+/// When the @ref HttpListener::stop is invoked, all active connections are
+/// closed and the listener stops accepting new connections.
class HttpListener {
public:
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new server endpoint using the specified IP
+ /// address and port. It also validates other specified parameters.
+ ///
+ /// This constructor does not start accepting new connections! To start
+ /// accepting connections run @ref HttpListener::start.
+ ///
+ /// @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 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.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
HttpListener(asiolink::IOService& io_service,
const asiolink::IOAddress& server_address,
const unsigned short server_port,
const HttpResponseCreatorFactoryPtr& creator_factory,
const long request_timeout);
+ /// @brief Destructor.
+ ///
+ /// Stops all active connections and closes TCP acceptor service.
~HttpListener();
+ /// @brief Starts accepting new connections.
+ ///
+ /// This method starts accepting and handling new HTTP connections on
+ /// the IP address and port number specified in the constructor.
+ ///
+ /// If the method is invoked successfully, it must not be invoked again
+ /// until @ref HttpListener::stop is called.
+ ///
+ /// @throw HttpListenerError if an error occurred.
void start();
+ /// @brief Stops all active connections and shuts down the service.
void stop();
private:
- void accept();
-
- void acceptHandler(const boost::system::error_code& ec);
+ /// @brief Pointer to the implementation of the @ref HttpListener.
+ boost::shared_ptr<HttpListenerImpl> impl_;
- asiolink::IOService& io_service_;
- HttpAcceptor acceptor_;
- boost::scoped_ptr<asiolink::TCPEndpoint> endpoint_;
- HttpConnectionPool connections_;
- HttpResponseCreatorFactoryPtr creator_factory_;
- long request_timeout_;
};
} // end of namespace isc::http
#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 {
+/// @brief Specifies the interface for implementing custom factory classes
+/// used to create instances of @ref HttpResponseCreator.
+///
+/// The @ref HttpResponseCreator defines an interface for the classes used
+/// to generate HTTP responses. Such classes are defined outside of this
+/// library and they are specific to the needs of the particular module.
+/// In some cases it may be desired to create new instance of the
+/// @ref HttpResponseCreator implementation for every request processed.
+/// The @ref HttpResponseCreatorFactory is an interface to the "factory"
+/// class which generates canned @ref HttpResponseCreator instances. The
+/// pointer to the factory class is passed to the @ref HttpListener and
+/// the listener propagates it down to other classes. These classes call
+/// @ref HttpResponseCreatorFactory::create to retrieve an instance of the
+/// appropriate @ref HttpResponseCreator, which is in turn used to generate
+/// HTTP response.
+///
+/// Note that an implementation of the @ref HttpResponseCreatorFactory::create
+/// may always return the same instance of the @ref HttpResponseCreator
+/// if creating new instance for each request is not required or undesired.
class HttpResponseCreatorFactory {
public:
+ /// @brief Virtual destructor.
+ ///
+ /// The implementation doesn't need to declare virtual destrtuctor because
+ /// it is already implemented here.
virtual ~HttpResponseCreatorFactory() { }
+ /// @brief Returns an instance of the @ref HttpResponseCreator.
+ ///
+ /// The implementation may create new instance every time this method
+ /// is called, or it may always return the same instance.
+ ///
+ /// @return Pointer to the instance of the @ref HttpResponseCreator to
+ /// be used to generate HTTP response.
virtual HttpResponseCreatorPtr create() const = 0;
};
+/// @brief Pointer to the @ref HttpResponseCreatorFactory.
typedef boost::shared_ptr<HttpResponseCreatorFactory>
HttpResponseCreatorFactoryPtr;
#include <list>
#include <string>
+using namespace boost::asio::ip;
using namespace isc::asiolink;
using namespace isc::http;
using namespace isc::http::test;
namespace {
+/// @brief IP address to which HTTP service is bound.
const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief Port number to which HTTP service is bound.
const unsigned short SERVER_PORT = 18123;
-const long REQUEST_TIMEOUT = 20000;
-/// @brief Test timeout in ms.
+/// @brief Request Timeout used in most of the tests (ms).
+const long REQUEST_TIMEOUT = 10000;
+
+/// @brief Test timeout (ms).
const long TEST_TIMEOUT = 10000;
/// @brief Test HTTP response.
}
};
+/// @brief Implementation of the test @ref HttpResponseCreatorFactory.
+///
+/// This factory class creates @ref TestHttpResponseCreator instances.
class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
public:
+ /// @brief Creates @ref TestHttpResponseCreator instance.
virtual HttpResponseCreatorPtr create() const {
HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator());
return (response_creator);
close();
}
+ /// @brief Send HTTP request specified in textual format.
+ ///
+ /// @param request HTTP request in the textual format.
void startRequest(const std::string& request) {
- boost::asio::ip::tcp::endpoint
- endpoint(boost::asio::ip::address::from_string(SERVER_ADDRESS),
- SERVER_PORT);
+ tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT);
socket_.async_connect(endpoint,
[this, request](const boost::system::error_code& ec) {
if (ec) {
});
}
+ /// @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,
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_),
[this](const boost::system::error_code& ec,
std::size_t bytes_transferred) {
if (ec) {
+ // IO service stopped so simply return.
+ if (ec == boost::asio::error::operation_aborted) {
+ return;
+ }
+
+ // Error occurred, bail...
ADD_FAILURE() << "error occurred while receiving HTTP"
" response from the server: " << ec.message();
io_service_.stop();
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 Pointer to the HttpClient.
typedef boost::shared_ptr<HttpClient> HttpClientPtr;
+/// @brief Test fixture class for @ref HttpListener.
class HttpListenerTest : public ::testing::Test {
public:
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts.
HttpListenerTest()
: io_service_(), factory_(new TestHttpResponseCreatorFactory()),
test_timer_(io_service_), clients_() {
TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
}
- ~HttpListenerTest() {
+ /// @brief Destructor.
+ ///
+ /// Removes active HTTP clients.
+ virtual ~HttpListenerTest() {
for (auto client = clients_.begin(); client != clients_.end();
++client) {
(*client)->close();
io_service_.stop();
}
+ /// @brief IO service used in the tests.
IOService io_service_;
+ /// @brief Pointer to the response creator factory.
HttpResponseCreatorFactoryPtr factory_;
/// @brief Asynchronous timer service to detect timeouts.
std::list<HttpClientPtr> clients_;
};
+// This test verifies that HTTP connection can be established and used to
+// transmit HTTP request and receive a response.
TEST_F(HttpListenerTest, listen) {
const std::string request = "POST /foo/bar HTTP/1.1\r\n"
"Content-Type: application/json\r\n"
io_service_.poll();
}
+// This test verifies that the HTTP listener can't be started twice.
+TEST_F(HttpListenerTest, startTwice) {
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ factory_, REQUEST_TIMEOUT);
+ ASSERT_NO_THROW(listener.start());
+ EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that Bad Request status is returned when the request
+// is malformed.
TEST_F(HttpListenerTest, badRequest) {
+ // Content-Type is wrong. This should result in Bad Request status.
const std::string request = "POST /foo/bar HTTP/1.1\r\n"
"Content-Type: foo\r\n"
"Content-Length: 3\r\n\r\n"
client->getResponse());
}
+// This test verifies that NULL pointer can't be specified for the
+// HttpResponseCreatorFactory.
TEST_F(HttpListenerTest, invalidFactory) {
EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
SERVER_PORT, HttpResponseCreatorFactoryPtr(),
HttpListenerError);
}
+// This test verifies that the timeout of 0 can't be specified for the
+// Request Timeout.
TEST_F(HttpListenerTest, invalidRequestTimeout) {
EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
SERVER_PORT, factory_, 0),
HttpListenerError);
}
+// This test verifies that listener can't be bound to the port to which
+// other server is bound.
TEST_F(HttpListenerTest, addressInUse) {
- boost::asio::ip::tcp::acceptor acceptor(io_service_.get_io_service());;
- boost::asio::ip::tcp::endpoint
- endpoint(boost::asio::ip::address::from_string(SERVER_ADDRESS),
- SERVER_PORT + 1);
+ tcp::acceptor acceptor(io_service_.get_io_service());
+ // Use other port than SERVER_PORT to make sure that this TCP connection
+ // doesn't affect subsequent tests.
+ tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT + 1);
acceptor.open(endpoint.protocol());
acceptor.bind(endpoint);
+ // Listener should report an error when we try to start it because another
+ // acceptor is bound to that port and address.
HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
SERVER_PORT + 1, factory_, REQUEST_TIMEOUT);
EXPECT_THROW(listener.start(), HttpListenerError);
}
+// This test verifies that HTTP Request Timeout status is returned as
+// expected.
TEST_F(HttpListenerTest, requestTimeout) {
+ // The part of the request specified here is correct but it is not
+ // a complete request.
const std::string request = "POST /foo/bar HTTP/1.1\r\n"
- "Content-Type: foo\r\n"
+ "Content-Type: application/json\r\n"
"Content-Length:";
+ // Open the listener with the Request Timeout of 1 sec and post the
+ // partial request.
HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
factory_, 1000);
ASSERT_NO_THROW(listener.start());
ASSERT_EQ(1, clients_.size());
HttpClientPtr client = *clients_.begin();
ASSERT_TRUE(client);
+
+ // 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("HTTP/1.1 408 Request Timeout\r\n"
"Content-Length: 44\r\n"
"Content-Type: application/json\r\n"