include/ylt/standalone/cinatra/coro_http_response.hpp (382 lines of code) (raw):

#pragma once #include <charconv> #include <functional> #include <optional> #include <string> #include <string_view> #include <system_error> #include <unordered_map> #include <vector> #include "async_simple/coro/Lazy.h" #include "async_simple/coro/SyncAwait.h" #include "cookie.hpp" #include "define.h" #ifdef CINATRA_ENABLE_GZIP #include "gzip.hpp" #endif #ifdef CINATRA_ENABLE_BROTLI #include "brzip.hpp" #endif #include "picohttpparser.h" #include "response_cv.hpp" #include "time_util.hpp" #include "utils.hpp" namespace cinatra { struct resp_header { std::string key; std::string value; }; struct resp_header_sv { std::string_view key; std::string_view value; }; enum class format_type { normal, chunked, }; class coro_http_connection; class coro_http_response { public: coro_http_response(coro_http_connection *conn) : status_(status_type::not_implemented), fmt_type_(format_type::normal), delay_(false), conn_(conn) {} void set_status(cinatra::status_type status) { status_ = status; } void set_content(std::string content) { content_ = std::move(content); has_set_content_ = true; } void set_status_and_content( status_type status, std::string content, content_encoding encoding = content_encoding::none, std::string_view client_encoding_type = "") { set_status_and_content_view(status, std::move(content), encoding, false, client_encoding_type); } template <typename String> void set_status_and_content_view( status_type status, String content = "", content_encoding encoding = content_encoding::none, bool is_view = true, std::string_view client_encoding_type = "") { status_ = status; #ifdef CINATRA_ENABLE_GZIP if (encoding == content_encoding::gzip) { if (client_encoding_type.empty() || client_encoding_type.find("gzip") != std::string_view::npos) { std::string encode_str; bool r = gzip_codec::compress(content, encode_str); if (!r) { set_status_and_content(status_type::internal_server_error, "gzip compress error"); } else { add_header("Content-Encoding", "gzip"); set_content(std::move(encode_str)); } } else { if (is_view) { content_view_ = content; } else { content_ = std::move(content); } } has_set_content_ = true; return; } if (encoding == content_encoding::deflate) { if (client_encoding_type.empty() || client_encoding_type.find("deflate") != std::string_view::npos) { std::string deflate_str; bool r = gzip_codec::deflate(content, deflate_str); if (!r) { set_status_and_content(status_type::internal_server_error, "deflate compress error"); } else { add_header("Content-Encoding", "deflate"); set_content(std::move(deflate_str)); } } else { if (is_view) { content_view_ = content; } else { content_ = std::move(content); } } has_set_content_ = true; return; } #endif #ifdef CINATRA_ENABLE_BROTLI if (encoding == content_encoding::br) { if (client_encoding_type.empty() || client_encoding_type.find("br") != std::string_view::npos) { std::string br_str; bool r = br_codec::brotli_compress(content, br_str); if (!r) { set_status_and_content(status_type::internal_server_error, "br compress error"); } else { add_header("Content-Encoding", "br"); set_content(std::move(br_str)); } } else { if (is_view) { content_view_ = content; } else { content_ = std::move(content); } } has_set_content_ = true; return; } #endif if (is_view) { content_view_ = content; } else { content_ = std::move(content); } has_set_content_ = true; } void set_delay(bool r) { delay_ = r; } bool get_delay() const { return delay_; } void set_format_type(format_type type) { fmt_type_ = type; } template <size_t N> void set_content_type() { content_type_ = get_content_type<N>(); } status_type status() { return status_; } std::string_view content() { return content_; } size_t content_size() { return content_.size(); } void add_header(auto k, auto v) { resp_headers_.emplace_back(resp_header{std::move(k), std::move(v)}); } void add_header_span(std::span<http_header> resp_headers) { resp_header_span_ = resp_headers; } void set_keepalive(bool r) { keepalive_ = r; } void need_date_head(bool r) { need_date_ = r; } bool need_date() { return need_date_; } void set_boundary(std::string_view boundary) { boundary_ = boundary; } std::string_view get_boundary() { return boundary_; } void to_buffers(std::vector<asio::const_buffer> &buffers, std::string &size_str) { buffers.push_back(asio::buffer(to_http_status_string(status_))); build_resp_head(buffers); if (!content_.empty()) { handle_content(buffers, size_str, content_); } else if (!content_view_.empty()) { handle_content(buffers, size_str, content_view_); } } void build_resp_str(std::string &resp_str) { resp_str.append(to_http_status_string(status_)); bool has_len = false; bool has_host = false; check_header(resp_headers_, has_len, has_host); if (!resp_header_span_.empty()) { check_header(resp_header_span_, has_len, has_host); } if (!has_host) { resp_str.append(CINATRA_HOST_SV); } if (content_.empty() && !has_set_content_ && fmt_type_ != format_type::chunked) { content_.append(default_status_content(status_)); } if (fmt_type_ == format_type::chunked) { resp_str.append(TRANSFER_ENCODING_SV); } else { if (!content_.empty() || !content_view_.empty()) { size_t content_size = content_.empty() ? content_view_.size() : content_.size(); auto [ptr, ec] = std::to_chars(buf_, buf_ + 32, content_size); resp_str.append(CONTENT_LENGTH_SV); resp_str.append(std::string_view(buf_, std::distance(buf_, ptr))); resp_str.append(CRCF); } else { if (!has_len && boundary_.empty()) resp_str.append(ZERO_LENGTH_SV); } } if (need_date_) { resp_str.append(DATE_SV); resp_str.append(get_gmt_time_str()); resp_str.append(CRCF); } if (keepalive_.has_value()) { bool keepalive = keepalive_.value(); keepalive ? resp_str.append(CONN_KEEP_SV) : resp_str.append(CONN_CLOSE_SV); } append_header_str(resp_str, resp_headers_); if (!resp_header_span_.empty()) { append_header_str(resp_str, resp_header_span_); } resp_str.append(CRCF); if (content_view_.empty()) { resp_str.append(content_); } else { resp_str.append(content_view_); } } void append_header_str(auto &resp_str, auto &resp_headers) { for (auto &[k, v] : resp_headers) { resp_str.append(k); resp_str.append(COLON_SV); resp_str.append(v); resp_str.append(CRCF); } } void check_header(auto &resp_headers, bool &has_len, bool &has_host) { for (auto &[k, v] : resp_headers) { if (k == "Server") { has_host = true; } else if (k == "Content-Length") { has_len = true; } else if (k == "Date") { need_date_ = false; } } } void build_resp_head(std::vector<asio::const_buffer> &buffers) { bool has_len = false; bool has_host = false; check_header(resp_headers_, has_len, has_host); if (!resp_header_span_.empty()) { check_header(resp_header_span_, has_len, has_host); } if (!has_host) { buffers.emplace_back(asio::buffer(CINATRA_HOST_SV)); } if (content_.empty() && !has_set_content_ && fmt_type_ != format_type::chunked) { content_.append(default_status_content(status_)); } if (fmt_type_ == format_type::chunked) { buffers.emplace_back(asio::buffer(TRANSFER_ENCODING_SV)); } else { if (!cookies_.empty()) { for (auto &[_, cookie] : cookies_) { resp_headers_.emplace_back( resp_header{"Set-Cookie", cookie.to_string()}); } } if (!content_.empty()) { if (!has_len) handle_content_len(buffers, content_); } else if (!content_view_.empty()) { if (!has_len) handle_content_len(buffers, content_view_); } else { if (!has_len && boundary_.empty()) buffers.emplace_back(asio::buffer(ZERO_LENGTH_SV)); } } if (need_date_) { buffers.emplace_back(asio::buffer(DATE_SV)); buffers.emplace_back(asio::buffer(get_gmt_time_str())); buffers.emplace_back(asio::buffer(CRCF)); } if (keepalive_.has_value()) { bool keepalive = keepalive_.value(); keepalive ? buffers.emplace_back(asio::buffer(CONN_KEEP_SV)) : buffers.emplace_back(asio::buffer(CONN_CLOSE_SV)); } if (!content_type_.empty()) { buffers.emplace_back(asio::buffer(content_type_)); } append_header(buffers, resp_headers_); if (!resp_header_span_.empty()) { append_header(buffers, resp_header_span_); } buffers.emplace_back(asio::buffer(CRCF)); } void append_header(auto &buffers, auto &resp_headers) { for (auto &[k, v] : resp_headers) { buffers.emplace_back(asio::buffer(k)); buffers.emplace_back(asio::buffer(COLON_SV)); buffers.emplace_back(asio::buffer(v)); buffers.emplace_back(asio::buffer(CRCF)); } } coro_http_connection *get_conn() { return conn_; } void clear() { content_.clear(); if (need_shrink_every_time_) { content_.shrink_to_fit(); } resp_headers_.clear(); keepalive_ = {}; delay_ = false; status_ = status_type::init; fmt_type_ = format_type::normal; boundary_.clear(); has_set_content_ = false; cookies_.clear(); need_date_ = true; } void set_shrink_to_fit(bool r) { need_shrink_every_time_ = r; } void add_cookie(const cookie &cookie) { cookies_[cookie.get_name()] = cookie; } void redirect(const std::string &url, bool is_forever = false) { add_header("Location", url); is_forever == false ? set_status_and_content(status_type::moved_temporarily, "") : set_status_and_content(status_type::moved_permanently, ""); } private: void handle_content(std::vector<asio::const_buffer> &buffers, std::string &size_str, std::string_view content) { if (fmt_type_ == format_type::chunked) { to_chunked_buffers(buffers, size_str, content, true); } else { buffers.push_back(asio::buffer(content)); } } void handle_content_len(std::vector<asio::const_buffer> &buffers, std::string_view content) { auto [ptr, ec] = std::to_chars(buf_, buf_ + 32, content.size()); buffers.emplace_back(asio::buffer(CONTENT_LENGTH_SV)); buffers.emplace_back( asio::buffer(std::string_view(buf_, std::distance(buf_, ptr)))); buffers.emplace_back(asio::buffer(CRCF)); } status_type status_; format_type fmt_type_; std::string content_; std::optional<bool> keepalive_; bool delay_; char buf_[32]; std::vector<resp_header> resp_headers_; std::span<http_header> resp_header_span_; coro_http_connection *conn_; std::string boundary_; bool has_set_content_ = false; bool need_shrink_every_time_ = false; bool need_date_ = true; std::unordered_map<std::string, cookie> cookies_; std::string_view content_type_; std::string_view content_view_; }; } // namespace cinatra