Browse Source

[5077] Implemented HTTP request parser.

Marcin Siodelski 8 years ago
parent
commit
0fc1359eba

+ 1 - 0
doc/Doxyfile

@@ -781,6 +781,7 @@ INPUT                  = ../src/bin/d2 \
                          ../src/lib/eval \
                          ../src/lib/exceptions \
                          ../src/lib/hooks \
+                         ../src/lib/http \
                          ../src/lib/log \
                          ../src/lib/log/compiler \
                          ../src/lib/log/interprocess \

+ 7 - 0
src/lib/http/Makefile.am

@@ -23,6 +23,12 @@ 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 += header_context.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
 
 nodist_libkea_http_la_SOURCES = http_messages.cc http_messages.h
 
@@ -34,6 +40,7 @@ libkea_http_la_LDFLAGS += -no-undefined -version-info 1:0:0
 libkea_http_la_LIBADD  =
 libkea_http_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 libkea_http_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
 libkea_http_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 libkea_http_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
 

+ 23 - 0
src/lib/http/header_context.h

@@ -0,0 +1,23 @@
+// 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_HEADER_CONTEXT_H
+#define HTTP_HEADER_CONTEXT_H
+
+#include <string>
+
+namespace isc {
+namespace http {
+
+struct HttpHeaderContext {
+    std::string name_;
+    std::string value_;
+};
+
+} // namespace http
+} // namespace isc
+
+#endif

+ 1 - 1
src/lib/http/http_log.cc

@@ -9,7 +9,7 @@
 #include <http/http_log.h>
 
 namespace isc {
-namespace process {
+namespace http {
 
 /// @brief Defines the logger used within libkea-http library.
 isc::log::Logger http_logger("http");

+ 20 - 0
src/lib/http/post_request.cc

@@ -0,0 +1,20 @@
+// 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/post_request.h>
+
+namespace isc {
+namespace http {
+
+PostHttpRequest::PostHttpRequest()
+    : HttpRequest() {
+    requireHttpMethod(Method::HTTP_POST);
+    requireHeader("Content-Length");
+    requireHeader("Content-Type");
+}
+
+} // namespace http
+} // namespace isc

+ 31 - 0
src/lib/http/post_request.h

@@ -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/.
+
+#ifndef HTTP_POST_REQUEST_H
+#define HTTP_POST_REQUEST_H
+
+#include <http/request.h>
+
+namespace isc {
+namespace http {
+
+/// @brief Represents HTTP POST request.
+///
+/// Instructs the parent class to require:
+/// - HTTP POST message type,
+/// - Content-Length header,
+/// - Content-Type header.
+class PostHttpRequest : public HttpRequest {
+public:
+
+    /// @brief Constructor.
+    PostHttpRequest();
+};
+
+} // namespace http
+} // namespace isc
+
+#endif

+ 75 - 0
src/lib/http/post_request_json.cc

@@ -0,0 +1,75 @@
+// 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/post_request_json.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace http {
+
+PostHttpRequestJson::PostHttpRequestJson()
+    : PostHttpRequest(), json_() {
+    requireHeaderValue("Content-Type", "application/json");
+}
+
+void
+PostHttpRequestJson::finalize() {
+    if (!created_) {
+        create();
+    }
+
+    // Parse JSON body and store.
+    parseBodyAsJson();
+    finalized_ = true;
+}
+
+void
+PostHttpRequestJson::reset() {
+    PostHttpRequest::reset();
+    json_.reset();
+}
+
+ConstElementPtr
+PostHttpRequestJson::getBodyAsJson() {
+    checkFinalized();
+    return (json_);
+}
+
+ConstElementPtr
+PostHttpRequestJson::getJsonElement(const std::string& element_name) {
+    try {
+        ConstElementPtr body = getBodyAsJson();
+        if (body) {
+            const std::map<std::string, ConstElementPtr>& map_value = body->mapValue();
+            auto map_element = map_value.find(element_name);
+            if (map_element != map_value.end()) {
+                return (map_element->second);
+            }
+        }
+
+    } catch (const std::exception& ex) {
+        isc_throw(HttpRequestJsonError, "unable to get JSON element "
+                  << element_name << ": " << ex.what());
+    }
+    return (ConstElementPtr());
+}
+
+void
+PostHttpRequestJson::parseBodyAsJson() {
+   try {
+       // Only parse the body if it hasn't been parsed yet.
+       if (!json_ && !context_->body_.empty()) {
+           json_ = Element::fromJSON(context_->body_);
+       }
+    } catch (const std::exception& ex) {
+        isc_throw(HttpRequestJsonError, "unable to parse the body of the HTTP"
+                  " request: " << ex.what());
+    }
+}
+
+} // namespace http
+} // namespace isc

+ 76 - 0
src/lib/http/post_request_json.h

@@ -0,0 +1,76 @@
+// 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_POST_REQUEST_JSON_H
+#define HTTP_POST_REQUEST_JSON_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <http/post_request.h>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when body of the HTTP message is not JSON.
+class HttpRequestJsonError : public HttpRequestError {
+public:
+    HttpRequestJsonError(const char* file, size_t line,
+                                 const char* what) :
+        HttpRequestError(file, line, what) { };
+};
+
+/// @brief Represents HTTP POST request with JSON body.
+///
+/// In addition to the requirements specified by the @ref PostHttpRequest
+/// this class requires that the "Content-Type" is "application/json".
+///
+/// This class provides methods to parse and retrieve JSON data structures.
+class PostHttpRequestJson : public PostHttpRequest {
+public:
+
+    /// @brief Constructor.
+    PostHttpRequestJson();
+
+    /// @brief Complete parsing of the HTTP request.
+    ///
+    /// This method parses the JSON body into the structure of
+    /// @ref data::ConstElementPtr objects.
+    virtual void finalize();
+
+    /// @brief Reset the state of the object.
+    virtual void reset();
+
+    /// @brief Retrieves JSON body.
+    ///
+    /// @return Pointer to the root element of the JSON structure.
+    /// @throw HttpRequestJsonError if an error occurred.
+    data::ConstElementPtr getBodyAsJson();
+
+    /// @brief Retrieves a single JSON element.
+    ///
+    /// The element must be at top level of the JSON structure.
+    ///
+    /// @param element_name Element name.
+    ///
+    /// @return Pointer to the specified element or NULL if such element
+    /// doesn't exist.
+    /// @throw HttpRequestJsonError if an error occurred.
+    data::ConstElementPtr getJsonElement(const std::string& element_name);
+
+protected:
+
+    void parseBodyAsJson();
+
+    /// @brief Pointer to the parsed JSON body.
+    data::ConstElementPtr json_;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif

+ 259 - 0
src/lib/http/request.cc

@@ -0,0 +1,259 @@
+// 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/request.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace http {
+
+HttpRequest::HttpRequest()
+    : required_methods_(),required_versions_(), required_headers_(),
+      created_(false), finalized_(false), method_(Method::HTTP_METHOD_UNKNOWN),
+      headers_(), context_(new HttpRequestContext()) {
+}
+
+HttpRequest::~HttpRequest() {
+}
+
+void
+HttpRequest::requireHttpMethod(const HttpRequest::Method& method) {
+    required_methods_.insert(method);
+}
+
+void
+HttpRequest::requireHttpVersion(const HttpVersion& version) {
+    required_versions_.insert(version);
+}
+
+void
+HttpRequest::requireHeader(const std::string& header_name) {
+    // Empty value denotes that the header is required but no specific
+    // value is expected.
+    required_headers_[header_name] = "";
+}
+
+void
+HttpRequest::requireHeaderValue(const std::string& header_name,
+                                const std::string& header_value) {
+    required_headers_[header_name] = header_value;
+}
+
+bool
+HttpRequest::requiresBody() const {
+    // If Content-Length is required the body must exist too. There may
+    // be probably some cases when Content-Length is not provided but
+    // the body is provided. But, probably not in our use cases.
+    return (required_headers_.find("Content-Length") != required_headers_.end());
+}
+
+void
+HttpRequest::create() {
+    try {
+        // The RequestParser doesn't validate the method name. Thus, this
+        // may throw an exception. But, we're fine with lower case names,
+        // e.g. get, post etc.
+        method_ = methodFromString(context_->method_);
+
+        // Check if the method is allowed for this request.
+        if (!inRequiredSet(method_, required_methods_)) {
+            isc_throw(BadValue, "use of HTTP " << methodToString(method_)
+                      << " not allowed");
+        }
+
+        // Check if the HTTP version is allowed for this request.
+        if (!inRequiredSet(std::make_pair(context_->http_version_major_,
+                                          context_->http_version_minor_),
+                           required_versions_)) {
+            isc_throw(BadValue, "use of HTTP version "
+                      << context_->http_version_major_ << "."
+                      << context_->http_version_minor_
+                      << " not allowed");
+        }
+
+        // Copy headers from the context.
+        for (auto header = context_->headers_.begin();
+             header != context_->headers_.end();
+             ++header) {
+            headers_[header->name_] = header->value_;
+        }
+
+        // Iterate over required headers and check that they exist
+        // in the HTTP request.
+        for (auto req_header = required_headers_.begin();
+             req_header != required_headers_.end();
+             ++req_header) {
+            auto header = headers_.find(req_header->first);
+            if (header == headers_.end()) {
+                isc_throw(BadValue, "required header " << header->first
+                          << " not found in the HTTP request");
+            } else if (!req_header->second.empty() &&
+                       header->second != req_header->second) {
+                // If specific value is required for the header, check
+                // that the value in the HTTP request matches it.
+                isc_throw(BadValue, "required header's " << header->first
+                          << " value is " << req_header->second
+                          << ", but " << header->second << " was found");
+            }
+        }
+
+    } catch (const std::exception& ex) {
+        // Reset the state of the object if we failed at any point.
+        reset();
+        isc_throw(HttpRequestError, ex.what());
+    }
+
+    // All ok.
+    created_ = true;
+}
+
+void
+HttpRequest::finalize() {
+    if (!created_) {
+        create();
+    }
+
+    // In this specific case, we don't need to do anything because the
+    // body is retrieved from the context object directly. We also don't
+    // know what type of body we have received. Derived classes should
+    // override this method and handle various types of bodies.
+    finalized_ = true;
+}
+
+void
+HttpRequest::reset() {
+    created_ = false;
+    finalized_ = false;
+    method_ = HttpRequest::Method::HTTP_METHOD_UNKNOWN;
+    headers_.clear();
+}
+
+HttpRequest::Method
+HttpRequest::getMethod() const {
+    checkCreated();
+    return (method_);
+}
+
+std::string
+HttpRequest::getUri() const {
+    checkCreated();
+    return (context_->uri_);
+}
+
+HttpRequest::HttpVersion
+HttpRequest::getHttpVersion() const {
+    checkCreated();
+    return (std::make_pair(context_->http_version_major_,
+                           context_->http_version_minor_));
+}
+
+std::string
+HttpRequest::getHeaderValue(const std::string& header) const {
+    checkCreated();
+
+    auto header_it = headers_.find(header);
+    if (header_it != headers_.end()) {
+        return (header_it->second);
+    }
+    // No such header.
+    isc_throw(HttpRequestNonExistingHeader, header << " HTTP header"
+              " not found in the request");
+}
+
+uint64_t
+HttpRequest::getHeaderValueAsUint64(const std::string& header) const {
+    // This will throw an exception if the header doesn't exist.
+    std::string header_value = getHeaderValue(header);
+
+    try {
+        return (boost::lexical_cast<uint64_t>(header_value));
+
+    } catch (const boost::bad_lexical_cast& ex) {
+        // The specified header does exist, but the value is not a number.
+        isc_throw(HttpRequestError, header << " HTTP header value "
+                  << header_value << " is not a valid number");
+    }
+}
+
+std::string
+HttpRequest::getBody() const {
+    checkFinalized();
+    return (context_->body_);
+}
+
+void
+HttpRequest::checkCreated() const {
+    if (!created_) {
+        isc_throw(HttpRequestError, "unable to retrieve values of HTTP"
+                  " request because the HttpRequest::create() must be"
+                  " called first. This is a programmatic error");
+    }
+}
+
+void
+HttpRequest::checkFinalized() const {
+    if (!finalized_) {
+        isc_throw(HttpRequestError, "unable to retrieve body of HTTP"
+                  " request because the HttpRequest::finalize() must be"
+                  " called first. This is a programmatic error");
+    }
+}
+
+template<typename T>
+bool
+HttpRequest::inRequiredSet(const T& element,
+                           const std::set<T>& element_set) const {
+    return (element_set.empty() || element_set.count(element) > 0);
+}
+
+
+HttpRequest::Method
+HttpRequest::methodFromString(std::string method) const {
+    boost::to_upper(method);
+    if (method == "GET") {
+        return (Method::HTTP_GET);
+    } else if (method == "POST") {
+        return (Method::HTTP_POST);
+    } else if (method == "HEAD") {
+        return (Method::HTTP_HEAD);
+    } else if (method == "PUT") {
+        return (Method::HTTP_PUT);
+    } else if (method == "DELETE") {
+        return (Method::HTTP_DELETE);
+    } else if (method == "OPTIONS") {
+        return (Method::HTTP_OPTIONS);
+    } else if (method == "CONNECT") {
+        return (Method::HTTP_CONNECT);
+    } else {
+        isc_throw(HttpRequestError, "unknown HTTP method " << method);
+    }
+}
+
+std::string
+HttpRequest::methodToString(const HttpRequest::Method& method) const {
+    switch (method) {
+    case Method::HTTP_GET:
+        return ("GET");
+    case Method::HTTP_POST:
+        return ("POST");
+    case Method::HTTP_HEAD:
+        return ("HEAD");
+    case Method::HTTP_PUT:
+        return ("PUT");
+    case Method::HTTP_DELETE:
+        return ("DELETE");
+    case Method::HTTP_OPTIONS:
+        return ("OPTIONS");
+    case Method::HTTP_CONNECT:
+        return ("CONNECT");
+    default:
+        return ("unknown HTTP method");
+    }
+}
+
+}
+}

