diff --git a/include/ylt/coro_io/coro_file.hpp b/include/ylt/coro_io/coro_file.hpp index a45633b67..bddf79063 100644 --- a/include/ylt/coro_io/coro_file.hpp +++ b/include/ylt/coro_io/coro_file.hpp @@ -332,11 +332,11 @@ class coro_file { return "rb"; case flags::create_write: case flags::write_only: - return "w"; + return "wb+"; case flags::read_write: return "rb+"; case flags::append: - return "a"; + return "ab+"; case flags::create_read_write_append: return "ab+"; case flags::truncate: diff --git a/include/ylt/thirdparty/cinatra/coro_http_client.hpp b/include/ylt/thirdparty/cinatra/coro_http_client.hpp index b250c24dc..0d6af95f8 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_client.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_client.hpp @@ -1630,11 +1630,16 @@ class coro_http_client : public std::enable_shared_from_this { std::string short_name = std::filesystem::path(part.filename).filename().string(); if (is_file) { - part_content_head.append("; filename=\"").append(short_name).append("\""); + part_content_head.append("; filename=\"") + .append(short_name) + .append("\"") + .append(CRCF); auto ext = std::filesystem::path(short_name).extension().string(); if (auto it = g_content_type_map.find(ext); it != g_content_type_map.end()) { - part_content_head.append("Content-Type: ").append(it->second); + part_content_head.append("Content-Type: ") + .append(it->second) + .append(CRCF); } std::error_code ec; @@ -1642,8 +1647,11 @@ class coro_http_client : public std::enable_shared_from_this { co_return resp_data{ std::make_error_code(std::errc::no_such_file_or_directory), 404}; } + part_content_head.append(CRCF); + } + else { + part_content_head.append(TWO_CRCF); } - part_content_head.append(TWO_CRCF); if (auto [ec, size] = co_await async_write(asio::buffer(part_content_head)); ec) { co_return resp_data{ec, 404}; @@ -1655,7 +1663,7 @@ class coro_http_client : public std::enable_shared_from_this { assert(file.is_open()); std::string file_data; detail::resize(file_data, max_single_part_size_); - while (!file.eof()) { + while (true) { auto [rd_ec, rd_size] = co_await file.async_read(file_data.data(), file_data.size()); if (auto [ec, size] = @@ -1663,19 +1671,22 @@ class coro_http_client : public std::enable_shared_from_this { ec) { co_return resp_data{ec, 404}; } + if (file.eof()) { + if (auto [ec, size] = co_await async_write(asio::buffer(CRCF)); ec) { + co_return resp_data{ec, 404}; + } + break; + } } } else { - if (auto [ec, size] = co_await async_write(asio::buffer(part.content)); - ec) { + std::array arr{asio::buffer(part.content), + asio::buffer(CRCF)}; + if (auto [ec, size] = co_await async_write(arr); ec) { co_return resp_data{ec, 404}; } } - if (auto [ec, size] = co_await async_write(asio::buffer(CRCF)); ec) { - co_return resp_data{ec, 404}; - } - co_return resp_data{{}, 200}; } diff --git a/include/ylt/thirdparty/cinatra/coro_http_connection.hpp b/include/ylt/thirdparty/cinatra/coro_http_connection.hpp index b1b36180d..e293ad057 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_connection.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_connection.hpp @@ -18,6 +18,7 @@ #include "sha1.hpp" #include "string_resize.hpp" #include "websocket.hpp" +#include "ylt/coro_io/coro_file.hpp" #include "ylt/coro_io/coro_io.hpp" namespace cinatra { @@ -27,6 +28,12 @@ struct chunked_result { 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; @@ -38,9 +45,11 @@ class coro_http_connection : public std::enable_shared_from_this { public: template - coro_http_connection(executor_t *executor, asio::ip::tcp::socket socket) + coro_http_connection(executor_t *executor, asio::ip::tcp::socket socket, + coro_http_router &router) : executor_(executor), socket_(std::move(socket)), + router_(router), request_(parser_, this), response_(this) { buffers_.reserve(3); @@ -125,9 +134,9 @@ class coro_http_connection head_buf_.consume(size); keep_alive_ = check_keep_alive(); - bool is_chunked = parser_.is_chunked(); + auto type = request_.get_content_type(); - if (!is_chunked) { + if (type != content_type::chunked && type != content_type::multipart) { size_t body_len = parser_.body_len(); if (body_len == 0) { if (parser_.method() == "GET"sv) { @@ -178,13 +187,12 @@ class coro_http_connection request_.set_body(body_); } - auto &router = coro_http_router::instance(); - if (auto handler = router.get_handler(key); handler) { - router.route(handler, request_, response_); + if (auto handler = router_.get_handler(key); handler) { + router_.route(handler, request_, response_); } else { - if (auto coro_handler = router.get_coro_handler(key); coro_handler) { - co_await router.route_coro(coro_handler, request_, response_); + if (auto coro_handler = router_.get_coro_handler(key); coro_handler) { + co_await router_.route_coro(coro_handler, request_, response_); } else { // not found @@ -339,6 +347,99 @@ 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); @@ -584,6 +685,7 @@ class coro_http_connection private: async_simple::Executor *executor_; asio::ip::tcp::socket socket_; + coro_http_router &router_; asio::streambuf head_buf_; std::string body_; asio::streambuf chunked_buf_; diff --git a/include/ylt/thirdparty/cinatra/coro_http_request.hpp b/include/ylt/thirdparty/cinatra/coro_http_request.hpp index 112a41121..07211d955 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_request.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_request.hpp @@ -49,21 +49,13 @@ class coro_http_request { std::string_view get_body() const { return body_; } - bool is_chunked() { - static bool thread_local is_chunk = parser_.is_chunked(); - return is_chunk; - } + bool is_chunked() { return parser_.is_chunked(); } bool is_resp_ranges() { return parser_.is_resp_ranges(); } bool is_req_ranges() { return parser_.is_req_ranges(); } content_type get_content_type() { - static content_type thread_local content_type = get_content_type_impl(); - return content_type; - } - - content_type get_content_type_impl() { if (is_chunked()) return content_type::chunked; @@ -93,6 +85,14 @@ class coro_http_request { return content_type::unknown; } + std::string_view get_boundary() { + auto content_type = get_header_value("content-type"); + if (content_type.empty()) { + return {}; + } + return content_type.substr(content_type.rfind("=") + 1); + } + coro_http_connection* get_conn() { return conn_; } bool is_upgrade() { diff --git a/include/ylt/thirdparty/cinatra/coro_http_router.hpp b/include/ylt/thirdparty/cinatra/coro_http_router.hpp index 18946143d..e269a23c0 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_router.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_router.hpp @@ -28,11 +28,6 @@ constexpr inline bool is_lazy_v = class coro_http_router { public: - static coro_http_router& instance() { - static coro_http_router instance; - return instance; - } - // eg: "GET hello/" as a key template void set_http_handler(std::string key, Func handler) { @@ -110,7 +105,6 @@ class coro_http_router { const auto& get_coro_handlers() const { return coro_handles_; } private: - coro_http_router() = default; std::set keys_; std::unordered_map< std::string_view, diff --git a/include/ylt/thirdparty/cinatra/coro_http_server.hpp b/include/ylt/thirdparty/cinatra/coro_http_server.hpp index 6f402fde4..5b4653ff2 100644 --- a/include/ylt/thirdparty/cinatra/coro_http_server.hpp +++ b/include/ylt/thirdparty/cinatra/coro_http_server.hpp @@ -128,13 +128,11 @@ class coro_http_server { void set_http_handler(std::string key, Func handler) { static_assert(sizeof...(method) >= 1, "must set http_method"); if constexpr (sizeof...(method) == 1) { - (coro_http_router::instance().set_http_handler( - std::move(key), std::move(handler)), + (router_.set_http_handler(std::move(key), std::move(handler)), ...); } else { - (coro_http_router::instance().set_http_handler(key, handler), - ...); + (router_.set_http_handler(key, handler), ...); } } @@ -181,6 +179,8 @@ class coro_http_server { } } + const coro_http_router &get_router() const { return router_; } + void set_file_resp_format_type(file_resp_format_type type) { format_type_ = type; } @@ -227,6 +227,7 @@ class coro_http_server { if (size_t pos = relative_path.find('\\') != std::string::npos) { replace_all(relative_path, "\\", "/"); } + if (static_dir_router_path_.empty()) { uri = relative_path; } @@ -405,8 +406,8 @@ class coro_http_server { uint64_t conn_id = ++conn_id_; CINATRA_LOG_DEBUG << "new connection comming, id: " << conn_id; - auto conn = - std::make_shared(executor, std::move(socket)); + auto conn = std::make_shared( + executor, std::move(socket), router_); if (no_delay_) { conn->tcp_socket().set_option(asio::ip::tcp::no_delay(true)); } @@ -532,5 +533,6 @@ class coro_http_server { std::string passwd_; bool use_ssl_ = false; #endif + coro_http_router router_; }; } // namespace cinatra \ No newline at end of file