Commit 05018f7c authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac5448'

parents 400c8b14 c176be37
......@@ -26,6 +26,8 @@ namespace {
const long REQUEST_TIMEOUT = 10000;
const long IDLE_TIMEOUT = 30000;
}
namespace isc {
......@@ -151,10 +153,11 @@ CtrlAgentProcess::configure(isc::data::ConstElementPtr config_set,
// Create http listener. It will open up a TCP socket and be
// prepared to accept incoming connection.
HttpListenerPtr http_listener(new HttpListener(*getIoService(),
server_address,
server_port, rcf,
REQUEST_TIMEOUT));
HttpListenerPtr
http_listener(new HttpListener(*getIoService(), server_address,
server_port, rcf,
HttpListener::RequestTimeout(REQUEST_TIMEOUT),
HttpListener::IdleTimeout(IDLE_TIMEOUT)));
// Instruct the http listener to actually open socket, install
// callback and start listening.
......
......@@ -28,6 +28,7 @@ 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_header.cc http_header.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
......
......@@ -30,9 +30,12 @@ HttpConnection:: HttpConnection(asiolink::IOService& io_service,
HttpConnectionPool& connection_pool,
const HttpResponseCreatorPtr& response_creator,
const HttpAcceptorCallback& callback,
const long request_timeout)
const long request_timeout,
const long idle_timeout)
: request_timer_(io_service),
request_timer_setup_(false),
request_timeout_(request_timeout),
idle_timeout_(idle_timeout),
socket_(io_service),
acceptor_(acceptor),
connection_pool_(connection_pool),
......@@ -50,6 +53,7 @@ HttpConnection::~HttpConnection() {
void
HttpConnection::close() {
request_timer_setup_ = false;
request_timer_.cancel();
socket_.close();
}
......@@ -117,7 +121,13 @@ HttpConnection::doWrite() {
output_buf_.length(),
cb);
} else {
stopThisConnection();
if (!request_->isPersistent()) {
stopThisConnection();
} else {
reinitProcessingState();
doRead();
}
}
} catch (const std::exception& ex) {
stopThisConnection();
......@@ -148,13 +158,8 @@ HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
HTTP_REQUEST_RECEIVE_START)
.arg(getRemoteEndpointAddressAsText())
.arg(static_cast<unsigned>(request_timeout_/1000));
// Pass raw pointer rather than shared_ptr to this object,
// because IntervalTimer already passes shared pointer to the
// IntervalTimerImpl to make sure that the callback remains
// valid.
request_timer_.setup(boost::bind(&HttpConnection::requestTimeoutCallback,
this),
request_timeout_, IntervalTimer::ONE_SHOT);
setupRequestTimer();
doRead();
}
}
......@@ -241,10 +246,47 @@ HttpConnection::socketWriteCallback(boost::system::error_code ec, size_t length)
} else {
output_buf_.clear();
stopThisConnection();
if (!request_->isPersistent()) {
stopThisConnection();
} else {
reinitProcessingState();
doRead();
}
}
}
void
HttpConnection::reinitProcessingState() {
request_ = response_creator_->createNewHttpRequest();
parser_.reset(new HttpRequestParser(*request_));
parser_->initModel();
setupIdleTimer();
}
void
HttpConnection::setupRequestTimer() {
// Pass raw pointer rather than shared_ptr to this object,
// because IntervalTimer already passes shared pointer to the
// IntervalTimerImpl to make sure that the callback remains
// valid.
if (!request_timer_setup_) {
request_timer_setup_ = true;
request_timer_.setup(boost::bind(&HttpConnection::requestTimeoutCallback,
this),
request_timeout_, IntervalTimer::ONE_SHOT);
}
}
void
HttpConnection::setupIdleTimer() {
request_timer_setup_ = false;
request_timer_.setup(boost::bind(&HttpConnection::idleTimeoutCallback,
this),
idle_timeout_, IntervalTimer::ONE_SHOT);
}
void
HttpConnection::requestTimeoutCallback() {
LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
......@@ -256,6 +298,14 @@ HttpConnection::requestTimeoutCallback() {
asyncSendResponse(response);
}
void
HttpConnection::idleTimeoutCallback() {
LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED)
.arg(getRemoteEndpointAddressAsText());
stopThisConnection();
}
std::string
HttpConnection::getRemoteEndpointAddressAsText() const {
try {
......
......@@ -92,12 +92,15 @@ public:
/// 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.
/// @param idle_timeout Timeout after which persistent HTTP connection is
/// closed by the server.
HttpConnection(asiolink::IOService& io_service,
HttpAcceptor& acceptor,
HttpConnectionPool& connection_pool,
const HttpResponseCreatorPtr& response_creator,
const HttpAcceptorCallback& callback,
const long request_timeout);
const long request_timeout,
const long idle_timeout);
/// @brief Destructor.
///
......@@ -166,12 +169,28 @@ private:
void socketWriteCallback(boost::system::error_code ec,
size_t length);
/// @brief Reinitializes request processing state after sending a response.
///
/// This method is only called for persistent connections, when the response
/// to a previous command has been sent. It initializes the state machine to
/// be able to process the next request. It also sets the persistent connection
/// idle timer to monitor the connection timeout.
void reinitProcessingState();
/// @brief Reset timer for detecting request timeouts.
void setupRequestTimer();
/// @brief Reset timer for detecing idle timeout in persistent connections.
void setupIdleTimer();
/// @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();
void idleTimeoutCallback();
/// @brief Stops current connection.
void stopThisConnection();
......@@ -181,9 +200,15 @@ private:
/// @brief Timer used to detect Request Timeout.
asiolink::IntervalTimer request_timer_;
bool request_timer_setup_;
/// @brief Configured Request Timeout in milliseconds.
long request_timeout_;
/// @brief Timeout after which the persistent HTTP connection is closed
/// by the server.
long idle_timeout_;
/// @brief Socket used by this connection.
asiolink::TCPSocket<SocketCallback> socket_;
......
// 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 <exceptions/exceptions.h>
#include <http/http_header.h>
#include <util/strutil.h>
#include <boost/lexical_cast.hpp>
namespace isc {
namespace http {
HttpHeader::HttpHeader(const std::string& header_name,
const std::string& header_value)
: header_name_(header_name), header_value_(header_value) {
}
uint64_t
HttpHeader::getUint64Value() const {
try {
return (boost::lexical_cast<uint64_t>(header_value_));
} catch (const boost::bad_lexical_cast& ex) {
isc_throw(BadValue, header_name_ << " HTTP header value "
<< header_value_ << " is not a valid number");
}
}
std::string
HttpHeader::getLowerCaseName() const {
std::string ln = header_name_;
util::str::lowercase(ln);
return (ln);
}
std::string
HttpHeader::getLowerCaseValue() const {
std::string lc = header_value_;
util::str::lowercase(lc);
return (lc);
}
bool
HttpHeader::isValueEqual(const std::string& v) const {
std::string lcv = v;
util::str::lowercase(lcv);
return (lcv == getLowerCaseValue());
}
} // end of namespace isc::http
} // end of namespace isc
// 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_HEADER_H
#define HTTP_HEADER_H
#include <boost/shared_ptr.hpp>
#include <string>
namespace isc {
namespace http {
/// @brief Represents HTTP header including a header name and value.
///
/// It includes methods for retrieving header name and value in lower case
/// and for case insensitive comparison of header values.
class HttpHeader {
public:
/// @brief Constructor.
///
/// @param header_name Header name.
/// @param header_value Header value.
explicit HttpHeader(const std::string& header_name,
const std::string& header_value = "");
/// @brief Returns header name.
std::string getName() const {
return (header_name_);
}
/// @brief Returns header value.
std::string getValue() const {
return (header_value_);
}
/// @brief Returns header value as unsigned integer.
///
/// @throw BadValue if the header value is not a valid number.
uint64_t getUint64Value() const;
/// @brief Returns lower case header name.
std::string getLowerCaseName() const;
/// @brief Returns lower case header value.
std::string getLowerCaseValue() const;
/// @brief Case insensitive comparison of header value.
///
/// @param v Value to be compared.
///
/// @return true if header value is equal, false otherwise.
bool isValueEqual(const std::string& v) const;
private:
std::string header_name_; ///< Header name.
std::string header_value_; ///< Header value.
};
/// @brief Pointer to the @c HttpHeader class.
typedef boost::shared_ptr<HttpHeader> HttpHeaderPtr;
} // end of namespace isc::http
} // end of namespace isc
#endif // HTTP_HEADER_H
......@@ -22,6 +22,10 @@ of the request. The first argument specifies the amount of received data.
The second argument specifies an address of the remote endpoint which
produced the data.
% HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED closing persistent connection with %1 as a result of a timeout
This debug message is issued when the persistent HTTP connection is being
closed as a result of being idle.
% HTTP_REQUEST_RECEIVED received HTTP request from %1
This debug message is issued when the server finished receiving a HTTP
request from the remote endpoint. The address of the remote endpoint is
......
......@@ -44,6 +44,30 @@ struct HttpVersion {
bool operator!=(const HttpVersion& rhs) const {
return (!operator==(rhs));
}
/// @name Methods returning @c HttpVersion object encapsulating typical
/// HTTP version numbers.
//@{
/// @brief HTTP version 1.0.
static const HttpVersion& HTTP_10() {
static HttpVersion ver(1, 0);
return (ver);
};
/// @brief HTTP version 1.1.
static const HttpVersion& HTTP_11() {
static HttpVersion ver(1, 1);
return (ver);
}
/// @brief HTTP version 2.0.
static const HttpVersion& HTTP_20() {
static HttpVersion ver(2, 0);
return (ver);
}
//@}
};
} // end of namespace isc::http
......
......@@ -37,6 +37,8 @@ public:
/// create @ref HttpResponseCreator instances.
/// @param request_timeout Timeout after which the HTTP Request Timeout
/// is generated.
/// @param idle_timeout Timeout after which an idle persistent HTTP
/// connection is closed by the server.
///
/// @throw HttpListenerError when any of the specified parameters is
/// invalid.
......@@ -44,7 +46,8 @@ public:
const asiolink::IOAddress& server_address,
const unsigned short server_port,
const HttpResponseCreatorFactoryPtr& creator_factory,
const long request_timeout);
const long request_timeout,
const long idle_timeout);
/// @brief Returns reference to the current listener endpoint.
const TCPEndpoint& getEndpoint() const;
......@@ -97,16 +100,21 @@ private:
/// @brief Timeout for HTTP Request Timeout desired.
long request_timeout_;
/// @brief Timeout after which idle persistent connection is closed by
/// the server.
long idle_timeout_;
};
HttpListenerImpl::HttpListenerImpl(IOService& io_service,
const asiolink::IOAddress& server_address,
const unsigned short server_port,
const HttpResponseCreatorFactoryPtr& creator_factory,
const long request_timeout)
const long request_timeout,
const long idle_timeout)
: io_service_(io_service), acceptor_(io_service),
endpoint_(), creator_factory_(creator_factory),
request_timeout_(request_timeout) {
request_timeout_(request_timeout), idle_timeout_(idle_timeout) {
// Try creating an endpoint. This may cause exceptions.
try {
endpoint_.reset(new TCPEndpoint(server_address, server_port));
......@@ -127,6 +135,12 @@ HttpListenerImpl::HttpListenerImpl(IOService& io_service,
isc_throw(HttpListenerError, "Invalid desired HTTP request timeout "
<< request_timeout_);
}
// Idle persistent connection timeout is signed and must be greater than 0.
if (idle_timeout_ <= 0) {
isc_throw(HttpListenerError, "Invalid desired HTTP idle persistent connection"
" timeout " << idle_timeout_);
}
}
const TCPEndpoint&
......@@ -169,7 +183,8 @@ HttpListenerImpl::accept() {
connections_,
response_creator,
acceptor_callback,
request_timeout_));
request_timeout_,
idle_timeout_));
// Add this new connection to the pool.
connections_.start(conn);
}
......@@ -185,9 +200,11 @@ HttpListener::HttpListener(IOService& io_service,
const asiolink::IOAddress& server_address,
const unsigned short server_port,
const HttpResponseCreatorFactoryPtr& creator_factory,
const long request_timeout)
const HttpListener::RequestTimeout& request_timeout,
const HttpListener::IdleTimeout& idle_timeout)
: impl_(new HttpListenerImpl(io_service, server_address, server_port,
creator_factory, request_timeout)) {
creator_factory, request_timeout.value_,
idle_timeout.value_)) {
}
HttpListener::~HttpListener() {
......
......@@ -51,6 +51,28 @@ class HttpListenerImpl;
class HttpListener {
public:
/// @brief HTTP request timeout value.
struct RequestTimeout {
/// @brief Constructor.
///
/// @param value Request timeout value in milliseconds.
explicit RequestTimeout(long value)
: value_(value) {
}
long value_; ///< Request timeout value specified.
};
/// @brief Idle connection timeout.
struct IdleTimeout {
/// @brief Constructor.
///
/// @param value Connection idle timeout value in milliseconds.
explicit IdleTimeout(long value)
: value_(value) {
}
long value_; ///< Connection idle timeout value specified.
};
/// @brief Constructor.
///
/// This constructor creates new server endpoint using the specified IP
......@@ -67,6 +89,8 @@ public:
/// create @ref HttpResponseCreator instances.
/// @param request_timeout Timeout after which the HTTP Request Timeout
/// is generated.
/// @param idle_timeout Timeout after which an idle persistent HTTP
/// connection is closed by the server.
///
/// @throw HttpListenerError when any of the specified parameters is
/// invalid.
......@@ -74,7 +98,8 @@ public:
const asiolink::IOAddress& server_address,
const unsigned short server_port,
const HttpResponseCreatorFactoryPtr& creator_factory,
const long request_timeout);
const RequestTimeout& request_timeout,
const IdleTimeout& idle_timeout);
/// @brief Destructor.
///
......
......@@ -34,13 +34,15 @@ void
HttpRequest::requireHeader(const std::string& header_name) {
// Empty value denotes that the header is required but no specific
// value is expected.
required_headers_[header_name] = "";
HttpHeaderPtr hdr(new HttpHeader(header_name));
required_headers_[hdr->getLowerCaseName()] = hdr;
}
void
HttpRequest::requireHeaderValue(const std::string& header_name,
const std::string& header_value) {
required_headers_[header_name] = header_value;
HttpHeaderPtr hdr(new HttpHeader(header_name, header_value));
required_headers_[hdr->getLowerCaseName()] = hdr;
}
bool
......@@ -48,7 +50,9 @@ HttpRequest::requiresBody() const {
// If Content-Length is required the body must exist too. There may
// be probably some cases when Content-Length is not provided but
// the body is provided. But, probably not in our use cases.
return (required_headers_.find("Content-Length") != required_headers_.end());
// Use lower case header name because this is how it is indexed in
// the storage.
return (required_headers_.find("content-length") != required_headers_.end());
}
void
......@@ -79,7 +83,8 @@ HttpRequest::create() {
for (auto header = context_->headers_.begin();
header != context_->headers_.end();
++header) {
headers_[header->name_] = header->value_;
HttpHeaderPtr hdr(new HttpHeader(header->name_, header->value_));
headers_[hdr->getLowerCaseName()] = hdr;
}
// Iterate over required headers and check that they exist
......@@ -91,13 +96,13 @@ HttpRequest::create() {
if (header == headers_.end()) {
isc_throw(BadValue, "required header " << req_header->first
<< " not found in the HTTP request");
} else if (!req_header->second.empty() &&
header->second != req_header->second) {
} else if (!req_header->second->getValue().empty() &&
!header->second->isValueEqual(req_header->second->getValue())) {
// If specific value is required for the header, check
// that the value in the HTTP request matches it.
isc_throw(BadValue, "required header's " << header->first
<< " value is " << req_header->second
<< ", but " << header->second << " was found");
<< " value is " << req_header->second->getValue()
<< ", but " << header->second->getValue() << " was found");
}
}
......@@ -151,31 +156,47 @@ HttpRequest::getHttpVersion() const {
context_->http_version_minor_));
}
std::string
HttpRequest::getHeaderValue(const std::string& header) const {
HttpHeaderPtr
HttpRequest::getHeader(const std::string& header_name) const {
HttpHeaderPtr http_header = getHeaderSafe(header_name);
// No such header.
if (!http_header) {
isc_throw(HttpRequestNonExistingHeader, header_name << " HTTP header"
" not found in the request");
}
// Header found.
return (http_header);
}
HttpHeaderPtr
HttpRequest::getHeaderSafe(const std::string& header_name) const {
checkCreated();
auto header_it = headers_.find(header);
HttpHeader hdr(header_name);
auto header_it = headers_.find(hdr.getLowerCaseName());
if (header_it != headers_.end()) {
return (header_it->second);
}
// No such header.
isc_throw(HttpRequestNonExistingHeader, header << " HTTP header"
" not found in the request");
// Header not found. Return null pointer.
return (HttpHeaderPtr());
}
uint64_t
HttpRequest::getHeaderValueAsUint64(const std::string& header) const {
// This will throw an exception if the header doesn't exist.
std::string header_value = getHeaderValue(header);
std::string
HttpRequest::getHeaderValue(const std::string& header_name) const {
return (getHeader(header_name)->getValue());
}
uint64_t
HttpRequest::getHeaderValueAsUint64(const std::string& header_name) const {
try {
return (boost::lexical_cast<uint64_t>(header_value));
return (getHeader(header_name)->getUint64Value());
} catch (const boost::bad_lexical_cast& ex) {
} catch (const std::exception& ex) {
// The specified header does exist, but the value is not a number.
isc_throw(HttpRequestError, header << " HTTP header value "
<< header_value << " is not a valid number");
isc_throw(HttpRequestError, ex.what());
}
}
......@@ -185,6 +206,20 @@ HttpRequest::getBody() const {
return (context_->body_);
}