From b6d6fdd10f79656c7f401becf89897c2d90ddcec Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 16 Jan 2024 12:45:50 +0800 Subject: [PATCH 1/2] update coro_http --- .../thirdparty/cinatra/coro_http_client.hpp | 123 ++-- .../cinatra/coro_http_connection.hpp | 285 +++++---- .../thirdparty/cinatra/coro_http_request.hpp | 124 +++- .../thirdparty/cinatra/coro_http_response.hpp | 85 +-- .../thirdparty/cinatra/coro_http_router.hpp | 183 +++++- .../thirdparty/cinatra/coro_http_server.hpp | 218 ++++++- .../thirdparty/cinatra/coro_radix_tree.hpp | 409 ++++++++++++ include/ylt/thirdparty/cinatra/define.h | 15 +- .../thirdparty/cinatra/function_traits.hpp | 96 --- include/ylt/thirdparty/cinatra/gzip.hpp | 143 +++++ .../ylt/thirdparty/cinatra/http_parser.hpp | 23 + .../thirdparty/cinatra/io_service_pool.hpp | 109 ++++ include/ylt/thirdparty/cinatra/mime_types.hpp | 7 +- include/ylt/thirdparty/cinatra/multipart.hpp | 118 ++++ .../ylt/thirdparty/cinatra/picohttpparser.h | 515 ++++++++++++++- .../ylt/thirdparty/cinatra/response_cv.hpp | 444 ++++--------- include/ylt/thirdparty/cinatra/sha1.hpp | 13 +- .../ylt/thirdparty/cinatra/smtp_client.hpp | 212 +++++++ .../ylt/thirdparty/cinatra/string_resize.hpp | 86 ++- include/ylt/thirdparty/cinatra/time_util.hpp | 2 +- include/ylt/thirdparty/cinatra/uri.hpp | 4 +- .../thirdparty/cinatra/url_encode_decode.hpp | 50 +- include/ylt/thirdparty/cinatra/use_asio.hpp | 17 + include/ylt/thirdparty/cinatra/utils.hpp | 600 ++---------------- include/ylt/thirdparty/cinatra/websocket.hpp | 1 - src/coro_http/examples/chat_room.cpp | 2 + src/coro_http/examples/example.cpp | 565 ++++++++--------- 27 files changed, 2910 insertions(+), 1539 deletions(-) create mode 100644 include/ylt/thirdparty/cinatra/coro_radix_tree.hpp delete mode 100644 include/ylt/thirdparty/cinatra/function_traits.hpp create mode 100644 include/ylt/thirdparty/cinatra/gzip.hpp create mode 100644 include/ylt/thirdparty/cinatra/io_service_pool.hpp create mode 100644 include/ylt/thirdparty/cinatra/multipart.hpp create mode 100644 include/ylt/thirdparty/cinatra/smtp_client.hpp create mode 100644 include/ylt/thirdparty/cinatra/use_asio.hpp diff --git a/include/ylt/thirdparty/cinatra/coro_http_client.hpp b/include/ylt/thirdparty/cinatra/coro_http_client.hpp index cd43ca035..8e0795a9f 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_client.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_client.hpp @@ -23,6 +23,7 @@ #include "async_simple/coro/Lazy.h" #include "cinatra_log_wrapper.hpp" #include "http_parser.hpp" +#include "multipart.hpp" #include "picohttpparser.h" #include "response_cv.hpp" #include "string_resize.hpp" @@ -147,7 +148,7 @@ class coro_http_client : public std::enable_shared_from_this { : executor_wrapper_(executor), timer_(&executor_wrapper_), socket_(std::make_shared(executor)), - read_buf_(socket_->read_buf_), + head_buf_(socket_->head_buf_), chunked_buf_(socket_->chunked_buf_) {} coro_http_client( @@ -185,9 +186,9 @@ class coro_http_client : public std::enable_shared_from_this { return true; } - ~coro_http_client() { async_close(); } + ~coro_http_client() { close(); } - void async_close() { + void close() { if (socket_ == nullptr || socket_->has_closed_) return; @@ -505,7 +506,7 @@ class coro_http_client : public std::enable_shared_from_this { co_return data; } - std::tie(ec, size) = co_await async_read(read_buf_, total_len_); + std::tie(ec, size) = co_await async_read(head_buf_, total_len_); if (ec) { if (!stop_bench_) @@ -517,8 +518,8 @@ class coro_http_client : public std::enable_shared_from_this { } else { const char *data_ptr = - asio::buffer_cast(read_buf_.data()); - read_buf_.consume(total_len_); + asio::buffer_cast(head_buf_.data()); + head_buf_.consume(total_len_); // check status if (data_ptr[9] > '3') { data.status = 404; @@ -526,7 +527,7 @@ class coro_http_client : public std::enable_shared_from_this { } } - read_buf_.consume(total_len_); + head_buf_.consume(total_len_); data.status = 200; data.total = total_len_; @@ -891,14 +892,14 @@ class coro_http_client : public std::enable_shared_from_this { std::string file_data; detail::resize(file_data, max_single_part_size_); - std::string chunk_size_str; if constexpr (is_stream_file) { while (!source->eof()) { size_t rd_size = source->read(file_data.data(), file_data.size()).gcount(); - auto bufs = cinatra::to_chunked_buffers( - file_data.data(), rd_size, chunk_size_str, source->eof()); + std::vector bufs; + cinatra::to_chunked_buffers(bufs, {file_data.data(), rd_size}, + source->eof()); if (std::tie(ec, size) = co_await async_write(bufs); ec) { break; } @@ -915,19 +916,20 @@ class coro_http_client : public std::enable_shared_from_this { while (!file.eof()) { auto [rd_ec, rd_size] = co_await file.async_read(file_data.data(), file_data.size()); - auto bufs = cinatra::to_chunked_buffers( - file_data.data(), rd_size, chunk_size_str, file.eof()); + std::vector bufs; + cinatra::to_chunked_buffers(bufs, {file_data.data(), rd_size}, + file.eof()); if (std::tie(ec, size) = co_await async_write(bufs); ec) { break; } } } else { - std::string chunk_size_str; while (true) { auto result = co_await source(); - auto bufs = cinatra::to_chunked_buffers( - result.buf.data(), result.buf.size(), chunk_size_str, result.eof); + std::vector bufs; + cinatra::to_chunked_buffers( + bufs, {result.buf.data(), result.buf.size()}, result.eof); if (std::tie(ec, size) = co_await async_write(bufs); ec) { break; } @@ -1168,7 +1170,7 @@ class coro_http_client : public std::enable_shared_from_this { struct socket_t { asio::ip::tcp::socket impl_; std::atomic has_closed_ = true; - asio::streambuf read_buf_; + asio::streambuf head_buf_; asio::streambuf chunked_buf_; #ifdef CINATRA_ENABLE_SSL std::unique_ptr> ssl_stream_; @@ -1184,6 +1186,9 @@ class coro_http_client : public std::enable_shared_from_this { std::pair handle_uri(resp_data &data, const S &uri) { uri_t u; if (!u.parse_from(uri.data())) { + CINATRA_LOG_WARNING + << uri + << ", the url is not right, maybe need to encode the url firstly"; data.net_err = std::make_error_code(std::errc::protocol_error); data.status = 404; return {false, {}}; @@ -1320,7 +1325,7 @@ class coro_http_client : public std::enable_shared_from_this { std::error_code handle_header(resp_data &data, http_parser &parser, size_t header_size) { // parse header - const char *data_ptr = asio::buffer_cast(read_buf_.data()); + const char *data_ptr = asio::buffer_cast(head_buf_.data()); int parse_ret = parser.parse_response(data_ptr, header_size, 0); #ifdef INJECT_FOR_HTTP_CLIENT_TEST @@ -1334,7 +1339,7 @@ class coro_http_client : public std::enable_shared_from_this { #endif return std::make_error_code(std::errc::protocol_error); } - read_buf_.consume(header_size); // header size + head_buf_.consume(header_size); // header size data.resp_headers = parser.get_headers(); data.status = parser.status(); return {}; @@ -1348,7 +1353,7 @@ class coro_http_client : public std::enable_shared_from_this { http_method method) { resp_data data{}; do { - if (std::tie(ec, size) = co_await async_read_until(read_buf_, TWO_CRCF); + if (std::tie(ec, size) = co_await async_read_until(head_buf_, TWO_CRCF); ec) { break; } @@ -1377,16 +1382,28 @@ class coro_http_client : public std::enable_shared_from_this { } if (parser_.is_chunked()) { is_keep_alive = true; - if (read_buf_.size() > 0) { + if (head_buf_.size() > 0) { const char *data_ptr = - asio::buffer_cast(read_buf_.data()); - chunked_buf_.sputn(data_ptr, read_buf_.size()); - read_buf_.consume(read_buf_.size()); + asio::buffer_cast(head_buf_.data()); + chunked_buf_.sputn(data_ptr, head_buf_.size()); + head_buf_.consume(head_buf_.size()); } ec = co_await handle_chunked(data, std::move(ctx)); break; } + if (parser_.is_multipart()) { + is_keep_alive = true; + if (head_buf_.size() > 0) { + const char *data_ptr = + asio::buffer_cast(head_buf_.data()); + chunked_buf_.sputn(data_ptr, head_buf_.size()); + head_buf_.consume(head_buf_.size()); + } + ec = co_await handle_multipart(data, std::move(ctx)); + break; + } + redirect_uri_.clear(); bool is_redirect = parser_.is_location(); if (is_redirect) @@ -1406,11 +1423,11 @@ class coro_http_client : public std::enable_shared_from_this { } } - if (content_len <= read_buf_.size()) { + if (content_len <= head_buf_.size()) { // Now get entire content, additional data will discard. // copy body. if (content_len > 0) { - auto data_ptr = asio::buffer_cast(read_buf_.data()); + auto data_ptr = asio::buffer_cast(head_buf_.data()); if (is_out_buf) { memcpy(out_buf_.data(), data_ptr, content_len); } @@ -1418,17 +1435,17 @@ class coro_http_client : public std::enable_shared_from_this { detail::resize(body_, content_len); memcpy(body_.data(), data_ptr, content_len); } - read_buf_.consume(read_buf_.size()); + head_buf_.consume(head_buf_.size()); } co_await handle_entire_content(data, content_len, is_ranges, ctx); break; } // read left part of content. - size_t part_size = read_buf_.size(); + size_t part_size = head_buf_.size(); size_t size_to_read = content_len - part_size; - auto data_ptr = asio::buffer_cast(read_buf_.data()); + auto data_ptr = asio::buffer_cast(head_buf_.data()); if (is_out_buf) { memcpy(out_buf_.data(), data_ptr, part_size); } @@ -1437,7 +1454,7 @@ class coro_http_client : public std::enable_shared_from_this { memcpy(body_.data(), data_ptr, part_size); } - read_buf_.consume(part_size); + head_buf_.consume(part_size); if (is_out_buf) { if (std::tie(ec, size) = co_await async_read( @@ -1474,7 +1491,7 @@ class coro_http_client : public std::enable_shared_from_this { auto &ctx) { if (content_len > 0) { const char *data_ptr; - if (read_buf_.size() == 0) { + if (head_buf_.size() == 0) { if (out_buf_.empty()) { data_ptr = body_.data(); } @@ -1483,7 +1500,7 @@ class coro_http_client : public std::enable_shared_from_this { } } else { - data_ptr = asio::buffer_cast(read_buf_.data()); + data_ptr = asio::buffer_cast(head_buf_.data()); } if (is_ranges) { @@ -1499,9 +1516,9 @@ class coro_http_client : public std::enable_shared_from_this { std::string_view reply(data_ptr, content_len); data.resp_body = reply; - read_buf_.consume(content_len); + head_buf_.consume(content_len); } - data.eof = (read_buf_.size() == 0); + data.eof = (head_buf_.size() == 0); } void handle_result(resp_data &data, std::error_code ec, bool is_keep_alive) { @@ -1522,6 +1539,39 @@ class coro_http_client : public std::enable_shared_from_this { } } + template + async_simple::coro::Lazy handle_multipart( + resp_data &data, req_context ctx) { + std::error_code ec{}; + std::string boundary = std::string{parser_.get_boundary()}; + multipart_reader_t multipart(this); + while (true) { + auto part_head = co_await multipart.read_part_head(); + if (part_head.ec) { + co_return part_head.ec; + } + + auto part_body = co_await multipart.read_part_body(boundary); + + if (ctx.stream) { + ec = co_await ctx.stream->async_write(part_body.data.data(), + part_body.data.size()); + } + else { + resp_chunk_str_.append(part_body.data.data(), part_body.data.size()); + } + + if (part_body.ec) { + co_return part_body.ec; + } + + if (part_body.eof) { + break; + } + } + co_return ec; + } + template async_simple::coro::Lazy handle_chunked( resp_data &data, req_context ctx) { @@ -1721,12 +1771,12 @@ class coro_http_client : public std::enable_shared_from_this { async_simple::coro::Lazy async_read_ws() { resp_data data{}; - read_buf_.consume(read_buf_.size()); + head_buf_.consume(head_buf_.size()); size_t header_size = 2; std::shared_ptr sock = socket_; auto on_ws_msg = std::move(on_ws_msg_); auto on_ws_close = std::move(on_ws_close_); - asio::streambuf &read_buf = sock->read_buf_; + asio::streambuf &read_buf = sock->head_buf_; bool has_init_ssl = false; #ifdef CINATRA_ENABLE_SSL has_init_ssl = has_init_ssl_; @@ -1927,11 +1977,12 @@ class coro_http_client : public std::enable_shared_from_this { return has_http_scheme; } + friend class multipart_reader_t; http_parser parser_; coro_io::ExecutorWrapper<> executor_wrapper_; coro_io::period_timer timer_; std::shared_ptr socket_; - asio::streambuf &read_buf_; + asio::streambuf &head_buf_; asio::streambuf &chunked_buf_; std::string body_; diff --git a/include/ylt/thirdparty/cinatra/coro_http_connection.hpp b/include/ylt/thirdparty/cinatra/coro_http_connection.hpp index e293ad057..c8e575d47 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_connection.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_connection.hpp @@ -15,6 +15,7 @@ #include "coro_http_router.hpp" #include "define.h" #include "http_parser.hpp" +#include "multipart.hpp" #include "sha1.hpp" #include "string_resize.hpp" #include "websocket.hpp" @@ -22,18 +23,6 @@ #include "ylt/coro_io/coro_io.hpp" namespace cinatra { -struct chunked_result { - std::error_code ec; - bool eof = false; - std::string_view data; -}; - -struct part_head_t { - std::error_code ec; - std::string name; - std::string filename; -}; - struct websocket_result { std::error_code ec; ws_frame_type type; @@ -183,20 +172,98 @@ class coro_http_connection parser_.method().data(), parser_.method().length() + 1 + parser_.url().length()}; + std::string decode_key; + if (parser_.url().find('%') != std::string_view::npos) { + decode_key = code_utils::url_decode(key); + key = decode_key; + } + if (!body_.empty()) { request_.set_body(body_); } if (auto handler = router_.get_handler(key); handler) { - router_.route(handler, request_, response_); + router_.route(handler, request_, response_, key); } else { if (auto coro_handler = router_.get_coro_handler(key); coro_handler) { - co_await router_.route_coro(coro_handler, request_, response_); + co_await router_.route_coro(coro_handler, request_, response_, key); } else { - // not found - response_.set_status(status_type::not_found); + bool is_exist = false; + std::function + handler; + std::string method_str{parser_.method()}; + std::string url_path = method_str; + url_path.append(" ").append(parser_.url()); + std::tie(is_exist, handler, request_.params_) = + router_.get_router_tree()->get(url_path, method_str); + if (is_exist) { + if (handler) { + (handler)(request_, response_); + } + else { + response_.set_status(status_type::not_found); + } + } + else { + bool is_coro_exist = false; + std::function( + coro_http_request & req, coro_http_response & resp)> + coro_handler; + + std::tie(is_coro_exist, coro_handler, request_.params_) = + router_.get_coro_router_tree()->get_coro(url_path, method_str); + + if (is_coro_exist) { + if (coro_handler) { + co_await (coro_handler)(request_, response_); + } + else { + response_.set_status(status_type::not_found); + } + } + else { + bool is_matched_regex_router = false; + // coro regex router + auto coro_regex_handlers = router_.get_coro_regex_handlers(); + if (coro_regex_handlers.size() != 0) { + for (auto &pair : coro_regex_handlers) { + std::string coro_regex_key{key}; + + if (std::regex_match(coro_regex_key, request_.matches_, + std::get<0>(pair))) { + auto coro_handler = std::get<1>(pair); + if (coro_handler) { + co_await (coro_handler)(request_, response_); + is_matched_regex_router = true; + } + } + } + } + // regex router + if (!is_matched_regex_router) { + auto regex_handlers = router_.get_regex_handlers(); + if (regex_handlers.size() != 0) { + for (auto &pair : regex_handlers) { + std::string regex_key{key}; + if (std::regex_match(regex_key, request_.matches_, + std::get<0>(pair))) { + auto handler = std::get<1>(pair); + if (handler) { + (handler)(request_, response_); + is_matched_regex_router = true; + } + } + } + } + } + // not found + if (!is_matched_regex_router) + response_.set_status(status_type::not_found); + } + } } } @@ -207,6 +274,9 @@ class coro_http_connection response_.clear(); buffers_.clear(); body_.clear(); + if (need_shrink_every_time_) { + body_.shrink_to_fit(); + } } } @@ -230,6 +300,36 @@ class coro_http_connection co_return true; } + std::string local_address() { + if (has_closed_) { + return ""; + } + + std::stringstream ss; + std::error_code ec; + ss << socket_.local_endpoint(ec); + if (ec) { + return ""; + } + return ss.str(); + } + + std::string remote_address() { + static std::string remote_addr; + if (has_closed_) { + return remote_addr; + } + + std::stringstream ss; + std::error_code ec; + ss << socket_.remote_endpoint(ec); + if (ec) { + return remote_addr; + } + remote_addr = ss.str(); + return ss.str(); + } + async_simple::coro::Lazy write_data(std::string_view message) { std::vector buffers; buffers.push_back(asio::buffer(message)); @@ -250,10 +350,8 @@ class coro_http_connection async_simple::coro::Lazy write_chunked_data(std::string_view buf, bool eof) { - std::string chunk_size_str = ""; - std::vector buffers = - to_chunked_buffers(buf.data(), buf.length(), - chunk_size_str, eof); + std::vector buffers; + to_chunked_buffers(buffers, buf, eof); auto [ec, _] = co_await async_write(std::move(buffers)); if (ec) { CINATRA_LOG_ERROR << "async_write error: " << ec.message(); @@ -281,7 +379,7 @@ class coro_http_connection bool eof = false) { response_.set_delay(true); buffers_.clear(); - response_.to_chunked_buffers(buffers_, chunked_data, eof); + to_chunked_buffers(buffers_, chunked_data, eof); co_return co_await reply(false); } @@ -289,6 +387,52 @@ class coro_http_connection co_return co_await write_chunked("", true); } + async_simple::coro::Lazy begin_multipart( + std::string_view boundary = "", std::string_view content_type = "") { + response_.set_delay(true); + response_.set_status(status_type::ok); + if (boundary.empty()) { + boundary = BOUNDARY; + } + if (content_type.empty()) { + content_type = "multipart/form-data"; + } + + std::string str{content_type}; + str.append("; ").append("boundary=").append(boundary); + response_.add_header("Content-Type", str); + response_.set_boundary(boundary); + co_return co_await reply(); + } + + async_simple::coro::Lazy write_multipart( + std::string_view part_data, std::string_view content_type) { + response_.set_delay(true); + buffers_.clear(); + std::string part_head = "--"; + part_head.append(response_.get_boundary()).append(CRCF); + part_head.append("Content-Type: ").append(content_type).append(CRCF); + part_head.append("Content-Length: ") + .append(std::to_string(part_data.size())) + .append(TWO_CRCF); + + buffers_.push_back(asio::buffer(part_head)); + buffers_.push_back(asio::buffer(part_data)); + buffers_.push_back(asio::buffer(CRCF)); + + auto [ec, _] = co_await async_write(buffers_); + co_return !ec; + } + + async_simple::coro::Lazy end_multipart() { + response_.set_delay(true); + buffers_.clear(); + std::string multipart_end = "--"; + multipart_end.append(response_.get_boundary()).append("--").append(CRCF); + auto [ec, _] = co_await async_write(asio::buffer(multipart_end)); + co_return !ec; + } + async_simple::coro::Lazy read_chunked() { if (head_buf_.size() > 0) { const char *data_ptr = asio::buffer_cast(head_buf_.data()); @@ -347,99 +491,6 @@ class coro_http_connection co_return result; } - async_simple::coro::Lazy read_part_head() { - if (head_buf_.size() > 0) { - const char *data_ptr = asio::buffer_cast(head_buf_.data()); - chunked_buf_.sputn(data_ptr, head_buf_.size()); - head_buf_.consume(head_buf_.size()); - } - - part_head_t result{}; - std::error_code ec{}; - size_t last_size = chunked_buf_.size(); - size_t size; - - auto get_part_name = [](std::string_view data, std::string_view name, - size_t start) { - start += name.length(); - size_t end = data.find("\"", start); - return data.substr(start, end - start); - }; - - constexpr std::string_view name = "name=\""; - constexpr std::string_view filename = "filename=\""; - - while (true) { - if (std::tie(ec, size) = co_await async_read_until(chunked_buf_, CRCF); - ec) { - result.ec = ec; - close(); - co_return result; - } - - const char *data_ptr = - asio::buffer_cast(chunked_buf_.data()); - chunked_buf_.consume(size); - if (*data_ptr == '-') { - continue; - } - std::string_view data{data_ptr, size}; - if (size == 2) { // got the head end: \r\n\r\n - break; - } - - if (size_t pos = data.find("name"); pos != std::string_view::npos) { - result.name = get_part_name(data, name, pos); - - if (size_t pos = data.find("filename"); pos != std::string_view::npos) { - result.filename = get_part_name(data, filename, pos); - } - continue; - } - } - - co_return result; - } - - async_simple::coro::Lazy read_part_body( - std::string_view boundary) { - chunked_result result{}; - std::error_code ec{}; - size_t size = 0; - - if (std::tie(ec, size) = co_await async_read_until(chunked_buf_, boundary); - ec) { - result.ec = ec; - close(); - co_return result; - } - - const char *data_ptr = asio::buffer_cast(chunked_buf_.data()); - chunked_buf_.consume(size); - result.data = std::string_view{ - data_ptr, size - boundary.size() - 4}; //-- boundary \r\n - - if (std::tie(ec, size) = co_await async_read_until(chunked_buf_, CRCF); - ec) { - result = {}; - result.ec = ec; - close(); - co_return result; - } - - data_ptr = asio::buffer_cast(chunked_buf_.data()); - std::string data{data_ptr, size}; - if (size > 2) { - constexpr std::string_view complete_flag = "--\r\n"; - if (data == complete_flag) { - result.eof = true; - } - } - - chunked_buf_.consume(size); - co_return result; - } - async_simple::coro::Lazy write_websocket( std::string_view msg, opcode op = opcode::text) { auto header = ws_.format_header(msg.length(), op); @@ -567,6 +618,8 @@ class coro_http_connection void set_ws_max_size(uint64_t max_size) { max_part_size_ = max_size; } + void set_shrink_to_fit(bool r) { need_shrink_every_time_ = r; } + template async_simple::coro::Lazy> async_read( AsioBuffer &&buffer, size_t size_to_read) noexcept { @@ -671,7 +724,7 @@ class coro_http_connection code_utils::base64_encode(accept_key, sha1buf, sizeof(sha1buf), 0); - response_.set_status(status_type::switching_protocols); + response_.set_status_and_content(status_type::switching_protocols); response_.add_header("Upgrade", "WebSocket"); response_.add_header("Connection", "Upgrade"); @@ -683,6 +736,7 @@ class coro_http_connection } private: + friend class multipart_reader_t; async_simple::Executor *executor_; asio::ip::tcp::socket socket_; coro_http_router &router_; @@ -707,5 +761,6 @@ class coro_http_connection std::unique_ptr> ssl_stream_; bool use_ssl_ = false; #endif + bool need_shrink_every_time_ = false; }; } // namespace cinatra diff --git a/include/ylt/thirdparty/cinatra/coro_http_request.hpp b/include/ylt/thirdparty/cinatra/coro_http_request.hpp index 07211d955..85383b242 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_request.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_request.hpp @@ -1,19 +1,103 @@ #pragma once + +#include +#include +#include +#include + #include "async_simple/coro/Lazy.h" #include "define.h" #include "http_parser.hpp" +#include "utils.hpp" #include "ws_define.h" namespace cinatra { + +inline std::vector> parse_ranges(std::string_view range_str, + size_t file_size, + bool &is_valid) { + range_str = trim_sv(range_str); + if (range_str.empty()) { + return {{0, file_size - 1}}; + } + + if (range_str.find("--") != std::string_view::npos) { + is_valid = false; + return {}; + } + + if (range_str == "-") { + return {{0, file_size - 1}}; + } + + std::vector> vec; + auto ranges = split_sv(range_str, ","); + for (auto range : ranges) { + auto sub_range = split_sv(range, "-"); + auto fist_range = trim_sv(sub_range[0]); + + int start = 0; + if (fist_range.empty()) { + start = -1; + } + else { + auto [ptr, ec] = std::from_chars( + fist_range.data(), fist_range.data() + fist_range.size(), start); + if (ec != std::errc{}) { + is_valid = false; + return {}; + } + } + + int end = 0; + if (sub_range.size() == 1) { + end = file_size - 1; + } + else { + auto second_range = trim_sv(sub_range[1]); + if (second_range.empty()) { + end = file_size - 1; + } + else { + auto [ptr, ec] = + std::from_chars(second_range.data(), + second_range.data() + second_range.size(), end); + if (ec != std::errc{}) { + is_valid = false; + return {}; + } + } + } + + if (start > 0 && (start >= file_size || start == end)) { + // out of range + is_valid = false; + return {}; + } + + if (end > 0 && end >= file_size) { + end = file_size - 1; + } + + if (start == -1) { + start = file_size - end; + end = file_size - 1; + } + + vec.push_back({start, end}); + } + return vec; +} + class coro_http_connection; class coro_http_request { public: - coro_http_request(http_parser& parser, coro_http_connection* conn) + coro_http_request(http_parser &parser, coro_http_connection *conn) : parser_(parser), conn_(conn) {} std::string_view get_header_value(std::string_view key) { auto headers = parser_.get_headers(); - for (auto& header : headers) { + for (auto &header : headers) { if (iequal0(header.name, key)) { return header.value; } @@ -37,9 +121,9 @@ class coro_http_request { std::span get_headers() const { return parser_.get_headers(); } - const auto& get_queries() const { return parser_.queries(); } + const auto &get_queries() const { return parser_.queries(); } - void set_body(std::string& body) { + void set_body(std::string &body) { body_ = body; auto type = get_content_type(); if (type == content_type::urlencoded) { @@ -85,6 +169,10 @@ class coro_http_request { return content_type::unknown; } + std::string_view get_url() { return parser_.url(); } + + std::string_view get_method() { return parser_.method(); } + std::string_view get_boundary() { auto content_type = get_header_value("content-type"); if (content_type.empty()) { @@ -93,7 +181,7 @@ class coro_http_request { return content_type.substr(content_type.rfind("=") + 1); } - coro_http_connection* get_conn() { return conn_; } + coro_http_connection *get_conn() { return conn_; } bool is_upgrade() { auto h = get_header_value("Connection"); @@ -118,10 +206,32 @@ class coro_http_request { return true; } + void set_aspect_data(const std::string &&key, const std::any &data) { + aspect_data_[key] = data; + } + + template + std::optional get_aspect_data(const std::string &&key) { + auto it = aspect_data_.find(key); + if (it == aspect_data_.end()) { + return std::optional{}; + } + + try { + return std::any_cast(it->second); // throws + } catch (const std::bad_any_cast &e) { + return std::optional{}; + } + } + + std::unordered_map params_; + std::smatch matches_; + private: - http_parser& parser_; + http_parser &parser_; std::string_view body_; - coro_http_connection* conn_; + coro_http_connection *conn_; bool is_websocket_; + std::unordered_map aspect_data_; }; } // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/coro_http_response.hpp b/include/ylt/thirdparty/cinatra/coro_http_response.hpp index eb720765d..a5e7dd30e 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_response.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_response.hpp @@ -13,6 +13,7 @@ #include "define.h" #include "response_cv.hpp" #include "time_util.hpp" +#include "utils.hpp" namespace cinatra { struct resp_header { @@ -33,7 +34,7 @@ enum class format_type { class coro_http_connection; class coro_http_response { public: - coro_http_response(coro_http_connection* conn) + coro_http_response(coro_http_connection *conn) : status_(status_type::not_implemented), fmt_type_(format_type::normal), delay_(false), @@ -42,10 +43,14 @@ class coro_http_response { } void set_status(cinatra::status_type status) { status_ = status; } - void set_content(std::string content) { content_ = std::move(content); } - void set_status_and_content(status_type status, std::string content) { + 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 = "") { status_ = status; content_ = std::move(content); + has_set_content_ = true; } void set_delay(bool r) { delay_ = r; } bool get_delay() const { return delay_; } @@ -57,10 +62,14 @@ class coro_http_response { void set_keepalive(bool r) { keepalive_ = r; } - void to_buffers(std::vector& buffers) { + void set_boundary(std::string_view boundary) { boundary_ = boundary; } + + std::string_view get_boundary() { return boundary_; } + + void to_buffers(std::vector &buffers) { build_resp_head(); - buffers.push_back(asio::buffer(to_rep_string(status_))); + buffers.push_back(asio::buffer(to_http_status_string(status_))); buffers.push_back(asio::buffer(head_)); if (!content_.empty()) { if (fmt_type_ == format_type::chunked) { @@ -72,42 +81,25 @@ class coro_http_response { } } - std::string_view to_hex_string(size_t val) { - static char buf[20]; - auto [ptr, ec] = std::to_chars(std::begin(buf), std::end(buf), val, 16); - return std::string_view{buf, size_t(std::distance(buf, ptr))}; - } - - void to_chunked_buffers(std::vector& buffers, - std::string_view chunk_data, bool eof) { - if (!chunk_data.empty()) { - // convert bytes transferred count to a hex string. - auto chunk_size = to_hex_string(chunk_data.size()); - - // Construct chunk based on rfc2616 section 3.6.1 - buffers.push_back(asio::buffer(chunk_size)); - buffers.push_back(asio::buffer(crlf)); - buffers.push_back(asio::buffer(chunk_data)); - buffers.push_back(asio::buffer(crlf)); - } - - // append last-chunk - if (eof) { - buffers.push_back(asio::buffer(last_chunk)); - buffers.push_back(asio::buffer(crlf)); + void build_resp_head() { + bool has_len = false; + bool has_host = false; + for (auto &[k, v] : resp_headers_) { + if (k == "Host") { + has_host = true; + } + if (k == "Content-Length") { + has_len = true; + } } - } - void build_resp_head() { - if (std::find_if(resp_headers_.begin(), resp_headers_.end(), - [](resp_header& header) { - return header.key == "Host"; - }) == resp_headers_.end()) { + if (!has_host) { resp_headers_sv_.emplace_back(resp_header_sv{"Host", "cinatra"}); } - if (status_ >= status_type::not_found) { - content_.append(to_string(status_)); + if (content_.empty() && !has_set_content_ && + fmt_type_ != format_type::chunked) { + content_.append(default_status_content(status_)); } if (fmt_type_ == format_type::chunked) { @@ -122,7 +114,8 @@ class coro_http_response { std::string_view(buf_, std::distance(buf_, ptr))}); } else { - resp_headers_sv_.emplace_back(resp_header_sv{"Content-Length", "0"}); + if (!has_len && boundary_.empty()) + resp_headers_sv_.emplace_back(resp_header_sv{"Content-Length", "0"}); } } @@ -139,11 +132,14 @@ class coro_http_response { head_.append(CRCF); } - coro_http_connection* get_conn() { return conn_; } + coro_http_connection *get_conn() { return conn_; } void clear() { head_.clear(); content_.clear(); + if (need_shrink_every_time_) { + content_.shrink_to_fit(); + } resp_headers_.clear(); resp_headers_sv_.clear(); @@ -151,10 +147,14 @@ class coro_http_response { delay_ = false; status_ = status_type::init; fmt_type_ = format_type::normal; + boundary_.clear(); + has_set_content_ = false; } - void append_head(auto& headers) { - for (auto& [k, v] : headers) { + void set_shrink_to_fit(bool r) { need_shrink_every_time_ = r; } + + void append_head(auto &headers) { + for (auto &[k, v] : headers) { head_.append(k); head_.append(":"); head_.append(v); @@ -172,6 +172,9 @@ class coro_http_response { char buf_[32]; std::vector resp_headers_; std::vector resp_headers_sv_; - coro_http_connection* conn_; + coro_http_connection *conn_; + std::string boundary_; + bool has_set_content_ = false; + bool need_shrink_every_time_ = false; }; } // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/coro_http_router.hpp b/include/ylt/thirdparty/cinatra/coro_http_router.hpp index e269a23c0..c50b6ab9f 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_router.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_router.hpp @@ -10,7 +10,9 @@ #include "cinatra/cinatra_log_wrapper.hpp" #include "cinatra/coro_http_request.hpp" +#include "cinatra/coro_radix_tree.hpp" #include "cinatra/response_cv.hpp" +#include "cinatra/utils.hpp" #include "coro_http_response.hpp" #include "ylt/util/type_traits.h" @@ -26,11 +28,23 @@ constexpr inline bool is_lazy_v = is_template_instant_of>::value; +struct base_aspect { + virtual bool before(coro_http_request& req, coro_http_response& resp) { + return true; + } + + virtual bool after(coro_http_request& req, coro_http_response& resp) { + return true; + } +}; + class coro_http_router { public: // eg: "GET hello/" as a key template - void set_http_handler(std::string key, Func handler) { + void set_http_handler( + std::string key, Func handler, + std::vector> aspects = {}) { constexpr auto method_name = cinatra::method_name(method); std::string whole_str; whole_str.append(method_name).append(" ").append(key); @@ -39,20 +53,70 @@ class coro_http_router { // std::string_view, avoid memcpy when route using return_type = typename util::function_traits::return_type; if constexpr (is_lazy_v) { - auto [it, ok] = coro_keys_.emplace(std::move(whole_str)); - if (!ok) { - CINATRA_LOG_WARNING << key << " has already registered."; - return; + if (whole_str.find(":") != std::string::npos) { + std::vector coro_method_names = {}; + std::string coro_method_str; + coro_method_str.append(method_name); + coro_method_names.push_back(coro_method_str); + coro_router_tree_->coro_insert(key, std::move(handler), + coro_method_names); + } + else { + if (whole_str.find("{") != std::string::npos || + whole_str.find(")") != std::string::npos) { + std::string pattern = whole_str; + + if (pattern.find("{}") != std::string::npos) { + replace_all(pattern, "{}", "([^/]+)"); + } + + coro_regex_handles_.emplace_back(std::regex(pattern), + std::move(handler)); + } + else { + auto [it, ok] = coro_keys_.emplace(std::move(whole_str)); + if (!ok) { + CINATRA_LOG_WARNING << key << " has already registered."; + return; + } + coro_handles_.emplace(*it, std::move(handler)); + if (!aspects.empty()) { + has_aspects_ = true; + aspects_.emplace(*it, std::move(aspects)); + } + } } - coro_handles_.emplace(*it, std::move(handler)); } else { - auto [it, ok] = keys_.emplace(std::move(whole_str)); - if (!ok) { - CINATRA_LOG_WARNING << key << " has already registered."; - return; + if (whole_str.find(':') != std::string::npos) { + std::vector method_names = {}; + std::string method_str; + method_str.append(method_name); + method_names.push_back(method_str); + router_tree_->insert(whole_str, std::move(handler), method_names); + } + else if (whole_str.find("{") != std::string::npos || + whole_str.find(")") != std::string::npos) { + std::string pattern = whole_str; + + if (pattern.find("{}") != std::string::npos) { + replace_all(pattern, "{}", "([^/]+)"); + } + + regex_handles_.emplace_back(std::regex(pattern), std::move(handler)); + } + else { + auto [it, ok] = keys_.emplace(std::move(whole_str)); + if (!ok) { + CINATRA_LOG_WARNING << key << " has already registered."; + return; + } + map_handles_.emplace(*it, std::move(handler)); + if (!aspects.empty()) { + has_aspects_ = true; + aspects_.emplace(*it, std::move(aspects)); + } } - map_handles_.emplace(*it, std::move(handler)); } } @@ -73,9 +137,21 @@ class coro_http_router { return nullptr; } - void route(auto handler, auto& req, auto& resp) { + void route(auto handler, auto& req, auto& resp, std::string_view key) { try { - (*handler)(req, resp); + if (has_aspects_) { + auto [it, ok] = handle_aspects(req, resp, key, true); + if (!ok) { + return; + } + (*handler)(req, resp); + if (it != aspects_.end()) { + handle_aspects(req, resp, it->second, false); + } + } + else { + (*handler)(req, resp); + } } catch (const std::exception& e) { CINATRA_LOG_WARNING << "exception in business function, reason: " << e.what(); @@ -86,10 +162,22 @@ class coro_http_router { } } - async_simple::coro::Lazy route_coro(auto handler, auto& req, - auto& resp) { + async_simple::coro::Lazy route_coro(auto handler, auto& req, auto& resp, + std::string_view key) { try { - co_await (*handler)(req, resp); + if (has_aspects_) { + auto [it, ok] = handle_aspects(req, resp, key, true); + if (!ok) { + co_return; + } + co_await (*handler)(req, resp); + if (it != aspects_.end()) { + handle_aspects(req, resp, it->second, false); + } + } + else { + co_await (*handler)(req, resp); + } } catch (const std::exception& e) { CINATRA_LOG_WARNING << "exception in business function, reason: " << e.what(); @@ -104,6 +192,48 @@ class coro_http_router { const auto& get_coro_handlers() const { return coro_handles_; } + std::shared_ptr get_router_tree() { return router_tree_; } + + std::shared_ptr get_coro_router_tree() { + return coro_router_tree_; + } + + const auto& get_coro_regex_handlers() { return coro_regex_handles_; } + + const auto& get_regex_handlers() { return regex_handles_; } + + bool handle_aspects(auto& req, auto& resp, auto& aspects, bool before) { + bool r = true; + for (auto& aspect : aspects) { + if (before) { + r = aspect->before(req, resp); + } + else { + r = aspect->after(req, resp); + } + if (!r) { + break; + } + } + return r; + } + + auto handle_aspects(auto& req, auto& resp, std::string_view key, + bool before) { + decltype(aspects_.begin()) it; + if (it = aspects_.find(key); it != aspects_.end()) { + auto& aspects = it->second; + bool r = handle_aspects(req, resp, aspects, before); + if (!r) { + return std::make_pair(aspects_.end(), false); + } + } + + return std::make_pair(it, true); + } + + void handle_after() {} + private: std::set keys_; std::unordered_map< @@ -116,5 +246,26 @@ class coro_http_router { std::function( coro_http_request& req, coro_http_response& resp)>> coro_handles_; + + std::shared_ptr router_tree_ = + std::make_shared(radix_tree()); + + std::shared_ptr coro_router_tree_ = + std::make_shared(radix_tree()); + + std::vector>> + regex_handles_; + + std::vector( + coro_http_request& req, coro_http_response& resp)>>> + coro_regex_handles_; + + std::unordered_map>> + aspects_; + bool has_aspects_ = false; }; } // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/coro_http_server.hpp b/include/ylt/thirdparty/cinatra/coro_http_server.hpp index 5b4653ff2..03934e1e6 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_server.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_server.hpp @@ -125,32 +125,41 @@ class coro_http_server { uint16_t port() const { return port_; } template - void set_http_handler(std::string key, Func handler) { + void set_http_handler( + std::string key, Func handler, + std::vector> aspects = {}) { static_assert(sizeof...(method) >= 1, "must set http_method"); if constexpr (sizeof...(method) == 1) { - (router_.set_http_handler(std::move(key), std::move(handler)), + (router_.set_http_handler(std::move(key), std::move(handler), + std::move(aspects)), ...); } else { - (router_.set_http_handler(key, handler), ...); + (router_.set_http_handler(key, handler, aspects), ...); } } - template - void set_http_handler(std::string key, Func handler, auto owner) { + template + void set_http_handler( + std::string key, Func handler, Owner &&owner, + std::vector> aspects = {}) { + static_assert(std::is_member_function_pointer_v, + "must be member function"); using return_type = typename util::function_traits::return_type; if constexpr (is_lazy_v) { std::function(coro_http_request & req, coro_http_response & resp)> f = std::bind(handler, owner, std::placeholders::_1, std::placeholders::_2); - set_http_handler(std::move(key), std::move(f)); + set_http_handler(std::move(key), std::move(f), + std::move(aspects)); } else { std::function f = std::bind(handler, owner, std::placeholders::_1, std::placeholders::_2); - set_http_handler(std::move(key), std::move(f)); + set_http_handler(std::move(key), std::move(f), + std::move(aspects)); } } @@ -187,8 +196,9 @@ class coro_http_server { void set_transfer_chunked_size(size_t size) { chunked_size_ = size; } - void set_static_res_dir(std::string_view uri_suffix = "", - std::string file_path = "www") { + void set_static_res_dir( + std::string_view uri_suffix = "", std::string file_path = "www", + std::vector> aspects = {}) { bool has_double_dot = (file_path.find("..") != std::string::npos) || (uri_suffix.find("..") != std::string::npos); if (std::filesystem::path(file_path).has_root_path() || @@ -245,11 +255,12 @@ class coro_http_server { coro_http_response &resp) -> async_simple::coro::Lazy { std::string_view extension = get_extension(file_name); std::string_view mime = get_mime_type(extension); + auto range_str = req.get_header_value("Range"); if (auto it = static_file_cache_.find(file_name); it != static_file_cache_.end()) { - auto range_header = - build_range_header(mime, file_name, fs::file_size(file_name)); + auto range_header = build_range_header( + mime, file_name, std::to_string(fs::file_size(file_name))); resp.set_delay(true); std::string &body = it->second; std::array arr{asio::buffer(range_header), @@ -269,7 +280,10 @@ class coro_http_server { co_return; } - if (format_type_ == file_resp_format_type::chunked) { + size_t file_size = fs::file_size(file_name); + + if (format_type_ == file_resp_format_type::chunked && + range_str.empty()) { resp.set_format_type(format_type::chunked); bool ok; if (ok = co_await resp.get_conn()->begin_chunked(); !ok) { @@ -298,8 +312,84 @@ class coro_http_server { } } else { - auto range_header = build_range_header( - mime, file_name, coro_io::coro_file::file_size(file_name)); + auto pos = range_str.find('='); + if (pos != std::string_view::npos) { + range_str = range_str.substr(pos + 1); + bool is_valid = true; + auto ranges = + parse_ranges(range_str, fs::file_size(file_name), is_valid); + if (!is_valid) { + resp.set_status(status_type::range_not_satisfiable); + co_return; + } + + assert(!ranges.empty()); + + if (ranges.size() == 1) { + // single part + auto [start, end] = ranges[0]; + in_file.seek(start, SEEK_SET); + size_t part_size = end + 1 - start; + int status = (part_size == file_size) ? 200 : 206; + std::string content_range = "Content-Range: bytes "; + content_range.append(std::to_string(start)) + .append("-") + .append(std::to_string(end)) + .append("/") + .append(std::to_string(file_size)) + .append(CRCF); + auto range_header = build_range_header( + mime, file_name, std::to_string(part_size), status, + content_range); + resp.set_delay(true); + bool r = co_await req.get_conn()->write_data(range_header); + if (!r) { + co_return; + } + + co_await send_single_part(in_file, content, req, resp, + part_size); + } + else { + // multipart ranges + resp.set_delay(true); + std::string file_size_str = std::to_string(file_size); + size_t content_len = 0; + std::vector multi_heads = build_part_heads( + ranges, mime, file_size_str, content_len); + auto range_header = build_multiple_range_header(content_len); + bool r = co_await req.get_conn()->write_data(range_header); + if (!r) { + co_return; + } + + for (int i = 0; i < ranges.size(); i++) { + std::string &part_header = multi_heads[i]; + r = co_await req.get_conn()->write_data(part_header); + if (!r) { + co_return; + } + + auto [start, end] = ranges[i]; + in_file.seek(start, SEEK_SET); + size_t part_size = end + 1 - start; + + std::string_view more = CRCF; + if (i == ranges.size() - 1) { + more = MULTIPART_END; + } + r = co_await send_single_part(in_file, content, req, resp, + part_size, more); + if (!r) { + co_return; + } + } + } + co_return; + } + + auto range_header = build_range_header(mime, file_name, + std::to_string(file_size)); resp.set_delay(true); bool r = co_await req.get_conn()->write_data(range_header); if (!r) { @@ -326,7 +416,8 @@ class coro_http_server { } } } - }); + }, + aspects); } } @@ -341,6 +432,8 @@ class coro_http_server { } } + void set_shrink_to_fit(bool r) { need_shrink_every_time_ = r; } + size_t connection_count() { std::scoped_lock lock(conn_mtx_); return connections_.size(); @@ -411,6 +504,9 @@ class coro_http_server { if (no_delay_) { conn->tcp_socket().set_option(asio::ip::tcp::no_delay(true)); } + if (need_shrink_every_time_) { + conn->set_shrink_to_fit(true); + } if (need_check_) { conn->set_check_timeout(true); } @@ -485,20 +581,101 @@ class coro_http_server { } } + std::string build_multiple_range_header(size_t content_len) { + std::string header_str = "HTTP/1.1 206 Partial Content\r\n"; + header_str.append("Content-Length: "); + header_str.append(std::to_string(content_len)).append(CRCF); + header_str.append("Content-Type: multipart/byteranges; boundary="); + header_str.append(BOUNDARY).append(TWO_CRCF); + return header_str; + } + + std::vector build_part_heads(auto &ranges, std::string_view mime, + std::string_view file_size_str, + size_t &content_len) { + std::vector multi_heads; + for (auto [start, end] : ranges) { + std::string part_header = "--"; + part_header.append(BOUNDARY).append(CRCF); + part_header.append("Content-Type: ").append(mime).append(CRCF); + part_header.append("Content-Range: ").append("bytes "); + part_header.append(std::to_string(start)) + .append("-") + .append(std::to_string(end)) + .append("/") + .append(file_size_str) + .append(TWO_CRCF); + content_len += part_header.size(); + multi_heads.push_back(std::move(part_header)); + size_t part_size = end + 1 - start + CRCF.size(); + content_len += part_size; + } + content_len += (BOUNDARY.size() + 4); + return multi_heads; + } + std::string build_range_header(std::string_view mime, - std::string_view filename, size_t file_size) { - std::string header_str = - "HTTP/1.1 200 OK\r\nAccess-Control-Allow-origin: " - "*\r\nAccept-Ranges: bytes\r\n"; + std::string_view filename, + std::string_view file_size_str, + int status = 200, + std::string_view content_range = "") { + std::string header_str = "HTTP/1.1 "; + header_str.append(std::to_string(status)); + header_str.append( + " OK\r\nAccess-Control-Allow-origin: " + "*\r\nAccept-Ranges: bytes\r\n"); + if (!content_range.empty()) { + header_str.append(content_range); + } header_str.append("Content-Disposition: attachment;filename="); header_str.append(filename).append("\r\n"); header_str.append("Connection: keep-alive\r\n"); header_str.append("Content-Type: ").append(mime).append("\r\n"); header_str.append("Content-Length: "); - header_str.append(std::to_string(file_size)).append("\r\n\r\n"); + header_str.append(file_size_str).append("\r\n\r\n"); return header_str; } + async_simple::coro::Lazy send_single_part(auto &in_file, auto &content, + auto &req, auto &resp, + size_t part_size, + std::string_view more = "") { + while (true) { + size_t read_size = (std::min)(part_size, chunked_size_); + if (read_size == 0) { + break; + } + auto [ec, size] = co_await in_file.async_read(content.data(), read_size); + if (ec) { + resp.set_status(status_type::no_content); + co_await resp.get_conn()->reply(); + co_return false; + } + + part_size -= read_size; + + bool r = true; + if (more.empty()) { + r = co_await req.get_conn()->write_data( + std::string_view(content.data(), size)); + } + else { + std::array arr{ + asio::buffer(content.data(), size), asio::buffer(more)}; + auto [ec, _] = co_await req.get_conn()->async_write(arr); + if (ec) { + r = false; + } + } + + if (!r) { + co_return false; + } + } + + co_return true; + } + private: std::unique_ptr pool_; asio::io_context *out_ctx_ = nullptr; @@ -534,5 +711,6 @@ class coro_http_server { bool use_ssl_ = false; #endif coro_http_router router_; + bool need_shrink_every_time_ = false; }; } // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/coro_radix_tree.hpp b/include/ylt/thirdparty/cinatra/coro_radix_tree.hpp new file mode 100644 index 000000000..585446b87 --- /dev/null +++ b/include/ylt/thirdparty/cinatra/coro_radix_tree.hpp @@ -0,0 +1,409 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include "cinatra/coro_http_request.hpp" +#include "coro_http_response.hpp" +#include "ylt/util/type_traits.h" + +namespace cinatra { +constexpr char type_asterisk = '*'; +constexpr char type_colon = ':'; +constexpr char type_slash = '/'; + +typedef std::tuple< + bool, std::function, + std::unordered_map> + parse_result; + +typedef std::tuple( + coro_http_request &req, coro_http_response &resp)>, + std::unordered_map> + coro_result; + +struct handler_t { + std::string method; + std::function handler; +}; + +struct coro_handler_t { + std::string method; + std::function(coro_http_request &req, + coro_http_response &resp)> + coro_handler; +}; + +struct radix_tree_node { + std::string path; + std::vector handlers; + std::vector coro_handlers; + std::string indices; + std::vector> children; + int max_params; + + radix_tree_node() = default; + radix_tree_node(const std::string &path) { this->path = path; } + ~radix_tree_node() {} + + std::function + get_handler(const std::string &method) { + for (auto &h : this->handlers) { + if (h.method == method) { + return h.handler; + } + } + return nullptr; + } + + std::function(coro_http_request &req, + coro_http_response &resp)> + get_coro_handler(const std::string &method) { + for (auto &h : this->coro_handlers) { + if (h.method == method) { + return h.coro_handler; + } + } + return nullptr; + } + + int add_handler( + std::function + handler, + const std::vector &methods) { + for (auto &m : methods) { + auto old_handler = this->get_handler(m); + this->handlers.push_back(handler_t{m, handler}); + } + return 0; + } + + int add_coro_handler(std::function( + coro_http_request &req, coro_http_response &resp)> + coro_handler, + const std::vector &methods) { + for (auto &m : methods) { + auto old_coro_handler = this->get_coro_handler(m); + this->coro_handlers.push_back(coro_handler_t{m, coro_handler}); + } + return 0; + } + + std::shared_ptr insert_child( + char index, std::shared_ptr child) { + auto i = this->get_index_position(index); + this->indices.insert(this->indices.begin() + i, index); + this->children.insert(this->children.begin() + i, child); + return child; + } + + std::shared_ptr get_child(char index) { + auto i = this->get_index_position(index); + return this->indices[i] != index ? nullptr : this->children[i]; + } + + int get_index_position(char target) { + int low = 0, high = this->indices.size(), mid; + + while (low < high) { + mid = low + ((high - low) >> 1); + if (this->indices[mid] < target) + low = mid + 1; + else + high = mid; + } + return low; + } +}; + +class radix_tree { + public: + radix_tree() { + this->root = std::make_shared(radix_tree_node()); + } + + ~radix_tree() {} + + int insert( + const std::string &path, + std::function + handler, + const std::vector &methods) { + auto root = this->root; + int i = 0, n = path.size(), param_count = 0, code = 0; + while (i < n) { + if (!root->indices.empty() && + (root->indices[0] == type_asterisk || path[i] == type_asterisk || + (path[i] != type_colon && root->indices[0] == type_colon) || + (path[i] == type_colon && root->indices[0] != type_colon) || + (path[i] == type_colon && root->indices[0] == type_colon && + path.substr(i + 1, find_pos(path, type_slash, i) - i - 1) != + root->children[0]->path))) { + code = -1; + break; + } + + auto child = root->get_child(path[i]); + if (!child) { + auto p = find_pos(path, type_colon, i); + + if (p == n) { + p = find_pos(path, type_asterisk, i); + + root = root->insert_child(path[i], std::make_shared( + path.substr(i, p - i))); + + if (p < n) { + root = root->insert_child( + type_asterisk, + std::make_shared(path.substr(p + 1))); + ++param_count; + } + + code = root->add_handler(handler, methods); + break; + } + + root = root->insert_child( + path[i], std::make_shared(path.substr(i, p - i))); + + i = find_pos(path, type_slash, p); + + root = root->insert_child( + type_colon, + std::make_shared(path.substr(p + 1, i - p - 1))); + ++param_count; + + if (i == n) { + code = root->add_handler(handler, methods); + break; + } + } + else { + root = child; + + if (path[i] == type_colon) { + ++param_count; + i += root->path.size() + 1; + + if (i == n) { + code = root->add_handler(handler, methods); + break; + } + } + else { + auto j = 0UL; + auto m = root->path.size(); + + for (; i < n && j < m && path[i] == root->path[j]; ++i, ++j) { + } + + if (j < m) { + std::shared_ptr child( + std::make_shared(root->path.substr(j))); + child->handlers = root->handlers; + child->indices = root->indices; + child->children = root->children; + + root->path = root->path.substr(0, j); + root->handlers = {}; + root->indices = child->path[0]; + root->children = {child}; + } + + if (i == n) { + code = root->add_handler(handler, methods); + break; + } + } + } + } + + if (param_count > this->root->max_params) + this->root->max_params = param_count; + + return code; + } + + int coro_insert(const std::string &path, + std::function( + coro_http_request &req, coro_http_response &resp)> + coro_handler, + const std::vector &methods) { + auto root = this->root; + int i = 0, n = path.size(), param_count = 0, code = 0; + while (i < n) { + if (!root->indices.empty() && + (root->indices[0] == type_asterisk || path[i] == type_asterisk || + (path[i] != type_colon && root->indices[0] == type_colon) || + (path[i] == type_colon && root->indices[0] != type_colon) || + (path[i] == type_colon && root->indices[0] == type_colon && + path.substr(i + 1, find_pos(path, type_slash, i) - i - 1) != + root->children[0]->path))) { + code = -1; + break; + } + + auto child = root->get_child(path[i]); + if (!child) { + auto p = find_pos(path, type_colon, i); + + if (p == n) { + p = find_pos(path, type_asterisk, i); + + root = root->insert_child(path[i], std::make_shared( + path.substr(i, p - i))); + + if (p < n) { + root = root->insert_child( + type_asterisk, + std::make_shared(path.substr(p + 1))); + ++param_count; + } + + code = root->add_coro_handler(coro_handler, methods); + break; + } + + root = root->insert_child( + path[i], std::make_shared(path.substr(i, p - i))); + + i = find_pos(path, type_slash, p); + + root = root->insert_child( + type_colon, + std::make_shared(path.substr(p + 1, i - p - 1))); + ++param_count; + + if (i == n) { + code = root->add_coro_handler(coro_handler, methods); + break; + } + } + else { + root = child; + + if (path[i] == type_colon) { + ++param_count; + i += root->path.size() + 1; + + if (i == n) { + code = root->add_coro_handler(coro_handler, methods); + break; + } + } + else { + auto j = 0UL; + auto m = root->path.size(); + + for (; i < n && j < m && path[i] == root->path[j]; ++i, ++j) { + } + + if (j < m) { + std::shared_ptr child( + std::make_shared(root->path.substr(j))); + child->handlers = root->handlers; + child->indices = root->indices; + child->children = root->children; + + root->path = root->path.substr(0, j); + root->handlers = {}; + root->indices = child->path[0]; + root->children = {child}; + } + + if (i == n) { + code = root->add_coro_handler(coro_handler, methods); + break; + } + } + } + } + + if (param_count > this->root->max_params) + this->root->max_params = param_count; + + return code; + } + + parse_result get(const std::string &path, const std::string &method) { + std::unordered_map params; + auto root = this->root; + + int i = 0, n = path.size(), p; + + while (i < n) { + if (root->indices.empty()) + return parse_result(); + + if (root->indices[0] == type_colon) { + root = root->children[0]; + + p = find_pos(path, type_slash, i); + params[root->path] = path.substr(i, p - i); + i = p; + } + else if (root->indices[0] == type_asterisk) { + root = root->children[0]; + params[root->path] = path.substr(i); + break; + } + else { + root = root->get_child(path[i]); + if (!root || path.substr(i, root->path.size()) != root->path) + return parse_result(); + i += root->path.size(); + } + } + + return parse_result{true, root->get_handler(method), params}; + } + + coro_result get_coro(const std::string &path, const std::string &method) { + std::unordered_map params; + auto root = this->root; + + int i = 0, n = path.size(), p; + + while (i < n) { + if (root->indices.empty()) + return coro_result(); + + if (root->indices[0] == type_colon) { + root = root->children[0]; + + p = find_pos(path, type_slash, i); + params[root->path] = path.substr(i, p - i); + i = p; + } + else if (root->indices[0] == type_asterisk) { + root = root->children[0]; + params[root->path] = path.substr(i); + break; + } + else { + root = root->get_child(path[i]); + if (!root || path.substr(i, root->path.size()) != root->path) + return coro_result(); + i += root->path.size(); + } + } + + return coro_result{true, root->get_coro_handler(method), params}; + } + + private: + int find_pos(const std::string &str, char target, int start) { + auto i = str.find(target, start); + return i == -1 ? str.size() : i; + } + + std::shared_ptr root; +}; +} // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/define.h b/include/ylt/thirdparty/cinatra/define.h index 8a48d1077..bca137af2 100644 --- a/include/ylt/thirdparty/cinatra/define.h +++ b/include/ylt/thirdparty/cinatra/define.h @@ -104,7 +104,20 @@ const static inline std::string CRCF = "\r\n"; const static inline std::string TWO_CRCF = "\r\n\r\n"; const static inline std::string BOUNDARY = "--CinatraBoundary2B8FAF4A80EDB307"; const static inline std::string MULTIPART_END = - CRCF + "--" + BOUNDARY + "--" + TWO_CRCF; + CRCF + "--" + BOUNDARY + "--" + CRCF; +constexpr std::string_view LAST_CHUNK = "0\r\n"; + +struct chunked_result { + std::error_code ec; + bool eof = false; + std::string_view data; +}; + +struct part_head_t { + std::error_code ec; + std::string name; + std::string filename; +}; inline std::unordered_map g_content_type_map = { {".css", "text/css"}, diff --git a/include/ylt/thirdparty/cinatra/function_traits.hpp b/include/ylt/thirdparty/cinatra/function_traits.hpp deleted file mode 100644 index 54cf88101..000000000 --- a/include/ylt/thirdparty/cinatra/function_traits.hpp +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once -#include -#include -#include - -// member function -#define TIMAX_FUNCTION_TRAITS(...) \ - template \ - struct function_traits_impl \ - : function_traits_impl {}; - -namespace timax { -/* - * 1. function type ==> - * Ret(Args...) - * 2. function pointer ==> - * Ret(*)(Args...) - * 3. function reference ==> - * Ret(&)(Args...) - * 4. pointer to non-static member function ==> Ret(T::*)(Args...) - * 5. function object and functor ==> &T::operator() - * 6. function with generic operator call ==> template &T::operator() - */ -template -struct function_traits_impl; - -template -struct function_traits - : function_traits_impl>> {}; - -template -struct function_traits_impl { - public: - enum { arity = sizeof...(Args) }; - typedef Ret function_type(Args...); - typedef Ret result_type; - using stl_function_type = std::function; - typedef Ret (*pointer)(Args...); - - template - struct args { - static_assert(I < arity, - "index is out of range, index must less than sizeof Args"); - using type = typename std::tuple_element>::type; - }; - - typedef std::tuple>...> - tuple_type; - using args_type_t = std::tuple; -}; - -template -using arg_type = typename function_traits::template args::type; - -// function pointer -template -struct function_traits_impl : function_traits { -}; - -// std::function -template -struct function_traits_impl> - : function_traits_impl {}; - -// pointer of non-static member function -TIMAX_FUNCTION_TRAITS() -TIMAX_FUNCTION_TRAITS(const) -TIMAX_FUNCTION_TRAITS(volatile) -TIMAX_FUNCTION_TRAITS(const volatile) - -// functor -template -struct function_traits_impl - : function_traits_impl {}; - -template -typename function_traits::stl_function_type to_function( - const Function &lambda) { - return static_cast::stl_function_type>( - lambda); -} - -template -typename function_traits::stl_function_type to_function( - Function &&lambda) { - return static_cast::stl_function_type>( - std::forward(lambda)); -} - -template -typename function_traits::pointer to_function_pointer( - const Function &lambda) { - return static_cast::pointer>(lambda); -} -} // namespace timax diff --git a/include/ylt/thirdparty/cinatra/gzip.hpp b/include/ylt/thirdparty/cinatra/gzip.hpp new file mode 100644 index 000000000..400ce6ff0 --- /dev/null +++ b/include/ylt/thirdparty/cinatra/gzip.hpp @@ -0,0 +1,143 @@ +#pragma once +#include + +#include +namespace cinatra::gzip_codec { +// from https://github.com/chafey/GZipCodec + +#define CHUNK 16384 +#define windowBits 15 +#define GZIP_ENCODING 16 + +// GZip Compression +// @param data - the data to compress (does not have to be string, can be binary +// data) +// @param compressed_data - the resulting gzip compressed data +// @param level - the gzip compress level -1 = default, 0 = no compression, 1= +// worst/fastest compression, 9 = best/slowest compression +// @return - true on success, false on failure +inline bool compress(std::string_view data, std::string &compressed_data, + int level = -1) { + unsigned char out[CHUNK]; + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + if (deflateInit2(&strm, level, Z_DEFLATED, windowBits | GZIP_ENCODING, 8, + Z_DEFAULT_STRATEGY) != Z_OK) { + return false; + } + strm.next_in = (unsigned char *)data.data(); + strm.avail_in = (uInt)data.length(); + do { + int have; + strm.avail_out = CHUNK; + strm.next_out = out; + if (deflate(&strm, Z_FINISH) == Z_STREAM_ERROR) { + return false; + } + have = CHUNK - strm.avail_out; + compressed_data.append((char *)out, have); + } while (strm.avail_out == 0); + if (deflateEnd(&strm) != Z_OK) { + return false; + } + return true; +} + +// GZip Decompression +// @param compressed_data - the gzip compressed data +// @param data - the resulting uncompressed data (may contain binary data) +// @return - true on success, false on failure +inline bool uncompress(std::string_view compressed_data, std::string &data) { + int ret; + unsigned have; + z_stream strm; + unsigned char out[CHUNK]; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + if (inflateInit2(&strm, 16 + MAX_WBITS) != Z_OK) { + return false; + } + + strm.avail_in = (uInt)compressed_data.length(); + strm.next_in = (unsigned char *)compressed_data.data(); + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm); + return false; + } + have = CHUNK - strm.avail_out; + data.append((char *)out, have); + } while (strm.avail_out == 0); + + if (inflateEnd(&strm) != Z_OK) { + return false; + } + + return true; +} + +inline int compress_file(const char *src_file, const char *out_file_name) { + char buf[BUFSIZ] = {0}; + uInt bytes_read = 0; + gzFile out = gzopen(out_file_name, "wb"); + if (!out) { + return -1; + } + + std::ifstream in(src_file, std::ios::binary); + if (!in.is_open()) { + return -1; + } + + while (true) { + in.read(buf, BUFSIZ); + bytes_read = (uInt)in.gcount(); + if (bytes_read == 0) + break; + int bytes_written = gzwrite(out, buf, bytes_read); + if (bytes_written == 0) { + gzclose(out); + return -1; + } + if (bytes_read != BUFSIZ) + break; + } + gzclose(out); + + return 0; +} + +inline int uncompress_file(const char *src_file, const char *out_file_name) { + char buf[BUFSIZ] = {0}; + std::ofstream out(out_file_name, std::ios::binary); + if (!out.is_open()) { + return -1; + } + + gzFile fi = gzopen(src_file, "rb"); + if (!fi) { + return -1; + } + + gzrewind(fi); + while (!gzeof(fi)) { + int len = gzread(fi, buf, BUFSIZ); + out.write(buf, len); + } + gzclose(fi); + + return 0; +} +} // namespace cinatra::gzip_codec \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/http_parser.hpp b/include/ylt/thirdparty/cinatra/http_parser.hpp index 55559a431..6c2fa898b 100644 --- a/include/ylt/thirdparty/cinatra/http_parser.hpp +++ b/include/ylt/thirdparty/cinatra/http_parser.hpp @@ -126,6 +126,29 @@ class http_parser { return false; } + bool is_multipart() { + auto content_type = get_header_value("Content-Type"); + if (content_type.empty()) { + return false; + } + + if (content_type.find("multipart") == std::string_view::npos) { + return false; + } + + return true; + } + + std::string_view get_boundary() { + auto content_type = get_header_value("Content-Type"); + size_t pos = content_type.find("=--"); + if (pos == std::string_view::npos) { + return ""; + } + + return content_type.substr(pos + 1); + } + bool is_req_ranges() const { auto value = this->get_header_value("Range"sv); return !value.empty(); diff --git a/include/ylt/thirdparty/cinatra/io_service_pool.hpp b/include/ylt/thirdparty/cinatra/io_service_pool.hpp new file mode 100644 index 000000000..8d6231114 --- /dev/null +++ b/include/ylt/thirdparty/cinatra/io_service_pool.hpp @@ -0,0 +1,109 @@ +#pragma once +#include +#include +#include + +#include "use_asio.hpp" +#include "utils.hpp" + +namespace cinatra { +class io_service_pool { + public: + using executor_type = asio::io_context::executor_type; + explicit io_service_pool(std::size_t pool_size) : next_io_context_(0) { + if (pool_size == 0) { + pool_size = 1; // set default value as 1 + } + + for (std::size_t i = 0; i < pool_size; ++i) { + io_context_ptr io_context(new asio::io_context); + work_ptr work(new asio::io_context::work(*io_context)); + io_contexts_.push_back(io_context); + work_.push_back(work); + } + } + + void run() { + std::vector> threads; + for (std::size_t i = 0; i < io_contexts_.size(); ++i) { + threads.emplace_back(std::make_shared( + [](io_context_ptr svr) { + svr->run(); + }, + io_contexts_[i])); + } + + for (std::size_t i = 0; i < threads.size(); ++i) { + threads[i]->join(); + } + promise_.set_value(); + } + + void stop() { + work_.clear(); + promise_.get_future().wait(); + } + + bool has_stop() const { return work_.empty(); } + + size_t current_io_context() { return next_io_context_ - 1; } + + asio::io_context::executor_type get_executor() { + asio::io_context &io_context = *io_contexts_[next_io_context_]; + ++next_io_context_; + if (next_io_context_ == io_contexts_.size()) { + next_io_context_ = 0; + } + return io_context.get_executor(); + } + + asio::io_service &get_io_service() { + asio::io_service &io_service = *io_contexts_[next_io_context_]; + ++next_io_context_; + if (next_io_context_ == io_contexts_.size()) + next_io_context_ = 0; + return io_service; + } + + private: + using io_context_ptr = std::shared_ptr; + using work_ptr = std::shared_ptr; + + std::vector io_contexts_; + std::vector work_; + std::size_t next_io_context_; + std::promise promise_; +}; + +class io_service_inplace : private noncopyable { + public: + explicit io_service_inplace() { + io_services_ = std::make_shared(); + work_ = std::make_shared(*io_services_); + } + + void run() { io_services_->run(); } + + intptr_t run_one() { return io_services_->run_one(); } + + intptr_t poll() { return io_services_->poll(); } + + intptr_t poll_one() { return io_services_->poll_one(); } + + void stop() { + work_ = nullptr; + + if (io_services_) + io_services_->stop(); + } + + asio::io_service &get_io_service() { return *io_services_; } + + private: + using io_service_ptr = std::shared_ptr; + using work_ptr = std::shared_ptr; + + io_service_ptr io_services_; + work_ptr work_; +}; +} // namespace cinatra diff --git a/include/ylt/thirdparty/cinatra/mime_types.hpp b/include/ylt/thirdparty/cinatra/mime_types.hpp index 345c60f4c..927114ffa 100644 --- a/include/ylt/thirdparty/cinatra/mime_types.hpp +++ b/include/ylt/thirdparty/cinatra/mime_types.hpp @@ -277,9 +277,8 @@ static const std::map mime_map = { {".ppm", "image/x-portable-pixmap"}, {".pps", "application/vnd.ms-powerpoint"}, {".ppt", "application/vnd.ms-powerpoint"}, - {".pptx", - "application/" - "vnd.openxmlformats-officedocument.presentationml.presentation"}, + {".pptx", "application/" + "vnd.openxmlformats-officedocument.presentationml.presentation"}, {".pqf", "application/x-cprplayer"}, {".pqi", "application/cprplayer"}, {".prc", "application/x-prc"}, @@ -509,4 +508,4 @@ inline std::string_view get_mime_type(std::string_view extension) { return it->second; } -} // namespace cinatra +} // namespace cinatra diff --git a/include/ylt/thirdparty/cinatra/multipart.hpp b/include/ylt/thirdparty/cinatra/multipart.hpp new file mode 100644 index 000000000..d40aaa711 --- /dev/null +++ b/include/ylt/thirdparty/cinatra/multipart.hpp @@ -0,0 +1,118 @@ +#pragma once +#include "define.h" + +namespace cinatra { + +template +class multipart_reader_t { + public: + multipart_reader_t(T *conn) + : conn_(conn), + head_buf_(conn_->head_buf_), + chunked_buf_(conn_->chunked_buf_) {} + + async_simple::coro::Lazy read_part_head() { + if (head_buf_.size() > 0) { + const char *data_ptr = asio::buffer_cast(head_buf_.data()); + chunked_buf_.sputn(data_ptr, head_buf_.size()); + head_buf_.consume(head_buf_.size()); + } + + part_head_t result{}; + std::error_code ec{}; + size_t last_size = chunked_buf_.size(); + size_t size; + + auto get_part_name = [](std::string_view data, std::string_view name, + size_t start) { + start += name.length(); + size_t end = data.find("\"", start); + return data.substr(start, end - start); + }; + + constexpr std::string_view name = "name=\""; + constexpr std::string_view filename = "filename=\""; + + while (true) { + if (std::tie(ec, size) = + co_await conn_->async_read_until(chunked_buf_, CRCF); + ec) { + result.ec = ec; + conn_->close(); + co_return result; + } + + const char *data_ptr = + asio::buffer_cast(chunked_buf_.data()); + chunked_buf_.consume(size); + if (*data_ptr == '-') { + continue; + } + std::string_view data{data_ptr, size}; + if (size == 2) { // got the head end: \r\n\r\n + break; + } + + if (size_t pos = data.find("name"); pos != std::string_view::npos) { + result.name = get_part_name(data, name, pos); + + if (size_t pos = data.find("filename"); pos != std::string_view::npos) { + result.filename = get_part_name(data, filename, pos); + } + continue; + } + } + + co_return result; + } + + async_simple::coro::Lazy read_part_body( + std::string_view boundary) { + chunked_result result{}; + std::error_code ec{}; + size_t size = 0; + + if (std::tie(ec, size) = + co_await conn_->async_read_until(chunked_buf_, boundary); + ec) { + result.ec = ec; + conn_->close(); + co_return result; + } + + const char *data_ptr = asio::buffer_cast(chunked_buf_.data()); + chunked_buf_.consume(size); + result.data = std::string_view{ + data_ptr, size - boundary.size() - 4}; //-- boundary \r\n + + if (std::tie(ec, size) = + co_await conn_->async_read_until(chunked_buf_, CRCF); + ec) { + result = {}; + result.ec = ec; + conn_->close(); + co_return result; + } + + data_ptr = asio::buffer_cast(chunked_buf_.data()); + std::string data{data_ptr, size}; + if (size > 2) { + constexpr std::string_view complete_flag = "--\r\n"; + if (data == complete_flag) { + result.eof = true; + } + } + + chunked_buf_.consume(size); + co_return result; + } + + private: + T *conn_; + asio::streambuf &head_buf_; + asio::streambuf &chunked_buf_; +}; + +template +multipart_reader_t(T *con) -> multipart_reader_t; +} // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/picohttpparser.h b/include/ylt/thirdparty/cinatra/picohttpparser.h index 081160ac5..031480cf5 100644 --- a/include/ylt/thirdparty/cinatra/picohttpparser.h +++ b/include/ylt/thirdparty/cinatra/picohttpparser.h @@ -32,8 +32,26 @@ #include +#ifdef CINATRA_SSE +#ifdef _MSC_VER +#include +#else +#include +#endif +#endif + +#ifdef CINATRA_AVX2 +#include +#endif + +#ifdef CINATRA_ARM_OPT +#include +#endif + #ifdef _MSC_VER #define ssize_t intptr_t +#else +#include #endif namespace cinatra { @@ -120,6 +138,32 @@ struct phr_chunked_decoder { CHECK_EOF(); \ EXPECT_CHAR_NO_CHECK(ch); +#ifdef CINATRA_ARM_OPT +#define ADVANCE_TOKEN(tok, toklen) \ + do { \ + const char *tok_start = buf; \ + int found2; \ + buf = findchar_nonprintable_fast(buf, buf_end, &found2); \ + if (!found2) { \ + CHECK_EOF(); \ + } \ + while (1) { \ + if (*buf == ' ') { \ + break; \ + } \ + else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ + if ((unsigned char)*buf < '\040' || *buf == '\177') { \ + *ret = -1; \ + return NULL; \ + } \ + } \ + ++buf; \ + CHECK_EOF(); \ + } \ + tok = tok_start; \ + toklen = buf - tok_start; \ + } while (0) +#else #define ADVANCE_TOKEN(tok, toklen) \ do { \ const char *tok_start = buf; \ @@ -145,6 +189,7 @@ struct phr_chunked_decoder { tok = tok_start; \ toklen = buf - tok_start; \ } while (0) +#endif static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" @@ -160,18 +205,142 @@ static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, int ranges_size, int *found) { *found = 0; +#ifdef CINATRA_SSE + if (likely(buf_end - buf >= 16)) { + __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges); + + size_t left = (buf_end - buf) & ~15; + do { + __m128i b16 = _mm_loadu_si128((const __m128i *)buf); + int r = _mm_cmpestri( + ranges16, ranges_size, b16, 16, + _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); + if (unlikely(r != 16)) { + buf += r; + *found = 1; + break; + } + buf += 16; + left -= 16; + } while (likely(left != 0)); + } +#else /* suppress unused parameter warning */ (void)buf_end; (void)ranges; (void)ranges_size; +#endif return buf; } +static const char *findchar_nonprintable_fast(const char *buf, + const char *buf_end, int *found) { +#ifdef CINATRA_ARM_OPT + *found = 0; + + const size_t block_size = sizeof(uint8x16_t) - 1; + const char *const end = + (size_t)(buf_end - buf) >= block_size ? buf_end - block_size : buf; + + for (; buf < end; buf += sizeof(uint8x16_t)) { + uint8x16_t v = vld1q_u8((const uint8_t *)buf); + + v = vorrq_u8(vcltq_u8(v, vmovq_n_u8('\041')), + vceqq_u8(v, vmovq_n_u8('\177'))); + + /* Pack the comparison result into 64 bits. */ + const uint8x8_t rv = vshrn_n_u16(vreinterpretq_u16_u8(v), 4); + uint64_t offset = vget_lane_u64(vreinterpret_u64_u8(rv), 0); + + if (offset) { + *found = 1; + __asm__("rbit %x0, %x0" : "+r"(offset)); + static_assert(sizeof(unsigned long long) == sizeof(uint64_t), + "Need the number of leading 0-bits in uint64_t."); + /* offset uses 4 bits per byte of input. */ + buf += __builtin_clzll(offset) / 4; + break; + } + } + + return buf; +#else + static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; + + return findchar_fast(buf, buf_end, ranges2, 4, found); +#endif +} + static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret) { const char *token_start = buf; +#ifdef CINATRA_SSE + static const char ranges1[] = + "\0\010" + /* allow HT */ + "\012\037" + /* allow SP and up to but not including DEL */ + "\177\177" + /* allow chars w. MSB set */ + ; + int found; + buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found); + if (found) + goto FOUND_CTL; +#elif defined(CINATRA_ARM_OPT) + const size_t block_size = 2 * sizeof(uint8x16_t) - 1; + const char *const end = + (size_t)(buf_end - buf) >= block_size ? buf_end - block_size : buf; + + for (; buf < end; buf += 2 * sizeof(uint8x16_t)) { + const uint8x16_t space = vmovq_n_u8('\040'); + const uint8x16_t threshold = vmovq_n_u8(0137u); + const uint8x16_t v1 = vld1q_u8((const uint8_t *)buf); + const uint8x16_t v2 = vld1q_u8((const uint8_t *)buf + sizeof(v1)); + uint8x16_t v3 = vsubq_u8(v1, space); + uint8x16_t v4 = vsubq_u8(v2, space); + + v3 = vcgeq_u8(v3, threshold); + v4 = vcgeq_u8(v4, threshold); + v3 = vorrq_u8(v3, v4); + /* Pack the comparison result into half a vector, i.e. 64 bits. */ + v3 = vpmaxq_u8(v3, v3); + + if (vgetq_lane_u64(vreinterpretq_u64_u8(v3), 0)) { + const uint8x16_t del = vmovq_n_u8('\177'); + /* This mask makes it possible to pack the comparison results into half a + * vector, which has the same size as uint64_t. */ + const uint8x16_t mask = vreinterpretq_u8_u32(vmovq_n_u32(0x40100401)); + const uint8x16_t tab = vmovq_n_u8('\011'); + + v3 = vcltq_u8(v1, space); + v4 = vcltq_u8(v2, space); + v3 = vbicq_u8(v3, vceqq_u8(v1, tab)); + v4 = vbicq_u8(v4, vceqq_u8(v2, tab)); + v3 = vorrq_u8(v3, vceqq_u8(v1, del)); + v4 = vorrq_u8(v4, vceqq_u8(v2, del)); + /* After masking, four consecutive bytes in the results do not have the + * same bits set. */ + v3 = vandq_u8(v3, mask); + v4 = vandq_u8(v4, mask); + /* Pack the comparison results into 128, and then 64 bits. */ + v3 = vpaddq_u8(v3, v4); + v3 = vpaddq_u8(v3, v3); + uint64_t offset = vgetq_lane_u64(vreinterpretq_u64_u8(v3), 0); + + if (offset) { + __asm__("rbit %x0, %x0" : "+r"(offset)); + static_assert(sizeof(unsigned long long) == sizeof(uint64_t), + "Need the number of leading 0-bits in uint64_t."); + /* offset uses 2 bits per byte of input. */ + buf += __builtin_clzll(offset) / 2; + goto FOUND_CTL; + } + } + } +#else /* find non-printable char within the next 8 bytes, this is the hottest code; * manually inlined */ while (likely(buf_end - buf >= 8)) { @@ -198,7 +367,7 @@ static const char *get_token_to_eol(const char *buf, const char *buf_end, } ++buf; } - +#endif for (;; ++buf) { CHECK_EOF(); if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { @@ -295,6 +464,348 @@ static const char *parse_http_version(const char *buf, const char *buf_end, return buf; } +#ifdef CINATRA_AVX2 +static unsigned long TZCNT(unsigned long long in) { + unsigned long res; + asm("tzcnt %1, %0\n\t" : "=r"(res) : "r"(in)); + return res; +} +/* Parse only 32 bytes */ +static void find_ranges32(__m256i b0, unsigned long *range0, + unsigned long *range1) { + const __m256i rr0 = _mm256_set1_epi8(0x00 - 1); + const __m256i rr1 = _mm256_set1_epi8(0x1f + 1); + const __m256i rr2 = _mm256_set1_epi8(0x3a); + const __m256i rr4 = _mm256_set1_epi8(0x7f); + const __m256i rr7 = _mm256_set1_epi8(0x09); + + /* 0<=x */ + __m256i gz0 = _mm256_cmpgt_epi8(b0, rr0); + /* 0== 96) { + b0 = _mm256_loadu_si256((void *)buf + 32 * 0); + b1 = _mm256_loadu_si256((void *)buf + 32 * 1); + b2 = _mm256_loadu_si256((void *)buf + 32 * 2); + b3 = _mm256_loadu_si256((void *)tmpbuf); + } + else if (dist >= 64) { + b0 = _mm256_loadu_si256((void *)buf + 32 * 0); + b1 = _mm256_loadu_si256((void *)buf + 32 * 1); + b2 = _mm256_loadu_si256((void *)tmpbuf); + b3 = _mm256_setzero_si256(); + } + else { + if (dist < 32) { + b0 = _mm256_loadu_si256((void *)tmpbuf); + return find_ranges32(b0, range0, range1); + } + else { + b0 = _mm256_loadu_si256((void *)buf + 32 * 0); + b1 = _mm256_loadu_si256((void *)tmpbuf); + return find_ranges64(b0, b1, range0, range1); + } + } + } + else { + /* Load 128 bytes */ + b0 = _mm256_loadu_si256((void *)buf + 32 * 0); + b1 = _mm256_loadu_si256((void *)buf + 32 * 1); + b2 = _mm256_loadu_si256((void *)buf + 32 * 2); + b3 = _mm256_loadu_si256((void *)buf + 32 * 3); + } + + /* 0<=x */ + __m256i gz0 = _mm256_cmpgt_epi8(b0, rr0); + __m256i gz1 = _mm256_cmpgt_epi8(b1, rr0); + __m256i gz2 = _mm256_cmpgt_epi8(b2, rr0); + __m256i gz3 = _mm256_cmpgt_epi8(b3, rr0); + /* 0== 65 && *buf <= 90)) { + if (!token_char_map[(unsigned char)*buf]) { + *ret = -1; + *num_headers = n_headers; + return NULL; + } + name = buf; + + /* Attempt to find a match in the index */ + found = 0; + do { + unsigned long distance = buf - prep_start; + /* Check if the bitmaps are still valid. An assumption I make is that + buf > 128 (i.e. the os will never allocate memory at address 0-128 */ + if (unlikely(distance >= + 128)) { /* Bitmaps are too old, make new ones */ + prep_start = buf; + distance = 0; + find_ranges(buf, buf_end, rr0, rr1); + } + else if (distance >= 64) { /* In the second half of the bitmap */ + unsigned long index = + rr0[1] >> (distance - 64); /* Correct offset of the bitmap */ + unsigned long find = TZCNT(index); /* Fine next set bit */ + if ((find < 64)) { /* Yey, we found a token */ + buf += find; + found = 1; + break; + } + buf = prep_start + 128; /* No token was found in the current bitmap */ + continue; + } + unsigned long index = + rr0[0] >> (distance); /* In the first half of the bitmap */ + unsigned long find = TZCNT(index); /* Find next set bit */ + if ((find < 64)) { /* Token found */ + buf += find; + found = 1; + break; + } /* Token not found, look at second half of bitmap */ + index = rr0[1]; + find = TZCNT(index); + if ((find < 64)) { + buf += 64 + find - distance; + found = 1; + break; + } + + buf = prep_start + 128; + } while (buf < buf_end); + + if (!found) + if (buf >= buf_end) { + *ret = -2; + *num_headers = n_headers; + return NULL; + } + name_len = buf - name; + ++buf; + CHECK_EOF(); + while ((*buf == ' ' || *buf == '\t')) { + ++buf; + CHECK_EOF(); + } + } + else { + name = NULL; + name_len = 0; + } + const char *token_start = buf; + + found = 0; + + do { + /* Too far */ + unsigned long distance = buf - prep_start; /* Same algorithm as above */ + if (unlikely(distance >= 128)) { + prep_start = buf; + distance = 0; + find_ranges(buf, buf_end, rr0, rr1); + } + else if (distance >= 64) { + unsigned long index = rr1[1] >> (distance - 64); + unsigned long find = TZCNT(index); + if ((find < 64)) { + buf += find; + found = 1; + break; + } + buf = prep_start + 128; + continue; + } + unsigned long index = rr1[0] >> (distance); + unsigned long find = TZCNT(index); + if ((find < 64)) { + buf += find; + found = 1; + break; + } + index = rr1[1]; + find = TZCNT(index); + if ((find < 64)) { + buf += 64 + find - distance; + found = 1; + break; + } + + buf = prep_start + 128; + } while (buf < buf_end); + + if (!found) + if (buf >= buf_end) { + *ret = -2; + *num_headers = n_headers; + return NULL; + } + + unsigned short two_char = *(unsigned short *)buf; + + if (likely(two_char == 0x0a0d)) { + value_len = buf - token_start; + buf += 2; + } + else if (unlikely(two_char & 0x0a == 0x0a)) { + value_len = buf - token_start; + ++buf; + } + else { + *ret = -1; + *num_headers = n_headers; + return NULL; + } + value = token_start; + headers[*num_headers] = {std::string_view{name, name_len}, + std::string_view{value, value_len}}; + } + *num_headers = n_headers; + return buf; +} + +#else + static const char *parse_headers(const char *buf, const char *buf_end, http_header *headers, size_t *num_headers, size_t max_headers, int *ret) { @@ -372,6 +883,8 @@ static const char *parse_headers(const char *buf, const char *buf_end, return buf; } +#endif + static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path, size_t *path_len, diff --git a/include/ylt/thirdparty/cinatra/response_cv.hpp b/include/ylt/thirdparty/cinatra/response_cv.hpp index b34031a20..a9333dafb 100644 --- a/include/ylt/thirdparty/cinatra/response_cv.hpp +++ b/include/ylt/thirdparty/cinatra/response_cv.hpp @@ -4,161 +4,162 @@ #include "define.h" namespace cinatra { +enum class content_encoding { gzip, none }; + enum class status_type { init, + http_continue = 100, switching_protocols = 101, + processing = 102, ok = 200, created = 201, accepted = 202, + nonauthoritative = 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, moved_temporarily = 302, 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, + request_entity_too_large = 413, + request_uri_too_long = 414, + unsupported_media_type = 415, + range_not_satisfiable = 416, + expectation_failed = 417, + im_a_teapot = 418, + enchance_your_calm = 420, + misdirected_request = 421, + unprocessable_entity = 422, + locked = 423, + failed_dependency = 424, + too_early = 425, + upgrade_required = 426, + precondition_required = 428, + too_many_requests = 429, + request_header_fields_too_large = 431, + unavailable_for_legal_reasons = 451, internal_server_error = 500, not_implemented = 501, bad_gateway = 502, - service_unavailable = 503 + service_unavailable = 503, + gateway_timeout = 504, + version_not_supported = 505, + variant_also_negotiates = 506, + insufficient_storage = 507, + loop_detected = 508, + not_extended = 510, + network_authentication_required = 511 }; -enum class content_encoding { gzip, none }; - -inline std::string_view ok_sv = "OK"; -inline std::string_view created = - "" - "Created" - "

