Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 137 additions & 52 deletions include/seastar/http/reply.hh
Original file line number Diff line number Diff line change
Expand Up @@ -55,62 +55,114 @@ class routes;

namespace http {

/**
* This type is moved to namespace level, so
* we can forward declare it.
*
* Wrapper type for HTTP status codes, including
* contants for the most common ones.
*
* Note: this was an enum, but changed to a
* struct wrapper type to make it extensible.
*
* This has the drawback of the type being
* weakly aliasable to int. This is however
* also a benefit.
*/
struct status_type {
int value;

constexpr explicit status_type(int v)
: value(v)
{}
constexpr operator int() const {
return value;
}
std::strong_ordering operator<=>(const status_type& s) const = default;

// Helper type to work around constexpr constants
// not being declarable inside their own type definition.
//
// Do not use this type directly.
struct status_init {
int value;
constexpr operator status_type() const {
return status_type(value);
}
constexpr operator int() const {
return value;
}
};

// The following list of status codes is part of the HTTP standard,
// and are defined in RFC 9110, and in a few case in older RFCs as
// listed in IANA's "HTTP Status Code Registry". Please do not add
// to this list non-standard error codes. Seastar applications should
// be able to use non-standard error codes, but shouldn't expect
// Seastar to give them official names.
static constexpr status_init continue_{100}; //!< continue
static constexpr status_init switching_protocols{101}; //!< switching_protocols
static constexpr status_init ok{200}; //!< ok
static constexpr status_init created{201}; //!< created
static constexpr status_init accepted{202}; //!< accepted
static constexpr status_init nonauthoritative_information{203}; //!< nonauthoritative_information
static constexpr status_init no_content{204}; //!< no_content
static constexpr status_init reset_content{205}; //!< reset_content
static constexpr status_init partial_content{206}; //! partial_content
static constexpr status_init multiple_choices{300}; //!< multiple_choices
static constexpr status_init moved_permanently{301}; //!< moved_permanently
static constexpr status_init moved_temporarily{302}; //!< moved_temporarily
static constexpr status_init found{moved_temporarily}; //!< found is modern name for moved_temporarily
static constexpr status_init see_other{303}; //!< see_other
static constexpr status_init not_modified{304}; //!< not_modified
static constexpr status_init use_proxy{305}; //!< use_proxy
static constexpr status_init temporary_redirect{307}; //!< temporary_redirect
static constexpr status_init permanent_redirect{308}; //!< permanent_redirect
static constexpr status_init bad_request{400}; //!< bad_request
static constexpr status_init unauthorized{401}; //!< unauthorized
static constexpr status_init payment_required{402}; //!< payment_required
static constexpr status_init forbidden{403}; //!< forbidden
static constexpr status_init not_found{404}; //!< not_found
static constexpr status_init method_not_allowed{405}; //!< method_not_allowed
static constexpr status_init not_acceptable{406}; //!< not_acceptable
static constexpr status_init proxy_authentication_required{407}; //<! proxy_authentication_required
static constexpr status_init request_timeout{408}; //!< request_timeout
static constexpr status_init conflict{409}; //!< conflict
static constexpr status_init gone{410}; //!< gone
static constexpr status_init length_required{411}; //!< length_required
static constexpr status_init precondition_failed{412}; //!< precondition_failed
static constexpr status_init payload_too_large{413}; //!< payload_too_large
static constexpr status_init uri_too_long{414}; //!< uri_too_long
static constexpr status_init unsupported_media_type{415}; //!< unsupported_media_type
static constexpr status_init range_not_satisfiable{416}; //!< range_not_satisfiable
static constexpr status_init expectation_failed{417}; //!< expectation_failed
static constexpr status_init page_expired{419}; //!< page_expired
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can those non-standard codes be marked as [[deprecated(...)]] early, to be removed eventually, but also early?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably a good idea.

static constexpr status_init misdirected_request{421}; //!< misdirected_request
static constexpr status_init unprocessable_entity{422}; //!< unprocessable_entity
static constexpr status_init upgrade_required{426}; //!< upgrade_required
static constexpr status_init too_many_requests{429}; //!< too_many_requests
static constexpr status_init login_timeout{440}; //!< login_timeout
static constexpr status_init internal_server_error{500}; //!< internal_server_error
static constexpr status_init not_implemented{501}; //!< not_implemented
static constexpr status_init bad_gateway{502}; //!< bad_gateway
static constexpr status_init service_unavailable{503}; //!< service_unavailable
static constexpr status_init gateway_timeout{504}; //!< gateway_timeout
static constexpr status_init http_version_not_supported{505}; //!< http_version_not_supported
static constexpr status_init insufficient_storage{507}; //!< insufficient_storage
static constexpr status_init bandwidth_limit_exceeded{509}; //!< bandwidth_limit_exceeded
static constexpr status_init network_read_timeout{598}; //!< network_read_timeout
static constexpr status_init network_connect_timeout{599}; //!< network_connect_timeout
};

/**
* A reply to be sent to a client.
*/
struct reply {
/**
* The status of the reply.
*/
enum class status_type {
continue_ = 100, //!< continue
switching_protocols = 101, //!< switching_protocols
ok = 200, //!< ok
created = 201, //!< created
accepted = 202, //!< accepted
nonauthoritative_information = 203, //!< nonauthoritative_information
no_content = 204, //!< no_content
reset_content = 205, //!< reset_content
partial_content = 206, //! partial_content
multiple_choices = 300, //!< multiple_choices
moved_permanently = 301, //!< moved_permanently
moved_temporarily = 302, //!< moved_temporarily
see_other = 303, //!< see_other
not_modified = 304, //!< not_modified
use_proxy = 305, //!< use_proxy
temporary_redirect = 307, //!< temporary_redirect
permanent_redirect = 308, //!< permanent_redirect
bad_request = 400, //!< bad_request
unauthorized = 401, //!< unauthorized
payment_required = 402, //!< payment_required
forbidden = 403, //!< forbidden
not_found = 404, //!< not_found
method_not_allowed = 405, //!< method_not_allowed
not_acceptable = 406, //!< not_acceptable
request_timeout = 408, //!< request_timeout
conflict = 409, //!< conflict
gone = 410, //!< gone
length_required = 411, //!< length_required
payload_too_large = 413, //!< payload_too_large
uri_too_long = 414, //!< uri_too_long
unsupported_media_type = 415, //!< unsupported_media_type
expectation_failed = 417, //!< expectation_failed
page_expired = 419, //!< page_expired
unprocessable_entity = 422, //!< unprocessable_entity
upgrade_required = 426, //!< upgrade_required
too_many_requests = 429, //!< too_many_requests
login_timeout = 440, //!< login_timeout
internal_server_error = 500, //!< internal_server_error
not_implemented = 501, //!< not_implemented
bad_gateway = 502, //!< bad_gateway
service_unavailable = 503, //!< service_unavailable
gateway_timeout = 504, //!< gateway_timeout
http_version_not_supported = 505, //!< http_version_not_supported
insufficient_storage = 507, //!< insufficient_storage
bandwidth_limit_exceeded = 509, //!< bandwidth_limit_exceeded
network_read_timeout = 598, //!< network_read_timeout
network_connect_timeout = 599, //!< network_connect_timeout
} _status;
using status_type = http::status_type;
status_type _status;

/**
* HTTP status classes
Expand All @@ -137,7 +189,7 @@ struct reply {
* @return one of the \ref status_class values
*/
static constexpr status_class classify_status(status_type http_status) {
auto sc = static_cast<std::underlying_type_t<status_type>>(http_status) / 100;
auto sc = int(http_status) / 100;
if (sc < 1 || sc > 5) [[unlikely]] {
return status_class::unclassified;
}
Expand Down Expand Up @@ -287,7 +339,28 @@ private:
friend class httpd::connection;
};

std::ostream& operator<<(std::ostream& os, reply::status_type st);
std::ostream& operator<<(std::ostream& os, status_type st);
std::ostream& operator<<(std::ostream& os, status_type::status_init st);

/**
* Binds a defined status value to a lexical name.
* Must be called at dlload time (i.e. as a static const declaraion
* on file level), as this modifies a structure that must be readonly
* during reactor runtime.
*
* Pattern:
*
* .hh file:
* constexpr seastar::http::status_type MY_ERROR(<number>);
*
* .cc file
*
* static const auto init_my_error = seastar::http::bind_status_name(MY_ERROR, "My Error that is nice");
*
* @return a view of the bound string name.
*
*/
std::string_view bind_status_name(status_type, std::string_view);

} // namespace http

Expand All @@ -299,5 +372,17 @@ SEASTAR_MODULE_EXPORT_END
}