+ 277 - 0
src/lib/http/request.h

@@ -0,0 +1,277 @@
+// 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_REQUEST_H
+#define HTTP_REQUEST_H
+
+#include <exceptions/exceptions.h>
+#include <http/request_context.h>
+#include <map>
+#include <set>
+#include <stdint.h>
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic exception thrown by @ref HttpRequest class.
+class HttpRequestError : public Exception {
+public:
+    HttpRequestError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when attempt is made to retrieve a
+/// non-existing header.
+class HttpRequestNonExistingHeader : public HttpRequestError {
+public:
+    HttpRequestNonExistingHeader(const char* file, size_t line,
+                                 const char* what) :
+        HttpRequestError(file, line, what) { };
+};
+
+/// @brief Represents HTTP request message.
+///
+/// This object represents parsed HTTP message. The @ref HttpRequestContext
+/// contains raw data used as input for this object. This class interprets the
+/// data. In particular, it verifies that the appropriate method, HTTP version,
+/// and headers were used. The derivations of this class provide specializations
+/// and specify the HTTP methods, versions and headers supported/required in
+/// the specific use cases.
+///
+/// For example, the @ref PostHttpRequest class derives from @ref HttpRequest
+/// and it requires that parsed messages use POST method. The
+/// @ref PostHttpRequestJson, which derives from @ref PostHttpRequest requires
+/// that the POST message includes body holding a JSON structure and provides
+/// methods to parse the JSON body.
+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,
+        HTTP_POST,
+        HTTP_HEAD,
+        HTTP_PUT,
+        HTTP_DELETE,
+        HTTP_OPTIONS,
+        HTTP_CONNECT,
+        HTTP_METHOD_UNKNOWN
+    };
+
+    /// @brief Constructor.
+    ///
+    /// Creates new context (@ref HttpRequestContext).
+    HttpRequest();
+
+    /// @brief Destructor.
+    virtual ~HttpRequest();
+
+    /// @brief Returns reference to the @ref HttpRequestContext.
+    ///
+    /// This method is called by the @ref HttpRequestParser to retrieve the
+    /// context in which parsed data is stored.
+    const HttpRequestContextPtr& context() const {
+        return (context_);
+    }
+
+    /// @brief Specifies an HTTP method allowed for the request.
+    ///
+    /// Allowed methods must be specified prior to calling @ref create method.
+    /// If no method is specified, all methods are supported.
+    ///
+    /// @param method HTTP method allowed for the request.
+    void requireHttpMethod(const HttpRequest::Method& method);
+
+    /// @brief Specifies HTTP version allowed.
+    ///
+    /// Allowed HTTP versions must be specified prior to calling @ref create
+    /// method. If no version is specified, all versions are allowed.
+    ///
+    /// @param version Version number allowed for the request.
+    void requireHttpVersion(const HttpVersion& version);
+
+    /// @brief Specifies a required HTTP header for the request.
+    ///
+    /// Required headers must be specified prior to calling @ref create method.
+    /// The specified header must exist in the received HTTP request. This puts
+    /// no requirement on the header value.
+    ///
+    /// @param header_name Required header name.
+    void requireHeader(const std::string& header_name);
+
+    /// @brief Specifies a required value of a header in the request.
+    ///
+    /// Required header values must be specified prior to calling @ref create
+    /// method. The specified header must exist and its value must be equal to
+    /// the value specified as second parameter.
+    ///
+    /// @param header_name HTTP header name.
+    /// @param header_value HTTP header valuae.
+    void requireHeaderValue(const std::string& header_name,
+                            const std::string& header_value);
+
+    /// @brief Checks if the body is required for the HTTP request.
+    ///
+    /// Current implementation simply checks if the "Content-Length" header
+    /// is required for the request.
+    ///
+    /// @return true if the body is required for this request.
+    bool requiresBody() const;
+
+    /// @brief Reads parsed request from the @ref HttpRequestContext, validates
+    /// the request and stores parsed information.
+    ///
+    /// This method must be called before retrieving parsed data using accessors
+    /// such as @ref getMethod, @ref getUri etc.
+    ///
+    /// This method doesn't parse the HTTP request body.
+    ///
+    /// @throw HttpRequestError if the parsed request doesn't meet the specified
+    /// requirements for it.
+    virtual void create();
+
+    /// @brief Complete parsing of the HTTP request.
+    ///
+    /// HTTP request parsing is performed in two stages: HTTP headers, then
+    /// request body. The @ref create method parses HTTP headers. Once this is
+    /// done, the caller can check if the "Content-Length" was specified and use
+    /// it's value to determine the size of the body which is parsed in the
+    /// second stage.
+    ///
+    /// This method generally performs the body parsing, but if it determines
+    /// that the @ref create method hasn't been called, it calls @ref create
+    /// before parsing the body.
+    ///
+    /// 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.
+    virtual void finalize();
+
+    /// @brief Reset the state of the object.
+    virtual void reset();
+
+    /// @name HTTP data accessors.
+    ///
+    //@{
+    /// @brief Returns HTTP method of the request.
+    Method getMethod() const;
+
+    /// @brief Returns HTTP request URI.
+    std::string getUri() const;
+
+    /// @brief Returns HTTP version number (major and minor).
+    HttpVersion getHttpVersion() const;
+
+    /// @brief Returns a value of the specified HTTP header.
+    ///
+    /// @param header Name of the HTTP header.
+    ///
+    /// @throw HttpRequestError if the header doesn't exist.
+    std::string getHeaderValue(const std::string& header) const;
+
+    /// @brief Returns a value of the specified HTTP header as number.
+    ///
+    /// @param header Name of the HTTP header.
+    ///
+    /// @throw HttpRequestError if the header doesn't exist or if the
+    /// header value is not number.
+    uint64_t getHeaderValueAsUint64(const std::string& header) const;
+
+    /// @brief Returns HTTP message body as string.
+    std::string getBody() const;
+
+    //@}
+
+protected:
+
+    /// @brief Checks if the @ref create was called.
+    ///
+    /// @throw HttpRequestError if @ref create wasn't called.
+    void checkCreated() const;
+
+    /// @brief Checks if the @ref finalize was called.
+    ///
+    /// @throw HttpRequestError if @ref finalize wasn't called.
+    void checkFinalized() const;
+
+    /// @brief Checks if the set is empty or the specified element belongs
+    /// to this set.
+    ///
+    /// This is a convenience method used by the class to verify that the
+    /// given HTTP method belongs to "required methods", HTTP version belongs
+    /// to "required versions" etc.
+    ///
+    /// @param element Reference to the element.
+    /// @param element_set Reference to the set of elements.
+    /// @tparam Element type, e.g. @ref Method, @ref HttpVersion etc.
+    ///
+    /// @return true if the element set is empty or if the element belongs
+    /// to the set.
+    template<typename T>
+    bool inRequiredSet(const T& element,
+                       const std::set<T>& element_set) const;
+
+    /// @brief Converts HTTP method specified in textual format to @ref Method.
+    ///
+    /// @param method HTTP method specified in the textual format. This value
+    /// is case insensitive.
+    ///
+    /// @return HTTP method as enum.
+    /// @throw HttpRequestError if unknown method specified.
+    Method methodFromString(std::string method) const;
+
+    /// @brief Converts HTTP method to string.
+    ///
+    /// @param method HTTP method specified as enum.
+    ///
+    /// @return HTTP method as string.
+    std::string methodToString(const HttpRequest::Method& method) const;
+
+    /// @brief Set of required HTTP methods.
+    ///
+    /// If the set is empty, all methods are allowed.
+    std::set<Method> required_methods_;
+
+    /// @brief Set of required HTTP versions.
+    ///
+    /// If the set is empty, all versions are allowed.
+    std::set<HttpVersion> required_versions_;
+
+    /// @brief Map holding required HTTP headers.
+    ///
+    /// The key of this map specifies the HTTP header name. The value
+    /// spefifies the HTTP header value. If the value is empty, the
+    /// header is required but the value of the header is not checked.
+    /// If the value is non-empty, the value in the HTTP request must
+    /// be equal to the value in the map.
+    std::map<std::string, std::string> required_headers_;
+
+    /// @brief Flag indicating whether @ref create was called.
+    bool created_;
+
+    /// @brief Flag indicating whether @ref finalize was called.
+    bool finalized_;
+
+    /// @brief HTTP method of the request.
+    Method method_;
+
+    /// @brief Parsed HTTP headers.
+    std::map<std::string, std::string> headers_;
+
+    /// @brief Pointer to the @ref HttpRequestContext holding parsed
+    /// data.
+    HttpRequestContextPtr context_;
+};
+
+}
+}
+
+#endif

