]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5451] Restructure HTTP requests/response to support inbound and outbound.
authorMarcin Siodelski <marcin@isc.org>
Tue, 19 Dec 2017 12:31:36 +0000 (13:31 +0100)
committerMarcin Siodelski <marcin@isc.org>
Tue, 19 Dec 2017 12:31:36 +0000 (13:31 +0100)
21 files changed:
src/bin/agent/ca_response_creator.cc
src/lib/http/header_context.h
src/lib/http/http_message.cc
src/lib/http/http_message.h
src/lib/http/post_request.cc
src/lib/http/post_request.h
src/lib/http/post_request_json.h
src/lib/http/request.cc
src/lib/http/request.h
src/lib/http/response.cc
src/lib/http/response.h
src/lib/http/response_context.h
src/lib/http/response_json.cc
src/lib/http/response_json.h
src/lib/http/tests/listener_unittests.cc
src/lib/http/tests/post_request_json_unittests.cc
src/lib/http/tests/request_test.h
src/lib/http/tests/request_unittests.cc
src/lib/http/tests/response_creator_unittests.cc
src/lib/http/tests/response_json_unittests.cc
src/lib/http/tests/response_unittests.cc

index 0f6d782b8fca414e6867cb0664e2e7c2d52ded5b..06b92173478c5475b5a30cd877194177386e7dc9 100644 (file)
@@ -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);
 }
index 9280d20b6670cb573265b01035bad94ec4b4937b..623e26395bcf2eb8b5819f9f36aefe7c12f4ba29 100644 (file)
@@ -7,6 +7,8 @@
 #ifndef HTTP_HEADER_CONTEXT_H
 #define HTTP_HEADER_CONTEXT_H
 
+#include <boost/lexical_cast.hpp>
+#include <cstdint>
 #include <string>
 
 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<std::string>(value)) {
+    }
 };
 
 } // namespace http
