Browse Source

[master] Merge branch 'trac5088'

Marcin Siodelski 8 years ago
parent
commit
715d18f961

+ 39 - 2
configure.ac

@@ -128,6 +128,14 @@ for retry in "none" "--std=c++11" "--std=c++0x" "--std=c++1x" "fail"; do
 		AC_MSG_WARN([unsupported C++11 feature])
 		AC_MSG_NOTICE([retrying by adding $retry to $CXX])
 		CXX="$CXX_SAVED $retry"
+		AC_MSG_CHECKING($retry support)
+		AC_COMPILE_IFELSE(
+			[AC_LANG_PROGRAM(
+				[],
+				[int myincr = 1;])],
+			[AC_MSG_RESULT([yes])],
+			[AC_MSG_RESULT([no])
+			 continue])
 	fi
 
 	AC_MSG_CHECKING(std::unique_ptr support)
@@ -140,6 +148,35 @@ for retry in "none" "--std=c++11" "--std=c++0x" "--std=c++1x" "fail"; do
 		[AC_MSG_RESULT([no])
 		 continue])
 
+	AC_MSG_CHECKING(cbegin/cend support)
+	feature="cbegin/cend"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[#include <string>],
+			[const std::string& s = "abcd";
+			 unsigned count = 0;
+			 for (std::string::const_iterator i = s.cbegin();
+			      i != s.cend(); ++i)
+				if (*i == 'b')
+					++count;])],
+		[AC_MSG_RESULT([yes])],
+		[AC_MSG_RESULT([no])
+		 continue])
+
+	AC_MSG_CHECKING(final method support)
+	feature="final method"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[],
+			[class Foo {
+			 public:
+			 	virtual ~Foo() {};
+				virtual void bar() final;
+			 }])],
+		 [AC_MSG_RESULT([yes])],
+		 [AC_MSG_RESULT([no])
+		  continue])
+
 	AC_MSG_CHECKING(aggregate initialization support)
 	feature="aggregate initialization"
 	AC_COMPILE_IFELSE(
@@ -155,7 +192,7 @@ for retry in "none" "--std=c++11" "--std=c++0x" "--std=c++1x" "fail"; do
 	AC_COMPILE_IFELSE(
 		[AC_LANG_PROGRAM(
 			[],
-			[auto incr = [[]](int x) { return x + 1; };])],
+			[auto myincr = [[]](int x) { return x + 1; };])],
 		[AC_MSG_RESULT([yes])
 		 break],
 		[AC_MSG_RESULT([no])
@@ -1280,7 +1317,7 @@ if test "x$enable_gtest" = "xyes" ; then
                 [AC_MSG_ERROR([no gtest source at $GTEST_SOURCE])])
         fi
         have_gtest_source=yes
-        GTEST_LDFLAGS="\$(top_builddir)/ext/gtest/libgtest.a"
+        GTEST_LDADD="\$(top_builddir)/ext/gtest/libgtest.a"
         DISTCHECK_GTEST_CONFIGURE_FLAG="--with-gtest-source=$GTEST_SOURCE"
         GTEST_INCLUDES="-I$GTEST_SOURCE -I$GTEST_SOURCE/include"
     fi

+ 6 - 1
src/lib/http/Makefile.am

@@ -22,13 +22,18 @@ EXTRA_DIST = http_messages.mes
 CLEANFILES = *.gcno *.gcda http_messages.h http_messages.cc s-messages
 
 lib_LTLIBRARIES = libkea-http.la
-libkea_http_la_SOURCES  = http_log.cc http_log.h
+libkea_http_la_SOURCES  = date_time.cc date_time.h
+libkea_http_la_SOURCES += http_log.cc http_log.h
 libkea_http_la_SOURCES += header_context.h
+libkea_http_la_SOURCES += http_types.h
 libkea_http_la_SOURCES += post_request.cc post_request.h
 libkea_http_la_SOURCES += post_request_json.cc post_request_json.h
 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_creator.cc response_creator.h
+libkea_http_la_SOURCES += response_json.cc response_json.h
 
 nodist_libkea_http_la_SOURCES = http_messages.cc http_messages.h
 

+ 156 - 0
src/lib/http/date_time.cc

@@ -0,0 +1,156 @@
+// 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/date_time.h>
+#include <boost/date_time/time_facet.hpp>
+#include <boost/date_time/local_time/local_time.hpp>
+#include <locale>
+#include <sstream>
+
+using namespace boost::local_time;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace http {
+
+HttpDateTime::HttpDateTime()
+    : time_(boost::posix_time::microsec_clock::universal_time()) {
+}
+
+HttpDateTime::HttpDateTime(const boost::posix_time::ptime& t)
+    : time_(t) {
+}
+
+std::string
+HttpDateTime::rfc1123Format() const {
+    return (toString("%a, %d %b %Y %H:%M:%S GMT", "RFC 1123"));
+}
+
+std::string
+HttpDateTime::rfc850Format() const {
+    return (toString("%A, %d-%b-%y %H:%M:%S GMT", "RFC 850"));
+}
+
+std::string
+HttpDateTime::asctimeFormat() const {
+    return (toString("%a %b %e %H:%M:%S %Y", "asctime"));
+}
+
+HttpDateTime
+HttpDateTime::fromRfc1123(const std::string& time_string) {
+    return (HttpDateTime(fromString(time_string,
+                                    "%a, %d %b %Y %H:%M:%S %ZP",
+                                    "RFC 1123")));
+}
+
+HttpDateTime
+HttpDateTime::fromRfc850(const std::string& time_string) {
+    return (HttpDateTime(fromString(time_string,
+                                    "%A, %d-%b-%y %H:%M:%S %ZP",
+                                    "RFC 850")));
+}
+
+HttpDateTime
+HttpDateTime::fromAsctime(const std::string& time_string) {
+    // The asctime() puts space instead of leading 0 in days of
+    // month. The %e # formatter of time_input_facet doesn't deal
+    // with this. To deal with this, we make a copy of the string
+    // holding formatted time and replace a space preceding day
+    // number with 0. Thanks to this workaround we can use the
+    // %d formatter which seems to work fine. This has a side
+    // effect of accepting timestamps such as Sun Nov 06 08:49:37 1994,
+    // but it should be ok to be liberal in this case.
+    std::string time_string_copy(time_string);
+    boost::replace_all(time_string_copy, "  ", " 0");
+    return (HttpDateTime(fromString(time_string_copy,
+                                    "%a %b %d %H:%M:%S %Y",
+                                    "asctime",
+                                    false)));
+}
+
+HttpDateTime
+HttpDateTime::fromAny(const std::string& time_string) {
+    HttpDateTime date_time;
+    // Try to parse as a timestamp specified in RFC 1123 format.
+    try {
+        date_time = fromRfc1123(time_string);
+        return (date_time);
+    } catch (...) {
+        // Ignore errors, simply try different format.
+    }
+
+    // Try to parse as a timestamp specified in RFC 850 format.
+    try {
+        date_time = fromRfc850(time_string);
+        return (date_time);
+    } catch (...) {
+        // Ignore errors, simply try different format.
+    }
+
+    // Try to parse as a timestamp output by asctime() function.
+    try {
+        date_time = fromAsctime(time_string);
+    } catch (...) {
+        isc_throw(HttpTimeConversionError,
+                  "unsupported time format of the '" << time_string
+                  << "'");
+    }
+
+    return (date_time);
+
+}
+
+std::string
+HttpDateTime::toString(const std::string& format,
+                       const std::string& method_name) const {
+    std::ostringstream s;
+    // Create raw pointer. The output stream will take responsibility for
+    // deleting the object.
+    time_facet* df(new time_facet(format.c_str()));
+    s.imbue(std::locale(std::locale::classic(), df));
+
+    // Convert time value to a string.
+    s << time_;
+    if (s.fail()) {
+        isc_throw(HttpTimeConversionError, "unable to convert "
+                  << "time value of '" << time_ << "'"
+                  << " to " << method_name << " format");
+    }
+    return (s.str());
+}
+
+
+ptime
+HttpDateTime::fromString(const std::string& time_string,
+                         const std::string& format,
+                         const std::string& method_name,
+                         const bool zone_check) {
+    std::istringstream s(time_string);
+    // Create raw pointer. The input stream will take responsibility for
+    // deleting the object.
+    time_input_facet* tif(new time_input_facet(format));
+    s.imbue(std::locale(std::locale::classic(), tif));
+
+    time_zone_ptr zone(new posix_time_zone("GMT"));
+    local_date_time ldt = local_microsec_clock::local_time(zone);
+
+    // Parse the time value. The stream will not automatically detect whether
+    // the zone is GMT. We need to check it on our own.
+    s >> ldt;
+    if (s.fail() ||
+        (zone_check && (!ldt.zone() ||
+                        ldt.zone()->std_zone_abbrev() != "GMT"))) {
+        isc_throw(HttpTimeConversionError, "unable to parse "
+                  << method_name << " time value of '"
+                  << time_string << "'");
+    }
+
+    return (ldt.local_time());
+}
+
+
+} // namespace http
+} // namespace isc