+ 44 - 0
src/lib/http/request_context.h

@@ -0,0 +1,44 @@
+// 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_REQUEST_CONTEXT_H
+#define HTTP_REQUEST_CONTEXT_H
+
+#include <http/header_context.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace http {
+
+/// @brief HTTP request context.
+///
+/// The context is used by the @ref HttpRequestParser to store parsed
+/// data. This data is later used to create an instance of the
+/// @ref HttpRequest or its derivation.
+struct HttpRequestContext {
+    /// @brief HTTP request method.
+    std::string method_;
+    /// @brief HTTP request URI.
+    std::string uri_;
+    /// @brief HTTP major version number.
+    unsigned int http_version_major_;
+    /// @brief HTTP minor version number.
+    unsigned int http_version_minor_;
+    /// @brief Collection of HTTP headers.
+    std::vector<HttpHeaderContext> headers_;
+    /// @brief HTTP request body.
+    std::string body_;
+};
+
+/// @brief Pointer to the @ref HttpRequestContext.
+typedef boost::shared_ptr<HttpRequestContext> HttpRequestContextPtr;
+
+} // namespace http
+} // namespace isc
+
+#endif

+ 677 - 0
src/lib/http/request_parser.cc

@@ -0,0 +1,677 @@
+// 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/request_parser.h>
+#include <boost/bind.hpp>
+#include <cctype>
+#include <iostream>
+
+using namespace isc::util;
+
+namespace isc {
+namespace http {
+
+const int HttpRequestParser::RECEIVE_START_ST;
+const int HttpRequestParser::HTTP_METHOD_ST;
+const int HttpRequestParser::HTTP_URI_ST;
+const int HttpRequestParser::HTTP_VERSION_H_ST;
+const int HttpRequestParser::HTTP_VERSION_T1_ST;
+const int HttpRequestParser::HTTP_VERSION_T2_ST;
+const int HttpRequestParser::HTTP_VERSION_P_ST;
+const int HttpRequestParser::HTTP_VERSION_SLASH_ST;
+const int HttpRequestParser::HTTP_VERSION_MAJOR_START_ST;
+const int HttpRequestParser::HTTP_VERSION_MAJOR_ST;
+const int HttpRequestParser::HTTP_VERSION_MINOR_START_ST;
+const int HttpRequestParser::HTTP_VERSION_MINOR_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE1_ST;
+const int HttpRequestParser::HEADER_LINE_START_ST;
+const int HttpRequestParser::HEADER_LWS_ST;
+const int HttpRequestParser::HEADER_NAME_ST;
+const int HttpRequestParser::SPACE_BEFORE_HEADER_VALUE_ST;
+const int HttpRequestParser::HEADER_VALUE_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE2_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE3_ST;
+const int HttpRequestParser::HTTP_BODY_ST;
+const int HttpRequestParser::HTTP_PARSE_OK_ST;
+const int HttpRequestParser::HTTP_PARSE_FAILED_ST;
+
+const int HttpRequestParser::DATA_READ_OK_EVT;
+const int HttpRequestParser::NEED_MORE_DATA_EVT;
+const int HttpRequestParser::MORE_DATA_PROVIDED_EVT;
+const int HttpRequestParser::HTTP_PARSE_OK_EVT;
+const int HttpRequestParser::HTTP_PARSE_FAILED_EVT;
+
+HttpRequestParser::HttpRequestParser(HttpRequest& request)
+    : StateModel(), buffer_(), request_(request),
+      context_(request_.context()), error_message_() {
+}
+
+void
+HttpRequestParser::initModel() {
+    // Intialize dictionaries of events and states.
+    initDictionaries();
+
+    // Set the current state to starting state and enter the run loop.
+    setState(RECEIVE_START_ST);
+
+    // Parsing starts from here.
+    postNextEvent(START_EVT);
+}
+
+void
+HttpRequestParser::poll() {
+    try {
+        // Run the parser until it runs out of input data or until
+        // parsing completes.
+        do {
+            getState(getCurrState())->run();
+
+        } while (!isModelDone() && (getNextEvent() != NOP_EVT) &&
+                 (getNextEvent() != NEED_MORE_DATA_EVT));
+    } catch (const std::exception& ex) {
+        abortModel(ex.what());
+    }
+}
+
+bool
+HttpRequestParser::needData() const {
+    return (getNextEvent() == NEED_MORE_DATA_EVT);
+}
+
+bool
+HttpRequestParser::httpParseOk() const {
+    return ((getNextEvent() == END_EVT) &&
+            (getLastEvent() == HTTP_PARSE_OK_EVT));
+}
+
+void
+HttpRequestParser::postBuffer(const void* buf, const size_t buf_size) {
+    if (buf_size > 0) {
+        // The next event is NEED_MORE_DATA_EVT when the parser wants to
+        // signal that more data is needed. This method is called to supply
+        // more data and thus it should change the next event to
+        // MORE_DATA_PROVIDED_EVT.
+        if (getNextEvent() == NEED_MORE_DATA_EVT) {
+            transition(getCurrState(), MORE_DATA_PROVIDED_EVT);
+        }
+        buffer_.insert(buffer_.end(), static_cast<const uint8_t*>(buf),
+                       static_cast<const uint8_t*>(buf) + buf_size);
+    }
+}
+
+void
+HttpRequestParser::defineEvents() {
+    StateModel::defineEvents();
+
+    // Define HTTP parser specific events.
+    defineEvent(DATA_READ_OK_EVT, "DATA_READ_OK_EVT");
+    defineEvent(NEED_MORE_DATA_EVT, "NEED_MORE_DATA_EVT");
+    defineEvent(MORE_DATA_PROVIDED_EVT, "MORE_DATA_PROVIDED_EVT");
+    defineEvent(HTTP_PARSE_OK_EVT, "HTTP_PARSE_OK_EVT");
+    defineEvent(HTTP_PARSE_FAILED_EVT, "HTTP_PARSE_FAILED_EVT");
+}
+
+void
+HttpRequestParser::verifyEvents() {
+    StateModel::verifyEvents();
+
+    getEvent(DATA_READ_OK_EVT);
+    getEvent(NEED_MORE_DATA_EVT);
+    getEvent(MORE_DATA_PROVIDED_EVT);
+    getEvent(HTTP_PARSE_OK_EVT);
+    getEvent(HTTP_PARSE_FAILED_EVT);
+}
+
+void
+HttpRequestParser::defineStates() {
+    // Call parent class implementation first.
+    StateModel::defineStates();
+
+    // Define HTTP parser specific states.
+    defineState(RECEIVE_START_ST, "RECEIVE_START_ST",
+                boost::bind(&HttpRequestParser::receiveStartHandler, this));
+
+    defineState(HTTP_METHOD_ST, "HTTP_METHOD_ST",
+                boost::bind(&HttpRequestParser::httpMethodHandler, this));
+
+    defineState(HTTP_URI_ST, "HTTP_URI_ST",
+                boost::bind(&HttpRequestParser::uriHandler, this));
+
+    defineState(HTTP_VERSION_H_ST, "HTTP_VERSION_H_ST",
+                boost::bind(&HttpRequestParser::versionHTTPHandler, this, 'H',
+                            HTTP_VERSION_T1_ST));
+
+    defineState(HTTP_VERSION_T1_ST, "HTTP_VERSION_T1_ST",
+                boost::bind(&HttpRequestParser::versionHTTPHandler, this, 'T',
+                            HTTP_VERSION_T2_ST));
+
+    defineState(HTTP_VERSION_T2_ST, "HTTP_VERSION_T2_ST",
+                boost::bind(&HttpRequestParser::versionHTTPHandler, this, 'T',
+                            HTTP_VERSION_P_ST));
+
+    defineState(HTTP_VERSION_P_ST, "HTTP_VERSION_P_ST",
+                boost::bind(&HttpRequestParser::versionHTTPHandler, this, 'P',
+                            HTTP_VERSION_SLASH_ST));
+
+    defineState(HTTP_VERSION_SLASH_ST, "HTTP_VERSION_SLASH_ST",
+                boost::bind(&HttpRequestParser::versionHTTPHandler, this, '/',
+                            HTTP_VERSION_MAJOR_ST));
+
+    defineState(HTTP_VERSION_MAJOR_START_ST, "HTTP_VERSION_MAJOR_START_ST",
+                boost::bind(&HttpRequestParser::versionNumberStartHandler, this,
+                            HTTP_VERSION_MAJOR_ST,
+                            &context_->http_version_major_));
+
+    defineState(HTTP_VERSION_MAJOR_ST, "HTTP_VERSION_MAJOR_ST",
+                boost::bind(&HttpRequestParser::versionNumberHandler, this,
+                            '.', HTTP_VERSION_MINOR_START_ST,
+                            &context_->http_version_major_));
+
+    defineState(HTTP_VERSION_MINOR_START_ST, "HTTP_VERSION_MINOR_START_ST",
+                boost::bind(&HttpRequestParser::versionNumberStartHandler, this,
+                            HTTP_VERSION_MINOR_ST,
+                            &context_->http_version_minor_));
+
+    defineState(HTTP_VERSION_MINOR_ST, "HTTP_VERSION_MINOR_ST",
+                boost::bind(&HttpRequestParser::versionNumberHandler, this,
+                            '\r', EXPECTING_NEW_LINE1_ST,
+                            &context_->http_version_minor_));
+
+    defineState(EXPECTING_NEW_LINE1_ST, "EXPECTING_NEW_LINE1_ST",
+                boost::bind(&HttpRequestParser::expectingNewLineHandler, this,
+                            HEADER_LINE_START_ST));
+
+    defineState(HEADER_LINE_START_ST, "HEADER_LINE_START_ST",
+                boost::bind(&HttpRequestParser::headerLineStartHandler, this));
+
+    defineState(HEADER_LWS_ST, "HEADER_LWS_ST",
+                boost::bind(&HttpRequestParser::headerLwsHandler, this));
+
+    defineState(HEADER_NAME_ST, "HEADER_NAME_ST",
+                boost::bind(&HttpRequestParser::headerNameHandler, this));
+
+    defineState(SPACE_BEFORE_HEADER_VALUE_ST, "SPACE_BEFORE_HEADER_VALUE_ST",
+                boost::bind(&HttpRequestParser::spaceBeforeHeaderValueHandler, this));
+
+    defineState(HEADER_VALUE_ST, "HEADER_VALUE_ST",
+                boost::bind(&HttpRequestParser::headerValueHandler, this));
+
+    defineState(EXPECTING_NEW_LINE2_ST, "EXPECTING_NEW_LINE2",
+                boost::bind(&HttpRequestParser::expectingNewLineHandler, this,
+                            HEADER_LINE_START_ST));
+
+    defineState(EXPECTING_NEW_LINE3_ST, "EXPECTING_NEW_LINE3_ST",
+                boost::bind(&HttpRequestParser::expectingNewLineHandler, this,
+                            HTTP_PARSE_OK_ST));
+
+    defineState(HTTP_BODY_ST, "HTTP_BODY_ST",
+                boost::bind(&HttpRequestParser::bodyHandler, this));
+
+    defineState(HTTP_PARSE_OK_ST, "HTTP_PARSE_OK_ST",
+                boost::bind(&HttpRequestParser::parseEndedHandler, this));
+
+    defineState(HTTP_PARSE_FAILED_ST, "HTTP_PARSE_FAILED_ST",
+                boost::bind(&HttpRequestParser::parseEndedHandler, this));
+}
+
+void
+HttpRequestParser::parseFailure(const std::string& error_msg) {
+    error_message_ = error_msg + " : " + getContextStr();
+    transition(HTTP_PARSE_FAILED_ST, HTTP_PARSE_FAILED_EVT);
+}
+
+void
+HttpRequestParser::onModelFailure(const std::string& explanation) {
+    if (error_message_.empty()) {
+        error_message_ = explanation;
+    }
+}
+
+char
+HttpRequestParser::getNextFromBuffer() {
+    unsigned int ev = getNextEvent();
+    char c = '\0';
+    // The caller should always provide additional data when the
+    // NEED_MORE_DATA_EVT occurrs. If the next event is still
+    // NEED_MORE_DATA_EVT it indicates that the caller hasn't provided
+    // the data.
+    if (ev == NEED_MORE_DATA_EVT) {
+        isc_throw(HttpRequestParserError,
+                  "HTTP request parser requires new data to progress, but no data"
+                  " have been provided. The transaction is aborted to avoid"
+                  " a deadlock. This is a Kea HTTP server logic error!");
+
+    } else {
+        // Try to pop next character from the buffer.
+        const bool data_exist = popNextFromBuffer(c);
+        if (!data_exist) {
+            // There is no more data so it is really not possible that we're
+            // at MORE_DATA_PROVIDED_EVT.
+            if (ev == MORE_DATA_PROVIDED_EVT) {
+                isc_throw(HttpRequestParserError,
+                          "HTTP server state indicates that new data have been"
+                          " provided to be parsed, but the transaction buffer"
+                          " contains no new data. This is a Kea HTTP server logic"
+                          " error!");
+
+            } else {
+                // If there is no more data we should set NEED_MORE_DATA_EVT
+                // event to indicate that new data should be provided.
+                transition(getCurrState(), NEED_MORE_DATA_EVT);
+            }
+        }
+    }
+    return (c);
+}
+
+void
+HttpRequestParser::invalidEventError(const std::string& handler_name,
+                                 const unsigned int event) {
+    isc_throw(HttpRequestParserError, handler_name << ": "
+              << " invalid event " << getEventLabel(static_cast<int>(event)));
+}
+
+void
+HttpRequestParser::stateWithReadHandler(const std::string& handler_name,
+                                    boost::function<void(const char c)>
+                                    after_read_logic) {
+    char c = getNextFromBuffer();
+    // Do nothing if we reached the end of buffer.
+    if (getNextEvent() != NEED_MORE_DATA_EVT) {
+        switch(getNextEvent()) {
+        case DATA_READ_OK_EVT:
+        case MORE_DATA_PROVIDED_EVT:
+            after_read_logic(c);
+            break;
+        default:
+            invalidEventError(handler_name, getNextEvent());
+        }
+    }
+}
+
+
+void
+HttpRequestParser::receiveStartHandler() {
+    char c = getNextFromBuffer();
+    if (getNextEvent() != NEED_MORE_DATA_EVT) {
+        switch(getNextEvent()) {
+        case START_EVT:
+            // The first byte should contain a first character of the
+            // HTTP method name.
+            if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+                parseFailure("invalid first character " + std::string(1, c) +
+                             " in HTTP method name");
+
+            } else {
+                context_->method_.push_back(c);
+                transition(HTTP_METHOD_ST, DATA_READ_OK_EVT);
+            }
+            break;
+
+        default:
+            invalidEventError("receiveStartHandler", getNextEvent());
+        }
+    }
+}
+
+void
+HttpRequestParser::httpMethodHandler() {
+    stateWithReadHandler("httpMethodHandler", [this](const char c) {
+        // Space character terminates the HTTP method name. Next thing
+        // is the URI.
+        if (c == ' ') {
+            transition(HTTP_URI_ST, DATA_READ_OK_EVT);
+
+        } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+            parseFailure("invalid character " + std::string(1, c) +
+                         " in HTTP method name");
+
+        } else {
+            // Still parsing the method. Append the next character to the
+            // method name.
+            context_->method_.push_back(c);
+            transition(getCurrState(), DATA_READ_OK_EVT);
+        }
+    });
+}
+
+void
+HttpRequestParser::uriHandler() {
+    stateWithReadHandler("uriHandler", [this](const char c) {
+        // Space character terminates the URI.
+        if (c == ' ') {
+            transition(HTTP_VERSION_H_ST, DATA_READ_OK_EVT);
+
+        } else if (isCtl(c)) {
+            parseFailure("control character found in HTTP URI");
+            transition(HTTP_PARSE_FAILED_ST, HTTP_PARSE_FAILED_EVT);
+
+        } else {
+            // Still parsing the URI. Append the next character to the
+            // method name.
+            context_->uri_.push_back(c);
+            transition(HTTP_URI_ST, DATA_READ_OK_EVT);
+        }
+    });
+}
+
+void
+HttpRequestParser::versionHTTPHandler(const char expected_letter,
+                                  const unsigned int next_state) {
+    stateWithReadHandler("versionHTTPHandler",
+                         [this, expected_letter, next_state](const char c) {
+        // We're handling one of the letters: 'H', 'T' or 'P'.
+        if (c == expected_letter) {
+            // The HTTP version is specified as "HTTP/X.Y". If the current
+            // character is a slash we're starting to parse major HTTP version
+            // number. Let's reset the version numbers.
+            if (c == '/') {
+                context_->http_version_major_ = 0;
+                context_->http_version_minor_ = 0;
+            }
+            // In all cases, let's transition to next specified state.
+            transition(next_state, DATA_READ_OK_EVT);
+
+        } else {
+            // Unexpected character found. Parsing fails.
+            parseFailure("unexpected character " + std::string(1, c) +
+                         " in HTTP version string");
+        }
+    });
+}
+
+void
+HttpRequestParser::versionNumberStartHandler(const unsigned int next_state,
+                                         unsigned int* storage) {
+    stateWithReadHandler("versionNumberStartHandler",
+                         [this, next_state, storage](const char c) mutable {
+        // HTTP version number must be a digit.
+        if (isdigit(c)) {
+            // Update the version number using new digit being parsed.
+            *storage = *storage * 10 + c - '0';
+            transition(next_state, DATA_READ_OK_EVT);
+
+        } else {
+            parseFailure("expected digit in HTTP version, found " +
+                         std::string(1, c));
+            transition(HTTP_PARSE_FAILED_ST, HTTP_PARSE_FAILED_EVT);
+        }
+    });
+}
+
+void
+HttpRequestParser::versionNumberHandler(const char following_character,
+                     const unsigned int next_state,
+                     unsigned int* const storage) {
+    stateWithReadHandler("versionNumberHandler",
+                         [this, following_character, next_state, storage](const char c)
+                         mutable {
+        // We're getting to the end of the version number, let's transition
+        // to next state.
+        if (c == following_character) {
+            transition(next_state, DATA_READ_OK_EVT);
+
+        } else if (isdigit(c)) {
+            // Current character is a digit, so update the version number.
+            *storage = *storage * 10 + c - '0';
+
+        } else {
+            parseFailure("expected digit in HTTP version, found " +
+                         std::string(1, c));
+            transition(HTTP_PARSE_FAILED_ST, HTTP_PARSE_FAILED_EVT);
+        }
+    });
+}
+
+void
+HttpRequestParser::expectingNewLineHandler(const unsigned int next_state) {
+    stateWithReadHandler("expectingNewLineHandler", [this, next_state](const char c) {
+        // Only a new line character is allowed in this state.
+        if (c == '\n') {
+            // If next state is HTTP_PARSE_OK_ST it means that we're
+            // parsing 3rd new line in the HTTP request message. This
+            // terminates the HTTP request (if there is no body) or marks the
+            // beginning of the body.
+            if (next_state == HTTP_PARSE_OK_ST) {
+                // Whether there is a body in this message or not, we should
+                // parse the HTTP headers to validate it and to check if there
+                // is "Content-Length" specified. The "Content-Length" is
+                // required for parsing body.
+                request_.create();
+                try {
+                    // This will throw exception if there is no Content-Length.
+                    uint64_t content_length =
+                        request_.getHeaderValueAsUint64("Content-Length");
+                    if (content_length > 0) {
+                        // There is body in this request, so let's parse it.
+                        transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+                    }
+                } catch (const std::exception& ex) {
+                    // There is no body in this message. If the body is required
+                    // parsing fails.
+                    if (request_.requiresBody()) {
+                        parseFailure("HTTP message lacks a body");
+
+                    } else {
+                        // Body not required so simply terminate parsing.
+                        transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+                    }
+                }
+
+            } else {
+                // This is 1st or 2nd new line, so let's transition to the
+                // next state required by this handler.
+                transition(next_state, DATA_READ_OK_EVT);
+            }
+        } else {
+            parseFailure("expecting new line after CR, found " +
+                         std::string(1, c));
+        }
+    });
+}
+
+void
+HttpRequestParser::headerLineStartHandler() {
+    stateWithReadHandler("headerLineStartHandler", [this](const char c) {
+        // If we're parsing HTTP headers and we found CR it marks the
+        // end of headers section.
+        if (c == '\r') {
+            transition(EXPECTING_NEW_LINE3_ST, DATA_READ_OK_EVT);
+
+        } else if (!context_->headers_.empty() && ((c == ' ') || (c == '\t'))) {
+            // New line in headers section followed by space or tab is an LWS,
+            // a line break within header value.
+            transition(HEADER_LWS_ST, DATA_READ_OK_EVT);
+
+        } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+            parseFailure("invalid character " + std::string(1, c) +
+                         " in header name");
+
+        } else {
+            // Update header name with the parse letter.
+            context_->headers_.push_back(HttpHeaderContext());
+            context_->headers_.back().name_.push_back(c);
+            transition(HEADER_NAME_ST, DATA_READ_OK_EVT);
+        }
+    });
+}
+
+void
+HttpRequestParser::headerLwsHandler() {
+    stateWithReadHandler("headerLwsHandler", [this](const char c) {
+        if (c == '\r') {
+            // Found CR during parsing a header value. Next value
+            // should be new line.
+            transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+        } else if ((c == ' ') || (c == '\t')) {
+            // Space and tab is used to mark LWS. Simply swallow
+            // this character.
+            transition(getCurrState(), DATA_READ_OK_EVT);
+
+        } else if (isCtl(c)) {
+            parseFailure("control character found in the HTTP header " +
+                        context_->headers_.back().name_);
+
+        } else {
+            // We're parsing header value, so let's update it.
+            context_->headers_.back().value_.push_back(c);
+            transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+        }
+    });
+}
+
+void
+HttpRequestParser::headerNameHandler() {
+    stateWithReadHandler("headerNameHandler", [this](const char c) {
+            // Colon follows header name and it has its own state.
+        if (c == ':') {
+            transition(SPACE_BEFORE_HEADER_VALUE_ST, DATA_READ_OK_EVT);
+
+        } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+            parseFailure("invalid character " + std::string(1, c) +
+                         " found in the HTTP header name");
+
+        } else {
+            // Parsing a header name, so update it.
+            context_->headers_.back().name_.push_back(c);
+            transition(getCurrState(), DATA_READ_OK_EVT);
+        }
+    });
+}
+
+void
+HttpRequestParser::spaceBeforeHeaderValueHandler() {
+    stateWithReadHandler("spaceBeforeHeaderValueHandler", [this](const char c) {
+        if (c == ' ') {
+            // Remove leading whitespace from the header value.
+            transition(getCurrState(), DATA_READ_OK_EVT);
+
+        } else if (c == '\r') {
+            // If CR found during parsing header value, it marks the end
+            // of this value.
+            transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+        } else if (isCtl(c)) {
+            parseFailure("control character found in the HTTP header "
+                         + context_->headers_.back().name_);
+
+        } else {
+            // Still parsing the value, so let's update it.
+            context_->headers_.back().value_.push_back(c);
+            transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+        }
+    });
+}
+
+void
+HttpRequestParser::headerValueHandler() {
+    stateWithReadHandler("headerValueHandler", [this](const char c) {
+        // If CR found during parsing header value, it marks the end
+        // of this value.
+        if (c == '\r') {
+            transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+        } else if (isCtl(c)) {
+            parseFailure("control character found in the HTTP header "
+                         + context_->headers_.back().name_);
+
+        } else {
+            // Still parsing the value, so let's update it.
+            context_->headers_.back().value_.push_back(c);
+            transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+        }
+    });
+}
+
+void
+HttpRequestParser::bodyHandler() {
+    stateWithReadHandler("bodyHandler", [this](const char c) {
+        // We don't validate the body at this stage. Simply record the
+        // number of characters specified within "Content-Length".
+        context_->body_.push_back(c);
+        if (context_->body_.length() <
+            request_.getHeaderValueAsUint64("Content-Length")) {
+            transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+        } else {
+            transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+        }
+    });
+}
+
+
+void
+HttpRequestParser::parseEndedHandler() {
+    switch(getNextEvent()) {
+    case HTTP_PARSE_OK_EVT:
+        request_.finalize();
+        transition(END_ST, END_EVT);
+        break;
+    case HTTP_PARSE_FAILED_EVT:
+        abortModel("HTTP request parsing failed");
+        break;
+
+    default:
+        invalidEventError("parseEndedHandler", getNextEvent());
+    }
+}
+
+bool
+HttpRequestParser::popNextFromBuffer(char& next) {
+    // If there are any characters in the buffer, pop next.
+    if (!buffer_.empty()) {
+        next = buffer_.front();
+        buffer_.pop_front();
+        return (true);
+    }
+    return (false);
+}
+
+
+bool
+HttpRequestParser::isChar(const char c) const {
+    return ((c >= 0) && (c <= 127));
+}
+
+bool
+HttpRequestParser::isCtl(const char c) const {
+    return (((c >= 0) && (c <= 31)) || (c == 127));
+}
+
+bool
+HttpRequestParser::isSpecial(const char c) const {
+    switch (c) {
+    case '(':
+    case ')':
+    case '<':
+    case '>':
+    case '@':
+    case ',':
+    case ';':
+    case ':':
+    case '\\':
+    case '"':
+    case '/':
+    case '[':
+    case ']':
+    case '?':
+    case '=':
+    case '{':
+    case '}':
+    case ' ':
+    case '\t':
+        return true;
+
+    default:
+        ;
+    }
+
+    return false;
+}
+
+
+} // namespace http
+} // namespace isc

