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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
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)
--- /dev/null
+// 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");
+}
+
+}
--- /dev/null
+// 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());
+}
+
+}