]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5088] Implemented HttpResponse and derived classes.
authorMarcin Siodelski <marcin@isc.org>
Fri, 16 Dec 2016 16:27:40 +0000 (17:27 +0100)
committerMarcin Siodelski <marcin@isc.org>
Thu, 22 Dec 2016 13:56:10 +0000 (14:56 +0100)
src/lib/http/Makefile.am
src/lib/http/http_types.h [new file with mode: 0644]
src/lib/http/response.cc [new file with mode: 0644]
src/lib/http/response.h [new file with mode: 0644]
src/lib/http/response_json.cc [new file with mode: 0644]
src/lib/http/response_json.h [new file with mode: 0644]
src/lib/http/tests/Makefile.am
src/lib/http/tests/response_json_unittests.cc [new file with mode: 0644]
src/lib/http/tests/response_unittests.cc [new file with mode: 0644]

index 70168851af9d11f4d3c7e9c4e3ccddd6fbf7e4ed..d5439a8be93c10594ab418e26f8357c0ac8b5a45 100644 (file)
@@ -24,11 +24,15 @@ 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 += http_types.h
 libkea_http_la_SOURCES += post_request.cc post_request.h
 libkea_http_la_SOURCES += post_request_json.cc post_request_json.h
 libkea_http_la_SOURCES += request.cc request.h
 libkea_http_la_SOURCES += request_context.h
 libkea_http_la_SOURCES += request_parser.cc request_parser.h
+libkea_http_la_SOURCES += response.cc response.h
+libkea_http_la_SOURCES += response_json.cc response_json.h
+
 
 nodist_libkea_http_la_SOURCES = http_messages.cc http_messages.h
 