+ 450 - 0
src/lib/http/request_parser.h

@@ -0,0 +1,450 @@
+// 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_REQUEST_PARSER_H
+#define HTTP_REQUEST_PARSER_H
+
+#include <exceptions/exceptions.h>
+#include <http/request.h>
+#include <util/state_model.h>
+#include <boost/function.hpp>
+#include <list>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when an error during parsing HTTP request
+/// has occurred.
+///
+/// The most common errors are due to receiving malformed requests.
+class HttpRequestParserError : public Exception {
+public:
+    HttpRequestParserError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief A generic parser for HTTP requests.
+///
+/// This class implements a parser for HTTP requests. The parser derives from
+/// @ref isc::util::StateModel class and implements its own state machine on
+/// top of it. The states of the parser reflect various parts of the HTTP
+/// message being parsed, e.g. parsing HTTP method, parsing URI, parsing
+/// message body etc. The descriptions of all parser states are provided
+/// below together with the constants defining these states.
+///
+/// HTTP uses TCP as a transport which is asynchronous in nature, i.e. the
+/// HTTP message is received in chunks and multiple TCP connections can be
+/// established at the same time. Multiplexing between these connections
+/// requires providing a separate state machine per connection to "remeber"
+/// the state of each transaction when the parser is waiting for asynchronous
+/// data to be delivered. While the parser is waiting for the data, it can
+/// parse requests received over other connections. This class provides means
+/// for parsing partial data received over the specific connection and
+/// interrupting data parsing to switch to a different context.
+///
+/// The request parser validates the syntax of the received message as it
+/// progresses with parsing the data. Though, it doesn't interpret the received
+/// data until it parses the whole message. In most cases we want to apply some
+/// restrictions on the message content, e.g. Kea Control API requires that
+/// commands are sent using HTTP POST, with a JSON command being carried in a
+/// message body. The parser doesn't verify if the message meets these
+/// restrictions until the whole message is parsed, i.e. stored in the
+/// @ref HttpRequestContext object. This object is associated with a
+/// @ref HttpRequest object (or its derivation). When the parsing is completed,
+/// the @ref HttpRequest::create method is called to retrieve the data from
+/// the @ref HttpRequestContext and interpret the data. In particular, the
+/// @ref HttpRequest or its derivation checks if the received message meets
+/// desired restrictions.
+///
+/// Kea Control API uses @ref PostHttpRequestJson class (which derives from the
+/// @ref HttpRequest) to interpret received request. This class requires
+/// that the HTTP request uses POST method and contains the following headers:
+/// - Content-Type: application/json,
+/// - Content-Length
+///
+/// If any of these restrictions is not met in the received message, an
+/// exception will be thrown, thereby @ref HttpRequestParser will fail parsing
+/// the message.
+///
+/// A new method @ref HttpRequestParser::poll has been created to run the
+/// parser's state machine as long as there are unparsed data in the parser's
+/// internal buffer. This method returns control to the caller when the parser
+/// runs out of data in this buffer. The caller must feed the buffer by calling
+/// @ref HttpRequestParser::postBuffer and then run @ref HttpRequestParser::poll
+//// again.
+///
+/// The @ref util::StateModel::runModel must not be used to run the
+/// @ref HttpRequestParser state machine, thus it is made private method.
+class HttpRequestParser : public util::StateModel {
+public:
+
+    /// @name States supported by the HttpRequestParser.
+    ///
+    //@{
+
+    /// @brief State indicating a beginning of parsing.
+    static const int RECEIVE_START_ST = SM_DERIVED_STATE_MIN + 1;
+
+    /// @brief Parsing HTTP method, e.g. GET, POST etc.
+    static const int HTTP_METHOD_ST = SM_DERIVED_STATE_MIN + 2;
+
+    /// @brief Parsing URI.
+    static const int HTTP_URI_ST = SM_DERIVED_STATE_MIN + 3;
+
+    /// @brief Parsing letter "H" of "HTTP".
+    static const int HTTP_VERSION_H_ST = SM_DERIVED_STATE_MIN + 4;
+
+    /// @brief Parsing first occurrence of "T" in "HTTP".
+    static const int HTTP_VERSION_T1_ST = SM_DERIVED_STATE_MIN + 5;
+
+    /// @brief Parsing second occurrence of "T" in "HTTP".
+    static const int HTTP_VERSION_T2_ST = SM_DERIVED_STATE_MIN + 6;
+
+    /// @brief Parsing letter "P" in "HTTP".
+    static const int HTTP_VERSION_P_ST = SM_DERIVED_STATE_MIN + 7;
+
+    /// @brief Parsing slash character in "HTTP/Y.X"
+    static const int HTTP_VERSION_SLASH_ST = SM_DERIVED_STATE_MIN + 8;
+
+    /// @brief Starting to parse major HTTP version number.
+    static const int HTTP_VERSION_MAJOR_START_ST = SM_DERIVED_STATE_MIN + 9;
+
+    /// @brief Parsing major HTTP version number.
+    static const int HTTP_VERSION_MAJOR_ST = SM_DERIVED_STATE_MIN + 10;
+
+    /// @brief Starting to parse minor HTTP version number.
+    static const int HTTP_VERSION_MINOR_START_ST = SM_DERIVED_STATE_MIN + 11;
+
+    /// @brief Parsing minor HTTP version number.
+    static const int HTTP_VERSION_MINOR_ST = SM_DERIVED_STATE_MIN + 12;
+
+    /// @brief Parsing first new line (after HTTP version number).
+    static const int EXPECTING_NEW_LINE1_ST = SM_DERIVED_STATE_MIN + 13;
+
+    /// @brief Starting to parse a header line.
+    static const int HEADER_LINE_START_ST = SM_DERIVED_STATE_MIN + 14;
+
+    /// @brief Parsing LWS (Linear White Space), i.e. new line with a space
+    /// or tab character while parsing a HTTP header.
+    static const int HEADER_LWS_ST = SM_DERIVED_STATE_MIN + 15;
+
+    /// @brief Parsing header name.
+    static const int HEADER_NAME_ST = SM_DERIVED_STATE_MIN + 16;
+
+    /// @brief Parsing space before header value.
+    static const int SPACE_BEFORE_HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 17;
+
+    /// @brief Parsing header value.
+    static const int HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 18;
+
+    /// @brief Expecting new line after parsing header value.
+    static const int EXPECTING_NEW_LINE2_ST = SM_DERIVED_STATE_MIN + 19;
+
+    /// @brief Expecting second new line marking end of HTTP headers.
+    static const int EXPECTING_NEW_LINE3_ST = SM_DERIVED_STATE_MIN + 20;
+
+    /// @brief Parsing body of a HTTP message.
+    static const int HTTP_BODY_ST = SM_DERIVED_STATE_MIN + 21;
+
+    /// @brief Parsing successfully completed.
+    static const int HTTP_PARSE_OK_ST = SM_DERIVED_STATE_MIN + 100;
+
+    /// @brief Parsing failed.
+    static const int HTTP_PARSE_FAILED_ST = SM_DERIVED_STATE_MIN + 101;
+
+    //@}
+
+
+    /// @name Events used during HTTP message parsing.
+    ///
+    //@{
+
+    /// @brief Chunk of data successfully read and parsed.
+    static const int DATA_READ_OK_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+    /// @brief Unable to proceed with parsing until new data is provided.
+    static const int NEED_MORE_DATA_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+    /// @brief New data provided and parsing should continue.
+    static const int MORE_DATA_PROVIDED_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+    /// @brief Parsing HTTP request sucessfull.
+    static const int HTTP_PARSE_OK_EVT = SM_DERIVED_EVENT_MIN + 100;
+
+    /// @brief Parsing HTTP request failed.
+    static const int HTTP_PARSE_FAILED_EVT = SM_DERIVED_EVENT_MIN + 101;
+
+    //@}
+
+    /// @brief Constructor.
+    ///
+    /// Creates new instance of the parser.
+    ///
+    /// @param request Reference to the @ref HttpRequest object or its
+    /// derivation that should be used to validate the parsed request and
+    /// to be used as a container for the parsed request.
+    HttpRequestParser(HttpRequest& request);
+
+    /// @brief Initialize the state model for parsing.
+    ///
+    /// This method must be  called before parsing the request, i.e. before
+    /// calling @ref HttpRequestParser::poll. It initializes dictionaries of
+    /// states and events and sets the initial model state to RECEIVE_START_ST.
+    void initModel();
+
+    /// @brief Run the parser as long as the amount of data is sufficient.
+    ///
+    /// The data to be parsed should be provided by calling
+    /// @ref HttpRequestParser::postBuffer. When the parser reaches the end of
+    /// the data buffer the @ref HttpRequestParser::poll sets the next event to
+    /// @ref NEED_MORE_DATA_EVT and returns. The caller should then invoke
+    /// @ref HttpRequestParser::postBuffer again to provide more data to the
+    /// parser, and call @ref HttpRequestParser::poll to continue parsing.
+    ///
+    /// This method also returns when parsing completes or fails. The last
+    /// event can be examined to check whether parsing was successful or not.
+    void poll();
+
+    /// @brief Returns true if the parser needs more data to continue.
+    ///
+    /// @return true if the next event is NEED_MORE_DATA_EVT.
+    bool needData() const;
+
+    /// @brief Returns true if a request has been parsed successfully.
+    bool httpParseOk() const;
+
+    /// @brief Returns error message.
+    std::string getErrorMessage() const {
+        return (error_message_);
+    }
+
+    /// @brief Provides more input data to the parser.
+    ///
+    /// This method must be called prior to calling @ref HttpRequestParser::poll
+    /// to deliver data to be parsed. HTTP requests are received over TCP and
+    /// multiple reads may be necessary to retrieve the entire request. There is
+    /// no need to accumulate the entire request to start parsing it. A chunk
+    /// of data can be provided to the parser using this method and parsed right
+    /// away using @ref HttpRequestParser::poll.
+    ///
+    /// @param buf A pointer to the buffer holding the data.
+    /// @param buf_size Size of the data within the buffer.
+    void postBuffer(const void* buf, const size_t buf_size);
+
+private:
+
+    /// @brief Make @ref runModel private to make sure that the caller uses
+    /// @ref poll method instead.
+    using StateModel::runModel;
+
+    /// @brief Define events used by the parser.
+    virtual void defineEvents();
+
+    /// @brief Verifies events used by the parser.
+    virtual void verifyEvents();
+
+    /// @brief Defines states of the parser.
+    virtual void defineStates();
+
+    /// @brief Transition parser to failure state.
+    ///
+    /// This method transitions the parser to @ref HTTP_PARSE_FAILED_ST and
+    /// sets next event to HTTP_PARSE_FAILED_EVT.
+    ///
+    /// @param error_msg Error message explaining the failure.
+    void parseFailure(const std::string& error_msg);
+
+    /// @brief A method called when parsing fails.
+    ///
+    /// @param explanation Error message explaining the reason for parsing
+    /// failure.
+    virtual void onModelFailure(const std::string& explanation);
+
+    /// @brief Retrieves next byte of data from the buffer.
+    ///
+    /// During normal operation, when there is no more data in the buffer,
+    /// the parser sets NEED_MORE_DATA_EVT as next event to signal the need for
+    /// calling @ref HttpRequestParser::postBuffer.
+    ///
+    /// @throw HttpRequestParserError If current event is already set to
+    /// NEED_MORE_DATA_EVT or MORE_DATA_PROVIDED_EVT. In the former case, it
+    /// indicates that the caller failed to provide new data using
+    /// @ref HttpRequestParser::postBuffer. The latter case is highly unlikely
+    /// as it indicates that no new data were provided but the state of the
+    /// parser was changed from NEED_MORE_DATA_EVT or the data were provided
+    /// but the data buffer is empty. In both cases, it is an internal server
+    /// error.
+    char getNextFromBuffer();
+
+    /// @brief This method is called when invalid event occurred in a particular
+    /// parser state.
+    ///
+    /// This method simply throws @ref HttpRequestParserError informing about
+    /// invalid event occurring for the particular parser state. The error
+    /// message includes the name of the handler in which the exception
+    /// has been thrown. It also includes the event which caused the
+    /// exception.
+    ///
+    /// @param handler_name Name of the handler in which the exception is
+    /// thrown.
+    /// @param event An event which caused the exception.
+    ///
+    /// @throw HttpRequestParserError.
+    void invalidEventError(const std::string& handler_name,
+                           const unsigned int event);
+
+    /// @brief Generic parser handler which reads a single byte of data and
+    /// parses it using specified callback function.
+    ///
+    /// This generic handler is used in most of the parser states to parse a
+    /// single byte of input data. If there is no more data it simply returns.
+    /// Otherwise, if the next event is DATA_READ_OK_EVT or
+    /// MORE_DATA_PROVIDED_EVT, it calls the provided callback function to
+    /// parse the new byte of data. For all other states it throws an exception.
+    ///
+    /// @param handler_name Name of the handler function which called this
+    /// method.
+    /// @param after_read_logic Callback function to parse the byte of data.
+    /// This callback function implements state specific logic.
+    ///
+    /// @throw HttpRequestParserError when invalid event occurred.
+    void stateWithReadHandler(const std::string& handler_name,
+                              boost::function<void(const char c)>
+                              after_read_logic);
+
+    /// @name State handlers.
+    ///
+    //@{
+
+    /// @brief Handler for RECEIVE_START_ST.
+    void receiveStartHandler();
+
+    /// @brief Handler for HTTP_METHOD_ST.
+    void httpMethodHandler();
+
+    /// @brief Handler for HTTP_URI_ST.
+    void uriHandler();
+
+    /// @brief Handler for states parsing "HTTP" string within the first line
+    /// of the HTTP request.
+    ///
+    /// @param expected_letter One of the 'H', 'T', 'P'.
+    /// @param next_state A state to which the parser should transition after
+    /// parsing the character.
+    void versionHTTPHandler(const char expected_letter,
+                            const unsigned int next_state);
+
+    /// @brief Handler for HTTP_VERSION_MAJOR_START_ST and
+    /// HTTP_VERSION_MINOR_START_ST.
+    ///
+    /// This handler calculates version number using the following equation:
+    /// @code
+    ///     storage = storage * 10 + c - '0';
+    /// @endcode
+    ///
+    /// @param next_state State to which the parser should transition.
+    /// @param [out] storage Reference to a number holding current product of
+    /// parsing major or minor version number.
+    void versionNumberStartHandler(const unsigned int next_state,
+                                   unsigned int* storage);
+
+    /// @brief Handler for HTTP_VERSION_MAJOR_ST and HTTP_VERSION_MINOR_ST.
+    ///
+    /// This handler calculates version number using the following equation:
+    /// @code
+    ///     storage = storage * 10 + c - '0';
+    /// @endcode
+    ///
+    /// @param following_character Character following the version number, i.e.
+    /// '.' for major version, \r for minor version.
+    /// @param next_state State to which the parser should transition.
+    /// @param [out] storage Pointer to a number holding current product of
+    /// parsing major or minor version number.
+    void versionNumberHandler(const char following_character,
+                              const unsigned int next_state,
+                              unsigned int* const storage);
+
+    /// @brief Handler for states related to new lines.
+    ///
+    /// If the next_state is HTTP_PARSE_OK_ST it indicates that the parsed
+    /// value is a 3rd new line within request HTTP message. In this case the
+    /// handler calls @ref HttpRequest::create to validate the received message
+    /// (excluding body). The hander then reads the "Content-Length" header to
+    /// check if the request contains a body. If the "Content-Length" is greater
+    /// than zero, the parser transitions to HTTP_BODY_ST. If the
+    /// "Content-Length" doesn't exist the parser transitions to
+    /// HTTP_PARSE_OK_ST.
+    ///
+    /// @param next_state A state to which parser should transition.
+    void expectingNewLineHandler(const unsigned int next_state);
+
+    /// @brief Handler for HEADER_LINE_START_ST.
+    void headerLineStartHandler();
+
+    /// @brief Handler for HEADER_LWS_ST.
+    void headerLwsHandler();
+
+    /// @brief Handler for HEADER_NAME_ST.
+    void headerNameHandler();
+
+    /// @brief Handler for SPACE_BEFORE_HEADER_VALUE_ST.
+    void spaceBeforeHeaderValueHandler();
+
+    /// @brief Handler for HEADER_VALUE_ST.
+    void headerValueHandler();
+
+    /// @brief Handler for HTTP_BODY_ST.
+    void bodyHandler();
+
+    /// @brief Handler for HTTP_PARSE_OK_ST and HTTP_PARSE_FAILED_ST.
+    ///
+    /// If parsing is successful, it calls @ref HttpRequest::create to validate
+    /// the HTTP request. In both cases it transitions the parser to the END_ST.
+    void parseEndedHandler();
+
+    /// @brief Tries to read next byte from buffer.
+    ///
+    /// @param [out] next A reference to the variable where read data should be
+    /// stored.
+    ///
+    /// @return true if character was sucessfully read, false otherwise.
+    bool popNextFromBuffer(char& next);
+
+    /// @brief Checks if specified value is a character.
+    ///
+    /// @return true, if specified value is a character.
+    bool isChar(const char c) const;
+
+    /// @brief Checks if specified value is a control value.
+    ///
+    /// @return true, if specified value is a control value.
+    bool isCtl(const char c) const;
+
+    /// @brief Checks if specified value is a special character.
+    ///
+    /// @return true, if specified value is a special character.
+    bool isSpecial(const char c) const;
+
+    /// @brief Internal buffer from which parser reads data.
+    std::list<char> buffer_;
+
+    /// @brief Reference to the request object specified in the constructor.
+    HttpRequest& request_;
+
+    /// @brief Pointer to the internal context of the @ref HttpRequest object.
+    HttpRequestContextPtr context_;
+
+    /// @brief Error message set by @ref onModelFailure.
+    std::string error_message_;
+};
+
+} // namespace http
+} // namespace isc
+
+#endif // HTTP_REQUEST_PARSER_H
+

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