index 0eb97708fe1b26b626b50ba8dcd9446c40e3d542..fd4e4bb9b265f35bb11a2a6a2754f4e0d40d1a45 100644 (file)
@@ -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() {
index c635b6f096d765e3534ee6cbdb019e3b498a8b09..61dfb9980218d166005a8cd5f143e518072bea04 100644 (file)
@@ -12,7 +12,7 @@
 #include <http/http_types.h>
 #include <map>
 #include <set>
-#include <stdint.h>
+#include <cstdint>
 #include <string>
 
 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
index 94caca1b26ebb353019bf6333a943675963a029e..3cc8d810b8277b6d926fd7f9b583bd3aeb92772e 100644 (file)
@@ -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
index dd161437344932e86793d993c8bdc742c39ec2ab..dbfe7d7026ae286f489cd92d649c5aa3b7a4103a 100644 (file)
@@ -29,7 +29,7 @@ typedef boost::shared_ptr<const PostHttpRequest> ConstPostHttpRequestPtr;
 class PostHttpRequest : public HttpRequest {
 public:
 
-    /// @brief Constructor.
+    /// @brief Constructor for inbound HTTP request.
     PostHttpRequest();
 };
 
index 973a4ac49baad8f1c5d0b32ddc9d0bbcb2419de4..57c6d922b18eba7624b8ab29c69ad1c1c9b3d3a6 100644 (file)
@@ -39,8 +39,8 @@ typedef boost::shared_ptr<const PostHttpRequestJson> ConstPostHttpRequestJsonPtr
 class PostHttpRequestJson : public PostHttpRequest {
 public:
 
-    /// @brief Constructor.
-    PostHttpRequestJson();
+    /// @brief Constructor for inbound HTTP request.
+    explicit PostHttpRequestJson();
 
     /// @brief Complete parsing of the HTTP request.
     ///
index 2d93c0c4cf74ab42bd7ded1a678bb9e1dcc8a653..0d3186d118b26ccd9ea9f905642bac3ee8792996 100644 (file)
@@ -9,13 +9,31 @@
 #include <boost/lexical_cast.hpp>
 #include <sstream>
 
+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<std::string>(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) {
index e41c601f7dbd932c3d6215a5074946e884a01dc9..0cad579df571aaa944e6579b9673c40c8dfe1633 100644 (file)
@@ -10,7 +10,7 @@
 #include <http/http_message.h>
 #include <http/request_context.h>
 #include <boost/shared_ptr.hpp>
-#include <stdint.h>
+#include <cstdint>
 
 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<const HttpRequest> 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:
index 774d1f5d1be86f9dd5c56fbebba7de737295b300..292d8f8683e93e41523c323ac5a31468eed8a081 100644 (file)
@@ -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<uint16_t>(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<std::string>
+                                                       (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<HttpStatusCode>(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<uint16_t>(status_code_);
-    s << " " << statusCodeToString(status_code_) << crlf;
+    s << " " << context_->status_code_;
+    s << " " << statusCodeToString(static_cast<HttpStatusCode>(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<std::string, std::string> 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());
 }
index 7dc67d21880a71efa60997c3d5fcfecb9464d28f..eabf476489b16ee1a8e2ad5cd3164e7bff7cc3e5 100644 (file)
@@ -7,23 +7,21 @@
 #ifndef HTTP_RESPONSE_H
 #define HTTP_RESPONSE_H
 
-#include <exceptions/exceptions.h>
-#include <http/http_types.h>
+#include <http/header_context.h>
+#include <http/http_message.h>
 #include <http/response_context.h>
 #include <boost/lexical_cast.hpp>
 #include <boost/shared_ptr.hpp>
-#include <cstdint>
-#include <map>
-#include <string>
+#include <vector>
 
 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<const HttpResponse> 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<typename ValueType>
-    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<typename ValueType>
-    void addHeaderInternal(const std::string& name, const ValueType& value,
-                           std::map<std::string, std::string>& headers) const {
-        try {
-            headers[name] = boost::lexical_cast<std::string>(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<std::string, std::string> headers_;
-
-    /// @brief Holds the body/content.
-    std::string body_;
-
     /// @brief Pointer to the @ref HttpResponseContext holding parsed
     /// data.
     HttpResponseContextPtr context_;
index c1c04ce1dfb11cef5ba2c4b6bbf14a9a598982ba..5154dc4c6dccbd17921c69001e67ce0fa31b53c4 100644 (file)
@@ -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_;
index fc780ae4cd7e656ddaad372253b40e479a4be3be..231d5beabc45ed3507ba5c67f99376339f31e4b7 100644 (file)
@@ -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;
 }
 
 
index b4136626da7a7b0d2829b387b543ed542a692ff1..4b4054863605ef4198a3054d6a4f5525cd8d78f8 100644 (file)
@@ -26,7 +26,10 @@ typedef boost::shared_ptr<HttpResponseJson> 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
index df3812f894f2043a5bcc1358dc7ba838cfcae093..7b35baed81dcf78a326ec7c3bcb5b990c2fe3d86 100644 (file)
@@ -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);
     }
 };
index 15ce685cb1fb0b8c0fce0edbcb23944fe1dde3c6..e8cdc792a6f432efd8e72a87f17cecaa6a7efe0f 100644 (file)
@@ -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");
 
index f2755ec8d23f7cc35019841c1cb33f9d7b829f26..8bd24e946cfdc92d6e883abf950ef5645cf5b3dc 100644 (file)
@@ -67,10 +67,7 @@ public:
     template<typename ValueType>
     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<std::string>(header_value);
+        request_.context()->headers_.push_back(HttpHeaderContext(header_name, header_value));
     }
 
     /// @brief Instance of the @ref HttpRequest or its derivation.
index 4a85f623dad4b9be1e71d33298e1d911bde25062..51b692c6ac8b14dd63b3dba6b641780b9f028f62 100644 (file)
@@ -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.
index d6a462c12d58555bc75e05cb9e0ccf767f354f26..b9ad03da7b383bd7374682a4bcfa34b7daa70da5 100644 (file)
@@ -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);
     }
 };
index 9b005e2c79391005dad41b149db5f376dad82b44..e3829b513e0404ae51efd2be4484e49da52ea59b 100644 (file)
@@ -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 <<
index a1f98d4819ea0890ca6f1b24550ccb3141103de1..d6d1529be988ebcdf8ecbf7df81fc167ddb669bb 100644 (file)
@@ -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<uint16_t>(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