+ 160 - 0
src/lib/http/date_time.h

@@ -0,0 +1,160 @@
+// Copyright (C) 2016 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_DATE_TIME_H
+#define HTTP_DATE_TIME_H
+
+#include <exceptions/exceptions.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when there is an error during time conversion.
+///
+/// The most common reason for this exception is that the unsupported time
+/// format was used as an input to the time parsing functions.
+class HttpTimeConversionError : public Exception {
+public:
+    HttpTimeConversionError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief This class parses and generates time values used in HTTP.
+///
+/// The HTTP protocol have historically allowed 3 different date/time formats
+/// (see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html). These are:
+/// - Sun, 06 Nov 1994 08:49:37 GMT
+/// - Sunday, 06-Nov-94 08:49:37 GMT
+/// - Sun Nov  6 08:49:37 1994
+///
+/// The first format is preferred but implementations must also support
+/// remaining two obsolete formats for compatibility. This class implements
+/// parsers and generators for all three formats. It uses @c boost::posix_time
+/// to represent time and date. It uses @c boost::date_time::time_facet
+/// and @c boost::date_time::time_input_facet to generate and parse the
+/// timestamps.
+class HttpDateTime {
+public:
+
+    /// @brief Default constructor.
+    ///
+    /// Sets current universal time as time value.
+    HttpDateTime();
+
+    /// @brief Construct from @c boost::posix_time::ptime object.
+    ///
+    /// @param t time value to be set.
+    explicit HttpDateTime(const boost::posix_time::ptime& t);
+
+    /// @brief Returns time encapsulated by this class.
+    ///
+    /// @return @c boost::posix_time::ptime value encapsulated by the instance
+    /// of this class.
+    boost::posix_time::ptime getPtime() const {
+        return (time_);
+    }
+
+    /// @brief Returns time value formatted as specified in RFC 1123.
+    ///
+    /// @return A string containing time value formatted as
+    /// Sun, 06 Nov 1994 08:49:37 GMT.
+    std::string rfc1123Format() const;
+
+    /// @brief Returns time value formatted as specified in RFC 850.
+    ///
+    /// @return A string containing time value formatted as
+    /// Sunday, 06-Nov-94 08:49:37 GMT.
+    std::string rfc850Format() const;
+
+    /// @brief Returns time value formatted as output of ANSI C's
+    /// asctime().
+    ///
+    /// @return A string containing time value formatted as
+    /// Sun Nov  6 08:49:37 1994.
+    std::string asctimeFormat() const;
+
+    /// @brief Creates an instance from a string containing time value
+    /// formatted as specified in RFC 1123.
+    ///
+    /// @param time_string Input string holding formatted time value.
+    /// @return Instance of @ref HttpDateTime.
+    /// @throw HttpTimeConversionError if provided timestamp has invalid
+    /// format.
+    static HttpDateTime fromRfc1123(const std::string& time_string);
+
+    /// @brief Creates an instance from a string containing time value
+    /// formatted as specified in RFC 850.
+    ///
+    /// @param time_string Input string holding formatted time value.
+    /// @return Instance of @ref HttpDateTime.
+    /// @throw HttpTimeConversionError if provided timestamp has invalid
+    /// format.
+    static HttpDateTime fromRfc850(const std::string& time_string);
+
+    /// @brief Creates an instance from a string containing time value
+    /// formatted as output from asctime() function.
+    ///
+    /// @param time_string Input string holding formatted time value.
+    /// @return Instance of @ref HttpDateTime.
+    /// @throw HttpTimeConversionError if provided timestamp has invalid
+    /// format.
+    static HttpDateTime fromAsctime(const std::string& time_string);
+
+    /// @brief Creates an instance from a string containing time value
+    /// formatted in one of the supported formats.
+    ///
+    /// This method will detect the format of the time value and parse it.
+    /// It tries parsing the value in the following order:
+    /// - a format specified in RFC 1123,
+    /// - a format specified in RFC 850,
+    /// - a format of asctime output.
+    ///
+    /// @param time_string Input string holding formatted time value.
+    /// @return Instance of @ref HttpDateTime.
+    /// @throw HttpTimeConversionError if provided value doesn't match any
+    /// of the supported formats.
+    static HttpDateTime fromAny(const std::string& time_string);
+
+private:
+
+    /// @brief Generic method formatting a time value to a specified format.
+    ////
+    /// @param format Time format as accepted by the
+    /// @c boost::date_time::time_facet.
+    std::string toString(const std::string& format,
+                         const std::string& method_name) const;
+
+    /// @brief Generic method parsing time value and converting it to the
+    /// instance of @c boost::posix_time::ptime.
+    ///
+    /// @param time_string Input string holding formatted time value.
+    /// @param format Time format as accepted by the
+    /// @c boost::date_time::time_input_facet.
+    /// @param method_name Name of the expected format to appear in the error
+    /// message if parsing fails, e.g. RFC 1123, RFC 850 or asctime.
+    /// @param zone_check Indicates if the time zone name should be validated
+    /// during parsing. This should be set to false for the formats which
+    /// lack time zones (e.g. asctime).
+    ///
+    /// @return Instance of the @ref boost::posix_time::ptime created from the
+    /// input string.
+    /// @throw HttpTimeConversionError if provided value doesn't match the
+    /// specified format.
+    static boost::posix_time::ptime
+    fromString(const std::string& time_string, const std::string& format,
+               const std::string& method_name, const bool zone_check = true);
+
+    /// @brief Time value encapsulated by this class instance.
+    boost::posix_time::ptime time_;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif

+ 46 - 0
src/lib/http/http_types.h

@@ -0,0 +1,46 @@
+// 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_TYPES_H
+#define HTTP_TYPES_H
+
+/// @brief HTTP protocol version.
+struct HttpVersion {
+    unsigned major_; ///< Major HTTP version.
+    unsigned minor_; ///< Minor HTTP version.
+
+    /// @brief Constructor.
+    ///
+    /// @param major Major HTTP version.
+    /// @param minor Minor HTTP version.
+    explicit HttpVersion(const unsigned major, const unsigned minor)
+        : major_(major), minor_(minor) {
+    }
+
+    /// @brief Operator less.
+    ///
+    /// @param rhs Version to compare to.
+    bool operator<(const HttpVersion& rhs) const {
+        return ((major_ < rhs.major_) ||
+                ((major_ == rhs.major_) && (minor_ < rhs.minor_)));
+    }
+
+    /// @brief Operator equal.
+    ///
+    /// @param rhs Version to compare to.
+    bool operator==(const HttpVersion& rhs) const {
+        return ((major_ == rhs.major_) && (minor_ == rhs.minor_));
+    }
+
+    /// @brief Operator not equal.
+    ///
+    /// @param rhs Version to compare to.
+    bool operator!=(const HttpVersion& rhs) const {
+        return (!operator==(rhs));
+    }
+};
+
+#endif

+ 6 - 6
src/lib/http/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
@@ -66,8 +66,8 @@ HttpRequest::create() {
         }
 
         // Check if the HTTP version is allowed for this request.
-        if (!inRequiredSet(std::make_pair(context_->http_version_major_,
-                                          context_->http_version_minor_),
+        if (!inRequiredSet(HttpVersion(context_->http_version_major_,
+                                       context_->http_version_minor_),
                            required_versions_)) {
             isc_throw(BadValue, "use of HTTP version "
                       << context_->http_version_major_ << "."
@@ -144,11 +144,11 @@ HttpRequest::getUri() const {
     return (context_->uri_);
 }
 
-HttpRequest::HttpVersion
+HttpVersion
 HttpRequest::getHttpVersion() const {
     checkCreated();
-    return (std::make_pair(context_->http_version_major_,
-                           context_->http_version_minor_));
+    return (HttpVersion(context_->http_version_major_,
+                        context_->http_version_minor_));
 }
 
 std::string

+ 21 - 4
src/lib/http/request.h

@@ -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
@@ -8,7 +8,9 @@
 #define HTTP_REQUEST_H
 
 #include <exceptions/exceptions.h>
+#include <http/http_types.h>
 #include <http/request_context.h>
+#include <boost/shared_ptr.hpp>
 #include <map>
 #include <set>
 #include <stdint.h>
@@ -34,6 +36,14 @@ public:
         HttpRequestError(file, line, what) { };
 };
 
+class HttpRequest;
+
+/// @brief Pointer to the @ref HttpRequest object.
+typedef boost::shared_ptr<HttpRequest> HttpRequestPtr;
+
+/// @brief Pointer to the const @ref HttpRequest object.
+typedef boost::shared_ptr<const HttpRequest> ConstHttpRequestPtr;
+
 /// @brief Represents HTTP request message.
 ///
 /// This object represents parsed HTTP message. The @ref HttpRequestContext
@@ -51,9 +61,6 @@ public:
 class HttpRequest {
 public:
 
-    /// @brief Type of HTTP version, including major and minor version number.
-    typedef std::pair<unsigned int, unsigned int> HttpVersion;
-
     /// @brief HTTP methods.
     enum class Method {
         HTTP_GET,
@@ -190,6 +197,16 @@ public:
     /// @brief Returns HTTP message body as string.
     std::string getBody() const;
 
+    /// @brief Checks if the request has been successfully finalized.
+    ///
+    /// The request is gets finalized on successfull call to
+    /// @ref HttpRequest::finalize.
+    ///
+    /// @return true if the request has been finalized, false otherwise.
+    bool isFinalized() const {
+        return (finalized_);
+    }
+
     //@}
 
 protected:

+ 138 - 0
src/lib/http/response.cc

@@ -0,0 +1,138 @@
+// 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/date_time.h>
+#include <http/response.h>
+#include <boost/date_time/local_time/local_time.hpp>
+#include <boost/date_time/time_facet.hpp>
+#include <sstream>
+
+using namespace boost::local_time;
+using namespace isc::http;
+
+namespace {
+
+/// @brief A map of status codes to status names.
+const std::map<HttpStatusCode, std::string> status_code_to_description = {
+    { HttpStatusCode::OK, "OK" },
+    { HttpStatusCode::CREATED, "Created" },
+    { HttpStatusCode::ACCEPTED, "Accepted" },
+    { HttpStatusCode::NO_CONTENT, "No Content" },
+    { HttpStatusCode::MULTIPLE_CHOICES, "Multiple Choices" },
+    { HttpStatusCode::MOVED_PERMANENTLY, "Moved Permanently" },
+    { HttpStatusCode::MOVED_TEMPORARILY, "Moved Temporarily" },
+    { HttpStatusCode::NOT_MODIFIED, "Not Modified" },
+    { HttpStatusCode::BAD_REQUEST, "Bad Request" },
+    { HttpStatusCode::UNAUTHORIZED, "Unauthorized" },
+    { HttpStatusCode::FORBIDDEN, "Forbidden" },
+    { HttpStatusCode::NOT_FOUND, "Not Found" },
+    { HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error" },
+    { HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented" },
+    { HttpStatusCode::BAD_GATEWAY, "Bad Gateway" },
+    { HttpStatusCode::SERVICE_UNAVAILABLE, "Service Unavailable" }
+};
+
+/// @brief New line (CRLF).
+const std::string crlf = "\r\n";
+
+}
+
+namespace isc {
+namespace http {
+
+HttpResponse::HttpResponse(const HttpVersion& version,
+                           const HttpStatusCode& status_code,
+                           const CallSetGenericBody& generic_body)
+    : http_version_(version), status_code_(status_code), headers_(),
+      body_() {
+    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.
+        setGenericBody(status_code);
+    }
+}
+
+void
+HttpResponse::setBody(const std::string& body) {
+    body_ = body;
+}
+
+bool
+HttpResponse::isClientError(const HttpStatusCode& status_code) {
+    // Client errors have status codes of 4XX.
+    uint16_t c = statusCodeToNumber(status_code);
+    return ((c >= 400) && (c < 500));
+}
+
+bool
+HttpResponse::isServerError(const HttpStatusCode& status_code) {
+    // Server errors have status codes of 5XX.
+    uint16_t c = statusCodeToNumber(status_code);
+    return ((c >= 500) && (c < 600));
+}
+
+std::string
+HttpResponse::statusCodeToString(const HttpStatusCode& status_code) {
+    auto status_code_it = status_code_to_description.find(status_code);
+    if (status_code_it == status_code_to_description.end()) {
+        isc_throw(HttpResponseError, "internal server error: no HTTP status"
+                  " description for the given status code "
+                  << static_cast<uint16_t>(status_code));
+    }
+    return (status_code_it->second);
+}
+
+uint16_t
+HttpResponse::statusCodeToNumber(const HttpStatusCode& status_code) {
+    return (static_cast<uint16_t>(status_code));
+}
+
+std::string
+HttpResponse::getDateHeaderValue() const {
+    // This returns current time in the recommended format.
+    HttpDateTime date_time;
+    return (date_time.rfc1123Format());
+}
+
+std::string
+HttpResponse::toString() const {
+    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;
+
+    // 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);
+
+    // Add "Content-Length" if body present.
+    if (!body_.empty()) {
+        addHeaderInternal("Content-Length", body_.length(), headers);
+    }
+
+    // Include all headers.
+    for (auto header = headers.cbegin(); header != headers.cend();
+         ++header) {
+        s << header->first << ": " << header->second << crlf;
+    }
+
+    s << crlf;
+
+    // Include message body.
+    s << body_;
+
+    return (s.str());
+}
+
+} // namespace http
+} // namespace isc

+ 238 - 0
src/lib/http/response.h

@@ -0,0 +1,238 @@
+// 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_H
+#define HTTP_RESPONSE_H
+
+#include <exceptions/exceptions.h>
+#include <http/http_types.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic exception thrown by @ref HttpResponse class.
+class HttpResponseError : public Exception {
+public:
+    HttpResponseError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief HTTP status codes (cf RFC 2068)
+enum class HttpStatusCode : std::uint16_t {
+    OK = 200,
+    CREATED = 201,
+    ACCEPTED = 202,
+    NO_CONTENT = 204,
+    MULTIPLE_CHOICES = 300,
+    MOVED_PERMANENTLY = 301,
+    MOVED_TEMPORARILY = 302,
+    NOT_MODIFIED = 304,
+    BAD_REQUEST = 400,
+    UNAUTHORIZED = 401,
+    FORBIDDEN = 403,
+    NOT_FOUND = 404,
+    INTERNAL_SERVER_ERROR = 500,
+    NOT_IMPLEMENTED = 501,
+    BAD_GATEWAY = 502,
+    SERVICE_UNAVAILABLE = 503
+};
+
+/// @brief Encapsulates the boolean value indicating if the @ref HttpResponse
+/// constructor should call its @c setGenericBody method during construction.
+struct CallSetGenericBody {
+
+    /// @brief Constructor.
+    ///
+    /// @param set A boolean value indicating if the method should be called
+    /// or not.
+    explicit CallSetGenericBody(const bool set)
+        : set_(set) {
+    }
+
+    /// @brief Returns encapsulated true.
+    static const CallSetGenericBody& yes() {
+        static CallSetGenericBody yes(true);
+        return (yes);
+    }
+
+    /// @brief Returns encapsulated false.
+    static const CallSetGenericBody& no() {
+        static CallSetGenericBody no(false);
+        return (no);
+    }
+
+    /// @brief A storage for the boolean flag.
+    bool set_;
+};
+
+class HttpResponse;
+
+/// @brief Pointer to the @ref HttpResponse object.
+typedef boost::shared_ptr<HttpResponse> HttpResponsePtr;
+
+/// @brief Pointer to the const @ref HttpResponse object.
+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.
+///
+/// It contains @c toString method generating a textual representation of
+/// the HTTP response, which is send to the client over TCP socket.
+class HttpResponse {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Creates basic instance of the object. It sets the HTTP version and the
+    /// status code to be included in the response.
+    ///
+    /// @param version HTTP version.
+    /// @param status_code HTTP status code.
+    /// @param generic_body Indicates if the constructor should call
+    /// @c setGenericBody to create a generic content for the given
+    /// status code. This should be set to "no" when the constructor is
+    /// called by the derived class which provides its own implementation
+    /// of the @c setGenericBody method.
+    explicit HttpResponse(const HttpVersion& version,
+                          const HttpStatusCode& status_code,
+                          const CallSetGenericBody& generic_body =
+                          CallSetGenericBody::yes());
+
+    /// @brief Destructor.
+    ///
+    /// A class having virtual methods must have a virtual destructor.
+    virtual ~HttpResponse() { }
+
+    /// @brief Adds HTTP header to 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 Assigns body/content to the message.
+    ///
+    /// @param body Body to be assigned.
+    void setBody(const std::string& body);
+
+    /// @brief Checks if the status code indicates client error.
+    ///
+    /// @param status_code HTTP status code.
+    /// @return true if the status code indicates client error.
+    static bool isClientError(const HttpStatusCode& status_code);
+
+    /// @brief Checks if the status code indicates server error.
+    ///
+    /// @param status_code HTTP status code.
+    /// @return true if the status code indicates server error.
+    static bool isServerError(const HttpStatusCode& status_code);
+
+    /// @brief Converts status code to string.
+    ///
+    /// @param status_code HTTP status code.
+    /// @return Textual representation of the status code.
+    static std::string statusCodeToString(const HttpStatusCode& status_code);
+
+    /// @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.
+    ///
+    /// @return Textual representation of the HTTP response.
+    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
+    /// to return a "predictable" value of time, e.g. constant value.
+    ///
+    /// @return Current time formatted as required by RFC 1123.
+    virtual std::string getDateHeaderValue() const;
+
+    /// @brief Convenience method converting status code to numeric value.
+    ///
+    /// @param status_code Status code represented as enum.
+    /// @return Numeric representation of the status code.
+    static uint16_t statusCodeToNumber(const HttpStatusCode& status_code);
+
+private:
+
+    /// @brief Sets generic body for the given status code.
+    ///
+    /// Most of the classes derived from @ref HttpResponse will expect
+    /// a certain content type. Depending on the content type used they
+    /// will use different body formats for error messages. For example,
+    /// a response using text/html will use HTML within the response
+    /// body. The application/json will use JSON body etc. There is a
+    /// need to implement class specific way of generating the body
+    /// for error messages. Thus, each derivation of this class is
+    /// required to implement class specific @ref setGenericBody function
+    /// which should be called in the class constructor.
+    ///
+    /// This is also the case for this class, though the implementation
+    /// of @c setGenericBody is currently no-op.
+    ///
+    /// Note that this class can't be declared virtual because it is
+    /// meant to be called from the class constructor.
+    ///
+    /// @param status_code Status code for which the body should be
+    /// 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_;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif

+ 31 - 0
src/lib/http/response_creator.cc

@@ -0,0 +1,31 @@
+// Copyright (C) 2016 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 <http/response_creator.h>
+
+namespace isc {
+namespace http {
+
+HttpResponsePtr
+HttpResponseCreator::createHttpResponse(const ConstHttpRequestPtr& request) {
+    // This should never happen. This method must only be called with a
+    // non null request, so we consider it unlikely internal server error.
+    if (!request) {
+        isc_throw(HttpResponseError, "internal server error: HTTP request is null");
+    }
+
+    // If not finalized, the request parsing failed. Generate HTTP 400.
+    if (!request->isFinalized()) {
+        return (createStockBadRequest(request));
+    }
+
+    // Message has been successfully parsed. Create implementation specific
+    // response to this request.
+    return (createDynamicHttpResponse(request));
+}
+
+}
+}

+ 94 - 0
src/lib/http/response_creator.h

@@ -0,0 +1,94 @@
+// 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_CREATOR_H
+#define HTTP_RESPONSE_CREATOR_H
+
+#include <http/request.h>
+#include <http/response.h>
+
+namespace isc {
+namespace http {
+
+/// @brief Specifies an interface for classes creating HTTP responses
+/// from HTTP requests.
+///
+/// HTTP is designed to carry various content types. Most commonly
+/// this is text/html. In Kea, the application/json content type is used
+/// to carry control commands in JSON format. The libkea-http library is
+/// meant to be generic and provide means for transferring different types
+/// of content, depending on the use case.
+///
+/// This abstract class specifies a common interface for generating HTTP
+/// responses from HTTP requests using specific content type and being
+/// used in some specific context. Kea modules providing HTTP services need to
+/// implement their specific derivations of the @ref HttpResponseCreator
+/// class. These derivations use classes derived from @ref HttpRequest as
+/// an input and classes derived from @ref HttpResponse as an output of
+/// @c createHttpResponse method.
+class HttpResponseCreator {
+public:
+
+    /// @brief Destructor.
+    ///
+    /// Classes with virtual functions need virtual destructors.
+    virtual ~HttpResponseCreator() { };
+
+    /// @brief Create HTTP response from HTTP request received.
+    ///
+    /// This class implements a generic logic for creating a HTTP response.
+    /// Derived classes do not override this method. They merely implement
+    /// the methods it calls.
+    ///
+    /// The request processing may generally fail at one of the two stages:
+    /// parsing or interpretation of the parsed request. During the former
+    /// stage the request's syntax is checked, i.e. HTTP version, URI,
+    /// headers etc. During the latter stage the HTTP server checks if the
+    /// request is valid within the specific context, e.g. valid HTTP version
+    /// used, expected content type etc.
+    ///
+    /// In the @ref HttpRequest terms, the request has gone through the
+    /// first stage if it is "finalized" (see @ref HttpRequest::finalize).
+    /// This method accepts instances of both finalized and not finalized
+    /// requests. If the request isn't finalized it indicates that
+    /// the request parsing has failed. In such case, this method calls
+    /// @c createStockBadRequest to generate a response with HTTP 400 status
+    /// code. If the request is finalized, this method calls
+    /// @c createDynamicHttpResponse to generate implementation specific
+    /// response to the received request.
+    ///
+    /// This method is marked virtual final to prevent derived classes from
+    /// overriding this method. Instead, the derived classes must implement
+    /// protected methods which this method calls.
+    ///
+    /// @param request Pointer to an object representing HTTP request.
+    /// @return Pointer to the object encapsulating generated HTTP response.
+    /// @throw HttpResponseError if request is a NULL pointer.
+    virtual HttpResponsePtr
+    createHttpResponse(const ConstHttpRequestPtr& request) final;
+
+protected:
+
+    /// @brief Creates implementation specific HTTP 400 response.
+    ///
+    /// @param request Pointer to an object representing HTTP request.
+    /// @return Pointer to an object representing HTTP 400 response.
+    virtual HttpResponsePtr
+    createStockBadRequest(const ConstHttpRequestPtr& request) const = 0;
+
+    /// @brief Creates implementation specific HTTP response.
+    ///
+    /// @param request Pointer to an object representing HTTP request.
+    /// @return Pointer to an object representing HTTP response.
+    virtual HttpResponsePtr
+    createDynamicHttpResponse(const ConstHttpRequestPtr& request) = 0;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif

+ 52 - 0
src/lib/http/response_json.cc

@@ -0,0 +1,52 @@
+// 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/response_json.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace http {
+
+HttpResponseJson::HttpResponseJson(const HttpVersion& version,
+                                   const HttpStatusCode& status_code,
+                                   const CallSetGenericBody& generic_body)
+    : HttpResponse(version, status_code, CallSetGenericBody::no()) {
+    addHeader("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
+    // generic content in the body.
+    if (generic_body.set_) {
+        setGenericBody(status_code);
+    }
+}
+
+void
+HttpResponseJson::setGenericBody(const HttpStatusCode& status_code) {
+    // Only generate the content for the client or server errors. For
+    // other status codes (status OK in particular) the content should
+    // be created using setBodyAsJson or setBody.
+    if (isClientError(status_code) || isServerError(status_code)) {
+        std::map<std::string, ConstElementPtr> map_elements;
+        map_elements["result"] =
+            ConstElementPtr(new IntElement(statusCodeToNumber(status_code)));
+        map_elements["text"] =
+            ConstElementPtr(new StringElement(statusCodeToString(status_code)));
+        auto body = Element::createMap();
+        body->setValue(map_elements);
+        setBodyAsJson(body);
+    }
+}
+
+void
+HttpResponseJson::setBodyAsJson(const ConstElementPtr& json_body) {
+    setBody(json_body->str());
+}
+
+
+} // namespace http
+} // namespace isc

+ 66 - 0
src/lib/http/response_json.h

@@ -0,0 +1,66 @@
+// Copyright (C) 2016 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_JSON_H
+#define HTTP_RESPONSE_JSON_H
+
+#include <cc/data.h>
+#include <http/response.h>
+
+namespace isc {
+namespace http {
+
+class HttpResponseJson;
+
+/// @brief Pointer to the @ref HttpResponseJson object.
+typedef boost::shared_ptr<HttpResponseJson> HttpResponseJsonPtr;
+
+/// @brief Represents HTTP response with JSON content.
+///
+/// This is a specialization of the @ref HttpResponse class which
+/// includes "Content-Type" equal to "application/json". It also provides
+/// methods to create JSON content within HTTP responses.
+class HttpResponseJson : public HttpResponse {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param version HTTP version.
+    /// @param status_code HTTP status code.
+    /// @param generic_body Indicates if the constructor should call
+    /// @c setGenericBody to create a generic content for the given
+    /// status code. This should be set to "no" when the constructor is
+    /// called by the derived class which provides its own implementation
+    /// of the @c setGenericBody method.
+    explicit HttpResponseJson(const HttpVersion& version,
+                              const HttpStatusCode& status_code,
+                              const CallSetGenericBody& generic_body =
+                              CallSetGenericBody::yes());
+
+    /// @brief Generates JSON content from the data structures represented
+    /// as @ref data::ConstElementPtr.
+    ///
+    /// @param json_body A data structure representing JSON content.
+    virtual void setBodyAsJson(const data::ConstElementPtr& json_body);
+
+private:
+
+    /// @brief Sets generic body for the given status code.
+    ///
+    /// This method generates JSON content for the HTTP client and server
+    /// errors. The generated JSON structure is a map containing "result"
+    /// value holding HTTP status code (e.g. 400) and the "text" string
+    /// holding a status code description.
+    ///
+    /// @param status_code Status code for which the body should be
+    /// generated.
+    void setGenericBody(const HttpStatusCode& status_code);
+};
+
+}
+}
+
+#endif

+ 6 - 1
src/lib/http/tests/Makefile.am

@@ -20,10 +20,15 @@ TESTS =
 if HAVE_GTEST
 TESTS += libhttp_unittests
 
-libhttp_unittests_SOURCES  = post_request_json_unittests.cc
+libhttp_unittests_SOURCES  = date_time_unittests.cc
+libhttp_unittests_SOURCES += post_request_json_unittests.cc
 libhttp_unittests_SOURCES += request_parser_unittests.cc
 libhttp_unittests_SOURCES += request_test.h
+libhttp_unittests_SOURCES += response_creator_unittests.cc
+libhttp_unittests_SOURCES += response_test.h
 libhttp_unittests_SOURCES += request_unittests.cc
+libhttp_unittests_SOURCES += response_unittests.cc
+libhttp_unittests_SOURCES += response_json_unittests.cc
 libhttp_unittests_SOURCES += run_unittests.cc
 
 libhttp_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 190 - 0
src/lib/http/tests/date_time_unittests.cc

@@ -0,0 +1,190 @@
+// Copyright (C) 2016 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 <config.h>
+#include <http/date_time.h>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+using namespace boost::gregorian;
+using namespace boost::posix_time;
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @ref HttpDateTime.
+class HttpDateTimeTest : public ::testing::Test {
+public:
+
+    /// @brief Checks time value against expected values.
+    ///
+    /// This method uses value of @ref date_time_ for the test.
+    ///
+    /// @param exp_day_of_week Expected day of week.
+    /// @param exp_day Expected day of month.
+    /// @param exp_month Expected month.
+    /// @param exp_year Expected year.
+    /// @param exp_hours Expected hour value.
+    /// @param exp_minutes Expected minutes value.
+    /// @param exp_seconds Expected seconds value.
+    void testDateTime(const unsigned short exp_day_of_week,
+                      const unsigned short exp_day,
+                      const unsigned short exp_month,
+                      const unsigned short exp_year,
+                      const long exp_hours,
+                      const long exp_minutes,
+                      const long exp_seconds) {
+        // Retrieve @c boost::posix_time::ptime value.
+        ptime as_ptime = date_time_.getPtime();
+        // Date is contained within this object.
+        date date_part = as_ptime.date();
+
+        // Verify weekday.
+        greg_weekday day_of_week = date_part.day_of_week();
+        EXPECT_EQ(exp_day_of_week, day_of_week.as_number());
+
+        // Verify day of month.
+        greg_day day = date_part.day();
+        EXPECT_EQ(exp_day, day.as_number());
+
+        // Verify month.
+        greg_month month = date_part.month();
+        EXPECT_EQ(exp_month, month.as_number());
+
+        // Verify year.
+        greg_year year = date_part.year();
+        EXPECT_EQ(exp_year, static_cast<unsigned short>(year));
+
+        // Retrieve time of the day and verify hour, minute and second.
+        time_duration time_of_day = as_ptime.time_of_day();
+        EXPECT_EQ(exp_hours, time_of_day.hours());
+        EXPECT_EQ(exp_minutes, time_of_day.minutes());
+        EXPECT_EQ(exp_seconds, time_of_day.seconds());
+    }
+
+    /// @brief Date/time value which should be set by the tests.
+    HttpDateTime date_time_;
+
+};
+
+// Test formatting as specified in RFC 1123.
+TEST_F(HttpDateTimeTest, rfc1123Format) {
+    date gdate(greg_year(2002), greg_month(1), greg_day(20));
+    time_duration tm(23, 59, 59, 0);
+    ptime t = ptime(gdate, tm);
+    HttpDateTime date_time(t);
+    std::string formatted;
+    ASSERT_NO_THROW(formatted = date_time.rfc1123Format());
+    EXPECT_EQ("Sun, 20 Jan 2002 23:59:59 GMT", formatted);
+}
+
+// Test formatting as specified in RFC 850.
+TEST_F(HttpDateTimeTest, rfc850Format) {
+    date gdate(greg_year(1994), greg_month(8), greg_day(6));
+    time_duration tm(11, 12, 13, 0);
+    ptime t = ptime(gdate, tm);
+
+    HttpDateTime date_time(t);
+    std::string formatted;
+    ASSERT_NO_THROW(formatted = date_time.rfc850Format());
+    EXPECT_EQ("Saturday, 06-Aug-94 11:12:13 GMT", formatted);
+}
+
+// Test formatting as output of asctime().
+TEST_F(HttpDateTimeTest, asctimeFormat) {
+    date gdate(greg_year(1999), greg_month(11), greg_day(2));
+    time_duration tm(03, 57, 12, 0);
+    ptime t = ptime(gdate, tm);
+
+    HttpDateTime date_time(t);
+    std::string formatted;
+    ASSERT_NO_THROW(formatted = date_time.asctimeFormat());
+    EXPECT_EQ("Tue Nov  2 03:57:12 1999", formatted);
+}
+
+// Test parsing time in RFC 1123 format.
+TEST_F(HttpDateTimeTest, fromRfc1123) {
+    ASSERT_NO_THROW(
+        date_time_ = HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 18:53:45 GMT")
+    );
+    testDateTime(3, 21, 12, 2016, 18, 53, 45);
+    EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dex 2016 18:53:45 GMT"),
+                 HttpTimeConversionError);
+    EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 43 Dec 2016 18:53:45 GMT"),
+                 HttpTimeConversionError);
+    EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 16 18:53:45 GMT"),
+                 HttpTimeConversionError);
+    EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 18:53:45"),
+                 HttpTimeConversionError);
+    EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 1853:45 GMT"),
+                 HttpTimeConversionError);
+}
+
+// Test parsing time in RFC 850 format.
+TEST_F(HttpDateTimeTest, fromRfc850) {
+    ASSERT_NO_THROW(
+        date_time_ = HttpDateTime::fromRfc850("Wednesday, 21-Dec-16 18:53:45 GMT");
+    );
+    testDateTime(3, 21, 12, 2016, 18, 53, 45);
+    EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 55-Dec-16 18:53:45 GMT"),
+                 HttpTimeConversionError);
+    EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dex-16 18:53:45 GMT"),
+                 HttpTimeConversionError);
+    EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dec-2016 18:53:45 GMT"),
+                 HttpTimeConversionError);
+    EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dec-16 1853:45 GMT"),
+                 HttpTimeConversionError);
+}
+
+// Test parsing time in asctime() format.
+TEST_F(HttpDateTimeTest, fromRfcAsctime) {
+    ASSERT_NO_THROW(
+        date_time_ = HttpDateTime::fromAsctime("Wed Dec 21 08:49:37 2016");
+    );
+    testDateTime(3, 21, 12, 2016, 8, 49, 37);
+    EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dex 21 08:49:37 2016"),
+                 HttpTimeConversionError);
+    EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 55 08:49:37 2016"),
+                 HttpTimeConversionError);
+    EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 21 08:49:37 16"),
+                 HttpTimeConversionError);
+    EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 21 08:4937 2016"),
+                 HttpTimeConversionError);
+}
+
+// Test parsing time in RFC 1123 format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyRfc1123) {
+    ASSERT_NO_THROW(
+        date_time_ = HttpDateTime::fromAny("Thu, 05 Jan 2017 09:15:06 GMT");
+    );
+    testDateTime(4, 5, 1, 2017, 9, 15, 06);
+}
+
+// Test parsing time in RFC 850 format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyRfc850) {
+    ASSERT_NO_THROW(
+        date_time_ = HttpDateTime::fromAny("Saturday, 18-Feb-17 01:02:10 GMT");
+    );
+    testDateTime(6, 18, 2, 2017, 1, 2, 10);
+}
+
+// Test parsing time in asctime() format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyAsctime) {
+    ASSERT_NO_THROW(
+        date_time_ = HttpDateTime::fromAny("Wed Mar  1 15:45:07 2017 GMT");
+    );
+    testDateTime(3, 1, 3, 2017, 15, 45, 7);
+}
+
+// Test that HttpDateTime::fromAny throws exception if unsupported format is
+// used.
+TEST_F(HttpDateTimeTest, fromAnyInvalidFormat) {
+    EXPECT_THROW(HttpDateTime::fromAsctime("20020131T235959"),
+                 HttpTimeConversionError);
+}
+
+}