diff --git a/src/lib/http/http_types.h b/src/lib/http/http_types.h
new file mode 100644 (file)
index 0000000..0e2eeed
--- /dev/null
@@ -0,0 +1,14 @@
+// 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_TYPES_H
+#define HTTP_TYPES_H
+
+#include <utility>
+
+typedef std::pair<unsigned, unsigned> HttpVersion;
+
+#endif
diff --git a/src/lib/http/response.cc b/src/lib/http/response.cc
new file mode 100644 (file)
index 0000000..6278832
--- /dev/null
@@ -0,0 +1,104 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/response.h>
+#include <sstream>
+
+using namespace isc::http;
+
+namespace {
+
+const std::map<HttpStatusCode, std::string> status_code_to_description = {
+    { HttpStatusCode::OK, "OK" },
+    { HttpStatusCode::CREATED, "Created" },
+    { HttpStatusCode::ACCEPTED, "Accepted" },
+    { HttpStatusCode::NO_CONTENT, "No Content" },
+    { HttpStatusCode::MULTIPLE_CHOICES, "Multiple Choices" },
+    { HttpStatusCode::MOVED_PERMANENTLY, "Moved Permanently" },
+    { HttpStatusCode::MOVED_TEMPORARILY, "Moved Temporarily" },
+    { HttpStatusCode::NOT_MODIFIED, "Not Modified" },
+    { HttpStatusCode::BAD_REQUEST, "Bad Request" },
+    { HttpStatusCode::UNAUTHORIZED, "Unauthorized" },
+    { HttpStatusCode::FORBIDDEN, "Forbidden" },
+    { HttpStatusCode::NOT_FOUND, "Not Found" },
+    { HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error" },
+    { HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented" },
+    { HttpStatusCode::BAD_GATEWAY, "Bad Gateway" },
+    { HttpStatusCode::SERVICE_UNAVAILABLE, "Service Unavailable" }
+};
+
+const std::string crlf = "\r\n";
+
+}
+
+namespace isc {
+namespace http {
+
+HttpResponse::HttpResponse(const HttpVersion& version,
+                           const HttpStatusCode& status_code)
+    : http_version_(version), status_code_(status_code), headers_(),
+      body_() {
+    setGenericBody(status_code);
+}
+
+void
+HttpResponse::setBody(const std::string& body) {
+    body_ = body;
+}
+
+bool
+HttpResponse::isClientError(const HttpStatusCode& status_code) {
+    uint16_t c = statusCodeToNumber(status_code);
+    return ((c >= 400) && (c <= 500));
+}
+
+bool
+HttpResponse::isServerError(const HttpStatusCode& status_code) {
+    uint16_t c = statusCodeToNumber(status_code);
+    return ((c >= 500) && (c <= 600));
+}
+
+std::string
+HttpResponse::statusCodeToString(const HttpStatusCode& status_code) {
+    auto status_code_it = status_code_to_description.find(status_code);
+    if (status_code_it == status_code_to_description.end()) {
+        isc_throw(HttpResponseError, "internal server error: no HTTP status"
+                  " description for the given status code "
+                  << static_cast<uint16_t>(status_code));
+    }
+    return (status_code_it->second);
+}
+
+uint16_t
+HttpResponse::statusCodeToNumber(const HttpStatusCode& status_code) {
+    return (static_cast<uint16_t>(status_code));
+}
+
+std::string
+HttpResponse::toString() const {
+    std::ostringstream s;
+    s << "HTTP/" << http_version_.first << "." << http_version_.second;
+    s << " " << static_cast<uint16_t>(status_code_);
+    s << " " << statusCodeToString(status_code_) << crlf;
+
+    for (auto header = headers_.cbegin(); header != headers_.cend();
+         ++header) {
+        s << header->first << ": " << header->second << crlf;
+    }
+
+    if (!body_.empty()) {
+        s << "Content-Length" << ": " << body_.length() << crlf;
+    }
+
+    s << crlf;
+
+    s << body_;
+
+    return (s.str());
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/response.h b/src/lib/http/response.h
new file mode 100644 (file)
index 0000000..92942e4
--- /dev/null
@@ -0,0 +1,96 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_H
+#define HTTP_RESPONSE_H
+
+#include <exceptions/exceptions.h>
+#include <http/http_types.h>
+#include <boost/lexical_cast.hpp>
+#include <cstdint>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic exception thrown by @ref HttpResponse class.
+class HttpResponseError : public Exception {
+public:
+    HttpResponseError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+enum class HttpStatusCode : std::uint16_t {
+    OK = 200,
+    CREATED = 201,
+    ACCEPTED = 202,
+    NO_CONTENT = 203,
+    MULTIPLE_CHOICES = 300,
+    MOVED_PERMANENTLY = 301,
+    MOVED_TEMPORARILY = 302,
+    NOT_MODIFIED = 304,
+    BAD_REQUEST = 400,
+    UNAUTHORIZED = 401,
+    FORBIDDEN = 403,
+    NOT_FOUND = 404,
+    INTERNAL_SERVER_ERROR = 500,
+    NOT_IMPLEMENTED = 501,
+    BAD_GATEWAY = 502,
+    SERVICE_UNAVAILABLE = 503
+};
+
+class HttpResponse {
+public:
+
+    HttpResponse(const HttpVersion& version, const HttpStatusCode& status_code);
+
+    virtual ~HttpResponse() {
+    }
+
+    template<typename ValueType>
+    void addHeader(const std::string& name, const ValueType& value) {
+        try {
+            headers_[name] = boost::lexical_cast<std::string>(value);
+
+        } catch (const boost::bad_lexical_cast& ex) {
+            isc_throw(HttpResponseError, "unable to convert header value"
+                      " to a string: " << ex.what());
+        }
+    }
+
+    void setGenericBody(const HttpStatusCode& status_code) { };
+
+    virtual void setBody(const std::string& body);
+
+    static bool isClientError(const HttpStatusCode& status_code);
+
+    static bool isServerError(const HttpStatusCode& status_code);
+
+    static std::string statusCodeToString(const HttpStatusCode& status_code);
+
+    std::string toString() const;
+
+protected:
+
+    static uint16_t statusCodeToNumber(const HttpStatusCode& status_code);
+
+private:
+
+    HttpVersion http_version_;
+
+    HttpStatusCode status_code_;
+
+    std::map<std::string, std::string> headers_;
+
+    std::string body_;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/response_json.cc b/src/lib/http/response_json.cc
new file mode 100644 (file)
index 0000000..77d4f15
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/response_json.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace http {
+
+HttpResponseJson::HttpResponseJson(const HttpVersion& version,
+                                   const HttpStatusCode& status_code)
+    : HttpResponse(version, status_code) {
+    addHeader("Content-Type", "application/json");
+    setGenericBody(status_code);
+}
+
+void
+HttpResponseJson::setGenericBody(const HttpStatusCode& status_code) {
+    if (isClientError(status_code) || isServerError(status_code)) {
+        std::map<std::string, ConstElementPtr> map_elements;
+        map_elements["result"] =
+            ConstElementPtr(new IntElement(statusCodeToNumber(status_code)));
+        map_elements["text"] =
+            ConstElementPtr(new StringElement(statusCodeToString(status_code)));
+        auto body = Element::createMap();
+        body->setValue(map_elements);
+        setBodyAsJson(body);
+    }
+}
+
+void
+HttpResponseJson::setBodyAsJson(const ConstElementPtr& json_body) {
+    std::ostringstream s;
+    setBody(json_body->str());
+}
+
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/response_json.h b/src/lib/http/response_json.h
new file mode 100644 (file)
index 0000000..43b3b4f
--- /dev/null
@@ -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_RESPONSE_JSON_H
+#define HTTP_RESPONSE_JSON_H
+
+#include <cc/data.h>
+#include <http/response.h>
+
+namespace isc {
+namespace http {
+
+class HttpResponseJson : public HttpResponse {
+public:
+
+    HttpResponseJson(const HttpVersion& version,
+                     const HttpStatusCode& status_code);
+
+    virtual void setGenericBody(const HttpStatusCode& status_code);
+
+    virtual void setBodyAsJson(const data::ConstElementPtr& json_body);
+
+};
+
+}
+}
+
+#endif
index 2cec216ad62ac6291b60a11e87d48c0a43e571ee..6a0b8e14e7f209f0608e3cb9244760b9a2ce0352 100644 (file)
@@ -24,6 +24,8 @@ 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 += response_unittests.cc
+libhttp_unittests_SOURCES += response_json_unittests.cc
 libhttp_unittests_SOURCES += run_unittests.cc
 
 libhttp_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
diff --git a/src/lib/http/tests/response_json_unittests.cc b/src/lib/http/tests/response_json_unittests.cc
new file mode 100644 (file)
index 0000000..553992a
--- /dev/null
@@ -0,0 +1,114 @@
+// 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/response_json.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::http;
+
+namespace {
+
+class HttpResponseJsonTest : public ::testing::Test {
+public:
+
+    HttpResponseJsonTest()
+        : json_(), json_string_(), json_string_from_json_() {
+        json_string_ =
+            "["
+            "  {"
+            "    \"pid\": 8080,"
+            "    \"status\": 10,"
+            "    \"comment\": \"Nice comment from 8080\""
+            "  },"
+            "  {"
+            "    \"pid\": 8081,"
+            "    \"status\": 12,"
+            "    \"comment\": \"A comment from 8081\""
+            "  }"
+            "]";
+
+        json_ = Element::fromJSON(json_string_);
+        json_string_from_json_ = json_->str();
+    }
+
+    void testGenericResponse(const HttpStatusCode& status_code,
+                             const std::string& status_message) {
+        HttpResponseJson response(HttpVersion(1, 0), status_code);
+        std::ostringstream status_message_json;
+        status_message_json << "{ \"result\": "
+                            << static_cast<uint16_t>(status_code)
+                            << ", \"text\": "
+                            << "\"" << status_message << "\" }";
+        std::ostringstream response_string;
+        response_string <<
+            "HTTP/1.0 " << static_cast<uint16_t>(status_code) << " "
+            << status_message << "\r\n"
+            "Content-Type: application/json\r\n";
+
+        if (HttpResponse::isClientError(status_code) ||
+            HttpResponse::isServerError(status_code)) {
+            response_string << "Content-Length: " << status_message_json.str().size()
+                            << "\r\n";
+        }
+
+        response_string << "\r\n";
+
+        if (HttpResponse::isClientError(status_code) ||
+            HttpResponse::isServerError(status_code)) {
+            response_string << status_message_json.str();
+        }
+
+        EXPECT_EQ(response_string.str(), response.toString());
+    }
+
+    ConstElementPtr json_;
+    std::string json_string_;
+    std::string json_string_from_json_;
+
+};
+
+TEST_F(HttpResponseJsonTest, responseWithContent) {
+    HttpResponseJson response(HttpVersion(1, 1), HttpStatusCode::OK);
+    ASSERT_NO_THROW(response.setBodyAsJson(json_));
+
+    std::ostringstream response_string;
+    response_string <<
+        "HTTP/1.1 200 OK\r\n"
+        "Content-Type: application/json\r\n"
+        "Content-Length: " << json_string_from_json_.length()
+                    << "\r\n\r\n" << json_string_from_json_;
+    EXPECT_EQ(response_string.str(), response.toString());
+}
+
+TEST_F(HttpResponseJsonTest, genericResponse) {
+    testGenericResponse(HttpStatusCode::OK, "OK");
+    testGenericResponse(HttpStatusCode::CREATED, "Created");
+    testGenericResponse(HttpStatusCode::ACCEPTED, "Accepted");
+    testGenericResponse(HttpStatusCode::NO_CONTENT, "No Content");
+    testGenericResponse(HttpStatusCode::MULTIPLE_CHOICES,
+                        "Multiple Choices");
+    testGenericResponse(HttpStatusCode::MOVED_PERMANENTLY,
+                        "Moved Permanently");
+    testGenericResponse(HttpStatusCode::MOVED_TEMPORARILY,
+                        "Moved Temporarily");
+    testGenericResponse(HttpStatusCode::NOT_MODIFIED, "Not Modified");
+    testGenericResponse(HttpStatusCode::BAD_REQUEST, "Bad Request");
+    testGenericResponse(HttpStatusCode::UNAUTHORIZED, "Unauthorized");
+    testGenericResponse(HttpStatusCode::FORBIDDEN, "Forbidden");
+    testGenericResponse(HttpStatusCode::NOT_FOUND, "Not Found");
+    testGenericResponse(HttpStatusCode::INTERNAL_SERVER_ERROR,
+                        "Internal Server Error");
+    testGenericResponse(HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented");
+    testGenericResponse(HttpStatusCode::BAD_GATEWAY, "Bad Gateway");
+    testGenericResponse(HttpStatusCode::SERVICE_UNAVAILABLE,
+                        "Service Unavailable");
+}
+
+}
diff --git a/src/lib/http/tests/response_unittests.cc b/src/lib/http/tests/response_unittests.cc
new file mode 100644 (file)
index 0000000..d76ed37
--- /dev/null
@@ -0,0 +1,39 @@
+// 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/http_types.h>
+#include <http/response.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::http;
+
+namespace {
+
+TEST(HttpResponseTest, construction) {
+    const std::string sample_body =
+        "<html>"
+        "<head><title>Kea page title</title></head>"
+        "<body><h1>Some header</h1></body>"
+        "</html>";
+
+    HttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK);
+    response.addHeader("Content-Type", "text/html");
+    response.addHeader("Host", "kea.example.org");
+    response.setBody(sample_body);
+
+    std::ostringstream response_string;
+    response_string <<
+        "HTTP/1.0 200 OK\r\n"
+        "Content-Type: text/html\r\n"
+        "Host: kea.example.org\r\n"
+        "Content-Length: " << sample_body.length()
+                    << "\r\n\r\n" << sample_body;
+    EXPECT_EQ(response_string.str(), response.toString());
+}
+
+}