From: Marcin Siodelski Date: Fri, 16 Dec 2016 16:27:40 +0000 (+0100) Subject: [5088] Implemented HttpResponse and derived classes. X-Git-Tag: trac5090_base~2^2~15 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d43e5ecd687632ff16af56bfa8753697c4dcbdf6;p=thirdparty%2Fkea.git [5088] Implemented HttpResponse and derived classes. --- diff --git a/src/lib/http/Makefile.am b/src/lib/http/Makefile.am index 70168851af..d5439a8be9 100644 --- a/src/lib/http/Makefile.am +++ b/src/lib/http/Makefile.am @@ -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 index 0000000000..0e2eeedbd0 --- /dev/null +++ b/src/lib/http/http_types.h @@ -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 + +typedef std::pair HttpVersion; + +#endif diff --git a/src/lib/http/response.cc b/src/lib/http/response.cc new file mode 100644 index 0000000000..6278832726 --- /dev/null +++ b/src/lib/http/response.cc @@ -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 +#include + +using namespace isc::http; + +namespace { + +const std::map 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(status_code)); + } + return (status_code_it->second); +} + +uint16_t +HttpResponse::statusCodeToNumber(const HttpStatusCode& status_code) { + return (static_cast(status_code)); +} + +std::string +HttpResponse::toString() const { + std::ostringstream s; + s << "HTTP/" << http_version_.first << "." << http_version_.second; + s << " " << static_cast(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 index 0000000000..92942e4cf6 --- /dev/null +++ b/src/lib/http/response.h @@ -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 +#include +#include +#include +#include +#include + +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 + void addHeader(const std::string& name, const ValueType& value) { + try { + headers_[name] = boost::lexical_cast(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 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 index 0000000000..77d4f15519 --- /dev/null +++ b/src/lib/http/response_json.cc @@ -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 + +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 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 index 0000000000..43b3b4f882 --- /dev/null +++ b/src/lib/http/response_json.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_RESPONSE_JSON_H +#define HTTP_RESPONSE_JSON_H + +#include +#include + +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 diff --git a/src/lib/http/tests/Makefile.am b/src/lib/http/tests/Makefile.am index 2cec216ad6..6a0b8e14e7 100644 --- a/src/lib/http/tests/Makefile.am +++ b/src/lib/http/tests/Makefile.am @@ -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 index 0000000000..553992a19b --- /dev/null +++ b/src/lib/http/tests/response_json_unittests.cc @@ -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 +#include +#include +#include +#include + +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(status_code) + << ", \"text\": " + << "\"" << status_message << "\" }"; + std::ostringstream response_string; + response_string << + "HTTP/1.0 " << static_cast(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 index 0000000000..d76ed37b9e --- /dev/null +++ b/src/lib/http/tests/response_unittests.cc @@ -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 +#include +#include +#include +#include + +using namespace isc::http; + +namespace { + +TEST(HttpResponseTest, construction) { + const std::string sample_body = + "" + "Kea page title" + "

Some header

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