201 Created

" - ""; - -inline std::string_view accepted = - "" - "Accepted" - "

202 Accepted

" - ""; - -inline std::string_view no_content = - "" - "No Content" - "

204 Content

" - ""; - -inline std::string_view multiple_choices = - "" - "Multiple Choices" - "

300 Multiple Choices

" - ""; - -inline std::string_view moved_permanently = - "" - "Moved Permanently" - "

301 Moved Permanently

" - ""; - -inline std::string_view temporary_redirect = - "" - "Temporary Redirect" - "

307 Temporary Redirect

" - ""; - -inline std::string_view moved_temporarily = - "" - "Moved Temporarily" - "

302 Moved Temporarily

" - ""; - -inline std::string_view not_modified = - "" - "Not Modified" - "

304 Not Modified

" - ""; - -inline std::string_view bad_request = - "" - "Bad Request" - "

400 Bad Request

" - ""; - -inline std::string_view unauthorized = - "" - "Unauthorized" - "

401 Unauthorized

" - ""; - -inline std::string_view forbidden = - "" - "Forbidden" - "

403 Forbidden

" - ""; - -inline std::string_view not_found = - "" - "Not Found" - "

404 Not Found

" - ""; - -inline std::string_view conflict = - "" - "Conflict" - "

409 Conflict