#if FMT_VERSION >= 90000
template <> struct fmt::formatter<seastar::http::reply::status_type> : fmt::ostream_formatter {};
template <> struct fmt::formatter<seastar::http::status_type> : fmt::ostream_formatter {};
template <> struct fmt::formatter<seastar::http::status_type::status_init> : fmt::formatter<seastar::http::status_type> {};
#endif

/**
* Temporary addition to enable existing code, using things
* like std::underlying_type_t<status_type> for casts etc
* to continue worksing. This is not a fully kosher way of
* treating this overload, but it is mostly harmless...
*/
template<>
struct std::underlying_type<seastar::http::status_type> {
using type = int;
};
148 changes: 90 additions & 58 deletions src/http/reply.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,84 +42,116 @@ module seastar;
#include <seastar/http/common.hh>
#include <seastar/http/response_parser.hh>
#include <seastar/core/loop.hh>
#include <seastar/core/reactor.hh>
#endif

template<>
struct std::hash<seastar::http::status_type> : public std::hash<int>
{};

namespace seastar {

namespace http {

namespace status_strings {

static const std::unordered_map<reply::status_type, std::string_view> status_strings = {
{reply::status_type::continue_, "100 Continue"},
{reply::status_type::switching_protocols, "101 Switching Protocols"},
{reply::status_type::ok, "200 OK"},
{reply::status_type::created, "201 Created"},
{reply::status_type::accepted, "202 Accepted"},
{reply::status_type::nonauthoritative_information, "203 Non-Authoritative Information"},
{reply::status_type::no_content, "204 No Content"},
{reply::status_type::reset_content, "205 Reset Content"},
{reply::status_type::partial_content, "206 Partial Content"},
{reply::status_type::multiple_choices, "300 Multiple Choices"},
{reply::status_type::moved_permanently, "301 Moved Permanently"},
{reply::status_type::moved_temporarily, "302 Moved Temporarily"},
{reply::status_type::see_other, "303 See Other"},
{reply::status_type::not_modified, "304 Not Modified"},
{reply::status_type::use_proxy, "305 Use Proxy"},
{reply::status_type::temporary_redirect, "307 Temporary Redirect"},
{reply::status_type::permanent_redirect, "308 Permanent Redirect"},
{reply::status_type::bad_request, "400 Bad Request"},
{reply::status_type::unauthorized, "401 Unauthorized"},
{reply::status_type::payment_required, "402 Payment Required"},
{reply::status_type::forbidden, "403 Forbidden"},
{reply::status_type::not_found, "404 Not Found"},
{reply::status_type::method_not_allowed, "405 Method Not Allowed"},
{reply::status_type::not_acceptable, "406 Not Acceptable"},
{reply::status_type::request_timeout, "408 Request Timeout"},
{reply::status_type::conflict, "409 Conflict"},
{reply::status_type::gone, "410 Gone"},
{reply::status_type::length_required, "411 Length Required"},
{reply::status_type::payload_too_large, "413 Payload Too Large"},
{reply::status_type::uri_too_long, "414 URI Too Long"},
{reply::status_type::unsupported_media_type, "415 Unsupported Media Type"},
{reply::status_type::expectation_failed, "417 Expectation Failed"},
{reply::status_type::page_expired, "419 Page Expired"},
{reply::status_type::unprocessable_entity, "422 Unprocessable Entity"},
{reply::status_type::upgrade_required, "426 Upgrade Required"},
{reply::status_type::too_many_requests, "429 Too Many Requests"},
{reply::status_type::login_timeout, "440 Login Timeout"},
{reply::status_type::internal_server_error, "500 Internal Server Error"},
{reply::status_type::not_implemented, "501 Not Implemented"},
{reply::status_type::bad_gateway, "502 Bad Gateway"},
{reply::status_type::service_unavailable, "503 Service Unavailable"},
{reply::status_type::gateway_timeout, "504 Gateway Timeout"},
{reply::status_type::http_version_not_supported, "505 HTTP Version Not Supported"},
{reply::status_type::insufficient_storage, "507 Insufficient Storage"},
{reply::status_type::bandwidth_limit_exceeded, "509 Bandwidth Limit Exceeded"},
{reply::status_type::network_read_timeout, "598 Network Read Timeout"},
{reply::status_type::network_connect_timeout, "599 Network Connect Timeout"}};
static auto& status_strings() {
static std::unordered_map<status_type, std::string_view> status_strings = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't this map remain a global static file-local variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we want to modify it at dllink time. If it was global, it would be subject to static init order fiasco. By placing it in a function we can ensure we init it before modifying.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Please add a code comment about it, because it's not immediately obvious

{reply::status_type::continue_, "Continue"},
{reply::status_type::switching_protocols, "Switching Protocols"},
{reply::status_type::ok, "OK"},
{reply::status_type::created, "Created"},
{reply::status_type::accepted, "Accepted"},
{reply::status_type::nonauthoritative_information, "Non-Authoritative Information"},
{reply::status_type::no_content, "No Content"},
{reply::status_type::reset_content, "Reset Content"},
{reply::status_type::partial_content, "Partial Content"},
{reply::status_type::multiple_choices, "Multiple Choices"},
{reply::status_type::moved_permanently, "Moved Permanently"},
{reply::status_type::moved_temporarily, "Found"},
{reply::status_type::see_other, "See Other"},
{reply::status_type::not_modified, "Not Modified"},
{reply::status_type::use_proxy, "Use Proxy"},
{reply::status_type::temporary_redirect, "Temporary Redirect"},
{reply::status_type::permanent_redirect, "Permanent Redirect"},
{reply::status_type::bad_request, "Bad Request"},
{reply::status_type::unauthorized, "Unauthorized"},
{reply::status_type::payment_required, "Payment Required"},
{reply::status_type::forbidden, "Forbidden"},
{reply::status_type::not_found, "Not Found"},
{reply::status_type::method_not_allowed, "Method Not Allowed"},
{reply::status_type::not_acceptable, "Not Acceptable"},
{reply::status_type::proxy_authentication_required, "Proxy Authentication Required"},
{reply::status_type::request_timeout, "Request Timeout"},
{reply::status_type::conflict, "Conflict"},
{reply::status_type::gone, "Gone"},
{reply::status_type::length_required, "Length Required"},
{reply::status_type::precondition_failed, "Precondition Failed"},
{reply::status_type::payload_too_large, "Content Too Large"},
{reply::status_type::uri_too_long, "URI Too Long"},
{reply::status_type::unsupported_media_type, "Unsupported Media Type"},
{reply::status_type::range_not_satisfiable, "Range Not Satisfiable"},
{reply::status_type::expectation_failed, "Expectation Failed"},
{reply::status_type::page_expired, "Page Expired"},
{reply::status_type::misdirected_request, "Misdirected Request"},
{reply::status_type::unprocessable_entity, "Unprocessable Content"},
{reply::status_type::upgrade_required, "Upgrade Required"},
{reply::status_type::too_many_requests, "Too Many Requests"},
{reply::status_type::login_timeout, "Login Timeout"},
{reply::status_type::internal_server_error, "Internal Server Error"},
{reply::status_type::not_implemented, "Not Implemented"},
{reply::status_type::bad_gateway, "Bad Gateway"},
{reply::status_type::service_unavailable, "Service Unavailable"},
{reply::status_type::gateway_timeout, "Gateway Timeout"},
{reply::status_type::http_version_not_supported, "HTTP Version Not Supported"},
{reply::status_type::insufficient_storage, "Insufficient Storage"},
{reply::status_type::bandwidth_limit_exceeded, "Bandwidth Limit Exceeded"},
{reply::status_type::network_read_timeout, "Network Read Timeout"},
{reply::status_type::network_connect_timeout, "Network Connect Timeout"},
{reply::status_type::insufficient_storage, "Insufficient Storage"}
};

return status_strings;
}

template<typename Func>
static auto with_string_view(reply::status_type status, Func&& func) -> std::invoke_result_t<Func, std::string_view> {
if (auto found = status_strings.find(status); found != status_strings.end()) [[likely]] {
static auto with_string_view(status_type status, Func&& func) -> std::invoke_result_t<Func, std::string_view> {
const auto& ss = status_strings();
if (auto found = ss.find(status); found != ss.end()) [[likely]] {
return func(found->second);
}
auto dummy_buf = std::to_string(int(status));
return func(dummy_buf);
}
auto dummy_buf = std::to_string(int(status));
return func(dummy_buf);
}

} // namespace status_strings

std::ostream& operator<<(std::ostream& os, reply::status_type st) {
std::string_view bind_status_name(status_type st, std::string_view name) {
if (engine_is_ready()) {
throw std::runtime_error("Cannot bind http status name in runtime");
}
auto& map = status_strings::status_strings();
auto p = map.try_emplace(st, name);
if (!p.second) {
throw std::invalid_argument(seastar::format("Status {} {} already bound. Previous name: {}",
int(st), name, p.first->second
));
}
return p.first->second;
}

std::ostream& operator<<(std::ostream& os, status_type st) {
return status_strings::with_string_view(st, [&](std::string_view txt) -> std::ostream& {
return os << txt;
return os << int(st) << " " << txt;
});
}

std::ostream& operator<<(std::ostream& os, status_type::status_init st) {
return os << status_type(st);
}

sstring reply::response_line() const {
return status_strings::with_string_view(_status, [this](std::string_view txt) {
return seastar::format("HTTP/{} {}\r\n", _version, txt);
});
return seastar::format("HTTP/{} {}\r\n", _version, _status);
}

void reply::write_body(const sstring& content_type, noncopyable_function<future<>(output_stream<char>&&)>&& body_writer) {
Expand Down
Loading