@@ -20,7 +20,11 @@ TESTS =
 if HAVE_GTEST
 TESTS += libhttp_unittests
 
-libhttp_unittests_SOURCES  = run_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 += request_unittests.cc
+libhttp_unittests_SOURCES += run_unittests.cc
 
 libhttp_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 libhttp_unittests_CXXFLAGS = $(AM_CXXFLAGS)
@@ -29,6 +33,7 @@ libhttp_unittests_LDFLAGS  = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 libhttp_unittests_LDADD  = $(top_builddir)/src/lib/http/libkea-http.la
 libhttp_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 libhttp_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
 libhttp_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 libhttp_unittests_LDADD += $(LOG4CPLUS_LIBS)
 libhttp_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)

+ 172 - 0
src/lib/http/tests/post_request_json_unittests.cc

@@ -0,0 +1,172 @@
+// 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 <cc/data.h>
+#include <http/post_request_json.h>
+#include <http/tests/request_test.h>
+#include <gtest/gtest.h>
+#include <map>
+
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test fixture class for @ref PostHttpRequestJson.
+class PostHttpRequestJsonTest :
+        public HttpRequestTestBase<PostHttpRequestJson> {
+public:
+
+    /// @brief Constructor.
+    PostHttpRequestJsonTest()
+        : HttpRequestTestBase<PostHttpRequestJson>(),
+        json_body_("{ \"service\": \"dhcp4\", \"param1\": \"foo\" }") {
+    }
+
+    /// @brief Sets new JSON body for the HTTP request context.
+    ///
+    /// If the body parameter is empty, it will use the value of
+    /// @ref json_body_ member. Otherwise, it will assign the body
+    /// provided as parameter.
+    ///
+    /// @param body new body value.
+    void setBody(const std::string& body = "") {
+        request_.context()->body_ = body.empty() ? json_body_ : body;
+    }
+
+    /// @brief Default value of the JSON body.
+    std::string json_body_;
+
+};
+
+// This test verifies that PostHttpRequestJson class only accepts
+// POST messages.
+TEST_F(PostHttpRequestJsonTest, requiredPost) {
+    // Use a GET method that is not supported.
+    setContextBasics("GET", "/isc/org", std::make_pair(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));
+    addHeaderToContext("Content-Length", json_body_.length());
+    addHeaderToContext("Content-Type", "application/json");
+
+    EXPECT_NO_THROW(request_.create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// 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));
+    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));
+    addHeaderToContext("Content-Length", json_body_.length());
+    addHeaderToContext("Content-Type", "application/json");
+
+    EXPECT_NO_THROW(request_.create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// header.
+TEST_F(PostHttpRequestJsonTest, requireContentLength) {
+    // "Content-Length" is not specified initially. It should fail.
+    setContextBasics("POST", "/isc/org", std::make_pair(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));
+    addHeaderToContext("Content-Length", json_body_.length());
+    addHeaderToContext("Content-Type", "application/json");
+}
+
+// This test verifies that JSON body can be retrieved from the
+// HTTP request.
+TEST_F(PostHttpRequestJsonTest, getBodyAsJson) {
+    // Create HTTP POST request with JSON body.
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    addHeaderToContext("Content-Length", json_body_.length());
+    addHeaderToContext("Content-Type", "application/json");
+    setBody();
+
+    ASSERT_NO_THROW(request_.finalize());
+
+    // Try to retrieve pointer to the root element of the JSON body.
+    ConstElementPtr json = request_.getBodyAsJson();
+    ASSERT_TRUE(json);
+
+    // Iterate over JSON values and store them in a simple map.
+    std::map<std::string, std::string> config_values;
+    for (auto config_element = json->mapValue().begin();
+         config_element != json->mapValue().end();
+         ++config_element) {
+        ASSERT_FALSE(config_element->first.empty());
+        ASSERT_TRUE(config_element->second);
+        config_values[config_element->first] = config_element->second->stringValue();
+    }
+
+    // Verify the values.
+    EXPECT_EQ("dhcp4", config_values["service"]);
+    EXPECT_EQ("foo", config_values["param1"]);
+}
+
+// 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));
+    addHeaderToContext("Content-Length", json_body_.length());
+    addHeaderToContext("Content-Type", "application/json");
+    // No colon before 123.
+    setBody("{ \"command\" 123 }" );
+
+    EXPECT_THROW(request_.finalize(), HttpRequestJsonError);
+}
+
+// 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));
+    addHeaderToContext("Content-Length", json_body_.length());
+    addHeaderToContext("Content-Type", "application/json");
+
+    ASSERT_NO_THROW(request_.finalize());
+
+    ConstElementPtr json = request_.getBodyAsJson();
+    EXPECT_FALSE(json);
+}
+
+// This test verifies that the specific JSON element can be retrieved.
+TEST_F(PostHttpRequestJsonTest, getJsonElement) {
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    addHeaderToContext("Content-Length", json_body_.length());
+    addHeaderToContext("Content-Type", "application/json");
+    setBody();
+
+    ASSERT_NO_THROW(request_.finalize());
+
+    ConstElementPtr element;
+    ASSERT_NO_THROW(element = request_.getJsonElement("service"));
+    ASSERT_TRUE(element);
+    EXPECT_EQ("dhcp4", element->stringValue());
+
+    // An attempt to retrieve non-existing element should return NULL.
+    EXPECT_FALSE(request_.getJsonElement("bar"));
+}
+
+}

