-// 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
namespace isc {
namespace http {
+/// @brief HTTP header context.
struct HttpHeaderContext {
std::string name_;
std::string value_;
+
+ /// @brief Constructor.
+ ///
+ /// Sets header name and value to empty strings.
+ HttpHeaderContext()
+ : name_(), value_() {
+ }
+
+ /// @brief Constructor.
+ ///
+ /// @param name Header name.
+ /// @param value Header value.
+ HttpHeaderContext(const std::string& name, const std::string& value)
+ : name_(name), value_(value) {
+ }
};
} // namespace http
#include <http/request.h>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
+#include <sstream>
namespace isc {
namespace http {
headers_[hdr->getLowerCaseName()] = hdr;
}
+ if (!context_->body_.empty() && (headers_.count("content-length") == 0)) {
+ HttpHeaderPtr hdr(new HttpHeader("Content-Length",
+ boost::lexical_cast<std::string>(context_->body_.length())));
+ headers_["content-length"] = hdr;
+ }
+
// Iterate over required headers and check that they exist
// in the HTTP request.
for (auto req_header = required_headers_.begin();
return (context_->body_);
}
+std::string
+HttpRequest::toText() const {
+ checkFinalized();
+
+ std::ostringstream s;
+ s << methodToString(getMethod()) << " " << getUri() << " HTTP/" <<
+ getHttpVersion().major_ << "." << getHttpVersion().minor_ << "\r\n";
+
+ for (auto header_it = headers_.cbegin(); header_it != headers_.cend();
+ ++header_it) {
+ s << header_it->second->getName() << ": " << header_it->second->getValue()
+ << "\r\n";
+ }
+
+ s << "\r\n";
+
+ s << getBody();
+
+ return (s.str());
+}
+
bool
HttpRequest::isPersistent() const {
HttpHeaderPtr conn = getHeaderSafe("connection");
/// requirements for it.
virtual void create();
- /// @brief Complete parsing of the HTTP request.
+ /// @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
/// that the @ref create method hasn't been called, it calls @ref create
/// before parsing the body.
///
+ /// 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.
/// @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.
+ virtual std::string toText() const;
+
/// @brief Checks if the request has been successfully finalized.
///
/// The request is gets finalized on successful call to
#include <config.h>
#include <http/request.h>
+#include <http/date_time.h>
#include <http/http_header.h>
#include <http/http_types.h>
#include <http/tests/request_test.h>
namespace {
+/// @brief Test fixture class for @c HttpRequest class.
class HttpRequestTest : public HttpRequestTestBase<HttpRequest> {
public:
);
}
+TEST_F(HttpRequestTest, clientRequest) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+
+ // Capture current date and time.
+ HttpDateTime date_time;
+
+ // Add headers.
+ request_.context()->headers_.push_back(HttpHeaderContext("Date", date_time.rfc1123Format()));
+ request_.context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html"));
+ request_.context()->headers_.push_back(HttpHeaderContext("Accept", "text/html"));
+ // Add a body.
+ request_.context()->body_ = "<html></html>";
+ // Commit and validate the data.
+ ASSERT_NO_THROW(request_.finalize());
+
+ // Check that the HTTP request in the textual format is correct. Note that
+ // it should include "Content-Length", even though we haven't explicitly set
+ // this header. It is dynamically computed from the body size.
+ EXPECT_EQ("POST /isc/org HTTP/1.0\r\n"
+ "Accept: text/html\r\n"
+ "Content-Length: 13\r\n"
+ "Content-Type: text/html\r\n"
+ "Date: " + date_time.rfc1123Format() + "\r\n"
+ "\r\n"
+ "<html></html>",
+ request_.toText());
+}
+
+TEST_F(HttpRequestTest, clientRequestNoBody) {
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
+ // Add headers.
+ request_.context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html"));
+ // Commit and validate the data.
+ ASSERT_NO_THROW(request_.finalize());
+
+ // Check that the HTTP request in the textual format is correct. Note that
+ // there should be no Content-Length included, because the body is empty.
+ EXPECT_EQ("GET /isc/org HTTP/1.1\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n",
+ request_.toText());
+}
+
}