From: Marcin Siodelski Date: Tue, 19 Dec 2017 12:31:36 +0000 (+0100) Subject: [5451] Restructure HTTP requests/response to support inbound and outbound. X-Git-Tag: trac5457_base~4^2~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e6fe7f50dc23f2b0ba2cb3ce5adc7b563f1a6595;p=thirdparty%2Fkea.git [5451] Restructure HTTP requests/response to support inbound and outbound. --- diff --git a/src/bin/agent/ca_response_creator.cc b/src/bin/agent/ca_response_creator.cc index 0f6d782b8f..06b9217347 100644 --- a/src/bin/agent/ca_response_creator.cc +++ b/src/bin/agent/ca_response_creator.cc @@ -40,6 +40,7 @@ createStockHttpResponse(const ConstHttpRequestPtr& request, } // This will generate the response holding JSON content. HttpResponsePtr response(new HttpResponseJson(http_version, status_code)); + response->finalize(); return (response); } @@ -75,6 +76,7 @@ createDynamicHttpResponse(const ConstHttpRequestPtr& request) { HttpResponseJsonPtr http_response = boost::dynamic_pointer_cast< HttpResponseJson>(createStockHttpResponse(request, HttpStatusCode::OK)); http_response->setBodyAsJson(response); + http_response->finalize(); return (http_response); } diff --git a/src/lib/http/header_context.h b/src/lib/http/header_context.h index 9280d20b66..623e26395b 100644 --- a/src/lib/http/header_context.h +++ b/src/lib/http/header_context.h @@ -7,6 +7,8 @@ #ifndef HTTP_HEADER_CONTEXT_H #define HTTP_HEADER_CONTEXT_H +#include +#include #include namespace isc { @@ -31,6 +33,14 @@ struct HttpHeaderContext { HttpHeaderContext(const std::string& name, const std::string& value) : name_(name), value_(value) { } + + /// @brief Constructor. + /// + /// @param name Header name. + /// @param value Numeric value for the header. + HttpHeaderContext(const std::string& name, const int64_t value) + : name_(name), value_(boost::lexical_cast(value)) { + } }; } // namespace http diff --git a/src/lib/http/http_message.cc b/src/lib/http/http_message.cc index 0eb97708fe..fd4e4bb9b2 100644 --- a/src/lib/http/http_message.cc +++ b/src/lib/http/http_message.cc @@ -9,10 +9,10 @@ namespace isc { namespace http { -HttpMessage::HttpMessage() - : required_versions_(), http_version_(HttpVersion::HTTP_10()), - required_headers_(), created_(false), finalized_(false), headers_(), - body_() { +HttpMessage::HttpMessage(const HttpMessage::Direction& direction) + : direction_(direction), required_versions_(), + http_version_(HttpVersion::HTTP_10()), required_headers_(), + created_(false), finalized_(false), headers_() { } HttpMessage::~HttpMessage() { diff --git a/src/lib/http/http_message.h b/src/lib/http/http_message.h index c635b6f096..61dfb99802 100644 --- a/src/lib/http/http_message.h +++ b/src/lib/http/http_message.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include namespace isc { @@ -36,15 +36,60 @@ public: /// @brief Base class for @ref HttpRequest and @ref HttpResponse. +/// +/// This abstract class provides a common functionality for the HTTP +/// requests and responses. Each such message can be marked as outbound +/// or inbound. An HTTP inbound request is the one received by the server +/// and HTTP inbound response is the response received by the client. +/// Conversely, an HTTP outbound request is the request created by the +/// client and HTTP outbound response is the response created by the +/// server. There are differences in how the inbound and outbound +/// messages are created. The inbound messages are received over the +/// TCP sockets and parsed by the parsers. The parsed information is +/// stored in a context, i.e. structure holding raw information and +/// associated with the given @c HttpMessage instance. Once the message +/// is parsed and all required information is stored in the context, +/// the @c create method is called to validate and fetch information +/// from the context into the message. The @c finalize method is called +/// to commit the HTTP message body into the message. +/// +/// The outbound message is created locally from the known data, e.g. +/// HTTP version number, URI, method etc. The headers can be then +/// appended to the message via the context. In order to use this message +/// the @c finalize method must be called to commit this information. +/// Them, @c toString method can be called to generate the message in +/// the textual form, which can be transferred via TCP socket. class HttpMessage { public: + /// @brief Specifies the direction of the HTTP message. + enum Direction { + INBOUND, + OUTBOUND + }; + /// @brief Constructor. - HttpMessage(); + /// + /// @param direction Direction of the message (inbound or outbound). + explicit HttpMessage(const Direction& direction); /// @brief Destructor. virtual ~HttpMessage(); + /// @brief Returns HTTP message direction. + Direction getDirection() const { + return (direction_); + } + + /// @brief Sets direction for the HTTP message. + /// + /// This is mostly useful in unit testing. + /// + /// @param direction New direction of the HTTP message. + void setDirection(const Direction& direction) { + direction_ = direction; + } + /// @brief Specifies HTTP version allowed. /// /// Allowed HTTP versions must be specified prior to calling @ref create @@ -163,12 +208,6 @@ public: 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. @@ -190,7 +229,7 @@ protected: /// /// @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. + /// @tparam T Element type, @ref HttpVersion etc. /// /// @return true if the element set is empty or if the element belongs /// to the set. @@ -200,6 +239,9 @@ protected: return (element_set.empty() || element_set.count(element) > 0); } + /// @brief Message direction (inbound or outbound). + Direction direction_; + /// @brief Set of required HTTP versions. /// /// If the set is empty, all versions are allowed. @@ -228,10 +270,6 @@ protected: /// @brief Parsed HTTP headers. HttpHeaderMap headers_; - - /// @brief HTTP body as string. - std::string body_; - }; } // end of namespace isc::http diff --git a/src/lib/http/post_request.cc b/src/lib/http/post_request.cc index 94caca1b26..3cc8d810b8 100644 --- a/src/lib/http/post_request.cc +++ b/src/lib/http/post_request.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-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 diff --git a/src/lib/http/post_request.h b/src/lib/http/post_request.h index dd16143734..dbfe7d7026 100644 --- a/src/lib/http/post_request.h +++ b/src/lib/http/post_request.h @@ -29,7 +29,7 @@ typedef boost::shared_ptr ConstPostHttpRequestPtr; class PostHttpRequest : public HttpRequest { public: - /// @brief Constructor. + /// @brief Constructor for inbound HTTP request. PostHttpRequest(); }; diff --git a/src/lib/http/post_request_json.h b/src/lib/http/post_request_json.h index 973a4ac49b..57c6d922b1 100644 --- a/src/lib/http/post_request_json.h +++ b/src/lib/http/post_request_json.h @@ -39,8 +39,8 @@ typedef boost::shared_ptr ConstPostHttpRequestJsonPtr class PostHttpRequestJson : public PostHttpRequest { public: - /// @brief Constructor. - PostHttpRequestJson(); + /// @brief Constructor for inbound HTTP request. + explicit PostHttpRequestJson(); /// @brief Complete parsing of the HTTP request. /// diff --git a/src/lib/http/request.cc b/src/lib/http/request.cc index 2d93c0c4cf..0d3186d118 100644 --- a/src/lib/http/request.cc +++ b/src/lib/http/request.cc @@ -9,13 +9,31 @@ #include #include +namespace { + +/// @brief New line (CRLF). +const std::string crlf = "\r\n"; + +} + namespace isc { namespace http { HttpRequest::HttpRequest() - : HttpMessage(), required_methods_(), + : HttpMessage(INBOUND), required_methods_(), + method_(Method::HTTP_METHOD_UNKNOWN), + context_(new HttpRequestContext()) { +} + +HttpRequest::HttpRequest(const Method& method, const std::string& uri, + const HttpVersion& version) + : HttpMessage(OUTBOUND), required_methods_(), method_(Method::HTTP_METHOD_UNKNOWN), context_(new HttpRequestContext()) { + context()->method_ = methodToString(method); + context()->uri_ = uri; + context()->http_version_major_ = version.major_; + context()->http_version_minor_ = version.minor_; } void @@ -56,7 +74,7 @@ HttpRequest::create() { headers_[hdr->getLowerCaseName()] = hdr; } - if (!context_->body_.empty() && (headers_.count("content-length") == 0)) { + if (getDirection() == HttpMessage::OUTBOUND) { HttpHeaderPtr hdr(new HttpHeader("Content-Length", boost::lexical_cast(context_->body_.length()))); headers_["content-length"] = hdr; @@ -99,7 +117,6 @@ HttpRequest::finalize() { // 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; } @@ -109,7 +126,6 @@ HttpRequest::reset() { finalized_ = false; method_ = HttpRequest::Method::HTTP_METHOD_UNKNOWN; headers_.clear(); - body_.clear(); } HttpRequest::Method @@ -136,15 +152,15 @@ HttpRequest::toString() const { std::ostringstream s; s << methodToString(getMethod()) << " " << getUri() << " HTTP/" << - getHttpVersion().major_ << "." << getHttpVersion().minor_ << "\r\n"; + getHttpVersion().major_ << "." << getHttpVersion().minor_ << crlf; for (auto header_it = headers_.cbegin(); header_it != headers_.cend(); ++header_it) { s << header_it->second->getName() << ": " << header_it->second->getValue() - << "\r\n"; + << crlf; } - s << "\r\n"; + s << crlf; s << getBody(); @@ -153,6 +169,10 @@ HttpRequest::toString() const { bool HttpRequest::isPersistent() const { + if (getDirection() == OUTBOUND) { + isc_throw(InvalidOperation, "can't call isPersistent for the outbound request"); + } + HttpHeaderPtr conn = getHeaderSafe("connection"); std::string conn_value; if (conn) { diff --git a/src/lib/http/request.h b/src/lib/http/request.h index e41c601f7d..0cad579df5 100644 --- a/src/lib/http/request.h +++ b/src/lib/http/request.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include namespace isc { namespace http { @@ -22,15 +22,6 @@ public: HttpMessageError(file, line, what) { }; }; -/// @brief Exception thrown when attempt is made to retrieve a -/// non-existing header. -class HttpRequestNonExistingHeader : public HttpRequestError { -public: - HttpRequestNonExistingHeader(const char* file, size_t line, - const char* what) : - HttpRequestError(file, line, what) { }; -}; - class HttpRequest; /// @brief Pointer to the @ref HttpRequest object. @@ -41,18 +32,19 @@ typedef boost::shared_ptr ConstHttpRequestPtr; /// @brief Represents HTTP request message. /// -/// This object represents parsed HTTP message. The @ref HttpRequestContext -/// contains raw data used as input for this object. This class interprets the -/// data. In particular, it verifies that the appropriate method, HTTP version, -/// and headers were used. The derivations of this class provide specializations -/// and specify the HTTP methods, versions and headers supported/required in -/// the specific use cases. +/// This derivation of the @c HttpMessage class is specialized to represent +/// HTTP requests. This class provides two constructors for creating an inbound +/// and outbound request instance respectively. This class is associated with +/// an instance of the @c HttpRequestContext, which is used to provide request +/// specific values, such as: HTTP method, version, URI and headers. /// -/// For example, the @ref PostHttpRequest class derives from @ref HttpRequest -/// and it requires that parsed messages use POST method. The -/// @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. +/// The derivations of this class provide specializations and specify the +/// HTTP methods, versions and headers supported/required in the specific use +/// cases. For example, the @c PostHttpRequest class derives from @c HttpRequest +/// and it requires that request uses POST method. The @c PostHttpRequestJson, +/// which derives from @c PostHttpRequest requires that the POST message +/// includes body holding a JSON structure and provides methods to parse the +/// JSON body. class HttpRequest : public HttpMessage { public: @@ -68,16 +60,21 @@ public: HTTP_METHOD_UNKNOWN }; - /// @brief Constructor. - /// - /// Creates new context (@ref HttpRequestContext). + /// @brief Constructor for inbound HTTP request. HttpRequest(); - /// @brief Returns reference to the @ref HttpRequestContext. + /// @brief Constructor for oubtound HTTP request. + /// + /// @param method HTTP method, e.g. POST. + /// @param uri URI. + /// @param version HTTP version. + HttpRequest(const Method& method, const std::string& uri, const HttpVersion& version); + + /// @brief Returns pointer to the @ref HttpRequestContext. /// /// 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. + /// the data are validated and committed into the @c HttpRequest. /// /// @return Pointer to the underlying @ref HttpRequestContext. const HttpRequestContextPtr& context() const { @@ -92,39 +89,22 @@ public: /// @param method HTTP method allowed for the request. void requireHttpMethod(const HttpRequest::Method& method); - /// @brief Reads parsed request from the @ref HttpRequestContext, validates - /// the request and stores parsed information. + /// @brief Commits information held in the context into the request. /// - /// This method must be called before retrieving parsed data using accessors - /// such as @ref getMethod, @ref getUri etc. - /// - /// This method doesn't parse the HTTP request body. + /// This function reads HTTP method, version and headers from the context + /// and validates their values. For the outbound messages, it automatically + /// appends Content-Length header to the request, based on the length of the + /// request body. /// /// @throw HttpRequestError if the parsed request doesn't meet the specified /// requirements for it. virtual void create(); - /// @brief Complete parsing of the HTTP request or create outbound HTTP request. - /// - /// HTTP request parsing is performed in two stages: HTTP headers, then - /// request body. The @ref create method parses HTTP headers. Once this is - /// done, the caller can check if the "Content-Length" was specified and use - /// it's value to determine the size of the body which is parsed in the - /// second stage. - /// - /// This method generally performs the body parsing, but if it determines - /// that the @ref create method hasn't been called, it calls @ref create - /// before parsing the body. + /// @brief Completes creation of the HTTP request. /// - /// For the outbound (client) request, this method must be called after - /// setting all required values in the request context. The Content-Length - /// is generally not explicitly set by the caller in this case. This method - /// computes the value of the Content-Length and inserts the suitable header - /// when it finds non-empty body. - /// - /// The derivations must call @ref create if it hasn't been called prior to - /// calling this method. It must set @ref finalized_ to true if the call - /// to @ref finalize was successful. + /// This method marks the message as finalized. The outbound request may now be + /// sent over the TCP socket. The information from the inbound message may be + /// read, including the request body. virtual void finalize(); /// @brief Reset the state of the object. @@ -140,13 +120,12 @@ public: /// @brief Returns HTTP message body as string. std::string getBody() const; - /// @brief Returns HTTP message as text. + /// @brief Returns HTTP message as string. /// /// 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 client has requested persistent connection. /// /// For the HTTP/1.0 case, the connection is persistent if the client has @@ -156,6 +135,7 @@ public: /// /// @return true if the client has requested persistent connection, false /// otherwise. + /// @throw InvalidOperation if the method is called for the outbound message. bool isPersistent() const; protected: diff --git a/src/lib/http/response.cc b/src/lib/http/response.cc index 774d1f5d1b..292d8f8683 100644 --- a/src/lib/http/response.cc +++ b/src/lib/http/response.cc @@ -44,11 +44,18 @@ const std::string crlf = "\r\n"; namespace isc { namespace http { +HttpResponse::HttpResponse() + : HttpMessage(INBOUND), context_(new HttpResponseContext()) { +} + HttpResponse::HttpResponse(const HttpVersion& version, const HttpStatusCode& status_code, const CallSetGenericBody& generic_body) - : http_version_(version), status_code_(status_code), headers_(), - body_(), context_(new HttpResponseContext()) { + : HttpMessage(OUTBOUND), context_(new HttpResponseContext()) { + context_->http_version_major_ = version.major_; + context_->http_version_minor_ = version.minor_; + context_->status_code_ = static_cast(status_code); + 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. @@ -58,11 +65,90 @@ HttpResponse::HttpResponse(const HttpVersion& version, void HttpResponse::create() { + try { + 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(http_version_, required_versions_)) { + isc_throw(BadValue, "use of HTTP version " + << http_version_.major_ << "." + << http_version_.minor_ + << " not allowed"); + } + + // Copy headers from the context. + for (auto header = context_->headers_.begin(); + header != context_->headers_.end(); + ++header) { + HttpHeaderPtr hdr(new HttpHeader(header->name_, header->value_)); + headers_[hdr->getLowerCaseName()] = hdr; + } + + if (getDirection() == HttpMessage::OUTBOUND) { + HttpHeaderPtr length_header(new HttpHeader("Content-Length", boost::lexical_cast + (context_->body_.length()))); + headers_["content-length"] = length_header; + + HttpHeaderPtr date_header(new HttpHeader("Date", getDateHeaderValue()));; + headers_["date"] = date_header; + } + + // Iterate over required headers and check that they exist + // in the HTTP response. + for (auto req_header = required_headers_.begin(); + req_header != required_headers_.end(); + ++req_header) { + auto header = headers_.find(req_header->first); + if (header == headers_.end()) { + isc_throw(BadValue, "required header " << req_header->first + << " not found in the HTTP response"); + } 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 response matches it. + isc_throw(BadValue, "required header's " << header->first + << " value is " << req_header->second->getValue() + << ", but " << header->second->getValue() << " was found"); + } + } + + } catch (const std::exception& ex) { + // Reset the state of the object if we failed at any point. + reset(); + isc_throw(HttpResponseError, ex.what()); + } + + // All ok. + created_ = true; +} + +void +HttpResponse::finalize() { + if (!created_) { + create(); + } + + finalized_ = true; } void -HttpResponse::setBody(const std::string& body) { - body_ = body; +HttpResponse::reset() { + created_ = false; + finalized_ = false; + headers_.clear(); +} + +HttpStatusCode +HttpResponse::getStatusCode() const { + checkCreated(); + return (static_cast(context_->status_code_)); +} + +std::string +HttpResponse::getBody() const { + checkFinalized(); + return (context_->body_); } bool @@ -104,43 +190,33 @@ HttpResponse::getDateHeaderValue() const { std::string HttpResponse::toBriefString() const { + checkFinalized(); + std::ostringstream s; // HTTP version number and status code. s << "HTTP/" << http_version_.major_ << "." << http_version_.minor_; - s << " " << static_cast(status_code_); - s << " " << statusCodeToString(status_code_) << crlf; + s << " " << context_->status_code_; + s << " " << statusCodeToString(static_cast(context_->status_code_)) << crlf; return (s.str()); } std::string HttpResponse::toString() const { + std::ostringstream s; // HTTP version number and status code. s << toBriefString(); - // We need to at least insert "Date" header into the HTTP headers. This - // method is const thus we can't insert it into the headers_ map. We'll - // work on the copy of the map. Admittedly, we could just append "Date" - // into the generated string but we prefer that headers are ordered - // alphabetically. - std::map headers(headers_); - - // Update or add "Date" header. - addHeaderInternal("Date", getDateHeaderValue(), headers); - - // Always add "Content-Length", perhaps equal to 0. - addHeaderInternal("Content-Length", body_.length(), headers); - - // Include all headers. - for (auto header = headers.cbegin(); header != headers.cend(); - ++header) { - s << header->first << ": " << header->second << crlf; + for (auto header_it = headers_.cbegin(); header_it != headers_.cend(); + ++header_it) { + s << header_it->second->getName() << ": " << header_it->second->getValue() + << crlf; } s << crlf; // Include message body. - s << body_; + s << getBody(); return (s.str()); } diff --git a/src/lib/http/response.h b/src/lib/http/response.h index 7dc67d2188..eabf476489 100644 --- a/src/lib/http/response.h +++ b/src/lib/http/response.h @@ -7,23 +7,21 @@ #ifndef HTTP_RESPONSE_H #define HTTP_RESPONSE_H -#include -#include +#include +#include #include #include #include -#include -#include -#include +#include namespace isc { namespace http { /// @brief Generic exception thrown by @ref HttpResponse class. -class HttpResponseError : public Exception { +class HttpResponseError : public HttpMessageError { public: HttpResponseError(const char* file, size_t line, const char* what) : - isc::Exception(file, line, what) { }; + HttpMessageError(file, line, what) { }; }; /// @brief HTTP status codes (cf RFC 2068) @@ -85,16 +83,24 @@ typedef boost::shared_ptr ConstHttpResponsePtr; /// @brief Represents HTTP response message. /// -/// This class represents HTTP response message. An instance of this object -/// or its derivation is typically created by the implementation of the -/// @ref HttpResponseCreator interface. +/// This derivation of the @c HttpMessage class is specialized to represent +/// HTTP responses. This class provides two constructors for creating an inbound +/// and outbound response instance respectively. This class is associated with +/// an instance of the @c HttpResponseContext, which is used to provide response +/// specific values, such as HTTP status and headers. /// -/// It contains @c toString method generating a textual representation of -/// the HTTP response, which is send to the client over TCP socket. -class HttpResponse { +/// The derivations of this class provide specializations and specify the HTTP +/// versions and headers supported/required in the specific use cases. For example, +/// the @c HttpResponseJson class derives from the @c HttpResponse and it requires +/// that response includes a body in the JSON format. +class HttpResponse : public HttpMessage { public: - /// @brief Constructor. + /// @brief Constructor for the inbound HTTP response. + explicit HttpResponse(); + + + /// @brief Constructor for outbound HTTP response. /// /// Creates basic instance of the object. It sets the HTTP version and the /// status code to be included in the response. @@ -111,46 +117,41 @@ public: const CallSetGenericBody& generic_body = CallSetGenericBody::yes()); - /// @brief Destructor. - /// - /// 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. + /// the data are validated and committed into the @c HttpResponse. /// /// @return Pointer to the underlying @ref HttpResponseContext. const HttpResponseContextPtr& context() const { return (context_); } - /// @brief Adds HTTP header to the response. + /// @brief Commits information held in the context into the response. /// - /// The "Content-Length" and "Date" headers should not be added using this - /// method because they are generated and added automatically when the - /// @c toString is called. - /// - /// @param name Header name. - /// @param value Header value. - /// @tparam ValueType Type of the header value. - template - void addHeader(const std::string& name, const ValueType& value) { - 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. + /// This function reads HTTP version, status code and headers from the + /// context and validates their values. For the outbound messages, it + /// automatically appends Content-Length and Date headers to the response. + /// The Content-Length is set to the body size. The Date is set to the + /// current date and time. virtual void create(); - /// @brief Assigns body/content to the message. + /// @brief Completes creation of the HTTP response. /// - /// @param body Body to be assigned. - void setBody(const std::string& body); + /// This method marks the response as finalized. The outbound response may now + /// be sent over the TCP socket. The information from the inbound message may + /// be read, including the response body. + virtual void finalize(); + + /// @brief Reset the state of the object. + virtual void reset(); + + /// @brief Returns HTTP status code. + HttpStatusCode getStatusCode() const; + + /// @brief Returns HTTP response body as string. + virtual std::string getBody() const; /// @brief Checks if the status code indicates client error. /// @@ -173,35 +174,14 @@ public: /// @brief Returns HTTP version and HTTP status as a string. std::string toBriefString() const; - /// @brief Returns textual representation of the HTTP response. - /// - /// It includes the "Date" header with the current time in RFC 1123 format. - /// It also includes "Content-Length" when the response has a non-empty - /// body. + /// @brief Returns HTTP response as string. /// - /// @return Textual representation of the HTTP response. - std::string toString() const ; + /// This method is called to generate the outbound HTTP response. Make + /// sure to call @c finalize prior to calling this method. + virtual std::string toString() const; protected: - /// @brief Adds HTTP header to the map. - /// - /// @param name Header name. - /// @param value Header value. - /// @param [out] headers A map to which header value should be inserted. - /// @tparam ValueType Type of the header value. - template - void addHeaderInternal(const std::string& name, const ValueType& value, - std::map& headers) const { - try { - headers[name] = boost::lexical_cast(value); - - } catch (const boost::bad_lexical_cast& ex) { - isc_throw(HttpResponseError, "unable to convert the " - << name << " header value to a string"); - } - } - /// @brief Returns current time formatted as required by RFC 1123. /// /// This method is virtual so as it can be overridden in unit tests @@ -240,18 +220,6 @@ private: /// generated. void setGenericBody(const HttpStatusCode& /*status_code*/) { }; - /// @brief Holds HTTP version for the response. - HttpVersion http_version_; - - /// @brief Holds status code for the response. - HttpStatusCode status_code_; - - /// @brief Holds HTTP headers for the response. - std::map headers_; - - /// @brief Holds the body/content. - std::string body_; - /// @brief Pointer to the @ref HttpResponseContext holding parsed /// data. HttpResponseContextPtr context_; diff --git a/src/lib/http/response_context.h b/src/lib/http/response_context.h index c1c04ce1df..5154dc4c6d 100644 --- a/src/lib/http/response_context.h +++ b/src/lib/http/response_context.h @@ -17,9 +17,9 @@ namespace http { /// @brief HTTP response context. /// -/// This context is used by the @ref HttpResponseParser to store parsed +/// This context is used by the @c HttpResponseParser to store parsed /// data. This data is later used to create an instance of the -/// @ref HttpResponse or its derivation. +/// @c HttpResponse or its derivation. struct HttpResponseContext { /// @brief HTTP major version number. unsigned int http_version_major_; diff --git a/src/lib/http/response_json.cc b/src/lib/http/response_json.cc index fc780ae4cd..231d5beabc 100644 --- a/src/lib/http/response_json.cc +++ b/src/lib/http/response_json.cc @@ -11,11 +11,17 @@ using namespace isc::data; namespace isc { namespace http { +HttpResponseJson::HttpResponseJson() + : HttpResponse() { + context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json")); +} + + HttpResponseJson::HttpResponseJson(const HttpVersion& version, const HttpStatusCode& status_code, const CallSetGenericBody& generic_body) : HttpResponse(version, status_code, CallSetGenericBody::no()) { - addHeader("Content-Type", "application/json"); + context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json")); // This class provides its own implementation of the setGenericBody. // We call it here unless the derived class calls this constructor // from its own constructor and indicates that we shouldn't set the @@ -44,7 +50,14 @@ HttpResponseJson::setGenericBody(const HttpStatusCode& status_code) { void HttpResponseJson::setBodyAsJson(const ConstElementPtr& json_body) { - setBody(json_body->str()); + if (json_body) { + context()->body_ = json_body->str(); + + } else { + context()->body_.clear(); + } + + json_ = json_body; } diff --git a/src/lib/http/response_json.h b/src/lib/http/response_json.h index b4136626da..4b40548636 100644 --- a/src/lib/http/response_json.h +++ b/src/lib/http/response_json.h @@ -26,7 +26,10 @@ typedef boost::shared_ptr HttpResponseJsonPtr; class HttpResponseJson : public HttpResponse { public: - /// @brief Constructor. + /// @brief Constructor for the inbound HTTP response. + explicit HttpResponseJson(); + + /// @brief Constructor for the outbound HTTP response. /// /// @param version HTTP version. /// @param status_code HTTP status code. @@ -58,9 +61,14 @@ private: /// @param status_code Status code for which the body should be /// generated. void setGenericBody(const HttpStatusCode& status_code); + +protected: + + /// @brief Pointer to the parsed JSON body. + data::ConstElementPtr json_; }; -} -} +} // end of namespace isc::http +} // end of namespace isc #endif diff --git a/src/lib/http/tests/listener_unittests.cc b/src/lib/http/tests/listener_unittests.cc index df3812f894..7b35baed81 100644 --- a/src/lib/http/tests/listener_unittests.cc +++ b/src/lib/http/tests/listener_unittests.cc @@ -79,6 +79,7 @@ private: request->context()->http_version_minor_); // This will generate the response holding JSON content. ResponsePtr response(new Response(http_version, status_code)); + response->finalize(); return (response); } @@ -92,6 +93,7 @@ private: // We don't need content to test our class. ResponsePtr response(new Response(request->getHttpVersion(), HttpStatusCode::OK)); + response->finalize(); return (response); } }; diff --git a/src/lib/http/tests/post_request_json_unittests.cc b/src/lib/http/tests/post_request_json_unittests.cc index 15ce685cb1..e8cdc792a6 100644 --- a/src/lib/http/tests/post_request_json_unittests.cc +++ b/src/lib/http/tests/post_request_json_unittests.cc @@ -173,6 +173,8 @@ TEST_F(PostHttpRequestJsonTest, getJsonElement) { // This test verifies that it is possible to create client side request // containing JSON body. TEST_F(PostHttpRequestJsonTest, clientRequest) { + request_.setDirection(HttpMessage::OUTBOUND); + setContextBasics("POST", "/isc/org", HttpVersion(1, 0)); addHeaderToContext("Content-Type", "application/json"); diff --git a/src/lib/http/tests/request_test.h b/src/lib/http/tests/request_test.h index f2755ec8d2..8bd24e946c 100644 --- a/src/lib/http/tests/request_test.h +++ b/src/lib/http/tests/request_test.h @@ -67,10 +67,7 @@ public: template void addHeaderToContext(const std::string& header_name, const ValueType& header_value) { - request_.context()->headers_.push_back(HttpHeaderContext()); - request_.context()->headers_.back().name_ = header_name; - request_.context()->headers_.back().value_ = - boost::lexical_cast(header_value); + request_.context()->headers_.push_back(HttpHeaderContext(header_name, header_value)); } /// @brief Instance of the @ref HttpRequest or its derivation. diff --git a/src/lib/http/tests/request_unittests.cc b/src/lib/http/tests/request_unittests.cc index 4a85f623da..51b692c6ac 100644 --- a/src/lib/http/tests/request_unittests.cc +++ b/src/lib/http/tests/request_unittests.cc @@ -229,6 +229,7 @@ TEST_F(HttpRequestTest, isPersistentHttp11Close) { } TEST_F(HttpRequestTest, clientRequest) { + request_.setDirection(HttpMessage::OUTBOUND); setContextBasics("POST", "/isc/org", HttpVersion(1, 0)); // Capture current date and time. diff --git a/src/lib/http/tests/response_creator_unittests.cc b/src/lib/http/tests/response_creator_unittests.cc index d6a462c12d..b9ad03da7b 100644 --- a/src/lib/http/tests/response_creator_unittests.cc +++ b/src/lib/http/tests/response_creator_unittests.cc @@ -54,6 +54,7 @@ private: request->context()->http_version_minor_); // This will generate the response holding JSON content. ResponsePtr response(new Response(http_version, status_code)); + response->finalize(); return (response); } @@ -67,6 +68,7 @@ private: // We don't need content to test our class. ResponsePtr response(new Response(request->getHttpVersion(), HttpStatusCode::OK)); + response->finalize(); return (response); } }; diff --git a/src/lib/http/tests/response_json_unittests.cc b/src/lib/http/tests/response_json_unittests.cc index 9b005e2c79..e3829b513e 100644 --- a/src/lib/http/tests/response_json_unittests.cc +++ b/src/lib/http/tests/response_json_unittests.cc @@ -61,6 +61,7 @@ public: void testGenericResponse(const HttpStatusCode& status_code, const std::string& status_message) { TestHttpResponseJson response(HttpVersion(1, 0), status_code); + ASSERT_NO_THROW(response.finalize()); std::ostringstream status_message_json; // Build the expected content. status_message_json << "{ \"result\": " @@ -109,6 +110,7 @@ public: TEST_F(HttpResponseJsonTest, responseWithContent) { TestHttpResponseJson response(HttpVersion(1, 1), HttpStatusCode::OK); ASSERT_NO_THROW(response.setBodyAsJson(json_)); + ASSERT_NO_THROW(response.finalize()); std::ostringstream response_string; response_string << diff --git a/src/lib/http/tests/response_unittests.cc b/src/lib/http/tests/response_unittests.cc index a1f98d4819..d6d1529be9 100644 --- a/src/lib/http/tests/response_unittests.cc +++ b/src/lib/http/tests/response_unittests.cc @@ -36,7 +36,8 @@ public: // it returns the fixed value of the Date header, which is // very useful in unit tests. TestHttpResponse response(HttpVersion(1, 0), status_code); - response.addHeader("Content-Type", "text/html"); + response.context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html")); + ASSERT_NO_THROW(response.finalize()); std::ostringstream response_string; response_string << "HTTP/1.0 " << static_cast(status_code) << " " << status_message << "\r\n"; @@ -61,9 +62,10 @@ TEST_F(HttpResponseTest, responseOK) { // Create the message and add some headers. TestHttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK); - response.addHeader("Content-Type", "text/html"); - response.addHeader("Host", "kea.example.org"); - response.setBody(sample_body); + response.context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html")); + response.context()->headers_.push_back(HttpHeaderContext("Host", "kea.example.org")); + response.context()->body_ = sample_body; + ASSERT_NO_THROW(response.finalize()); // Create a string holding expected response. Note that the Date // is a fixed value returned by the customized TestHttpResponse