+ 72 - 0
src/lib/http/tests/post_request_unittests.cc

@@ -0,0 +1,72 @@
+// 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/post_request.h>
+#include <http/tests/request_test.h>
+#include <gtest/gtest.h>
+
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test fixutre class for @ref PostHttpRequest.
+typedef HttpRequestTestBase<PostHttpRequest> PostHttpRequestTest;
+
+// This test verifies that PostHttpRequest class only accepts POST
+// messages.
+TEST_F(PostHttpRequestTest, requirePost) {
+    // Use a GET method that is not supported.
+    setContextBasics("GET", "/isc/org", std::make_pair(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));
+    addHeaderToContext("Content-Length", json_body_.length());
+    addHeaderToContext("Content-Type", "application/json");
+
+    EXPECT_NO_THROW(request_.create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// header.
+TEST_F(PostHttpRequestTest, requireContentType) {
+    // No "Content-Type". It should fail.
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    addHeaderToContext("Content-Length", json_body_.length());
+
+    ASSERT_THROW(request_.create(), HttpRequestError);
+
+    // There is "Content-Type". It should pass.
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    addHeaderToContext("Content-Length", json_body_.length());
+    addHeaderToContext("Content-Type", "text/html");
+
+    EXPECT_NO_THROW(request_.create());
+
+}
+
+// This test verifies that PostHttpRequest requires "Content-Type"
+// header.
+TEST_F(PostHttpRequestTest, requireContentLength) {
+    // No "Content-Length". It should fail.
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    addHeaderToContext("Content-Type", "text/html");
+
+    ASSERT_THROW(request_.create(), HttpRequestError);
+
+    // There is "Content-Length". It should pass.
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    addHeaderToContext("Content-Length", json_body_.length());
+    addHeaderToContext("Content-Type", "application/json");
+}
+
+}

+ 286 - 0
src/lib/http/tests/request_parser_unittests.cc

@@ -0,0 +1,286 @@
+// 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 <cc/data.h>
+#include <http/request_parser.h>
+#include <http/post_request_json.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @ref HttpRequestParser.
+class HttpRequestParserTest : public ::testing::Test {
+public:
+
+    /// @brief Creates HTTP request string.
+    ///
+    /// @param preamble A string including HTTP request's first line
+    /// and all headers except "Content-Length".
+    /// @param payload A string containing HTTP request payload.
+    std::string createRequestString(const std::string& preamble,
+                                    const std::string& payload) {
+        std::ostringstream s;
+        s << preamble;
+        s << "Content-Length: " << payload.length() << "\r\n\r\n"
+          << payload;
+        return (s.str());
+    }
+
+    /// @brief Parses the HTTP request and checks that parsing was
+    /// successful.
+    ///
+    /// @param http_req HTTP request string.
+    void doParse(const std::string& http_req) {
+        HttpRequestParser parser(request_);
+        ASSERT_NO_THROW(parser.initModel());
+
+        parser.postBuffer(&http_req[0], http_req.size());
+        ASSERT_NO_THROW(parser.poll());
+
+        ASSERT_FALSE(parser.needData());
+        ASSERT_TRUE(parser.httpParseOk());
+        EXPECT_TRUE(parser.getErrorMessage().empty());
+    }
+
+    /// @brief Tests that parsing fails when malformed HTTP request
+    /// is received.
+    ///
+    /// @param http_req HTTP request string.
+    void testInvalidHttpRequest(const std::string& http_req) {
+        HttpRequestParser parser(request_);
+        ASSERT_NO_THROW(parser.initModel());
+
+        parser.postBuffer(&http_req[0], http_req.size());
+        ASSERT_NO_THROW(parser.poll());
+
+        EXPECT_FALSE(parser.needData());
+        EXPECT_FALSE(parser.httpParseOk());
+        EXPECT_FALSE(parser.getErrorMessage().empty());
+    }
+
+    /// @brief Instance of the HttpRequest used by the unit tests.
+    HttpRequest request_;
+};
+
+// Test test verifies that an HTTP request including JSON body is parsed
+// successfully.
+TEST_F(HttpRequestParserTest, postHttpRequestWithJson) {
+    std::string http_req = "POST /foo/bar HTTP/1.0\r\n"
+        "Content-Type: application/json\r\n";
+    std::string json = "{ \"service\": \"dhcp4\", \"command\": \"shutdown\" }";
+
+    http_req = createRequestString(http_req, json);
+
+    // Create HTTP request which accepts POST method and JSON as a body.
+    PostHttpRequestJson request;
+
+    // Create a parser and make it use the request we created.
+    HttpRequestParser parser(request);
+    ASSERT_NO_THROW(parser.initModel());
+
+    // Simulate receiving HTTP request in chunks.
+    for (auto i = 0; i < http_req.size(); i += http_req.size() / 10) {
+        auto done = false;
+        // Get the size of the data chunk. 
+        auto chunk = http_req.size() / 10;
+        // When we're near the end of the data stream, the chunk length may
+        // vary.
+        if (i + chunk > http_req.size()) {
+            chunk = http_req.size() - i;
+            done = true;
+        }
+        // Feed the parser with a data chunk and parse it.
+        parser.postBuffer(&http_req[i], chunk);
+        parser.poll();
+        if (!done) {
+            ASSERT_TRUE(parser.needData());
+        }
+    }
+
+    // Parser should have parsed the request and should expect no more data.
+    ASSERT_FALSE(parser.needData());
+    // Parsing should be successful.
+    ASSERT_TRUE(parser.httpParseOk());
+    // There should be no error message.
+    EXPECT_TRUE(parser.getErrorMessage().empty());
+
+    // Verify parsed headers etc.
+    EXPECT_EQ(HttpRequest::Method::HTTP_POST, request.getMethod());
+    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);
+
+    // Try to retrieve values carried in JSON payload.
+    ConstElementPtr json_element;
+    ASSERT_NO_THROW(json_element = request.getJsonElement("service"));
+    EXPECT_EQ("dhcp4", json_element->stringValue());
+
+    ASSERT_NO_THROW(json_element = request.getJsonElement("command"));
+    EXPECT_EQ("shutdown", json_element->stringValue());
+}
+
+// This test verifies that LWS is parsed correctly. The LWS marks line breaks
+// in the HTTP header values.
+TEST_F(HttpRequestParserTest, getLWS) {
+    // "User-Agent" header contains line breaks with whitespaces in the new
+    // lines to mark continuation of the header value.
+    std::string http_req = "GET /foo/bar HTTP/1.1\r\n"
+        "Content-Type: text/html\r\n"
+        "User-Agent: Kea/1.2 Command \r\n"
+        " Control \r\n"
+        "\tClient\r\n\r\n";
+
+    ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+    // Verify parsed values.
+    EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+    EXPECT_EQ("/foo/bar", request_.getUri());
+    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);
+}
+
+// This test verifies that the HTTP request with no headers is
+// parsed correctly.
+TEST_F(HttpRequestParserTest, noHeaders) {
+    std::string http_req = "GET /foo/bar HTTP/1.1\r\n\r\n";
+
+    ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+    // 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);
+}
+
+// This test verifies that the HTTP method can be specified in lower
+// case.
+TEST_F(HttpRequestParserTest, getLowerCase) {
+    std::string http_req = "get /foo/bar HTTP/1.1\r\n"
+        "Content-Type: text/html\r\n\r\n";
+
+    ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+    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);
+}
+
+// This test verifies that other value of the HTTP version can be
+// specified in the request.
+TEST_F(HttpRequestParserTest, http20) {
+    std::string http_req = "get /foo/bar HTTP/2.0\r\n"
+        "Content-Type: text/html\r\n\r\n";
+
+    ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+    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);
+}
+
+// This test verifies that the header with no whitespace between the
+// colon and header value is accepted.
+TEST_F(HttpRequestParserTest, noHeaderWhitespace) {
+    std::string http_req = "get /foo/bar HTTP/1.0\r\n"
+        "Content-Type:text/html\r\n\r\n";
+
+    ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+    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);
+}
+
+// This test verifies that the header value preceded with multiple
+// whitespaces is accepted.
+TEST_F(HttpRequestParserTest, multipleLeadingHeaderWhitespaces) {
+    std::string http_req = "get /foo/bar HTTP/1.0\r\n"
+        "Content-Type:     text/html\r\n\r\n";
+
+    ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+    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);
+}
+
+// This test verifies that error is reported when unsupported HTTP
+// method is used.
+TEST_F(HttpRequestParserTest, unsupportedMethod) {
+    std::string http_req = "POSTX /foo/bar HTTP/2.0\r\n"
+        "Content-Type: text/html\r\n\r\n";
+    testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when URI contains
+// an invalid character.
+TEST_F(HttpRequestParserTest, invalidUri) {
+    std::string http_req = "POST /foo/\r HTTP/2.0\r\n"
+        "Content-Type: text/html\r\n\r\n";
+    testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that the request containing a typo in the
+// HTTP version string causes parsing error.
+TEST_F(HttpRequestParserTest, invalidHTTPString) {
+    std::string http_req = "POST /foo/ HTLP/2.0\r\n"
+        "Content-Type: text/html\r\n\r\n";
+    testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when the HTTP version
+// string doesn't contain a slash character.
+TEST_F(HttpRequestParserTest, invalidHttpVersionNoSlash) {
+    std::string http_req = "POST /foo/ HTTP 1.1\r\n"
+        "Content-Type: text/html\r\n\r\n";
+    testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP version string
+// doesn't contain the minor version number.
+TEST_F(HttpRequestParserTest, invalidHttpNoMinorVersion) {
+    std::string http_req = "POST /foo/ HTTP/1\r\n"
+        "Content-Type: text/html\r\n\r\n";
+    testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP header name
+// contains an invalid character.
+TEST_F(HttpRequestParserTest, invalidHeaderName) {
+    std::string http_req = "POST /foo/ HTTP/1.1\r\n"
+        "Content-;: text/html\r\n\r\n";
+    testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP header value
+// is not preceded with the colon character.
+TEST_F(HttpRequestParserTest, noColonInHttpHeader) {
+    std::string http_req = "POST /foo/ HTTP/1.1\r\n"
+        "Content-Type text/html\r\n\r\n";
+    testInvalidHttpRequest(http_req);
+}
+
+}

+ 83 - 0
src/lib/http/tests/request_test.h

@@ -0,0 +1,83 @@
+// 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_REQUEST_TEST_H
+#define HTTP_REQUEST_TEST_H
+
+#include <http/request.h>
+#include <boost/lexical_cast.hpp>
+#include <gtest/gtest.h>
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace http {
+namespace test {
+
+/// @brief Base test fixture class for testing @ref HttpRequest class and its
+/// derivations.
+///
+/// @tparam HttpRequestType Class under test.
+template<typename HttpRequestType>
+class HttpRequestTestBase : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Creates HTTP request to be used in unit tests.
+    HttpRequestTestBase()
+        : request_() {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Does nothing.
+    virtual ~HttpRequestTestBase() {
+    }
+
+    /// @brief Initializes HTTP request context with basic information.
+    ///
+    /// It sets:
+    /// - HTTP method,
+    /// - URI,
+    /// - HTTP version number.
+    ///
+    /// @param method HTTP method as string.
+    /// @param uri URI.
+    /// @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) {
+        request_.context()->method_ = method;
+        request_.context()->uri_ = uri;
+        request_.context()->http_version_major_ = version.first;
+        request_.context()->http_version_minor_ = version.second;
+    }
+
+    /// @brief Adds HTTP header to the context.
+    ///
+    /// @param header_name HTTP header name.
+    /// @param header_value HTTP header value. This value will be converted to
+    /// a string using @c boost::lexical_cast.
+    /// @tparam ValueType Header value type.
+    template<typename ValueType>
+    void addHeaderToContext(const std::string& header_name,
+                            const ValueType& header_value) {
+        request_.context()->headers_.push_back(HttpHeaderContext());
+        request_.context()->headers_.back().name_ = header_name;
+        request_.context()->headers_.back().value_ =
+            boost::lexical_cast<std::string>(header_value);
+    }
+
+    /// @brief Instance of the @ref HttpRequest or its derivation.
+    HttpRequestType request_;
+};
+
+} // namespace test
+} // namespace http
+} // namespace isc
+
+#endif

+ 157 - 0
src/lib/http/tests/request_unittests.cc

@@ -0,0 +1,157 @@
+// 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/request.h>
+#include <http/tests/request_test.h>
+#include <boost/lexical_cast.hpp>
+#include <gtest/gtest.h>
+#include <utility>
+
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+typedef HttpRequestTestBase<HttpRequest> HttpRequestTest;
+
+TEST_F(HttpRequestTest, minimal) {
+    setContextBasics("GET", "/isc/org", std::make_pair(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_THROW(request_.getHeaderValue("Content-Length"),
+                 HttpRequestNonExistingHeader);
+}
+
+TEST_F(HttpRequestTest, includeHeaders) {
+    setContextBasics("POST", "/isc/org", std::make_pair(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);
+
+    std::string content_type;
+    ASSERT_NO_THROW(content_type = request_.getHeaderValue("Content-Type"));
+    EXPECT_EQ("application/json", content_type);
+
+    uint64_t content_length;
+    ASSERT_NO_THROW(
+        content_length = request_.getHeaderValueAsUint64("Content-Length")
+    );
+    EXPECT_EQ(1024, content_length);
+}
+
+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));
+
+    ASSERT_NO_THROW(request_.create());
+
+    request_.context()->method_ = "POST";
+    ASSERT_NO_THROW(request_.create());
+
+    request_.context()->method_ = "PUT";
+    EXPECT_THROW(request_.create(), HttpRequestError);
+}
+
+TEST_F(HttpRequestTest, requiredHttpVersion) {
+    request_.requireHttpVersion(std::make_pair(1, 0));
+    request_.requireHttpVersion(std::make_pair(1, 1));
+
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    EXPECT_NO_THROW(request_.create());
+
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 1));
+    EXPECT_NO_THROW(request_.create());
+
+    setContextBasics("POST", "/isc/org", std::make_pair(2, 0));
+    EXPECT_THROW(request_.create(), HttpRequestError);
+}
+
+TEST_F(HttpRequestTest, requiredHeader) {
+    request_.requireHeader("Content-Length");
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+
+    ASSERT_THROW(request_.create(), HttpRequestError);
+
+    addHeaderToContext("Content-Type", "application/json");
+    ASSERT_THROW(request_.create(), HttpRequestError);
+
+    addHeaderToContext("Content-Length", "2048");
+    EXPECT_NO_THROW(request_.create());
+}
+
+TEST_F(HttpRequestTest, requiredHeaderValue) {
+    request_.requireHeaderValue("Content-Type", "application/json");
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    addHeaderToContext("Content-Type", "text/html");
+
+    ASSERT_THROW(request_.create(), HttpRequestError);
+
+    addHeaderToContext("Content-Type", "application/json");
+
+    EXPECT_NO_THROW(request_.create());
+}
+
+TEST_F(HttpRequestTest, notCreated) {
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    addHeaderToContext("Content-Type", "text/html");
+    addHeaderToContext("Content-Length", "1024");
+
+    EXPECT_THROW(static_cast<void>(request_.getMethod()), HttpRequestError);
+    EXPECT_THROW(static_cast<void>(request_.getHttpVersion()),
+                 HttpRequestError);
+    EXPECT_THROW(static_cast<void>(request_.getUri()), HttpRequestError);
+    EXPECT_THROW(static_cast<void>(request_.getHeaderValue("Content-Type")),
+                 HttpRequestError);
+    EXPECT_THROW(static_cast<void>(request_.getHeaderValueAsUint64("Content-Length")),
+                 HttpRequestError);
+    EXPECT_THROW(static_cast<void>(request_.getBody()), HttpRequestError);
+
+    ASSERT_NO_THROW(request_.finalize());
+
+    EXPECT_NO_THROW(static_cast<void>(request_.getMethod()));
+    EXPECT_NO_THROW(static_cast<void>(request_.getHttpVersion()));
+    EXPECT_NO_THROW(static_cast<void>(request_.getUri()));
+    EXPECT_NO_THROW(static_cast<void>(request_.getHeaderValue("Content-Type")));
+    EXPECT_NO_THROW(
+        static_cast<void>(request_.getHeaderValueAsUint64("Content-Length"))
+    );
+    EXPECT_NO_THROW(static_cast<void>(request_.getBody()));
+}
+
+TEST_F(HttpRequestTest, getBody) {
+    std::string json_body = "{ \"param1\": \"foo\" }";
+
+    setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+    addHeaderToContext("Content-Length", json_body.length());
+
+    request_.context()->body_ = json_body;
+
+    ASSERT_NO_THROW(request_.finalize());
+
+    EXPECT_EQ(json_body, request_.getBody());
+}
+
+TEST_F(HttpRequestTest, requiresBody) {
+    ASSERT_FALSE(request_.requiresBody());
+    request_.requireHeader("Content-Length");
+    EXPECT_TRUE(request_.requiresBody());
+}
+
+}