From: Marcin Siodelski Date: Tue, 31 Jan 2017 09:51:55 +0000 (+0100) Subject: [5099] Updated documentation in the new HTTP classes. X-Git-Tag: trac1205_base~3^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0515ecda7aadaa63f47247ac7c77b1376fc037c3;p=thirdparty%2Fkea.git [5099] Updated documentation in the new HTTP classes. --- diff --git a/src/lib/http/connection.h b/src/lib/http/connection.h index ee2c25d060..02b31b647d 100644 --- a/src/lib/http/connection.h +++ b/src/lib/http/connection.h @@ -21,40 +21,76 @@ 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 HttpConnectionPtr; +/// @brief Accepts and handles a single HTTP connection. class HttpConnection : public boost::enable_shared_from_this { private: + /// @brief Type of the function implementing a callback invoked by the + /// @c SocketCallback functor. typedef boost::function 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, @@ -62,60 +98,124 @@ public: 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 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 buf_; + /// @brief Buffer used for outbound data. std::string output_buf_; }; -} -} +} // end of namespace isc::http +} // end of namespace isc #endif diff --git a/src/lib/http/connection_pool.cc b/src/lib/http/connection_pool.cc index 362c29197b..387a8f255a 100644 --- a/src/lib/http/connection_pool.cc +++ b/src/lib/http/connection_pool.cc @@ -19,7 +19,6 @@ HttpConnectionPool::start(const HttpConnectionPtr& connection) { void HttpConnectionPool::stop(const HttpConnectionPtr& connection) { connections_.erase(connection); -// connection->close(); } void diff --git a/src/lib/http/connection_pool.h b/src/lib/http/connection_pool.h index 1cfa7121b6..a6dd556d64 100644 --- a/src/lib/http/connection_pool.h +++ b/src/lib/http/connection_pool.h @@ -13,17 +13,42 @@ 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 connections_; }; diff --git a/src/lib/http/listener.cc b/src/lib/http/listener.cc index 156cb5ff20..bca6df6bfe 100644 --- a/src/lib/http/listener.cc +++ b/src/lib/http/listener.cc @@ -5,21 +5,106 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include +#include +#include +#include +#include #include +#include 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 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)); @@ -27,23 +112,22 @@ HttpListener::HttpListener(IOService& io_service, 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)); @@ -51,37 +135,67 @@ HttpListener::start() { 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 diff --git a/src/lib/http/listener.h b/src/lib/http/listener.h index d8ba74bcd0..6aac7628aa 100644 --- a/src/lib/http/listener.h +++ b/src/lib/http/listener.h @@ -9,50 +9,96 @@ #include #include -#include #include -#include -#include -#include #include -#include +#include 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 impl_; - asiolink::IOService& io_service_; - HttpAcceptor acceptor_; - boost::scoped_ptr endpoint_; - HttpConnectionPool connections_; - HttpResponseCreatorFactoryPtr creator_factory_; - long request_timeout_; }; } // end of namespace isc::http diff --git a/src/lib/http/response_creator_factory.h b/src/lib/http/response_creator_factory.h index 1278f30e7d..4a36afeb8b 100644 --- a/src/lib/http/response_creator_factory.h +++ b/src/lib/http/response_creator_factory.h @@ -7,23 +7,52 @@ #ifndef HTTP_RESPONSE_CREATOR_FACTORY_H #define HTTP_RESPONSE_CREATOR_FACTORY_H -#include -#include #include #include 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 HttpResponseCreatorFactoryPtr; diff --git a/src/lib/http/tests/listener_unittests.cc b/src/lib/http/tests/listener_unittests.cc index 889bf7d737..f224f6ead1 100644 --- a/src/lib/http/tests/listener_unittests.cc +++ b/src/lib/http/tests/listener_unittests.cc @@ -20,17 +20,23 @@ #include #include +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. @@ -85,9 +91,13 @@ private: } }; +/// @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); @@ -116,10 +126,12 @@ public: 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) { @@ -131,10 +143,16 @@ public: }); } + /// @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, @@ -146,25 +164,36 @@ public: 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(); @@ -175,8 +204,13 @@ public: 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(); } }); @@ -209,9 +243,13 @@ private: /// @brief Pointer to the HttpClient. typedef boost::shared_ptr 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_() { @@ -219,7 +257,10 @@ public: 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(); @@ -244,8 +285,10 @@ public: 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. @@ -255,6 +298,8 @@ public: std::list 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" @@ -278,7 +323,18 @@ TEST_F(HttpListenerTest, listen) { 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" @@ -301,6 +357,8 @@ TEST_F(HttpListenerTest, badRequest) { 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(), @@ -308,30 +366,43 @@ TEST_F(HttpListenerTest, invalidFactory) { 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()); @@ -340,6 +411,10 @@ TEST_F(HttpListenerTest, requestTimeout) { 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"