Commit 166ef01e authored by Marcin Siodelski's avatar Marcin Siodelski

[5451] HttpRequest can now be used for outbound messages.

parent 323865db
// 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
......@@ -12,9 +12,25 @@
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
......
......@@ -7,6 +7,7 @@
#include <http/request.h>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <sstream>
namespace isc {
namespace http {
......@@ -87,6 +88,12 @@ HttpRequest::create() {
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();
......@@ -206,6 +213,27 @@ HttpRequest::getBody() const {
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");
......
......@@ -148,7 +148,7 @@ public:
/// 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
......@@ -160,6 +160,12 @@ public:
/// 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.
......@@ -221,6 +227,13 @@ public:
/// @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
......
......@@ -7,6 +7,7 @@
#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>
......@@ -19,6 +20,7 @@ using namespace isc::http::test;
namespace {
/// @brief Test fixture class for @c HttpRequest class.
class HttpRequestTest : public HttpRequestTestBase<HttpRequest> {
public:
......@@ -226,4 +228,47 @@ TEST_F(HttpRequestTest, isPersistentHttp11Close) {
);
}
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());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment