From: Marcin Siodelski Date: Mon, 18 Dec 2017 14:01:39 +0000 (+0100) Subject: [5451] Extended HttpResponse for inbound messages. X-Git-Tag: trac5457_base~4^2~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=12bf626ccda9b031dcf65856c19fee0d0834219a;p=thirdparty%2Fkea.git [5451] Extended HttpResponse for inbound messages. --- diff --git a/src/lib/http/Makefile.am b/src/lib/http/Makefile.am index 599711500c..13cca2f315 100644 --- a/src/lib/http/Makefile.am +++ b/src/lib/http/Makefile.am @@ -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 diff --git a/src/lib/http/http_message.cc b/src/lib/http/http_message.cc new file mode 100644 index 0000000000..0eb97708fe --- /dev/null +++ b/src/lib/http/http_message.cc @@ -0,0 +1,120 @@ +// 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 + +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 diff --git a/src/lib/http/http_message.h b/src/lib/http/http_message.h new file mode 100644 index 0000000000..c635b6f096 --- /dev/null +++ b/src/lib/http/http_message.h @@ -0,0 +1,240 @@ +// 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 +#include +#include +#include +#include +#include +#include + +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 + bool inRequiredSet(const T& element, + const std::set& 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 required_versions_; + + /// @brief HTTP version numbers. + HttpVersion http_version_; + + /// @brief Map of HTTP headers indexed by lower case header names. + typedef std::map 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 diff --git a/src/lib/http/post_request_json.cc b/src/lib/http/post_request_json.cc index d7c401663f..0a93312720 100644 --- a/src/lib/http/post_request_json.cc +++ b/src/lib/http/post_request_json.cc @@ -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(); } } diff --git a/src/lib/http/request.cc b/src/lib/http/request.cc index 0352e2e856..2d93c0c4cf 100644 --- a/src/lib/http/request.cc +++ b/src/lib/http/request.cc @@ -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 -bool -HttpRequest::inRequiredSet(const T& element, - const std::set& element_set) const { - return (element_set.empty() || element_set.count(element) > 0); -} - - HttpRequest::Method HttpRequest::methodFromString(std::string method) const { boost::to_upper(method); diff --git a/src/lib/http/request.h b/src/lib/http/request.h index 3824877880..e41c601f7d 100644 --- a/src/lib/http/request.h +++ b/src/lib/http/request.h @@ -7,25 +7,19 @@ #ifndef HTTP_REQUEST_H #define HTTP_REQUEST_H -#include -#include -#include +#include #include #include -#include -#include #include -#include -#include 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 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. - void requireHeaderValue(const std::string& header_name, - const std::string& header_value); - - /// @brief Checks if the body is required for the HTTP request. - /// - /// Current implementation simply checks if the "Content-Length" header - /// is required for the request. - /// - /// @return true if the body is required for this request. - bool requiresBody() const; - /// @brief Reads parsed request from the @ref HttpRequestContext, validates /// the request and stores parsed information. /// @@ -174,75 +130,22 @@ public: /// @brief Reset the state of the object. virtual void reset(); - /// @name HTTP data accessors. - /// - //@{ /// @brief Returns HTTP method of the request. Method getMethod() const; /// @brief Returns HTTP request URI. std::string getUri() const; - /// @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 HttpRequestNonExistingHeader if header with the specified name - /// doesn't exist. - /// @throw HttpRequestError 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 HttpRequestError 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 HttpRequestError 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 HttpRequestError 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. std::string getBody() const; /// @brief Returns HTTP message as text. /// - /// This method is called to generate the outbound HTTP message to be sent - /// to a server. Make sure to call @c HttpRequest::finalize prior to - /// calling this method. + /// 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; - /// @brief Checks if the request has been successfully finalized. - /// - /// The request is gets finalized on successful call to - /// @ref HttpRequest::finalize. - /// - /// @return true if the request has been finalized, false otherwise. - bool isFinalized() const { - return (finalized_); - } /// @brief Checks if the client has requested persistent connection. /// @@ -255,37 +158,8 @@ public: /// otherwise. bool isPersistent() const; - //@} - protected: - /// @brief Checks if the @ref create was called. - /// - /// @throw HttpRequestError if @ref create wasn't called. - void checkCreated() const; - - /// @brief Checks if the @ref finalize was called. - /// - /// @throw HttpRequestError 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 - bool inRequiredSet(const T& element, - const std::set& element_set) const; - /// @brief Converts HTTP method specified in textual format to @ref Method. /// /// @param method HTTP method specified in the textual format. This value @@ -307,35 +181,9 @@ protected: /// If the set is empty, all methods are allowed. std::set required_methods_; - /// @brief Set of required HTTP versions. - /// - /// If the set is empty, all versions are allowed. - std::set required_versions_; - - /// @brief Map of HTTP headers indexed by lower case header names. - typedef std::map 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 HTTP method of the request. Method method_; - /// @brief Parsed HTTP headers. - HttpHeaderMap headers_; - /// @brief Pointer to the @ref HttpRequestContext holding parsed /// data. HttpRequestContextPtr context_; diff --git a/src/lib/http/response.cc b/src/lib/http/response.cc index 5764b3c300..774d1f5d1b 100644 --- a/src/lib/http/response.cc +++ b/src/lib/http/response.cc @@ -48,7 +48,7 @@ HttpResponse::HttpResponse(const HttpVersion& version, const HttpStatusCode& status_code, const CallSetGenericBody& generic_body) : http_version_(version), status_code_(status_code), headers_(), - body_() { + body_(), context_(new HttpResponseContext()) { if (generic_body.set_) { // This currently does nothing, but it is useful to have it here as // an example how to implement it in the derived classes. @@ -56,6 +56,10 @@ HttpResponse::HttpResponse(const HttpVersion& version, } } +void +HttpResponse::create() { +} + void HttpResponse::setBody(const std::string& body) { body_ = body; diff --git a/src/lib/http/response.h b/src/lib/http/response.h index a69ecd87ae..7dc67d2188 100644 --- a/src/lib/http/response.h +++ b/src/lib/http/response.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -115,6 +116,17 @@ public: /// A class having virtual methods must have a virtual destructor. virtual ~HttpResponse() { } + /// @brief Returns pointer to the @ref HttpResponseContext. + /// + /// The context holds intermediate data for creating a response. The response + /// parser stores parsed raw data in the context. When parsing is finished, + /// the data are validated and committed into the @ref HttpResponse. + /// + /// @return Pointer to the underlying @ref HttpResponseContext. + const HttpResponseContextPtr& context() const { + return (context_); + } + /// @brief Adds HTTP header to the response. /// /// The "Content-Length" and "Date" headers should not be added using this @@ -129,6 +141,12 @@ public: addHeaderInternal(name, value, headers_); } + /// @brief Reads parsed response from the @ref HttpResponseContext, validates + /// the response and stores parsed information. + /// + /// This method doesn't prse the HTTP response body. + virtual void create(); + /// @brief Assigns body/content to the message. /// /// @param body Body to be assigned. @@ -234,6 +252,9 @@ private: /// @brief Holds the body/content. std::string body_; + /// @brief Pointer to the @ref HttpResponseContext holding parsed + /// data. + HttpResponseContextPtr context_; }; } // namespace http diff --git a/src/lib/http/response_context.h b/src/lib/http/response_context.h new file mode 100644 index 0000000000..c1c04ce1df --- /dev/null +++ b/src/lib/http/response_context.h @@ -0,0 +1,42 @@ +// 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_RESPONSE_CONTEXT_H +#define HTTP_RESPONSE_CONTEXT_H + +#include +#include +#include +#include + +namespace isc { +namespace http { + +/// @brief HTTP response context. +/// +/// This context is used by the @ref HttpResponseParser to store parsed +/// data. This data is later used to create an instance of the +/// @ref HttpResponse or its derivation. +struct HttpResponseContext { + /// @brief HTTP major version number. + unsigned int http_version_major_; + /// @brief HTTP minor version number. + unsigned int http_version_minor_; + /// @brief HTTP status code. + uint16_t status_code_; + /// @brief Collection of HTTP headers. + std::vector headers_; + /// @brief HTTP request body. + std::string body_; +}; + +/// @brief Pointer to the @ref HttpResponseContext. +typedef boost::shared_ptr HttpResponseContextPtr; + +} // end of namespace http +} // end of namespace isc + +#endif // endif HTTP_RESPONSE_CONTEXT_H diff --git a/src/lib/http/tests/post_request_json_unittests.cc b/src/lib/http/tests/post_request_json_unittests.cc index 0b6374b85c..15ce685cb1 100644 --- a/src/lib/http/tests/post_request_json_unittests.cc +++ b/src/lib/http/tests/post_request_json_unittests.cc @@ -178,6 +178,7 @@ TEST_F(PostHttpRequestJsonTest, clientRequest) { ElementPtr json = Element::fromJSON(json_body_); request_.setBodyAsJson(json); + // Commit and validate the data. ASSERT_NO_THROW(request_.finalize()); diff --git a/src/lib/http/tests/request_unittests.cc b/src/lib/http/tests/request_unittests.cc index 22b69a189f..4a85f623da 100644 --- a/src/lib/http/tests/request_unittests.cc +++ b/src/lib/http/tests/request_unittests.cc @@ -77,7 +77,7 @@ TEST_F(HttpRequestTest, minimal) { EXPECT_EQ(1, request_.getHttpVersion().minor_); EXPECT_THROW(request_.getHeaderValue("Content-Length"), - HttpRequestNonExistingHeader); + HttpMessageNonExistingHeader); } TEST_F(HttpRequestTest, includeHeaders) { @@ -161,15 +161,15 @@ TEST_F(HttpRequestTest, notCreated) { addHeaderToContext("Content-Type", "text/html"); addHeaderToContext("Content-Length", "1024"); - EXPECT_THROW(static_cast(request_.getMethod()), HttpRequestError); + EXPECT_THROW(static_cast(request_.getMethod()), HttpMessageError); EXPECT_THROW(static_cast(request_.getHttpVersion()), - HttpRequestError); - EXPECT_THROW(static_cast(request_.getUri()), HttpRequestError); + HttpMessageError); + EXPECT_THROW(static_cast(request_.getUri()), HttpMessageError); EXPECT_THROW(static_cast(request_.getHeaderValue("Content-Type")), - HttpRequestError); + HttpMessageError); EXPECT_THROW(static_cast(request_.getHeaderValueAsUint64("Content-Length")), - HttpRequestError); - EXPECT_THROW(static_cast(request_.getBody()), HttpRequestError); + HttpMessageError); + EXPECT_THROW(static_cast(request_.getBody()), HttpMessageError); ASSERT_NO_THROW(request_.finalize());