From e5cfe9c11f520839f0ca0271daeab4f447ed8efd Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 30 May 2017 15:02:14 -0700 Subject: [PATCH] Refactor treatment of status code and obsolete reason (API Change): fix #398 A new enum status is added for the status code. The function obsolete_reason returns default reason phrasing. If a response has an empty reason, the serializer will automatically insert the default reason phrase for the status code. --- CHANGELOG.md | 1 + doc/quickref.xml | 3 +- include/beast/http.hpp | 1 + include/beast/http/impl/message.ipp | 65 ------------- include/beast/http/impl/serializer.ipp | 9 +- include/beast/http/impl/status.ipp | 118 ++++++++++++++++++++++++ include/beast/http/message.hpp | 4 - include/beast/http/reason.hpp | 26 ------ include/beast/http/status.hpp | 101 ++++++++++++++++++++ include/beast/websocket/impl/stream.ipp | 3 - test/Jamfile | 1 + test/http/CMakeLists.txt | 1 + test/http/message.cpp | 8 -- test/http/status.cpp | 100 ++++++++++++++++++++ 14 files changed, 333 insertions(+), 108 deletions(-) create mode 100644 include/beast/http/impl/status.ipp delete mode 100644 include/beast/http/reason.hpp create mode 100644 include/beast/http/status.hpp create mode 100644 test/http/status.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f0a06c4..75052faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Version 47 API Changes: * Refactor treatment of request-method +* Refactor treatment of status code and obsolete reason -------------------------------------------------------------------------------- diff --git a/doc/quickref.xml b/doc/quickref.xml index 93f5a67e..b2a15f7f 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -65,11 +65,11 @@ is_keep_alive is_upgrade make_serializer + obsolete_reason operator<< prepare read read_some - reason_string string_to_verb swap to_string @@ -88,6 +88,7 @@ connection error + status verb Concepts diff --git a/include/beast/http.hpp b/include/beast/http.hpp index 398bf211..16eb2625 100644 --- a/include/beast/http.hpp +++ b/include/beast/http.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/include/beast/http/impl/message.ipp b/include/beast/http/impl/message.ipp index 1ee68257..d951f951 100644 --- a/include/beast/http/impl/message.ipp +++ b/include/beast/http/impl/message.ipp @@ -254,71 +254,6 @@ prepare(message& msg, "invalid version for Connection: upgrade"}); } -namespace detail { - -template -string_view -reason_string(int status) -{ - switch(status) - { - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 307: return "Temporary Redirect"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-URI Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "HTTP Version Not Supported"; - - case 306: return ""; - default: - break; - } - return ""; -} - -} // detail - -inline -string_view const -reason_string(int status) -{ - return detail::reason_string(status); -} - } // http } // beast diff --git a/include/beast/http/impl/serializer.ipp b/include/beast/http/impl/serializer.ipp index 7b3600f4..b8f9db29 100644 --- a/include/beast/http/impl/serializer.ipp +++ b/include/beast/http/impl/serializer.ipp @@ -9,9 +9,11 @@ #define BEAST_HTTP_IMPL_SERIALIZER_IPP #include +#include #include #include + namespace beast { namespace http { namespace detail { @@ -41,7 +43,12 @@ write_start_line(std::ostream& os, case 10: os << "HTTP/1.0 "; break; case 11: os << "HTTP/1.1 "; break; } - os << msg.status << " " << msg.reason() << "\r\n"; + auto const reason = msg.reason(); + if(reason.empty()) + os << msg.status << " " << msg.reason() << "\r\n"; + else + os << msg.status << " " << + obsolete_reason(static_cast(msg.status)) << "\r\n"; } template diff --git a/include/beast/http/impl/status.ipp b/include/beast/http/impl/status.ipp new file mode 100644 index 00000000..93f9e104 --- /dev/null +++ b/include/beast/http/impl/status.ipp @@ -0,0 +1,118 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_STATUS_IPP +#define BEAST_HTTP_IMPL_STATUS_IPP + +namespace beast { +namespace http { +namespace detail { + +template +string_view +status_to_string(int v) +{ + switch(static_cast(v)) + { + // 1xx + case status::continue_: return "Continue"; + case status::switching_protocols: return "Switching Protocols"; + case status::processing: return "Processing"; + + // 2xx + case status::ok: return "OK"; + case status::created: return "Created"; + case status::accepted: return "Accepted"; + case status::non_authoritative_information: return "Non-Authoritative Information"; + case status::no_content: return "No Content"; + case status::reset_content: return "Reset Content"; + case status::partial_content: return "Partial Content"; + case status::multi_status: return "Multi-Status"; + case status::already_reported: return "Already Reported"; + case status::im_used: return "IM Used"; + + // 3xx + case status::multiple_choices: return "Multiple Choices"; + case status::moved_permanently: return "Moved Permanently"; + case status::found: return "Found"; + case status::see_other: return "See Other"; + case status::not_modified: return "Not Modified"; + case status::use_proxy: return "Use Proxy"; + case status::temporary_redirect: return "Temporary Redirect"; + case status::permanent_redirect: return "Permanent Redirect"; + + // 4xx + case status::bad_request: return "Bad Request"; + case status::unauthorized: return "Unauthorized"; + case status::payment_required: return "Payment Required"; + case status::forbidden: return "Forbidden"; + case status::not_found: return "Not Found"; + case status::method_not_allowed: return "Method Not Allowed"; + case status::not_acceptable: return "Not Acceptable"; + case status::proxy_authentication_required: return "Proxy Authentication Required"; + case status::request_timeout: return "Request Timeout"; + case status::conflict: return "Conflict"; + case status::gone: return "Gone"; + case status::length_required: return "Length Required"; + case status::precondition_failed: return "Precondition Failed"; + case status::payload_too_large: return "Payload Too Large"; + case status::uri_too_long: return "URI Too Long"; + case status::unsupported_media_type: return "Unsupported Media Type"; + case status::range_not_satisfiable: return "Range Not Satisfiable"; + case status::expectation_failed: return "Expectation Failed"; + case status::misdirected_request: return "Misdirected Request"; + case status::unprocessable_entity: return "Unprocessable Entity"; + case status::locked: return "Locked"; + case status::failed_dependency: return "Failed Dependency"; + case status::upgrade_required: return "Upgrade Required"; + case status::precondition_required: return "Precondition Required"; + case status::too_many_requests: return "Too Many Requests"; + case status::request_header_fields_too_large: return "Request Header Fields Too Large"; + case status::connection_closed_without_response: return "Connection Closed Without Response"; + case status::unavailable_for_legal_reasons: return "Unavailable For Legal Reasons"; + case status::client_closed_request: return "Client Closed Request"; + // 5xx + case status::internal_server_error: return "Internal Server Error"; + case status::not_implemented: return "Not Implemented"; + case status::bad_gateway: return "Bad Gateway"; + case status::service_unavailable: return "Service Unavailable"; + case status::gateway_timeout: return "Gateway Timeout"; + case status::http_version_not_supported: return "HTTP Version Not Supported"; + case status::variant_also_negotiates: return "Variant Also Negotiates"; + case status::insufficient_storage: return "Insufficient Storage"; + case status::loop_detected: return "Loop Detected"; + case status::not_extended: return "Not Extended"; + case status::network_authentication_required: return "Network Authentication Required"; + case status::network_connect_timeout_error: return "Network Connect Timeout Error"; + + default: + break; + } + return "Unknown Status"; +} + +} // detail + +inline +string_view +obsolete_reason(status v) +{ + return detail::status_to_string( + static_cast(v)); +} + +inline +std::ostream& +operator<<(std::ostream& os, status v) +{ + return os << obsolete_reason(v); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp index 5ee7589c..629fe72e 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -527,10 +527,6 @@ void prepare(message& msg, Options&&... options); -/** Returns the text for a known HTTP status code. */ -string_view const -reason_string(int status); - } // http } // beast diff --git a/include/beast/http/reason.hpp b/include/beast/http/reason.hpp deleted file mode 100644 index fb8d3a15..00000000 --- a/include/beast/http/reason.hpp +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_REASON_HPP -#define BEAST_HTTP_REASON_HPP - -#include -#include - -namespace beast { -namespace http { - -namespace detail { - - -} // detail - - -} // http -} // beast - -#endif diff --git a/include/beast/http/status.hpp b/include/beast/http/status.hpp new file mode 100644 index 00000000..3fcfd7ce --- /dev/null +++ b/include/beast/http/status.hpp @@ -0,0 +1,101 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_STATUS_HPP +#define BEAST_HTTP_STATUS_HPP + +#include +#include +#include + +namespace beast { +namespace http { + +enum class status : int +{ + continue_ = 100, + switching_protocols = 101, + processing = 102, + + ok = 200, + created = 201, + accepted = 202, + non_authoritative_information = 203, + no_content = 204, + reset_content = 205, + partial_content = 206, + multi_status = 207, + already_reported = 208, + im_used = 226, + + multiple_choices = 300, + moved_permanently = 301, + found = 302, + see_other = 303, + not_modified = 304, + use_proxy = 305, + temporary_redirect = 307, + permanent_redirect = 308, + + bad_request = 400, + unauthorized = 401, + payment_required = 402, + forbidden = 403, + not_found = 404, + method_not_allowed = 405, + not_acceptable = 406, + proxy_authentication_required = 407, + request_timeout = 408, + conflict = 409, + gone = 410, + length_required = 411, + precondition_failed = 412, + payload_too_large = 413, + uri_too_long = 414, + unsupported_media_type = 415, + range_not_satisfiable = 416, + expectation_failed = 417, + misdirected_request = 421, + unprocessable_entity = 422, + locked = 423, + failed_dependency = 424, + upgrade_required = 426, + precondition_required = 428, + too_many_requests = 429, + request_header_fields_too_large = 431, + connection_closed_without_response = 444, + unavailable_for_legal_reasons = 451, + client_closed_request = 499, + + internal_server_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503, + gateway_timeout = 504, + http_version_not_supported = 505, + variant_also_negotiates = 506, + insufficient_storage = 507, + loop_detected = 508, + not_extended = 510, + network_authentication_required = 511, + network_connect_timeout_error = 599 +}; + +/// Returns the obsolete reason-phrase text for a status code. +string_view +obsolete_reason(status v); + +/// Outputs the reason phrase of a status code to a stream. +std::ostream& +operator<<(std::ostream& os, status v); + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index 7083ef0a..dcdde93c 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -217,7 +217,6 @@ build_response(http::header const& req, { response_type res; res.status = 400; - res.reason(http::reason_string(res.status)); res.version = req.version; res.body = text; prepare(res); @@ -248,7 +247,6 @@ build_response(http::header const& req, { response_type res; res.status = 426; - res.reason(http::reason_string(res.status)); res.version = req.version; res.fields.insert("Sec-WebSocket-Version", "13"); prepare(res); @@ -266,7 +264,6 @@ build_response(http::header const& req, res.fields, unused, offer, pmd_opts_); } res.status = 101; - res.reason(http::reason_string(res.status)); res.version = req.version; res.fields.insert("Upgrade", "websocket"); res.fields.insert("Connection", "upgrade"); diff --git a/test/Jamfile b/test/Jamfile index 2254674e..902fd5dc 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -53,6 +53,7 @@ unit-test http-tests : http/read.cpp http/rfc7230.cpp http/serializer.cpp + http/status.cpp http/string_body.cpp http/type_traits.cpp http/verb.cpp diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index fb87e7ea..8e54e9fa 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable (http-tests read.cpp rfc7230.cpp serializer.cpp + status.cpp string_body.cpp type_traits.cpp verb.cpp diff --git a/test/http/message.cpp b/test/http/message.cpp index 8e9f2695..14b817b6 100644 --- a/test/http/message.cpp +++ b/test/http/message.cpp @@ -296,13 +296,6 @@ public: }(); } - void - testReasonString() - { - for(int i = 1; i <= 999; ++i) - BEAST_EXPECT(! reason_string(i).empty()); - } - void run() override { @@ -312,7 +305,6 @@ public: testPrepare(); testSwap(); testSpecialMembers(); - testReasonString(); } }; diff --git a/test/http/status.cpp b/test/http/status.cpp new file mode 100644 index 00000000..e7389d5a --- /dev/null +++ b/test/http/status.cpp @@ -0,0 +1,100 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include + +namespace beast { +namespace http { + +class status_test + : public beast::unit_test::suite +{ +public: + void + testStatus() + { + auto const good = + [&](status v) + { + BEAST_EXPECT(obsolete_reason(v) != "Unknown Status"); + }; + good(status::continue_); + good(status::switching_protocols); + good(status::processing); + good(status::ok); + good(status::created); + good(status::accepted); + good(status::non_authoritative_information); + good(status::no_content); + good(status::reset_content); + good(status::partial_content); + good(status::multi_status); + good(status::already_reported); + good(status::im_used); + good(status::multiple_choices); + good(status::moved_permanently); + good(status::found); + good(status::see_other); + good(status::not_modified); + good(status::use_proxy); + good(status::temporary_redirect); + good(status::permanent_redirect); + good(status::bad_request); + good(status::unauthorized); + good(status::payment_required); + good(status::forbidden); + good(status::not_found); + good(status::method_not_allowed); + good(status::not_acceptable); + good(status::proxy_authentication_required); + good(status::request_timeout); + good(status::conflict); + good(status::gone); + good(status::length_required); + good(status::precondition_failed); + good(status::payload_too_large); + good(status::uri_too_long); + good(status::unsupported_media_type); + good(status::range_not_satisfiable); + good(status::expectation_failed); + good(status::misdirected_request); + good(status::unprocessable_entity); + good(status::locked); + good(status::failed_dependency); + good(status::upgrade_required); + good(status::precondition_required); + good(status::too_many_requests); + good(status::request_header_fields_too_large); + good(status::unavailable_for_legal_reasons); + good(status::internal_server_error); + good(status::not_implemented); + good(status::bad_gateway); + good(status::service_unavailable); + good(status::gateway_timeout); + good(status::http_version_not_supported); + good(status::variant_also_negotiates); + good(status::insufficient_storage); + good(status::loop_detected); + good(status::not_extended); + good(status::network_authentication_required); + } + + void + run() + { + testStatus(); + } +}; + +BEAST_DEFINE_TESTSUITE(status,http,beast); + +} // http +} // beast +