/// @brief Creates unfinalized stock HTTP response.
///
+ /// The unfinilized response is the response that can't be sent over the
+ /// wire until @c finalize() is called, which commits the contents of the
+ /// message body.
+ ///
/// @param request Pointer to an object representing HTTP request.
/// @param status_code Status code of the response.
/// @return Pointer to an @ref isc::http::HttpResponseJson object
namespace {
-/// @brief Default request timeout of 10s.
-const long REQUEST_TIMEOUT = 10000;
-
/// @brief TCP socket callback function type.
typedef boost::function<void(boost::system::error_code ec, size_t length)>
SocketCallbackFunction;
void doTransaction(const HttpRequestPtr& request, const HttpResponsePtr& response,
const long request_timeout, const HttpClient::RequestHandler& callback);
- /// @brief Closes connection and removes it from the connection pool.
- void stop();
-
/// @brief Closes the socket and cancels the request timer.
void close();
}
}
-void
-Connection::stop() {
- ConnectionPoolPtr conn_pool = conn_pool_.lock();
- conn_pool->closeConnection(url_);
-}
-
void
Connection::close() {
timer_.cancel();
void
HttpClient::asyncSendRequest(const Url& url, const HttpRequestPtr& request,
const HttpResponsePtr& response,
- const HttpClient::RequestHandler& callback) {
- asyncSendRequest(url, request, response,
- HttpClient::RequestTimeout(REQUEST_TIMEOUT),
- callback);
-}
-
-void
-HttpClient::asyncSendRequest(const Url& url, const HttpRequestPtr& request,
- const HttpResponsePtr& response,
- const HttpClient::RequestTimeout& request_timeout,
- const HttpClient::RequestHandler& callback) {
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::RequestTimeout& request_timeout) {
if (!url.isValid()) {
isc_throw(HttpClientError, "invalid URL specified for the HTTP client");
}
class HttpClientImpl;
/// @brief HTTP client class.
+///
+/// This class implements an asynchronous HTTP client. The caller can schedule
+/// transmission of the HTTP request using @ref HttpClient::asyncSendRequest
+/// method. The caller specifies target URL for each request. The caller also
+/// specifies a pointer to the @ref HttpRequest or derived class, holding a
+/// request that should be transmitted to the destination. Such request must
+/// be finalized, i.e. @ref HttpRequest::finalize method must be called prior
+/// to sending it. The caller must also provide a pointer to the
+/// @ref HttpResponse object or an object derived from it. The type of the
+/// response object must match the expected content type to be returned in the
+/// server's response. The last argument specified in this call is the pointer
+/// to the callback function, which should be launched when the response is
+/// received, an error occurs or when a timeout in the transmission is
+/// signalled.
+///
+/// The HTTP client supports multiple simultaneous and persistent connections
+/// with different destinations. The client determines if the connection is
+/// persistent by looking into the Connection header and HTTP version of the
+/// request. If the connection should be persistent the client doesn't
+/// close the connection after sending a request and receiving a response from
+/// the server. If the client is provided with the request to be sent to the
+/// particular destination, but there is an ongoing communication with this
+/// destination, e.g. as a result of sending previous request, the new
+/// request is queued in the FIFO queue. When the previous request completes,
+/// the next request in the queue for the particular URL will be initiated.
+///
+/// The client tests the persistent connection for usability before sending
+/// a request by trying to read from the socket (with message peeking). If
+/// the socket is usable the client uses it to transmit the request.
+///
+/// All errors are reported to the caller via the callback function supplied
+/// to the @ref HttpClient::asyncSendRequest. The IO errors are communicated
+/// via the @c boost::system::error code value. The response parsing errors
+/// are returned via the 3rd parameter of the callback.
class HttpClient {
public:
/// @brief Queues new asynchronous HTTP request.
///
- /// The client creates one connection for the specified URL. If the connection
- /// with the particular destination already exists, it will be re-used for the
- /// new transaction scheduled with this call. If another transaction is still
- /// in progress, the new transaction is queued. The queued transactions are
- /// started in the FIFO order one after another. If the connection is idle or
- /// the connection doesn't exist, the new transaction is started immediatelly.
+ /// The client creates one connection for the specified URL. If the
+ /// connection with the particular destination already exists, it will be
+ /// re-used for the new transaction scheduled with this call. If another
+ /// transaction is still in progress, the new transaction is queued. The
+ /// queued transactions are started in the FIFO order one after another. If
+ /// the connection is idle or the connection doesn't exist, the new
+ /// transaction is started immediatelly.
///
- /// The existing connection is tested before it is used for the new transaction
- /// by attempting to read (with message peeking) from the open TCP socket. If the
- /// read attempt is successful, the client will transmit the HTTP request to
- /// the server using this connection. It is possible that the server closes the
- /// connection between the connection test and sending the request. In such case,
- /// an error will be returned and the caller will need to try re-sending the
- /// request.
+ /// The existing connection is tested before it is used for the new
+ /// transaction by attempting to read (with message peeking) from the open
+ /// TCP socket. If the read attempt is successful, the client will transmit
+ /// the HTTP request to the server using this connection. It is possible
+ /// that the server closes the connection between the connection test and
+ /// sending the request. In such case, an error will be returned and the
+ /// caller will need to try re-sending the request.
///
- /// If the connection test fails, the client will close the socket and reconnect
- /// to the server prior to sending the request.
+ /// If the connection test fails, the client will close the socket and
+ /// reconnect to the server prior to sending the request.
///
- /// Pointers to the request and response objects are provided as arguments of
- /// this method. These pointers should have appropriate types derived from the
- /// @ref HttpRequest and @ref HttpResponse classes. For example, if the request
- /// has content type "application/json", a pointer to the
+ /// Pointers to the request and response objects are provided as arguments
+ /// of this method. These pointers should have appropriate types derived
+ /// from the @ref HttpRequest and @ref HttpResponse classes. For example,
+ /// if the request has content type "application/json", a pointer to the
/// @ref HttpResponseJson should be passed. In this case, the response type
- /// should be @ref HttpResponseJson. These types are used to validate both the
- /// request provided by the caller and the response received from the server.
+ /// should be @ref HttpResponseJson. These types are used to validate both
+ /// the request provided by the caller and the response received from the
+ /// server.
///
- /// The callback function provided by the caller is invoked when the transaction
- /// terminates, i.e. when the server has responded or when an error occured. The
- /// callback is expected to be exception safe, but the client internally guards
- /// against exceptions thrown by the callback.
+ /// The callback function provided by the caller is invoked when the
+ /// transaction terminates, i.e. when the server has responded or when an
+ /// error occured. The callback is expected to be exception safe, but the
+ /// client internally guards against exceptions thrown by the callback.
///
- /// The first argument of the callback indicates an IO error during communication
- /// with the server. If the communication is successful the error code of 0 is
- /// returned. However, in this case it is still possible that the transaction is
- /// unsuccessful due to HTTP response parsing error, e.g. invalid content type,
- /// malformed response etc. Such errors are indicated via third argument.
+ /// The first argument of the callback indicates an IO error during
+ /// communication with the server. If the communication is successful the
+ /// error code of 0 is returned. However, in this case it is still possible
+ /// that the transaction is unsuccessful due to HTTP response parsing error,
+ /// e.g. invalid content type, malformed response etc. Such errors are
+ /// indicated via third argument.
///
- /// If message parsing was successful the second argument of the callback contains
- /// a pointer to the parsed response (the same pointer as provided by the caller
- /// as the argument). If parsing was unsuccessful, the null pointer is returned.
+ /// If message parsing was successful the second argument of the callback
+ /// contains a pointer to the parsed response (the same pointer as provided
+ ///by the caller as the argument). If parsing was unsuccessful, the null
+ /// pointer is returned.
///
- /// The default timeout for the transaction is set to 10 seconds (10 000 ms). This
- /// variant of the method doesn't allow for specifying a custom timeout. If the
- /// timeout occurs, the callback is invoked with the error code of
- /// @c boost::asio::error::timed_out.
+ /// The default timeout for the transaction is set to 10 seconds
+ /// (10 000 ms). If the timeout occurs, the callback is invoked with the
+ //// error code of @c boost::asio::error::timed_out.
///
/// @param url URL where the request should be send.
/// @param request Pointer to the object holding a request.
void asyncSendRequest(const Url& url,
const HttpRequestPtr& request,
const HttpResponsePtr& response,
- const RequestHandler& callback);
-
- /// @brief Queues new asynchronous HTTP request with timeout.
- ///
- /// @param url URL where the request should be send.
- /// @param request Pointer to the object holding a request.
- /// @param response Pointer to the object where response should be stored.
- /// @param request_timeout Timeout for the transaction in milliseconds.
- /// @param callback Pointer to the user callback function.
- ///
- /// @throw HttpClientError If invalid arguments were provided.
- void asyncSendRequest(const Url& url,
- const HttpRequestPtr& request,
- const HttpResponsePtr& response,
- const RequestTimeout& request_timeout,
- const RequestHandler& callback);
+ const RequestHandler& callback,
+ const RequestTimeout& request_timeout =
+ RequestTimeout(10000));
/// @brief Closes all connections.
void stop();
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 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
HttpHeaderPtr
HttpMessage::getHeader(const std::string& header_name) const {
- HttpHeaderPtr http_header = getHeaderSafe(header_name);
-
- // No such header.
- if (!http_header) {
- isc_throw(HttpMessageNonExistingHeader, header_name << " HTTP header"
- " not found in the request");
- }
-
- // Header found.
- return (http_header);
-}
-
-HttpHeaderPtr
-HttpMessage::getHeaderSafe(const std::string& header_name) const {
checkCreated();
HttpHeader hdr(header_name);
return (header_it->second);
}
- // Header not found. Return null pointer.
- return (HttpHeaderPtr());
+ isc_throw(HttpMessageNonExistingHeader, header_name << " HTTP header"
+ " not found in the request");
}
std::string
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 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
/// @throw HttpMessageError if the request hasn't been created.
HttpHeaderPtr getHeader(const std::string& header_name) const;
- /// @brief Returns object encapsulating HTTP header.
- ///
- /// This variant doesn't throw an exception if the header doesn't exist.
- /// It will throw if the request hasn't been created using @c create()
- /// method.
- ///
- /// @param header_name HTTP header name.
- ///
- /// @return Pointer to the specified header, or null if such header doesn't
- /// exist.
- /// @throw HttpMessageError if the request hasn't been created.
- HttpHeaderPtr getHeaderSafe(const std::string& header_name) const;
-
/// @brief Returns a value of the specified HTTP header.
///
/// @param header_name Name of the HTTP header.
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 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
// NEED_MORE_DATA_EVT it indicates that the caller hasn't provided
// the data.
if (ev == NEED_MORE_DATA_EVT) {
- isc_throw(HttpMessageParserBaseError,
+ isc_throw(HttpParseError,
"HTTP request parser requires new data to progress, but no data"
" have been provided. The transaction is aborted to avoid"
" a deadlock. This is a Kea HTTP server logic error!");
// There is no more data so it is really not possible that we're
// at MORE_DATA_PROVIDED_EVT.
if (ev == MORE_DATA_PROVIDED_EVT) {
- isc_throw(HttpMessageParserBaseError,
+ isc_throw(HttpParseError,
"HTTP server state indicates that new data have been"
" provided to be parsed, but the transaction buffer"
" contains no new data. This is a Kea HTTP server logic"
void
HttpMessageParserBase::invalidEventError(const std::string& handler_name,
const unsigned int event) {
- isc_throw(HttpMessageParserBaseError, handler_name << ": "
+ isc_throw(HttpParseError, handler_name << ": "
<< " invalid event " << getEventLabel(static_cast<int>(event)));
}
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 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
/// has occurred.
///
/// The most common errors are due to receiving malformed requests.
-class HttpMessageParserBaseError : public Exception {
+class HttpParseError : public Exception {
public:
- HttpMessageParserBaseError(const char* file, size_t line, const char* what) :
+ HttpParseError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
bool
HttpRequest::isPersistent() const {
- HttpHeaderPtr conn = getHeaderSafe("connection");
+ HttpHeaderPtr conn;
+
+ try {
+ conn = getHeader("connection");
+
+ } catch (...) {
+ // If there is an exception, it means that the header was not found.
+ }
+
std::string conn_value;
if (conn) {
conn_value = conn->getLowerCaseValue();
-// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2018 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
namespace isc {
namespace http {
-/// @brief Exception thrown when an error during parsing HTTP request
-/// has occurred.
-///
-/// The most common errors are due to receiving malformed requests.
-class HttpRequestParserError : public HttpMessageParserBaseError {
-public:
- HttpRequestParserError(const char* file, size_t line, const char* what) :
- HttpMessageParserBaseError(file, line, what) { };
-};
-
class HttpRequestParser;
/// @brief Pointer to the @ref HttpRequestParser.
-// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2018 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
/// @brief Completes creation of the HTTP response.
///
/// This method marks the response as finalized. The JSON structure is
- /// created and can be used to retrieve the parsed data.
+ /// created and can be used to retrieve the parsed data. If this is the
+ /// outbound message, it can be transmitted over the wire as the body
+ /// for the message is now committed.
virtual void finalize();
/// @brief Reset the state of the object.
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 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
namespace isc {
namespace http {
-/// @brief Exception thrown when an error during parsing HTTP response
-/// has occurred.
-///
-/// The most common errors are due to receiving malformed response.
-class HttpResponseParserError : public HttpMessageParserBaseError {
-public:
- HttpResponseParserError(const char* file, size_t line, const char* what) :
- HttpMessageParserBaseError(file, line, what) { };
-};
-
class HttpResponseParser;
/// @brief Pointer to the @ref HttpResponseParser.
-// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2018 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
// Commit and validate the data.
ASSERT_NO_THROW(request_.finalize());
- std::ostringstream expected_response;
- expected_response << "POST /isc/org HTTP/1.0\r\n"
+ std::ostringstream expected_request_text;
+ expected_request_text << "POST /isc/org HTTP/1.0\r\n"
"Content-Length: " << json->str().size() << "\r\n"
"Content-Type: application/json\r\n"
"\r\n"
<< json->str();
- EXPECT_EQ(expected_response.str(), request_.toString());
+ EXPECT_EQ(expected_request_text.str(), request_.toString());
}
}
PostHttpRequestJsonPtr request1 = createRequest("partial-response", true);
HttpResponseJsonPtr response1(new HttpResponseJson());
ASSERT_NO_THROW(client.asyncSendRequest(url, request1, response1,
- HttpClient::RequestTimeout(100),
[this, &cb_num](const boost::system::error_code& ec,
const HttpResponsePtr& response,
const std::string&) {
EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
// There should be no response returned.
EXPECT_FALSE(response);
- }));
+ }, HttpClient::RequestTimeout(100)));
// Create another request after the timeout. It should be handled ok.
PostHttpRequestJsonPtr request2 = createRequest("sequence", 1);
// If there is anything left in the URL, we consider it a path.
if (offset2 != std::string::npos) {
path_ = url_.substr(offset2);
+ if (path_.empty()) {
+ path_ = "/";
+ }
}
valid_ = true;