+ 12 - 11
src/lib/http/tests/post_request_json_unittests.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
@@ -7,6 +7,7 @@
 #include <config.h>
 
 #include <cc/data.h>
+#include <http/http_types.h>
 #include <http/post_request_json.h>
 #include <http/tests/request_test.h>
 #include <gtest/gtest.h>
@@ -49,14 +50,14 @@ public:
 // POST messages.
 TEST_F(PostHttpRequestJsonTest, requiredPost) {
     // Use a GET method that is not supported.
-    setContextBasics("GET", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("GET", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Length", json_body_.length());
     addHeaderToContext("Content-Type", "application/json");
 
     ASSERT_THROW(request_.create(), HttpRequestError);
 
     // Now use POST. It should be accepted.
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Length", json_body_.length());
     addHeaderToContext("Content-Type", "application/json");
 
@@ -67,14 +68,14 @@ TEST_F(PostHttpRequestJsonTest, requiredPost) {
 // header equal to "application/json".
 TEST_F(PostHttpRequestJsonTest, requireContentTypeJson) {
     // Specify "Content-Type" other than "application/json".
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Length", json_body_.length());
     addHeaderToContext("Content-Type", "text/html");
 
     ASSERT_THROW(request_.create(), HttpRequestError);
 
     // This time specify correct "Content-Type". It should pass.
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Length", json_body_.length());
     addHeaderToContext("Content-Type", "application/json");
 
@@ -85,13 +86,13 @@ TEST_F(PostHttpRequestJsonTest, requireContentTypeJson) {
 // header.
 TEST_F(PostHttpRequestJsonTest, requireContentLength) {
     // "Content-Length" is not specified initially. It should fail.
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Type", "text/html");
 
     ASSERT_THROW(request_.create(), HttpRequestError);
 
     // Specify "Content-Length". It should pass.
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Length", json_body_.length());
     addHeaderToContext("Content-Type", "application/json");
 }
@@ -100,7 +101,7 @@ TEST_F(PostHttpRequestJsonTest, requireContentLength) {
 // HTTP request.
 TEST_F(PostHttpRequestJsonTest, getBodyAsJson) {
     // Create HTTP POST request with JSON body.
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Length", json_body_.length());
     addHeaderToContext("Content-Type", "application/json");
     setBody();
@@ -129,7 +130,7 @@ TEST_F(PostHttpRequestJsonTest, getBodyAsJson) {
 // This test verifies that an attempt to parse/retrieve malformed
 // JSON structure will cause an exception.
 TEST_F(PostHttpRequestJsonTest, getBodyAsJsonMalformed) {
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Length", json_body_.length());
     addHeaderToContext("Content-Type", "application/json");
     // No colon before 123.
@@ -141,7 +142,7 @@ TEST_F(PostHttpRequestJsonTest, getBodyAsJsonMalformed) {
 // This test verifies that NULL pointer is returned when trying to
 // retrieve root element of the empty JSON structure.
 TEST_F(PostHttpRequestJsonTest, getEmptyJsonBody) {
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Length", json_body_.length());
     addHeaderToContext("Content-Type", "application/json");
 
@@ -153,7 +154,7 @@ TEST_F(PostHttpRequestJsonTest, getEmptyJsonBody) {
 
 // This test verifies that the specific JSON element can be retrieved.
 TEST_F(PostHttpRequestJsonTest, getJsonElement) {
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Length", json_body_.length());
     addHeaderToContext("Content-Type", "application/json");
     setBody();

+ 16 - 15
src/lib/http/tests/request_parser_unittests.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
@@ -7,6 +7,7 @@
 #include <config.h>
 
 #include <cc/data.h>
+#include <http/http_types.h>
 #include <http/request_parser.h>
 #include <http/post_request_json.h>
 #include <gtest/gtest.h>
@@ -118,8 +119,8 @@ TEST_F(HttpRequestParserTest, postHttpRequestWithJson) {
     EXPECT_EQ("/foo/bar", request.getUri());
     EXPECT_EQ("application/json", request.getHeaderValue("Content-Type"));
     EXPECT_EQ(json.length(), request.getHeaderValueAsUint64("Content-Length"));
-    EXPECT_EQ(1, request.getHttpVersion().first);
-    EXPECT_EQ(0, request.getHttpVersion().second);
+    EXPECT_EQ(1, request.getHttpVersion().major_);
+    EXPECT_EQ(0, request.getHttpVersion().minor_);
 
     // Try to retrieve values carried in JSON payload.
     ConstElementPtr json_element;
@@ -190,8 +191,8 @@ TEST_F(HttpRequestParserTest, getLWS) {
     EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
     EXPECT_EQ("Kea/1.2 Command Control Client",
               request_.getHeaderValue("User-Agent"));
-    EXPECT_EQ(1, request_.getHttpVersion().first);
-    EXPECT_EQ(1, request_.getHttpVersion().second);
+    EXPECT_EQ(1, request_.getHttpVersion().major_);
+    EXPECT_EQ(1, request_.getHttpVersion().minor_);
 }
 
 // This test verifies that the HTTP request with no headers is
@@ -204,8 +205,8 @@ TEST_F(HttpRequestParserTest, noHeaders) {
     // Verify the values.
     EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
     EXPECT_EQ("/foo/bar", request_.getUri());
-    EXPECT_EQ(1, request_.getHttpVersion().first);
-    EXPECT_EQ(1, request_.getHttpVersion().second);
+    EXPECT_EQ(1, request_.getHttpVersion().major_);
+    EXPECT_EQ(1, request_.getHttpVersion().minor_);
 }
 
 // This test verifies that the HTTP method can be specified in lower
@@ -219,8 +220,8 @@ TEST_F(HttpRequestParserTest, getLowerCase) {
     EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
     EXPECT_EQ("/foo/bar", request_.getUri());
     EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
-    EXPECT_EQ(1, request_.getHttpVersion().first);
-    EXPECT_EQ(1, request_.getHttpVersion().second);
+    EXPECT_EQ(1, request_.getHttpVersion().major_);
+    EXPECT_EQ(1, request_.getHttpVersion().minor_);
 }
 
 // This test verifies that other value of the HTTP version can be
@@ -234,8 +235,8 @@ TEST_F(HttpRequestParserTest, http20) {
     EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
     EXPECT_EQ("/foo/bar", request_.getUri());
     EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
-    EXPECT_EQ(2, request_.getHttpVersion().first);
-    EXPECT_EQ(0, request_.getHttpVersion().second);
+    EXPECT_EQ(2, request_.getHttpVersion().major_);
+    EXPECT_EQ(0, request_.getHttpVersion().minor_);
 }
 
 // This test verifies that the header with no whitespace between the
@@ -249,8 +250,8 @@ TEST_F(HttpRequestParserTest, noHeaderWhitespace) {
     EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
     EXPECT_EQ("/foo/bar", request_.getUri());
     EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
-    EXPECT_EQ(1, request_.getHttpVersion().first);
-    EXPECT_EQ(0, request_.getHttpVersion().second);
+    EXPECT_EQ(1, request_.getHttpVersion().major_);
+    EXPECT_EQ(0, request_.getHttpVersion().minor_);
 }
 
 // This test verifies that the header value preceded with multiple
@@ -264,8 +265,8 @@ TEST_F(HttpRequestParserTest, multipleLeadingHeaderWhitespaces) {
     EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
     EXPECT_EQ("/foo/bar", request_.getUri());
     EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
-    EXPECT_EQ(1, request_.getHttpVersion().first);
-    EXPECT_EQ(0, request_.getHttpVersion().second);
+    EXPECT_EQ(1, request_.getHttpVersion().major_);
+    EXPECT_EQ(0, request_.getHttpVersion().minor_);
 }
 
 // This test verifies that error is reported when unsupported HTTP

+ 5 - 4
src/lib/http/tests/request_test.h

@@ -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
@@ -7,6 +7,7 @@
 #ifndef HTTP_REQUEST_TEST_H
 #define HTTP_REQUEST_TEST_H
 
+#include <http/http_types.h>
 #include <http/request.h>
 #include <boost/lexical_cast.hpp>
 #include <gtest/gtest.h>
@@ -50,11 +51,11 @@ public:
     /// @param version A pair of values of which the first is the major HTTP
     /// version and the second is the minor HTTP version.
     void setContextBasics(const std::string& method, const std::string& uri,
-                          const std::pair<unsigned int, unsigned int>& version) {
+                          const HttpVersion& version) {
         request_.context()->method_ = method;
         request_.context()->uri_ = uri;
-        request_.context()->http_version_major_ = version.first;
-        request_.context()->http_version_minor_ = version.second;
+        request_.context()->http_version_major_ = version.major_;
+        request_.context()->http_version_minor_ = version.minor_;
     }
 
     /// @brief Adds HTTP header to the context.

+ 18 - 17
src/lib/http/tests/request_unittests.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
@@ -7,6 +7,7 @@
 #include <config.h>
 
 #include <http/request.h>
+#include <http/http_types.h>
 #include <http/tests/request_test.h>
 #include <boost/lexical_cast.hpp>
 #include <gtest/gtest.h>
@@ -20,28 +21,28 @@ namespace {
 typedef HttpRequestTestBase<HttpRequest> HttpRequestTest;
 
 TEST_F(HttpRequestTest, minimal) {
-    setContextBasics("GET", "/isc/org", std::make_pair(1, 1));
+    setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
     ASSERT_NO_THROW(request_.create());
 
     EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
     EXPECT_EQ("/isc/org", request_.getUri());
-    EXPECT_EQ(1, request_.getHttpVersion().first);
-    EXPECT_EQ(1, request_.getHttpVersion().second);
+    EXPECT_EQ(1, request_.getHttpVersion().major_);
+    EXPECT_EQ(1, request_.getHttpVersion().minor_);
 
     EXPECT_THROW(request_.getHeaderValue("Content-Length"),
                  HttpRequestNonExistingHeader);
 }
 
 TEST_F(HttpRequestTest, includeHeaders) {
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Length", "1024");
     addHeaderToContext("Content-Type", "application/json");
     ASSERT_NO_THROW(request_.create());
 
     EXPECT_EQ(HttpRequest::Method::HTTP_POST, request_.getMethod());
     EXPECT_EQ("/isc/org", request_.getUri());
-    EXPECT_EQ(1, request_.getHttpVersion().first);
-    EXPECT_EQ(0, request_.getHttpVersion().second);
+    EXPECT_EQ(1, request_.getHttpVersion().major_);
+    EXPECT_EQ(0, request_.getHttpVersion().minor_);
 
     std::string content_type;
     ASSERT_NO_THROW(content_type = request_.getHeaderValue("Content-Type"));
@@ -58,7 +59,7 @@ TEST_F(HttpRequestTest, requiredMethods) {
     request_.requireHttpMethod(HttpRequest::Method::HTTP_GET);
     request_.requireHttpMethod(HttpRequest::Method::HTTP_POST);
 
-    setContextBasics("GET", "/isc/org", std::make_pair(1, 1));
+    setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
 
     ASSERT_NO_THROW(request_.create());
 
@@ -70,22 +71,22 @@ TEST_F(HttpRequestTest, requiredMethods) {
 }
 
 TEST_F(HttpRequestTest, requiredHttpVersion) {
-    request_.requireHttpVersion(std::make_pair(1, 0));
-    request_.requireHttpVersion(std::make_pair(1, 1));
+    request_.requireHttpVersion(HttpVersion(1, 0));
+    request_.requireHttpVersion(HttpVersion(1, 1));
 
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     EXPECT_NO_THROW(request_.create());
 
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 1));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 1));
     EXPECT_NO_THROW(request_.create());
 
-    setContextBasics("POST", "/isc/org", std::make_pair(2, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(2, 0));
     EXPECT_THROW(request_.create(), HttpRequestError);
 }
 
 TEST_F(HttpRequestTest, requiredHeader) {
     request_.requireHeader("Content-Length");
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
 
     ASSERT_THROW(request_.create(), HttpRequestError);
 
@@ -98,7 +99,7 @@ TEST_F(HttpRequestTest, requiredHeader) {
 
 TEST_F(HttpRequestTest, requiredHeaderValue) {
     request_.requireHeaderValue("Content-Type", "application/json");
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Type", "text/html");
 
     ASSERT_THROW(request_.create(), HttpRequestError);
@@ -109,7 +110,7 @@ TEST_F(HttpRequestTest, requiredHeaderValue) {
 }
 
 TEST_F(HttpRequestTest, notCreated) {
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Type", "text/html");
     addHeaderToContext("Content-Length", "1024");
 
@@ -138,7 +139,7 @@ TEST_F(HttpRequestTest, notCreated) {
 TEST_F(HttpRequestTest, getBody) {
     std::string json_body = "{ \"param1\": \"foo\" }";
 
-    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
     addHeaderToContext("Content-Length", json_body.length());
 
     request_.context()->body_ = json_body;

+ 113 - 0
src/lib/http/tests/response_creator_unittests.cc

@@ -0,0 +1,113 @@
+// 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <http/http_types.h>
+#include <http/request.h>
+#include <http/response.h>
+#include <http/response_creator.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+private:
+
+    /// @brief Creates HTTP 400 response.
+    ///
+    /// @param request Pointer to the HTTP request.
+    /// @return Pointer to the generated HTTP 400 response.
+    virtual HttpResponsePtr
+    createStockBadRequest(const ConstHttpRequestPtr& request) const {
+        // The request hasn't been finalized so the request object
+        // doesn't contain any information about the HTTP version number
+        // used. But, the context should have this data (assuming the
+        // HTTP version is parsed ok).
+        HttpVersion http_version(request->context()->http_version_major_,
+                                 request->context()->http_version_minor_);
+        // This will generate the response holding JSON content.
+        ResponsePtr response(new Response(http_version,
+                                          HttpStatusCode::BAD_REQUEST));
+        return (response);
+    }
+
+    /// @brief Creates HTTP response.
+    ///
+    /// @param request Pointer to the HTTP request.
+    /// @return Pointer to the generated HTTP OK response with no content.
+    virtual HttpResponsePtr
+    createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+        // The simplest thing is to create a response with no content.
+        // We don't need content to test our class.
+        ResponsePtr response(new Response(request->getHttpVersion(),
+                                          HttpStatusCode::OK));
+        return (response);
+    }
+};
+
+// This test verifies that Bad Request status is generated when the request
+// hasn't been finalized.
+TEST(HttpResponseCreatorTest, badRequest) {
+    HttpResponsePtr response;
+    // Create a request but do not finalize it.
+    HttpRequestPtr request(new HttpRequest());
+    request->context()->http_version_major_ = 1;
+    request->context()->http_version_minor_ = 0;
+    request->context()->method_ = "GET";
+    request->context()->uri_ = "/foo";
+
+    // Use test specific implementation of Response Creator. It should
+    // generate HTTP error 400.
+    TestHttpResponseCreator creator;
+    ASSERT_NO_THROW(response = creator.createHttpResponse(request));
+    ASSERT_TRUE(response);
+
+    EXPECT_EQ("HTTP/1.0 400 Bad Request\r\n"
+              "Content-Length: 40\r\n"
+              "Content-Type: application/json\r\n"
+              "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n\r\n"
+              "{ \"result\": 400, \"text\": \"Bad Request\" }",
+              response->toString());
+}
+
+// This test verifies that response is generated successfully from the
+// finalized/parsed request.
+TEST(HttpResponseCreatorTest, goodRequest) {
+    HttpResponsePtr response;
+    // Create request and finalize it.
+    HttpRequestPtr request(new HttpRequest());
+    request->context()->http_version_major_ = 1;
+    request->context()->http_version_minor_ = 0;
+    request->context()->method_ = "GET";
+    request->context()->uri_ = "/foo";
+    ASSERT_NO_THROW(request->finalize());
+
+    // Use test specific implementation of the Response Creator to generate
+    // a response.
+    TestHttpResponseCreator creator;
+    ASSERT_NO_THROW(response = creator.createHttpResponse(request));
+    ASSERT_TRUE(response);
+
+    EXPECT_EQ("HTTP/1.0 200 OK\r\n"
+              "Content-Type: application/json\r\n"
+              "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n\r\n",
+              response->toString());
+}
+
+}

+ 146 - 0
src/lib/http/tests/response_json_unittests.cc

@@ -0,0 +1,146 @@
+// 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <cc/data.h>
+#include <http/http_types.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Response type used in tests.
+typedef TestHttpResponseBase<HttpResponseJson> TestHttpResponseJson;
+
+/// @brief Test fixture class for @ref HttpResponseJson.
+class HttpResponseJsonTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes the following class members:
+    /// - json_string_ - which is a pretty formatted JSON content,
+    /// - json_ - A structure of Element objects representing the JSON,
+    /// - json_string_from_json_ - which is a JSON text converted back from
+    ///   the json_ data structure. It is the same content as json_string_
+    ///   but has different whitespaces.
+    HttpResponseJsonTest()
+        : json_(), json_string_(), json_string_from_json_() {
+        json_string_ =
+            "["
+            "  {"
+            "    \"pid\": 8080,"
+            "    \"status\": 10,"
+            "    \"comment\": \"Nice comment from 8080\""
+            "  },"
+            "  {"
+            "    \"pid\": 8081,"
+            "    \"status\": 12,"
+            "    \"comment\": \"A comment from 8081\""
+            "  }"
+            "]";
+
+        json_ = Element::fromJSON(json_string_);
+        json_string_from_json_ = json_->str();
+    }
+
+    /// @brief Test that the response format is correct.
+    ///
+    /// @param status_code HTTP status code for which the response should
+    /// be tested.
+    /// @param status_message HTTP status message.
+    void testGenericResponse(const HttpStatusCode& status_code,
+                             const std::string& status_message) {
+        TestHttpResponseJson response(HttpVersion(1, 0), status_code);
+        std::ostringstream status_message_json;
+        // Build the expected content.
+        status_message_json << "{ \"result\": "
+                            << static_cast<uint16_t>(status_code)
+                            << ", \"text\": "
+                            << "\"" << status_message << "\" }";
+        std::ostringstream response_string;
+        response_string <<
+            "HTTP/1.0 " << static_cast<uint16_t>(status_code) << " "
+            << status_message << "\r\n";
+
+        // The content must only be generated for error codes.
+        if (HttpResponse::isClientError(status_code) ||
+            HttpResponse::isServerError(status_code)) {
+            response_string << "Content-Length: " << status_message_json.str().size()
+                            << "\r\n";
+        }
+
+        // Content-Type and Date are automatically included.
+        response_string << "Content-Type: application/json\r\n"
+            "Date: " << response.getDateHeaderValue() << "\r\n\r\n";
+
+        if (HttpResponse::isClientError(status_code) ||
+            HttpResponse::isServerError(status_code)) {
+            response_string << status_message_json.str();
+        }
+
+        // Check that the output is as expected.
+        EXPECT_EQ(response_string.str(), response.toString());
+    }
+
+    /// @brief JSON content represented as structure of Element objects.
+    ConstElementPtr json_;
+
+    /// @brief Pretty formatted JSON content.
+    std::string json_string_;
+
+    /// @brief JSON content parsed back from json_ structure.
+    std::string json_string_from_json_;
+
+};
+
+// Test that the response with custom JSON content is generated properly.
+TEST_F(HttpResponseJsonTest, responseWithContent) {
+    TestHttpResponseJson response(HttpVersion(1, 1), HttpStatusCode::OK);
+    ASSERT_NO_THROW(response.setBodyAsJson(json_));
+
+    std::ostringstream response_string;
+    response_string <<
+        "HTTP/1.1 200 OK\r\n"
+        "Content-Length: " << json_string_from_json_.length() << "\r\n"
+        "Content-Type: application/json\r\n"
+        "Date: " << response.getDateHeaderValue() << "\r\n\r\n"
+                    << json_string_from_json_;
+    EXPECT_EQ(response_string.str(), response.toString());
+}
+
+// Test that generic responses are created properly.
+TEST_F(HttpResponseJsonTest, genericResponse) {
+    testGenericResponse(HttpStatusCode::OK, "OK");
+    testGenericResponse(HttpStatusCode::CREATED, "Created");
+    testGenericResponse(HttpStatusCode::ACCEPTED, "Accepted");
+    testGenericResponse(HttpStatusCode::NO_CONTENT, "No Content");
+    testGenericResponse(HttpStatusCode::MULTIPLE_CHOICES,
+                        "Multiple Choices");
+    testGenericResponse(HttpStatusCode::MOVED_PERMANENTLY,
+                        "Moved Permanently");
+    testGenericResponse(HttpStatusCode::MOVED_TEMPORARILY,
+                        "Moved Temporarily");
+    testGenericResponse(HttpStatusCode::NOT_MODIFIED, "Not Modified");
+    testGenericResponse(HttpStatusCode::BAD_REQUEST, "Bad Request");
+    testGenericResponse(HttpStatusCode::UNAUTHORIZED, "Unauthorized");
+    testGenericResponse(HttpStatusCode::FORBIDDEN, "Forbidden");
+    testGenericResponse(HttpStatusCode::NOT_FOUND, "Not Found");
+    testGenericResponse(HttpStatusCode::INTERNAL_SERVER_ERROR,
+                        "Internal Server Error");
+    testGenericResponse(HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented");
+    testGenericResponse(HttpStatusCode::BAD_GATEWAY, "Bad Gateway");
+    testGenericResponse(HttpStatusCode::SERVICE_UNAVAILABLE,
+                        "Service Unavailable");
+}
+
+}

+ 38 - 0
src/lib/http/tests/response_test.h

@@ -0,0 +1,38 @@
+// 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_TEST_H
+#define HTTP_RESPONSE_TEST_H
+
+#include <http/http_types.h>
+#include <http/response.h>
+
+namespace isc {
+namespace http {
+namespace test {
+
+template<typename HttpResponseType>
+class TestHttpResponseBase : public HttpResponseType {
+public:
+
+    TestHttpResponseBase(const HttpVersion& version, const HttpStatusCode& status_code)
+        : HttpResponseType(version, status_code) {
+    }
+
+    virtual std::string getDateHeaderValue() const {
+        return ("Tue, 19 Dec 2016 18:53:35 GMT");
+    }
+
+    std::string generateDateHeaderValue() const {
+        return (HttpResponseType::getDateHeaderValue());
+    }
+};
+
+} // namespace test
+} // namespace http
+} // namespace isc
+
+#endif

+ 161 - 0
src/lib/http/tests/response_unittests.cc

@@ -0,0 +1,161 @@
+// Copyright (C) 2016 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 <config.h>
+#include <http/date_time.h>
+#include <http/http_types.h>
+#include <http/response.h>
+#include <http/tests/response_test.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+using namespace boost::posix_time;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Response type used in tests.
+typedef TestHttpResponseBase<HttpResponse> TestHttpResponse;
+
+/// @brief Test fixture class for @ref HttpResponse.
+class HttpResponseTest : public ::testing::Test {
+public:
+
+    /// @brief Checks if the format of the response is correct.
+    ///
+    /// @param status_code HTTP status code in the response.
+    /// @param status_message HTTP status message in the response.
+    void testResponse(const HttpStatusCode& status_code,
+                      const std::string& status_message) {
+        // Create the response. Because we're using derived class
+        // 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");
+        std::ostringstream response_string;
+        response_string << "HTTP/1.0 " << static_cast<uint16_t>(status_code)
+            << " " << status_message << "\r\n"
+            << "Content-Type: text/html\r\n"
+            << "Date: " << response.getDateHeaderValue() << "\r\n\r\n";
+
+        EXPECT_EQ(response_string.str(), response.toString());
+    }
+};
+
+// Test the case of HTTP OK message.
+TEST_F(HttpResponseTest, responseOK) {
+    // Include HTML body.
+    const std::string sample_body =
+        "<html>"
+        "<head><title>Kea page title</title></head>"
+        "<body><h1>Some header</h1></body>"
+        "</html>";
+
+    // 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);
+
+    // Create a string holding expected response. Note that the Date
+    // is a fixed value returned by the customized TestHttpResponse
+    // classs.
+    std::ostringstream response_string;
+    response_string <<
+        "HTTP/1.0 200 OK\r\n"
+        "Content-Length: " << sample_body.length() << "\r\n"
+        "Content-Type: text/html\r\n"
+        "Date: " << response.getDateHeaderValue() << "\r\n"
+        "Host: kea.example.org\r\n\r\n" << sample_body;
+
+    EXPECT_EQ(response_string.str(), response.toString());
+}
+
+// Test generic responses for various status codes.
+TEST_F(HttpResponseTest, genericResponse) {
+    testResponse(HttpStatusCode::OK, "OK");
+    testResponse(HttpStatusCode::CREATED, "Created");
+    testResponse(HttpStatusCode::ACCEPTED, "Accepted");
+    testResponse(HttpStatusCode::NO_CONTENT, "No Content");
+    testResponse(HttpStatusCode::MULTIPLE_CHOICES, "Multiple Choices");
+    testResponse(HttpStatusCode::MOVED_PERMANENTLY, "Moved Permanently");
+    testResponse(HttpStatusCode::MOVED_TEMPORARILY, "Moved Temporarily");
+    testResponse(HttpStatusCode::NOT_MODIFIED, "Not Modified");
+    testResponse(HttpStatusCode::BAD_REQUEST, "Bad Request");
+    testResponse(HttpStatusCode::UNAUTHORIZED, "Unauthorized");
+    testResponse(HttpStatusCode::FORBIDDEN, "Forbidden");
+    testResponse(HttpStatusCode::NOT_FOUND, "Not Found");
+    testResponse(HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error");
+    testResponse(HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented");
+    testResponse(HttpStatusCode::BAD_GATEWAY, "Bad Gateway");
+    testResponse(HttpStatusCode::SERVICE_UNAVAILABLE, "Service Unavailable");
+}
+
+// Test if the class correctly identifies client errors.
+TEST_F(HttpResponseTest, isClientError) {
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::OK));
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::CREATED));
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::ACCEPTED));
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NO_CONTENT));
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MULTIPLE_CHOICES));
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MOVED_PERMANENTLY));
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MOVED_TEMPORARILY));
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NOT_MODIFIED));
+    EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::BAD_REQUEST));
+    EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::UNAUTHORIZED));
+    EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::FORBIDDEN));
+    EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::NOT_FOUND));
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::INTERNAL_SERVER_ERROR));
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NOT_IMPLEMENTED));
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::BAD_GATEWAY));
+    EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::SERVICE_UNAVAILABLE));
+}
+
+// Test if the class correctly identifies server errors.
+TEST_F(HttpResponseTest, isServerError) {
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::OK));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::CREATED));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::ACCEPTED));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NO_CONTENT));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MULTIPLE_CHOICES));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MOVED_PERMANENTLY));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MOVED_TEMPORARILY));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NOT_MODIFIED));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::BAD_REQUEST));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::UNAUTHORIZED));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::FORBIDDEN));
+    EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NOT_FOUND));
+    EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::INTERNAL_SERVER_ERROR));
+    EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::NOT_IMPLEMENTED));
+    EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::BAD_GATEWAY));
+    EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::SERVICE_UNAVAILABLE));
+}
+
+// Test that the generated time value, being included in the Date
+// header, is correct.
+TEST_F(HttpResponseTest, getDateHeaderValue) {
+    // Create a response and retrieve the value to be included in the
+    // Date header. This value should hold a current time in the
+    // RFC1123 format.
+    TestHttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK);
+    std::string generated_date = response.generateDateHeaderValue();
+
+    // Use our date/time utilities to parse this value into the ptime.
+    HttpDateTime parsed_time = HttpDateTime::fromRfc1123(generated_date);
+
+    // Now that we have it converted back, we can check how far this
+    // value is from the current time. To be on the safe side, we check
+    // that it is not later than 10 seconds apart, rather than checking
+    // it for equality. In fact, checking it for equality would almost
+    // certainly cause an error. Especially on a virtual machine.
+    time_duration parsed_to_current =
+        microsec_clock::universal_time() - parsed_time.getPtime();
+    EXPECT_LT(parsed_to_current.seconds(), 10);
+}
+
+}