Commit 12bf626c authored by Marcin Siodelski's avatar Marcin Siodelski

[5451] Extended HttpResponse for inbound messages.

parent 78c52317
......@@ -29,6 +29,7 @@ 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_message.cc http_message.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
......@@ -37,6 +38,7 @@ libkea_http_la_SOURCES += request.cc request.h
libkea_http_la_SOURCES += request_context.h
libkea_http_la_SOURCES += request_parser.cc request_parser.h
libkea_http_la_SOURCES += response.cc response.h
libkea_http_la_SOURCES += response_context.h
libkea_http_la_SOURCES += response_creator.cc response_creator.h
libkea_http_la_SOURCES += response_creator_factory.h
libkea_http_la_SOURCES += response_json.cc response_json.h
......
// 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 <http/http_message.h>
namespace isc {
namespace http {
HttpMessage::HttpMessage()
: required_versions_(), http_version_(HttpVersion::HTTP_10()),
required_headers_(), created_(false), finalized_(false), headers_(),
body_() {
}
HttpMessage::~HttpMessage() {
}
void
HttpMessage::requireHttpVersion(const HttpVersion& version) {
required_versions_.insert(version);
}
void
HttpMessage::requireHeader(const std::string& header_name) {
// Empty value denotes that the header is required but no specific
// value is expected.
HttpHeaderPtr hdr(new HttpHeader(header_name));
required_headers_[hdr->getLowerCaseName()] = hdr;
}
void
HttpMessage::requireHeaderValue(const std::string& header_name,
const std::string& header_value) {
HttpHeaderPtr hdr(new HttpHeader(header_name, header_value));
required_headers_[hdr->getLowerCaseName()] = hdr;
}
bool
HttpMessage::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.
// Use lower case header name because this is how it is indexed in
// the storage.
return (required_headers_.find("content-length") != required_headers_.end());
}
HttpVersion
HttpMessage::getHttpVersion() const {
checkCreated();
return (http_version_);
}
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);
auto header_it = headers_.find(hdr.getLowerCaseName());
if (header_it != headers_.end()) {
return (header_it->second);
}
// Header not found. Return null pointer.
return (HttpHeaderPtr());
}
std::string
HttpMessage::getHeaderValue(const std::string& header_name) const {
return (getHeader(header_name)->getValue());
}
uint64_t
HttpMessage::getHeaderValueAsUint64(const std::string& header_name) const {
try {
return (getHeader(header_name)->getUint64Value());
} catch (const std::exception& ex) {
// The specified header does exist, but the value is not a number.
isc_throw(HttpMessageError, ex.what());
}
}
void
HttpMessage::checkCreated() const {
if (!created_) {
isc_throw(HttpMessageError, "unable to retrieve values of HTTP"
" message because the HttpMessage::create() must be"
" called first. This is a programmatic error");
}
}
void
HttpMessage::checkFinalized() const {
if (!finalized_) {
isc_throw(HttpMessageError, "unable to retrieve body of HTTP"
" message because the HttpMessage::finalize() must be"
" called first. This is a programmatic error");
}
}
} // 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_MESSAGE_H
#define HTTP_MESSAGE_H
#include <exceptions/exceptions.h>
#include <http/http_header.h>
#include <http/http_types.h>
#include <map>
#include <set>
#include <stdint.h>
#include <string>
namespace isc {
namespace http {
/// @brief Generic exception thrown by @ref HttpMessage class.
class HttpMessageError : public Exception {
public:
HttpMessageError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Exception thrown when attempt is made to retrieve a
/// non-existing header.
class HttpMessageNonExistingHeader : public HttpMessageError {
public:
HttpMessageNonExistingHeader(const char* file, size_t line,
const char* what) :
HttpMessageError(file, line, what) { };
};
/// @brief Base class for @ref HttpRequest and @ref HttpResponse.
class HttpMessage {
public:
/// @brief Constructor.
HttpMessage();
/// @brief Destructor.
virtual ~HttpMessage();
/// @brief Specifies HTTP version allowed.
///
/// Allowed HTTP versions must be specified prior to calling @ref create
/// method. If no version is specified, all versions are allowed.
///
/// @param version Version number allowed for the request.
void requireHttpVersion(const HttpVersion& version);
/// @brief Specifies a required HTTP header for the HTTP message.
///
/// Required headers must be specified prior to calling @ref create method.
/// The specified header must exist in the received HTTP request. This puts
/// no requirement on the header value.
///
/// @param header_name Required header name.
void requireHeader(const std::string& header_name);
/// @brief Specifies a required value of a header in the message.
///
/// Required header values must be specified prior to calling @ref create
/// method. The specified header must exist and its value must be equal to
/// the value specified as second parameter.
///
/// @param header_name HTTP header name.
/// @param header_value HTTP header value.
void requireHeaderValue(const std::string& header_name,
const std::string& header_value);
/// @brief Checks if the body is required for the HTTP message.
///
/// Current implementation simply checks if the "Content-Length" header
/// is required.
///
/// @return true if the body is required, false otherwise.
bool requiresBody() const;
/// @brief Reads parsed message from the context, validates the message and
/// stores parsed information.
///
/// This method must be called before retrieving parsed data using accessors.
/// This method doesn't parse the HTTP request body.
virtual void create() = 0;
/// @brief Complete parsing HTTP message or creating an HTTP outbound message.
///
/// This method is used in two situations: when a message has been received
/// into a context and may be fully parsed (including the body) or when the
/// data for the creation of the outbound message have been stored in a context
/// and the message can be now created from the context.
///
/// This method should call @c create method if it hasn't been called yet and
/// then read the message body from the context and interpret it. If the body
/// doesn't adhere to the requirements for the message (in particular, when the
/// content type of the body is invalid) an exception should be thrown.
virtual void finalize() = 0;
/// @brief Reset the state of the object.
virtual void reset() = 0;
/// @brief Returns HTTP version number (major and minor).
HttpVersion getHttpVersion() const;
/// @brief Returns object encapsulating HTTP header.
///
/// @param header_name HTTP header name.
///
/// @return Non-null pointer to the header.
/// @throw HttpMessageNonExistingHeader if header with the specified name
/// doesn't exist.
/// @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.
///
/// @throw HttpMessageError if the header doesn't exist.
std::string getHeaderValue(const std::string& header_name) const;
/// @brief Returns a value of the specified HTTP header as number.
///
/// @param header_name Name of the HTTP header.
///
/// @throw HttpMessageError if the header doesn't exist or if the
/// header value is not number.
uint64_t getHeaderValueAsUint64(const std::string& header_name) const;
/// @brief Returns HTTP message body as string.
virtual std::string getBody() const = 0;
/// @brief Returns HTTP message as text.
///
/// This method is called to generate the outbound HTTP message. Make
/// sure to call @c finalize prior to calling this method.
virtual std::string toString() const = 0;
/// @brief Checks if the message has been successfully finalized.
///
/// The message gets finalized on successful call to @c finalize.
///
/// @return true if the message has been finalized, false otherwise.
bool isFinalized() const {
return (finalized_);
}
/// @brief Checks if the message indicates persistent connection.
///
/// @return true if the message indicates persistent connection, false
/// otherwise.
virtual bool isPersistent() const = 0;
protected:
/// @brief Checks if the @ref create was called.
///
/// @throw HttpMessageError if @ref create wasn't called.
void checkCreated() const;
/// @brief Checks if the @ref finalize was called.
///
/// @throw HttpMessageError if @ref finalize wasn't called.
void checkFinalized() const;
/// @brief Checks if the set is empty or the specified element belongs
/// to this set.
///
/// This is a convenience method used by the class to verify that the
/// given HTTP method belongs to "required methods", HTTP version belongs
/// to "required versions" etc.
///
/// @param element Reference to the element.
/// @param element_set Reference to the set of elements.
/// @tparam Element type, e.g. @ref Method, @ref HttpVersion etc.
///
/// @return true if the element set is empty or if the element belongs
/// to the set.
template<typename T>
bool inRequiredSet(const T& element,
const std::set<T>& element_set) const {
return (element_set.empty() || element_set.count(element) > 0);
}
/// @brief Set of required HTTP versions.
///
/// If the set is empty, all versions are allowed.
std::set<HttpVersion> required_versions_;
/// @brief HTTP version numbers.
HttpVersion http_version_;
/// @brief Map of HTTP headers indexed by lower case header names.
typedef std::map<std::string, HttpHeaderPtr> HttpHeaderMap;
/// @brief Map holding required HTTP headers.
///
/// The key of this map specifies the lower case HTTP header name.
/// If the value of the HTTP header is empty, the header is required
/// but the value of the header is not checked. If the value is
/// non-empty, the value in the HTTP request must be equal (case
/// insensitive) to the value in the map.
HttpHeaderMap required_headers_;
/// @brief Flag indicating whether @ref create was called.
bool created_;
/// @brief Flag indicating whether @ref finalize was called.
bool finalized_;
/// @brief Parsed HTTP headers.
HttpHeaderMap headers_;
/// @brief HTTP body as string.
std::string body_;
};
} // end of namespace isc::http
} // end of namespace isc
#endif // HTTP_MESSAGE_H
......@@ -42,11 +42,11 @@ PostHttpRequestJson::getBodyAsJson() const {
void
PostHttpRequestJson::setBodyAsJson(const data::ConstElementPtr& body) {
if (body) {
context()->body_ = body->str();
context_->body_ = body->str();
json_ = body;
} else {
context()->body_.clear();
context_->body_.clear();
}
}
......
......@@ -13,12 +13,9 @@ namespace isc {
namespace http {
HttpRequest::HttpRequest()
: required_methods_(),required_versions_(), required_headers_(),
created_(false), finalized_(false), method_(Method::HTTP_METHOD_UNKNOWN),
headers_(), context_(new HttpRequestContext()) {
}
HttpRequest::~HttpRequest() {
: HttpMessage(), required_methods_(),
method_(Method::HTTP_METHOD_UNKNOWN),
context_(new HttpRequestContext()) {
}
void
......@@ -26,36 +23,6 @@ HttpRequest::requireHttpMethod(const HttpRequest::Method& method) {
required_methods_.insert(method);
}
void
HttpRequest::requireHttpVersion(const HttpVersion& version) {
required_versions_.insert(version);
}
void
HttpRequest::requireHeader(const std::string& header_name) {
// Empty value denotes that the header is required but no specific
// value is expected.
HttpHeaderPtr hdr(new HttpHeader(header_name));
required_headers_[hdr->getLowerCaseName()] = hdr;
}
void
HttpRequest::requireHeaderValue(const std::string& header_name,
const std::string& header_value) {
HttpHeaderPtr hdr(new HttpHeader(header_name, header_value));
required_headers_[hdr->getLowerCaseName()] = hdr;
}
bool
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.
// 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
HttpRequest::create() {
try {
......@@ -70,13 +37,14 @@ HttpRequest::create() {
<< " not allowed");
}
http_version_.major_ = context_->http_version_major_;
http_version_.minor_ = context_->http_version_minor_;
// Check if the HTTP version is allowed for this request.
if (!inRequiredSet(HttpVersion(context_->http_version_major_,
context_->http_version_minor_),
required_versions_)) {
if (!inRequiredSet(http_version_, required_versions_)) {
isc_throw(BadValue, "use of HTTP version "
<< context_->http_version_major_ << "."
<< context_->http_version_minor_
<< http_version_.major_ << "."
<< http_version_.minor_
<< " not allowed");
}
......@@ -129,10 +97,9 @@ HttpRequest::finalize() {
create();
}
// In this specific case, we don't need to do anything because the
// body is retrieved from the context object directly. We also don't
// know what type of body we have received. Derived classes should
// override this method and handle various types of bodies.
// Copy the body from the context. Derive classes may further
// interpret the body contents, e.g. against the Content-Type.
body_ = context_->body_;
finalized_ = true;
}
......@@ -142,6 +109,7 @@ HttpRequest::reset() {
finalized_ = false;
method_ = HttpRequest::Method::HTTP_METHOD_UNKNOWN;
headers_.clear();
body_.clear();
}
HttpRequest::Method
......@@ -156,57 +124,6 @@ HttpRequest::getUri() const {
return (context_->uri_);
}
HttpVersion
HttpRequest::getHttpVersion() const {
checkCreated();
return (HttpVersion(context_->http_version_major_,
context_->http_version_minor_));
}
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();
HttpHeader hdr(header_name);
auto header_it = headers_.find(hdr.getLowerCaseName());
if (header_it != headers_.end()) {
return (header_it->second);
}
// Header not found. Return null pointer.
return (HttpHeaderPtr());
}
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 (getHeader(header_name)->getUint64Value());
} catch (const std::exception& ex) {
// The specified header does exist, but the value is not a number.
isc_throw(HttpRequestError, ex.what());
}
}
std::string
HttpRequest::getBody() const {
checkFinalized();
......@@ -248,32 +165,6 @@ HttpRequest::isPersistent() const {
((HttpVersion::HTTP_10() < ver) && (conn_value.empty() || (conn_value != "close"))));
}
void
HttpRequest::checkCreated() const {
if (!created_) {
isc_throw(HttpRequestError, "unable to retrieve values of HTTP"
" request because the HttpRequest::create() must be"
" called first. This is a programmatic error");
}
}
void
HttpRequest::checkFinalized() const {
if (!finalized_) {
isc_throw(HttpRequestError, "unable to retrieve body of HTTP"
" request because the HttpRequest::finalize() must be"
" called first. This is a programmatic error");
}
}
template<typename T>
bool
HttpRequest::inRequiredSet(const T& element,
const std::set<T>& element_set) const {
return (element_set.empty() || element_set.count(element) > 0);
}
HttpRequest::Method
HttpRequest::methodFromString(std::string method) const {
boost::to_upper(method);
......
......@@ -7,25 +7,19 @@
#ifndef HTTP_REQUEST_H
#define HTTP_REQUEST_H
#include <exceptions/exceptions.h>
#include <http/http_header.h>
#include <http/http_types.h>
#include <http/http_message.h>
#include <http/request_context.h>
#include <boost/shared_ptr.hpp>
#include <map>
#include <set>
#include <stdint.h>
#include <string>
#include <utility>
namespace isc {
namespace http {
/// @brief Generic exception thrown by @ref HttpRequest class.
class HttpRequestError : public Exception {
class HttpRequestError : public HttpMessageError {
public:
HttpRequestError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
HttpMessageError(file, line, what) { };
};
/// @brief Exception thrown when attempt is made to retrieve a
......@@ -59,7 +53,7 @@ typedef boost::shared_ptr<const HttpRequest> ConstHttpRequestPtr;
/// @ref PostHttpRequestJson, which derives from @ref PostHttpRequest requires
/// that the POST message includes body holding a JSON structure and provides
/// methods to parse the JSON body.
class HttpRequest {
class HttpRequest : public HttpMessage {
public:
/// @brief HTTP methods.
......@@ -79,13 +73,11 @@ public:
/// Creates new context (@ref HttpRequestContext).
HttpRequest();
/// @brief Destructor.
virtual ~HttpRequest();
/// @brief Returns reference to the @ref HttpRequestContext.
///
/// This method is called by the @ref HttpRequestParser to retrieve the
/// context in which parsed data is stored.
/// The context holds intermediate data for creating a request. The request
/// parser stores parsed raw data in the context. When parsing is finished,
/// the data are validated and committed into the @ref HttpRequest.
///
/// @return Pointer to the underlying @ref HttpRequestContext.
const HttpRequestContextPtr& context() const {
......@@ -100,42 +92,6 @@ public:
/// @param method HTTP method allowed for the request.
void requireHttpMethod(const HttpRequest::Method& method);
/// @brief Specifies HTTP version allowed.
///
/// Allowed HTTP versions must be specified prior to calling @ref create
/// method. If no version is specified, all versions are allowed.
///
/// @param version Version number allowed for the request.
void requireHttpVersion(const HttpVersion& version);
/// @brief Specifies a required HTTP header for the request.
///
/// Required headers must be specified prior to calling @ref create method.
/// The specified header must exist in the received HTTP request. This puts
/// no requirement on the header value.
///
/// @param header_name Required header name.
void requireHeader(const std::string& header_name);
/// @brief Specifies a required value of a header in the request.
///
/// Required header values must be specified prior to calling @ref create
/// method. The specified header must exist and its value must be equal to
/// the value specified as second parameter.
///
/// @param header_name HTTP header name.
/// @param header_value HTTP header valuae.