" - ""; - -inline std::string_view internal_server_error = - "" - "Internal Server Error" - "

500 Internal Server Error

" - ""; - -inline std::string_view not_implemented = - "" - "Not Implemented" - "

501 Not Implemented

" - ""; - -inline std::string_view bad_gateway = - "" - "Bad Gateway" - "

502 Bad Gateway

" - ""; - -inline std::string_view service_unavailable = - "" - "Service Unavailable" - "

503 Service Unavailable

" - ""; - +// http response status string +namespace http_status_string { +inline constexpr std::string_view http_continue = "HTTP/1.1 100 Continue\r\n"; inline constexpr std::string_view switching_protocols = "HTTP/1.1 101 Switching Protocals\r\n"; +inline constexpr std::string_view processing = "HTTP/1.1 102 Processing\r\n"; inline constexpr std::string_view rep_ok = "HTTP/1.1 200 OK\r\n"; inline constexpr std::string_view rep_created = "HTTP/1.1 201 Created\r\n"; inline constexpr std::string_view rep_accepted = "HTTP/1.1 202 Accepted\r\n"; +inline constexpr std::string_view rep_nonauthoritative = + "HTTP/1.1 203 Nonauthoritative\r\n"; inline constexpr std::string_view rep_no_content = "HTTP/1.1 204 No Content\r\n"; +inline constexpr std::string_view rep_reset_content = + "HTTP/1.1 205 Reset Content\r\n"; inline constexpr std::string_view rep_partial_content = "HTTP/1.1 206 Partial Content\r\n"; +inline constexpr std::string_view rep_multi_status = + "HTTP/1.1 207 Multi Status\r\n"; +inline constexpr std::string_view rep_already_reported = + "HTTP/1.1 208 Already Reported\r\n"; +inline constexpr std::string_view rep_im_used = "HTTP/1.1 226 Im Used\r\n"; inline constexpr std::string_view rep_multiple_choices = "HTTP/1.1 300 Multiple Choices\r\n"; inline constexpr std::string_view rep_moved_permanently = "HTTP/1.1 301 Moved Permanently\r\n"; -inline constexpr std::string_view rep_temporary_redirect = - "HTTP/1.1 307 Temporary Redirect\r\n"; inline constexpr std::string_view rep_moved_temporarily = "HTTP/1.1 302 Moved Temporarily\r\n"; inline constexpr std::string_view rep_not_modified = "HTTP/1.1 304 Not Modified\r\n"; +inline constexpr std::string_view rep_use_proxy = "HTTP/1.1 305 Use Proxy\r\n"; +inline constexpr std::string_view rep_temporary_redirect = + "HTTP/1.1 307 Temporary Redirect\r\n"; +inline constexpr std::string_view rep_permanent_redirect = + "HTTP/1.1 308 Permanent Redirect\r\n"; inline constexpr std::string_view rep_bad_request = "HTTP/1.1 400 Bad Request\r\n"; inline constexpr std::string_view rep_unauthorized = "HTTP/1.1 401 Unauthorized\r\n"; +inline constexpr std::string_view rep_payment_required = + "HTTP/1.1 402 Payment Required\r\n"; inline constexpr std::string_view rep_forbidden = "HTTP/1.1 403 Forbidden\r\n"; inline constexpr std::string_view rep_not_found = "HTTP/1.1 404 Not Found\r\n"; +inline constexpr std::string_view rep_method_not_allowed = + "HTTP/1.1 405 Method Not Allowed\r\n"; +inline constexpr std::string_view rep_not_acceptable = + "HTTP/1.1 406 Not Acceptable\r\n"; +inline constexpr std::string_view rep_proxy_authentication_required = + "HTTP/1.1 407 Proxy Authentication Required\r\n"; +inline constexpr std::string_view rep_request_timeout = + "HTTP/1.1 408 Request Timeout\r\n"; inline constexpr std::string_view rep_conflict = "HTTP/1.1 409 Conflict\r\n"; +inline constexpr std::string_view rep_gone = "HTTP/1.1 410 Gone\r\n"; +inline constexpr std::string_view rep_length_required = + "HTTP/1.1 411 Length Required\r\n"; +inline constexpr std::string_view rep_precondition_failed = + "HTTP/1.1 412 Precondition Failed\r\n"; +inline constexpr std::string_view rep_request_entity_too_large = + "HTTP/1.1 413 Request Entity Too Large\r\n"; +inline constexpr std::string_view rep_request_uri_too_long = + "HTTP/1.1 414 Request Uri Too Long\r\n"; +inline constexpr std::string_view rep_unsupported_media_type = + "HTTP/1.1 415 Unsupported Media Type\r\n"; +inline constexpr std::string_view rep_range_not_satisfiable = + "HTTP/1.1 416 Requested Range Not Satisfiable\r\n"; +inline constexpr std::string_view rep_expectation_failed = + "HTTP/1.1 417 Expectation Failed\r\n"; +inline constexpr std::string_view rep_im_a_teapot = + "HTTP/1.1 418 Im a Teapot\r\n"; +inline constexpr std::string_view rep_enchance_your_calm = + "HTTP/1.1 420 Enchance Your Calm\r\n"; +inline constexpr std::string_view rep_misdirected_request = + "HTTP/1.1 421 Misdirected Request\r\n"; +inline constexpr std::string_view rep_unprocessable_entity = + "HTTP/1.1 422 Unprocessable Entity\r\n"; +inline constexpr std::string_view rep_locked = "HTTP/1.1 423 Locked\r\n"; +inline constexpr std::string_view rep_failed_dependency = + "HTTP/1.1 424 Failed_Dependency\r\n"; +inline constexpr std::string_view rep_too_early = "HTTP/1.1 425 Too Early\r\n"; +inline constexpr std::string_view rep_upgrade_required = + "HTTP/1.1 426 Upgrade Required\r\n"; +inline constexpr std::string_view rep_precondition_required = + "HTTP/1.1 428 Precondition Required\r\n"; +inline constexpr std::string_view rep_too_many_requests = + "HTTP/1.1 429 Too Many Requests\r\n"; +inline constexpr std::string_view rep_request_header_fields_too_large = + "HTTP/1.1 431 Request Header Fields Too Large\r\n"; +inline constexpr std::string_view rep_unavailable_for_legal_reasons = + "HTTP/1.1 451 Unavailabl For Legal Reasons\r\n"; inline constexpr std::string_view rep_internal_server_error = "HTTP/1.1 500 Internal Server Error\r\n"; inline constexpr std::string_view rep_not_implemented = @@ -167,252 +168,77 @@ inline constexpr std::string_view rep_bad_gateway = "HTTP/1.1 502 Bad Gateway\r\n"; inline constexpr std::string_view rep_service_unavailable = "HTTP/1.1 503 Service Unavailable\r\n"; - -inline constexpr std::string_view rep_html = - "Content-Type: text/html; charset=UTF-8\r\n"; -inline constexpr std::string_view rep_json = - "Content-Type: application/json; charset=UTF-8\r\n"; -inline constexpr std::string_view rep_string = - "Content-Type: text/plain; charset=UTF-8\r\n"; -inline constexpr std::string_view rep_multipart = - "Content-Type: multipart/form-data; boundary="; - -inline constexpr std::string_view rep_keep = "Connection: keep-alive\r\n"; -inline constexpr std::string_view rep_close = "Connection: close \r\n"; -inline constexpr std::string_view rep_len = "Content-Length: "; -inline constexpr std::string_view rep_crcf = "\r\n"; -inline constexpr std::string_view rep_server = "Server: cinatra\r\n"; - -inline const char name_value_separator[] = {':', ' '}; -// inline std::string_view crlf = "\r\n"; - -constexpr std::string_view crlf = "\r\n"; -constexpr std::string_view last_chunk = "0\r\n"; -inline const std::string http_chunk_header = - "HTTP/1.1 200 OK\r\n" - "Transfer-Encoding: chunked\r\n"; -/*"Content-Type: video/mp4\r\n" -"\r\n";*/ - -inline const std::string http_range_chunk_header = - "HTTP/1.1 206 Partial Content\r\n" - "Transfer-Encoding: chunked\r\n"; -/*"Content-Type: video/mp4\r\n" -"\r\n";*/ - -inline constexpr std::string_view to_content_type_str(req_content_type type) { - switch (type) { - case req_content_type::html: - return rep_html; - case req_content_type::json: - return rep_json; - case req_content_type::string: - return rep_string; - case req_content_type::multipart: - return rep_multipart; - default: - return ""; - } -} - -namespace detail { -template -struct to_chars { - static constexpr std::array value = { - 'C', - 'o', - 'n', - 't', - 'e', - 'n', - 't', - '-', - 'L', - 'e', - 'n', - 'g', - 't', - 'h', - ':', - ' ', - ('0' + digits)..., - '\r', - '\n'}; -}; - -// template -// const char to_chars::value[] = { ('0' + digits)... , '\r', -// '\n', 0 }; - -template -struct explode : explode {}; - -template -struct explode<0, digits...> : to_chars {}; -} // namespace detail - -template -struct num_to_string : detail::explode {}; - -template -inline decltype(auto) to_buffer(status_type status) { - switch (status) { - case status_type::switching_protocols: - return T(switching_protocols.data(), switching_protocols.length()); - case status_type::ok: - return T(rep_ok.data(), rep_ok.length()); - case status_type::created: - return T(rep_created.data(), rep_created.length()); - case status_type::accepted: - return T(rep_accepted.data(), rep_created.length()); - case status_type::no_content: - return T(rep_no_content.data(), rep_no_content.length()); - case status_type::partial_content: - return T(rep_partial_content.data(), rep_partial_content.length()); - case status_type::multiple_choices: - return T(rep_multiple_choices.data(), rep_multiple_choices.length()); - case status_type::moved_permanently: - return T(rep_moved_permanently.data(), rep_moved_permanently.length()); - case status_type::temporary_redirect: - return T(rep_temporary_redirect.data(), rep_temporary_redirect.length()); - case status_type::moved_temporarily: - return T(rep_moved_temporarily.data(), rep_moved_temporarily.length()); - case status_type::not_modified: - return T(rep_not_modified.data(), rep_not_modified.length()); - case status_type::bad_request: - return T(rep_bad_request.data(), rep_bad_request.length()); - case status_type::unauthorized: - return T(rep_unauthorized.data(), rep_unauthorized.length()); - case status_type::forbidden: - return T(rep_forbidden.data(), rep_forbidden.length()); - case status_type::not_found: - return T(rep_not_found.data(), rep_not_found.length()); - case status_type::conflict: - return T(rep_conflict.data(), rep_conflict.length()); - case status_type::internal_server_error: - return T(rep_internal_server_error.data(), - rep_internal_server_error.length()); - case status_type::not_implemented: - return T(rep_not_implemented.data(), rep_not_implemented.length()); - case status_type::bad_gateway: - return T(rep_bad_gateway.data(), rep_bad_gateway.length()); - case status_type::service_unavailable: - return T(rep_service_unavailable.data(), - rep_service_unavailable.length()); - default: - return T(rep_internal_server_error.data(), - rep_internal_server_error.length()); - } -} - -inline constexpr std::string_view to_rep_string(status_type status) { +inline constexpr std::string_view rep_gateway_timeout = + "HTTP/1.1 504 Gateway Timeout\r\n"; +inline constexpr std::string_view rep_version_not_supported = + "HTTP/1.1 505 Version Not Supported\r\n"; +inline constexpr std::string_view rep_variant_also_negotiates = + "HTTP/1.1 506 Variant Also Negotiates\r\n"; +inline constexpr std::string_view rep_insufficient_storage = + "HTTP/1.1 507 Insufficient Storage\r\n"; +inline constexpr std::string_view rep_loop_detected = + "HTTP/1.1 508 Loop Detected\r\n"; +inline constexpr std::string_view rep_not_extended = + "HTTP/1.1 510 Not Extended\r\n"; +inline constexpr std::string_view rep_network_authentication_required = + "HTTP/1.1 511 Network Authentication Required\r\n"; +} // namespace http_status_string + +inline constexpr std::string_view to_http_status_string(status_type status) { + using namespace http_status_string; switch (status) { case cinatra::status_type::switching_protocols: return switching_protocols; - break; case cinatra::status_type::ok: return rep_ok; - break; case cinatra::status_type::created: return rep_created; - break; case cinatra::status_type::accepted: return rep_accepted; - break; case cinatra::status_type::no_content: return rep_no_content; - break; case cinatra::status_type::partial_content: return rep_partial_content; - break; case cinatra::status_type::multiple_choices: return rep_multiple_choices; - break; case cinatra::status_type::moved_permanently: return rep_moved_permanently; - break; case cinatra::status_type::moved_temporarily: return rep_moved_temporarily; - break; case cinatra::status_type::not_modified: return rep_not_modified; - break; case cinatra::status_type::temporary_redirect: return rep_temporary_redirect; - break; case cinatra::status_type::bad_request: return rep_bad_request; - break; case cinatra::status_type::unauthorized: return rep_unauthorized; - break; case cinatra::status_type::forbidden: return rep_forbidden; - break; case cinatra::status_type::not_found: return rep_not_found; - break; + case cinatra::status_type::method_not_allowed: + return rep_method_not_allowed; case cinatra::status_type::conflict: return rep_conflict; - break; + case cinatra::status_type::range_not_satisfiable: + return rep_range_not_satisfiable; case cinatra::status_type::internal_server_error: return rep_internal_server_error; - break; case cinatra::status_type::not_implemented: return rep_not_implemented; - break; case cinatra::status_type::bad_gateway: return rep_bad_gateway; - break; case cinatra::status_type::service_unavailable: return rep_service_unavailable; - break; default: return rep_not_implemented; - break; } } -inline std::string_view to_string(status_type status) { - switch (status) { - case status_type::ok: - return ok_sv; - case status_type::created: - return created; - case status_type::accepted: - return accepted; - case status_type::no_content: - return no_content; - case status_type::multiple_choices: - return multiple_choices; - case status_type::moved_permanently: - return moved_permanently; - case status_type::moved_temporarily: - return moved_temporarily; - case status_type::temporary_redirect: - return temporary_redirect; - case status_type::not_modified: - return not_modified; - case status_type::bad_request: - return bad_request; - case status_type::unauthorized: - return unauthorized; - case status_type::forbidden: - return forbidden; - case status_type::not_found: - return not_found; - case status_type::conflict: - return conflict; - case status_type::internal_server_error: - return internal_server_error; - case status_type::not_implemented: - return not_implemented; - case status_type::bad_gateway: - return bad_gateway; - case status_type::service_unavailable: - return service_unavailable; - default: - return internal_server_error; - } +inline constexpr std::string_view default_status_content(status_type status) { + std::string_view str = to_http_status_string(status); + return str.substr(9, str.size() - 11); } + } // namespace cinatra diff --git a/include/ylt/thirdparty/cinatra/sha1.hpp b/include/ylt/thirdparty/cinatra/sha1.hpp index eeed4b8bc..ffb0d3451 100644 --- a/include/ylt/thirdparty/cinatra/sha1.hpp +++ b/include/ylt/thirdparty/cinatra/sha1.hpp @@ -172,7 +172,7 @@ void transform(std::uint32_t digest[], std::uint32_t block[BLOCK_INTS]) { digest[4] += e; } -} // namespace sha1 +} // namespace sha1 struct sha1_context { static unsigned int constexpr block_size = sha1::BLOCK_BYTES; @@ -184,8 +184,7 @@ struct sha1_context { std::uint8_t buf[block_size]; }; -template -inline void init(sha1_context &ctx) noexcept { +template inline void init(sha1_context &ctx) noexcept { ctx.buflen = 0; ctx.blocks = 0; ctx.digest[0] = 0x67452301; @@ -224,12 +223,14 @@ inline void finish(sha1_context &ctx, void *digest) noexcept { // pad ctx.buf[ctx.buflen++] = 0x80; auto const buflen = ctx.buflen; - while (ctx.buflen < 64) ctx.buf[ctx.buflen++] = 0x00; + while (ctx.buflen < 64) + ctx.buf[ctx.buflen++] = 0x00; std::uint32_t block[BLOCK_INTS]; sha1::make_block(ctx.buf, block); if (buflen > BLOCK_BYTES - 8) { sha1::transform(ctx.digest, block); - for (size_t i = 0; i < BLOCK_INTS - 2; i++) block[i] = 0; + for (size_t i = 0; i < BLOCK_INTS - 2; i++) + block[i] = 0; } /* Append total_bits, split this uint64_t into two uint32_t */ @@ -244,4 +245,4 @@ inline void finish(sha1_context &ctx, void *digest) noexcept { d[0] = (ctx.digest[i] >> 24) & 0xff; } } -} // namespace cinatra +} // namespace cinatra diff --git a/include/ylt/thirdparty/cinatra/smtp_client.hpp b/include/ylt/thirdparty/cinatra/smtp_client.hpp new file mode 100644 index 000000000..74e8d206d --- /dev/null +++ b/include/ylt/thirdparty/cinatra/smtp_client.hpp @@ -0,0 +1,212 @@ +#pragma once +#include +#include + +#include "utils.hpp" + +namespace cinatra::smtp { +struct email_server { + std::string server; + std::string port; + std::string user; + std::string password; +}; +struct email_data { + std::string from_email; + std::vector to_email; + std::string subject; + std::string text; + std::string filepath; +}; + +template +class client { + public: + static constexpr bool IS_SSL = std::is_same_v; + client(asio::io_service &io_service) + : io_context_(io_service), socket_(io_service), resolver_(io_service) {} + + ~client() { close(); } + + void set_email_server(const email_server &server) { server_ = server; } + void set_email_data(const email_data &data) { data_ = data; } + + void start() { + std::string host = server_.server; + size_t pos = host.find("://"); + if (pos != std::string::npos) { + host.erase(0, pos + 3); + } + + asio::ip::tcp::resolver::query qry( + host, server_.port, asio::ip::resolver_query_base::numeric_service); + std::error_code ec; + auto endpoint_iterator = resolver_.resolve(qry, ec); + asio::connect(socket_, endpoint_iterator, ec); + if (ec) { + return; + } + + if constexpr (IS_SSL) { + upgrade_to_ssl(); + } + + build_request(); + + asio::write(socket(), request_, ec); + if (ec) { + return; + } + + while (true) { + asio::read(socket(), response_, asio::transfer_at_least(1), ec); + if (ec) { + return; + } + + std::stringstream stream; + stream << &response_; + std::string content = stream.str(); + + if (content.find("250 Mail OK") != std::string::npos) { + return; + } + } + } + + private: + auto &socket() { +#ifdef CINATRA_ENABLE_SSL + if constexpr (IS_SSL) { + assert(ssl_socket_); + return *ssl_socket_; + } + else +#endif + { + return socket_; + } + } + void upgrade_to_ssl() { +#ifdef CINATRA_ENABLE_SSL + asio::ssl::context ctx(asio::ssl::context::sslv23); + ctx.set_default_verify_paths(); + ctx.set_verify_mode(ctx.verify_fail_if_no_peer_cert); + + ssl_socket_ = std::make_unique>( + socket_, ctx); + ssl_socket_->set_verify_mode(asio::ssl::verify_none); + ssl_socket_->set_verify_callback([](auto preverified, auto &ctx) { + char subject_name[256]; + X509 *cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); + X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256); + + return preverified; + }); + + std::error_code ec; + ssl_socket_->handshake(asio::ssl::stream_base::client, ec); +#endif + } + + std::string load_file_contents(const std::string &filepath) { + std::ifstream fin(filepath.c_str(), std::ios::in | std::ios::binary); + if (!fin) { + throw std::invalid_argument("not exist"); + } + + std::ostringstream oss; + oss << fin.rdbuf(); + return oss.str(); + } + + void build_smtp_content(std::ostream &out) { + out << "Content-Type: multipart/mixed; boundary=\"cinatra\"\r\n\r\n"; + out << "--cinatra\r\nContent-Type: text/plain;\r\n\r\n"; + out << data_.text << "\r\n\r\n"; + } + + void build_smtp_file(std::ostream &out) { + if (data_.filepath.empty()) { + return; + } + + std::string filename = + std::filesystem::path(data_.filepath).filename().string(); + out << "--cinatra\r\nContent-Type: application/octet-stream; name=\"" + << filename << "\"\r\n"; + out << "Content-Transfer-Encoding: base64\r\n"; + out << "Content-Disposition: attachment; filename=\"" << filename + << "\"\r\n"; + out << "\r\n"; + + std::string file_content = load_file_contents(data_.filepath); + size_t file_size = file_content.size(); + + std::string encoded = base64_encode(file_content); + + int SEND_BUF_SIZE = 1024; + + int no_of_rows = (int)file_size / SEND_BUF_SIZE + 1; + + for (int i = 0; i != no_of_rows; ++i) { + std::string sub_buf = encoded.substr(i * SEND_BUF_SIZE, SEND_BUF_SIZE); + + out << sub_buf << "\r\n"; + } + } + + void build_request() { + std::ostream out(&request_); + + out << "EHLO " << server_.server << "\r\n"; + out << "AUTH LOGIN\r\n"; + out << base64_encode(server_.user) << "\r\n"; + out << base64_encode(server_.password) << "\r\n"; + out << "MAIL FROM:<" << data_.from_email << ">\r\n"; + for (auto to : data_.to_email) out << "RCPT TO:<" << to << ">\r\n"; + out << "DATA\r\n"; + out << "FROM: " << data_.from_email << "\r\n"; + for (auto to : data_.to_email) out << "TO: " << to << "\r\n"; + out << "SUBJECT: " << data_.subject << "\r\n"; + + build_smtp_content(out); + build_smtp_file(out); + + out << "--cinatra--\r\n"; + out << ".\r\n"; + } + + void close() { + std::error_code ignore_ec; + if constexpr (IS_SSL) { +#ifdef CINATRA_ENABLE_SSL + ssl_socket_->shutdown(ignore_ec); +#endif + } + + socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ignore_ec); + socket_.close(ignore_ec); + } + + private: + asio::io_context &io_context_; + asio::ip::tcp::socket socket_; +#ifdef CINATRA_ENABLE_SSL + std::unique_ptr> ssl_socket_; +#endif + asio::ip::tcp::resolver resolver_; + + email_server server_; + email_data data_; + + asio::streambuf request_; + asio::streambuf response_; +}; + +template +static inline auto get_smtp_client(asio::io_service &io_service) { + return smtp::client(io_service); +} + +} // namespace cinatra::smtp diff --git a/include/ylt/thirdparty/cinatra/string_resize.hpp b/include/ylt/thirdparty/cinatra/string_resize.hpp index f63c7406a..bb000f361 100644 --- a/include/ylt/thirdparty/cinatra/string_resize.hpp +++ b/include/ylt/thirdparty/cinatra/string_resize.hpp @@ -6,30 +6,35 @@ namespace cinatra::detail { #if __cpp_lib_string_resize_and_overwrite >= 202110L -inline void resize(std::string& str, std::size_t sz) { - str.resize_and_overwrite(sz, [](char*, std::size_t sz) { +template +inline void resize(std::basic_string &str, std::size_t sz) { + str.resize_and_overwrite(sz, [](ch *, std::size_t sz) { return sz; }); } -#elif (defined(__clang_major__) && __clang_major__ <= 11) || \ - (defined(_MSC_VER) && _MSC_VER <= 1920) -// old clang has bug in global friend function. discard it. +#elif (defined(_MSC_VER) && _MSC_VER <= 1920) // old msvc don't support visit private, discard it. -inline void resize(std::string& str, std::size_t sz) { str.resize(sz); } + #else template class string_thief { public: - friend void string_set_length_hacker(std::string& self, std::size_t sz) { + friend void string_set_length_hacker(std::string &self, std::size_t sz) { #if defined(_MSVC_STL_VERSION) (self.*func_ptr)._Myval2._Mysize = sz; #else +#if defined(_LIBCPP_VERSION) + (self.*func_ptr)(sz); +#else #if (_GLIBCXX_USE_CXX11_ABI == 0) && defined(__GLIBCXX__) (self.*func_ptr)()->_M_set_length_and_sharable(sz); #else +#if defined(__GLIBCXX__) (self.*func_ptr)(sz); #endif +#endif +#endif #endif } }; @@ -50,16 +55,75 @@ template class string_thief; #endif -void string_set_length_hacker(std::string&, std::size_t); +void string_set_length_hacker(std::string &, std::size_t); -inline void resize(std::string& str, std::size_t sz) { +template +inline void resize(std::basic_string &raw_str, std::size_t sz) { + std::string &str = *reinterpret_cast(&raw_str); #if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) || \ defined(_MSVC_STL_VERSION) - str.reserve(sz); + if (sz > str.capacity()) { + str.reserve(sz); + } string_set_length_hacker(str, sz); str[sz] = '\0'; #else - str.resize(sz); + raw_str.resize(sz); +#endif +} + +#endif + +#if (defined(_MSC_VER) && _MSC_VER <= 1920) +#else +void vector_set_length_hacker(std::vector &self, std::size_t sz); + +template +class vector_thief { + public: + friend void vector_set_length_hacker(std::vector &self, + std::size_t sz) { +#if defined(_MSVC_STL_VERSION) + (self.*func_ptr)._Myval2._Mylast = self.data() + sz; +#else +#if defined(_LIBCPP_VERSION) +#if _LIBCPP_VERSION < 14000 + ((*(std::__vector_base > *)(&self)).*func_ptr) = + self.data() + sz; +#else + (self.*func_ptr) = self.data() + sz; +#endif +#else +#if defined(__GLIBCXX__) + ((*(std::_Vector_base > *)(&self)).*func_ptr) + ._M_finish = self.data() + sz; +#endif +#endif +#endif + } +}; + +#if defined(__GLIBCXX__) // libstdc++ +template class vector_thief::_M_impl), + &std::vector::_M_impl>; +#elif defined(_LIBCPP_VERSION) +template class vector_thief::__end_), + &std::vector::__end_>; +#elif defined(_MSVC_STL_VERSION) +template class vector_thief::_Mypair), + &std::vector::_Mypair>; +#endif + +template +inline void resize(std::vector &raw_vec, std::size_t sz) { +#if defined(__GLIBCXX__) || \ + (defined(_LIBCPP_VERSION) && defined(_LIBCPP_HAS_NO_ASAN)) || \ + defined(_MSVC_STL_VERSION) + std::vector &vec = *reinterpret_cast *>(&raw_vec); + vec.reserve(sz); + vector_set_length_hacker(vec, sz); +#else + raw_vec.resize(sz); #endif } #endif diff --git a/include/ylt/thirdparty/cinatra/time_util.hpp b/include/ylt/thirdparty/cinatra/time_util.hpp index cd7f4685e..0ef7c29e6 100644 --- a/include/ylt/thirdparty/cinatra/time_util.hpp +++ b/include/ylt/thirdparty/cinatra/time_util.hpp @@ -409,4 +409,4 @@ inline std::string_view get_gmt_time_str() { return get_gmt_time_str(std::chrono::system_clock::now()); } -} // namespace cinatra +} // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/uri.hpp b/include/ylt/thirdparty/cinatra/uri.hpp index 397504048..409432561 100644 --- a/include/ylt/thirdparty/cinatra/uri.hpp +++ b/include/ylt/thirdparty/cinatra/uri.hpp @@ -308,7 +308,7 @@ struct context { port(u.get_port()), path(u.get_path()), query(u.get_query()), - method(mthd), - body(std::move(b)) {} + body(std::move(b)), + method(mthd) {} }; } // namespace cinatra \ No newline at end of file diff --git a/include/ylt/thirdparty/cinatra/url_encode_decode.hpp b/include/ylt/thirdparty/cinatra/url_encode_decode.hpp index 3d1eb27a6..75efacefc 100644 --- a/include/ylt/thirdparty/cinatra/url_encode_decode.hpp +++ b/include/ylt/thirdparty/cinatra/url_encode_decode.hpp @@ -1,3 +1,7 @@ +// +// Created by xmh on 18-3-16. +// + #ifndef CPPWEBSERVER_URL_ENCODE_DECODE_HPP #define CPPWEBSERVER_URL_ENCODE_DECODE_HPP #include @@ -25,24 +29,40 @@ inline static std::string url_encode(const std::string &value) noexcept { return result; } -inline static std::string url_decode(const std::string &value) noexcept { +inline static std::string url_decode(std::string_view str) noexcept { std::string result; - result.reserve(value.size() / 3 + - (value.size() % 3)); // Minimum size of result - - for (std::size_t i = 0; i < value.size(); ++i) { - auto &chr = value[i]; - if (chr == '%' && i + 2 < value.size()) { - auto hex = value.substr(i + 1, 2); - auto decoded_chr = - static_cast(std::strtol(hex.c_str(), nullptr, 16)); - result += decoded_chr; - i += 2; + result.reserve(str.size()); + + for (size_t i = 0; i < str.size(); ++i) { + char ch = str[i]; + if (ch == '%') { + constexpr char hex[] = "0123456789ABCDEF"; + + if (++i == str.size()) { + result.push_back('?'); + break; + } + + int hi = (int)(std::find(hex, hex + 16, toupper(str[i])) - hex); + + if (++i == str.size()) { + result.push_back('?'); + break; + } + + int lo = (int)(std::find(hex, hex + 16, toupper(str[i])) - hex); + + if ((hi >= 16) || (lo >= 16)) { + result.push_back('?'); + break; + } + + result.push_back((char)((hi << 4) + lo)); } - else if (chr == '+') - result += ' '; + else if (ch == '+') + result.push_back(' '); else - result += chr; + result.push_back(ch); } return result; diff --git a/include/ylt/thirdparty/cinatra/use_asio.hpp b/include/ylt/thirdparty/cinatra/use_asio.hpp new file mode 100644 index 000000000..9a4d9689e --- /dev/null +++ b/include/ylt/thirdparty/cinatra/use_asio.hpp @@ -0,0 +1,17 @@ +#pragma once + +#if defined(ASIO_STANDALONE) +// MSVC : define environment path 'ASIO_STANDALONE_INCLUDE', e.g. +// 'E:\bdlibs\asio-1.10.6\include' + +#include +#ifdef CINATRA_ENABLE_SSL +#include +#endif +#include + +using tcp_socket = asio::ip::tcp::socket; +#ifdef CINATRA_ENABLE_SSL +using ssl_socket = asio::ssl::stream; +#endif +#endif diff --git a/include/ylt/thirdparty/cinatra/utils.hpp b/include/ylt/thirdparty/cinatra/utils.hpp index 860e6a6ec..8e7bdd836 100644 --- a/include/ylt/thirdparty/cinatra/utils.hpp +++ b/include/ylt/thirdparty/cinatra/utils.hpp @@ -8,7 +8,9 @@ #pragma once #include #include +#include #include +#include #include //std::byte #include #include @@ -22,7 +24,6 @@ #include "define.h" #include "response_cv.hpp" -#include "sha1.hpp" namespace cinatra { struct ci_less { @@ -46,158 +47,6 @@ struct ci_less { } }; -class noncopyable { - public: - noncopyable() = default; - ~noncopyable() = default; - - private: - noncopyable(const noncopyable &) = delete; - noncopyable &operator=(const noncopyable &) = delete; -}; - -using namespace std::string_view_literals; - -template -struct sv_char_trait : std::char_traits { - using base_t = std::char_traits; - using char_type = typename base_t::char_type; - - static constexpr int compare(std::string_view s1, - std::string_view s2) noexcept { - if (s1.length() != s2.length()) - return -1; - - size_t n = s1.length(); - for (size_t i = 0; i < n; ++i) { - if (!base_t::eq(s1[i], s2[i])) { - return base_t::eq(s1[i], s2[i]) ? -1 : 1; - } - } - - return 0; - } - - static constexpr size_t find(std::string_view str, - const char_type &a) noexcept { - auto s = str.data(); - for (size_t i = 0; i < str.length(); ++i) { - if (base_t::eq(s[i], a)) { - return i; - } - } - - return std::string_view::npos; - } -}; - -inline std::string_view trim_left(std::string_view v) { - v.remove_prefix((std::min)(v.find_first_not_of(" "), v.size())); - return v; -} - -inline std::string_view trim_right(std::string_view v) { - v.remove_suffix((std::min)(v.size() - v.find_last_not_of(" ") - 1, v.size())); - return v; -} - -inline std::string_view trim(std::string_view v) { - v.remove_prefix((std::min)(v.find_first_not_of(" "), v.size())); - v.remove_suffix((std::min)(v.size() - v.find_last_not_of(" ") - 1, v.size())); - return v; -} - -inline std::pair get_domain_url( - std::string_view path) { - size_t size = path.size(); - size_t pos = std::string_view::npos; - for (size_t i = 0; i < size; i++) { - if (path[i] == '/') { - if (i == size - 1) { - pos = i; - break; - } - - if (i + 1 < size - 1 && path[i + 1] == '/') { - i++; - continue; - } - else { - pos = i; - break; - } - } - } - - if (pos == std::string_view::npos) { - return {path, "/"}; - } - - std::string_view host = path.substr(0, pos); - std::string_view url = path.substr(pos); - if (url.length() > 1 && url.back() == '/') { - url = url.substr(0, url.length() - 1); - } - - return {host, url}; -} -inline std::string_view remove_www(std::string_view path) { - if (path.back() == '/') { - path = std::string_view(path.data(), path.length() - 1); - } - if (path.find("www.") != std::string_view::npos) - return path.substr(4); - - return path; -} - -inline std::pair get_host_port(std::string_view path, - bool is_ssl) { - std::string_view old_path = path; - size_t pos = path.rfind(':'); - if (pos == std::string_view::npos) { - if (path.find("https") != std::string_view::npos) { - return {std::string(remove_www(path)), "https"}; - } - - return {std::string(remove_www(path)), is_ssl ? "https" : "http"}; - } - - if (pos > path.length() - 1) { - return {}; - } - - if (path.find("http") != std::string_view::npos) { - size_t pos1 = path.find(':'); - if (pos1 + 3 > path.length() - 1) - return {}; - - path = path.substr(pos1 + 3); - if (pos >= (pos1 + 3)) - pos -= (pos1 + 3); - } - - if (old_path[pos - 1] == 'p') { - return {std::string(remove_www(path)), "http"}; - } - else if (old_path[pos - 1] == 's') { - return {std::string(remove_www(path)), "https"}; - } - - return {std::string(path.substr(0, pos)), std::string(path.substr(pos + 1))}; -} - -inline void SHA1(uint8_t *key_src, size_t size, uint8_t *sha1buf) { - sha1_context ctx; - init(ctx); - update(ctx, key_src, size); - finish(ctx, sha1buf); -} - -template -constexpr bool is_int64_v = - std::is_same_v || std::is_same_v; - inline std::string get_content_type_str(req_content_type type) { std::string str; switch (type) { @@ -233,104 +82,6 @@ inline std::string get_content_type_str(req_content_type type) { return str; } -constexpr auto type_to_name( - std::integral_constant) noexcept { - return "DELETE"sv; -} -constexpr auto type_to_name( - std::integral_constant) noexcept { - return "GET"sv; -} -constexpr auto type_to_name( - std::integral_constant) noexcept { - return "HEAD"sv; -} - -constexpr auto type_to_name( - std::integral_constant) noexcept { - return "POST"sv; -} -constexpr auto type_to_name( - std::integral_constant) noexcept { - return "PUT"sv; -} - -constexpr auto type_to_name( - std::integral_constant) noexcept { - return "CONNECT"sv; -} -constexpr auto type_to_name( - std::integral_constant) noexcept { - return "OPTIONS"sv; -} -constexpr auto type_to_name( - std::integral_constant) noexcept { - return "TRACE"sv; -} - -inline bool iequal(const char *s, size_t l, const char *t) { - if (strlen(t) != l) - return false; - - for (size_t i = 0; i < l; i++) { - if (std::tolower(s[i]) != std::tolower(t[i])) - return false; - } - - return true; -} - -inline bool iequal(const char *s, size_t l, const char *t, size_t size) { - if (size != l) - return false; - - for (size_t i = 0; i < l; i++) { - if (std::tolower(s[i]) != std::tolower(t[i])) - return false; - } - - return true; -} - -inline bool iequal(std::string_view a, std::string_view b) { - return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { - return tolower(a) == tolower(b); - }); -} - -template -inline bool find_strIC(const T &src, const T &dest) { - auto it = std::search(src.begin(), src.end(), dest.begin(), dest.end(), - [](char ch1, char ch2) { - return std::toupper(ch1) == std::toupper(ch2); - }); - return (it != src.end()); -} - -inline std::vector split(std::string_view s, - std::string_view delimiter) { - size_t start = 0; - size_t end = s.find_first_of(delimiter); - - std::vector output; - - while (end <= std::string_view::npos) { - output.emplace_back(s.substr(start, end - start)); - - if (end == std::string_view::npos) - break; - - start = end + 1; - end = s.find_first_of(delimiter, start); - } - - return output; -} - -inline void remove_char(std::string &str, const char ch) { - str.erase(std::remove(str.begin(), str.end(), ch), str.end()); -} - inline void replace_all(std::string &out, const std::string &from, const std::string &to) { if (from.empty()) @@ -342,95 +93,6 @@ inline void replace_all(std::string &out, const std::string &from, } } -template -inline void print(Args... args) { - ((std::cout << args << ' '), ...); - std::cout << "\n"; -} - -inline void print(const std::error_code &ec) { - print(ec.value(), ec.message()); -} - -// var bools = []; -// var valid_chr = -// 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-'; for(var -// i = 0; i <= 127; ++ i) { var contain = -// valid_chr.indexOf(String.fromCharCode(i)) == -1; -// bools.push(contain?false:true); -// } -// console.log(JSON.stringify(bools)) - -inline const constexpr bool valid_chr[128] = { - false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, - false, true, true, false, true, true, true, true, true, true, true, - true, true, true, false, false, false, false, false, false, false, true, - true, true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, true, - true, true, true, false, false, false, false, true, false, true, true, - true, true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, true, - true, true, false, false, false, false, false}; - -inline std::ostringstream "e_impl(std::ostringstream &os, - std::string_view str, - std::string_view safe) { - os << std::setiosflags(std::ios::right) << std::setfill('0'); - auto begin = reinterpret_cast(str.data()); - auto end = begin + sizeof(char) * str.size(); - std::for_each(begin, end, [&os, &safe](auto &chr) { - char chrval = (char)chr; - unsigned int intval = (unsigned int)chr; - if ((intval > 127 || !valid_chr[intval]) && - safe.find(chrval) == std::string_view::npos) - os << '%' << std::setw(2) << std::hex << std::uppercase << intval; - else - os << chrval; - }); - return os; -} - -inline const std::string quote(std::string_view str) { - std::ostringstream os; - return quote_impl(os, str, "/").str(); -} - -inline const std::string quote_plus(std::string_view str) { - if (str.find(' ') == std::string_view::npos) - return quote(str); - - std::ostringstream os; - auto strval = quote_impl(os, str, " ").str(); - std::replace(strval.begin(), strval.end(), ' ', '+'); - return strval; -} - -inline std::string form_urldecode(const std::string &src) { - std::string ret; - char ch; - int i, ii; - for (i = 0; i < src.length(); i++) { - if (int(src[i]) == 37) { - sscanf(src.substr(i + 1, 2).c_str(), "%x", &ii); - ch = static_cast(ii); - ret += ch; - i = i + 2; - } - else { - ret += src[i]; - } - } - return ret; -} - -inline bool is_form_url_encode(std::string_view str) { - return str.find("%") != std::string_view::npos || - str.find("+") != std::string_view::npos; -} - inline std::string_view get_extension(std::string_view name) { size_t pos = name.rfind('.'); if (pos == std::string_view::npos) { @@ -440,16 +102,6 @@ inline std::string_view get_extension(std::string_view name) { return name.substr(pos); } -inline bool is_status_ok(int status) { - return (status == 200) || (status >= 301 && status <= 307 && status != 306); -} - -inline std::string to_hex_string(std::size_t value) { - std::ostringstream stream; - stream << std::hex << value; - return stream.str(); -} - inline int64_t hex_to_int(std::string_view s) { if (s.empty()) return -1; @@ -463,29 +115,57 @@ inline int64_t hex_to_int(std::string_view s) { return n; } -template -inline std::vector to_chunked_buffers(const char *chunk_data, size_t length, - std::string &chunk_size, bool eof) { - std::vector buffers; +inline std::vector split_sv(std::string_view s, + std::string_view delimiter) { + size_t start = 0; + size_t end = s.find_first_of(delimiter); + + std::vector output; + + while (end <= std::string_view::npos) { + output.emplace_back(s.substr(start, end - start)); + + if (end == std::string_view::npos) + break; + + start = end + 1; + end = s.find_first_of(delimiter, start); + } + + return output; +} + +inline std::string_view trim_sv(std::string_view v) { + v.remove_prefix((std::min)(v.find_first_not_of(" "), v.size())); + v.remove_suffix((std::min)(v.size() - v.find_last_not_of(" ") - 1, v.size())); + return v; +} + +inline std::string_view to_hex_string(size_t val) { + static char buf[20]; + auto [ptr, ec] = std::to_chars(std::begin(buf), std::end(buf), val, 16); + return std::string_view{buf, size_t(std::distance(buf, ptr))}; +} +inline void to_chunked_buffers(std::vector &buffers, + std::string_view chunk_data, bool eof) { + size_t length = chunk_data.size(); if (length > 0) { // convert bytes transferred count to a hex string. - chunk_size = to_hex_string(length); + auto chunk_size = to_hex_string(length); // Construct chunk based on rfc2616 section 3.6.1 - buffers.push_back(T(chunk_size.data(), chunk_size.size())); - buffers.push_back(T(crlf.data(), crlf.size())); - buffers.push_back(T(chunk_data, length)); - buffers.push_back(T(crlf.data(), crlf.size())); + buffers.push_back(asio::buffer(chunk_size)); + buffers.push_back(asio::buffer(CRCF)); + buffers.push_back(asio::buffer(chunk_data, length)); + buffers.push_back(asio::buffer(CRCF)); } // append last-chunk if (eof) { - buffers.push_back(T(last_chunk.data(), last_chunk.size())); - buffers.push_back(T(crlf.data(), crlf.size())); + buffers.push_back(asio::buffer(LAST_CHUNK)); + buffers.push_back(asio::buffer(CRCF)); } - - return buffers; } static const std::string base64_chars = @@ -539,52 +219,6 @@ inline std::string base64_encode(const std::string &str) { return ret; } -inline std::string base64_decode(std::string const &encoded_string) { - int in_len = static_cast(encoded_string.size()); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; - - while (in_len-- && (encoded_string[in_] != '=') && - is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; - in_++; - if (i == 4) { - for (i = 0; i < 4; i++) - char_array_4[i] = - static_cast(base64_chars.find(char_array_4[i])); - - char_array_3[0] = - (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = - ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (i = 0; (i < 3); i++) ret += char_array_3[i]; - i = 0; - } - } - - if (i) { - for (j = i; j < 4; j++) char_array_4[j] = 0; - - for (j = 0; j < 4; j++) - char_array_4[j] = - static_cast(base64_chars.find(char_array_4[j])); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = - ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; - } - - return ret; -} - // from h2o inline const char *MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -674,154 +308,6 @@ inline bool is_valid_utf8(unsigned char *s, size_t length) { return true; } -template -inline std::string to_str(T &&value) { - using U = std::remove_const_t>; - if constexpr (std::is_integral_v && !is_int64_v) { - std::vector temp(20, '\0'); - itoa_fwd(value, temp.data()); - return std::string(temp.data()); - } - else if constexpr (is_int64_v) { - std::vector temp(65, '\0'); - xtoa(value, temp.data(), 10, std::is_signed_v); - return std::string(temp.data()); - } - else if constexpr (std::is_floating_point_v) { - std::vector temp(20, '\0'); - sprintf(temp.data(), "%f", value); - return std::string(temp.data()); - } - else if constexpr (std::is_same_v || - std::is_same_v) { - return value; - } - else { - std::cout << "this type has not supported yet" << std::endl; - } -} - -// for is_detective -namespace { -struct nonesuch { - nonesuch() = delete; - ~nonesuch() = delete; - nonesuch(const nonesuch &) = delete; - void operator=(const nonesuch &) = delete; -}; - -template class Op, - class... Args> -struct detector { - using value_t = std::false_type; - using type = Default; -}; - -template class Op, class... Args> -struct detector>, Op, Args...> { - using value_t = std::true_type; - using type = Op; -}; - -template