Skip to content

Commit

Permalink
[coro_http_server][feat]Support multipart (#549)
Browse files Browse the repository at this point in the history
  • Loading branch information
qicosmos authored Dec 28, 2023
1 parent 5272b07 commit 08d0c6d
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 41 deletions.
4 changes: 2 additions & 2 deletions include/ylt/coro_io/coro_file.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
31 changes: 21 additions & 10 deletions include/ylt/thirdparty/cinatra/coro_http_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1630,20 +1630,28 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
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;
if (!std::filesystem::exists(part.filename, ec)) {
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};
Expand All @@ -1655,27 +1663,30 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
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] =
co_await async_write(asio::buffer(file_data.data(), rd_size));
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<asio::const_buffer, 2> 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};
}

Expand Down
118 changes: 110 additions & 8 deletions include/ylt/thirdparty/cinatra/coro_http_connection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand All @@ -38,9 +45,11 @@ class coro_http_connection
: public std::enable_shared_from_this<coro_http_connection> {
public:
template <typename executor_t>
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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -339,6 +347,99 @@ class coro_http_connection
co_return result;
}

async_simple::coro::Lazy<part_head_t> read_part_head() {
if (head_buf_.size() > 0) {
const char *data_ptr = asio::buffer_cast<const char *>(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<const char *>(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<chunked_result> 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<const char *>(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<const char *>(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<std::error_code> write_websocket(
std::string_view msg, opcode op = opcode::text) {
auto header = ws_.format_header(msg.length(), op);
Expand Down Expand Up @@ -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_;
Expand Down
18 changes: 9 additions & 9 deletions include/ylt/thirdparty/cinatra/coro_http_request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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() {
Expand Down
6 changes: 0 additions & 6 deletions include/ylt/thirdparty/cinatra/coro_http_router.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <http_method method, typename Func>
void set_http_handler(std::string key, Func handler) {
Expand Down Expand Up @@ -110,7 +105,6 @@ class coro_http_router {
const auto& get_coro_handlers() const { return coro_handles_; }

private:
coro_http_router() = default;
std::set<std::string> keys_;
std::unordered_map<
std::string_view,
Expand Down
14 changes: 8 additions & 6 deletions include/ylt/thirdparty/cinatra/coro_http_server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<method>(
std::move(key), std::move(handler)),
(router_.set_http_handler<method>(std::move(key), std::move(handler)),
...);
}
else {
(coro_http_router::instance().set_http_handler<method>(key, handler),
...);
(router_.set_http_handler<method>(key, handler), ...);
}
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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<coro_http_connection>(executor, std::move(socket));
auto conn = std::make_shared<coro_http_connection>(
executor, std::move(socket), router_);
if (no_delay_) {
conn->tcp_socket().set_option(asio::ip::tcp::no_delay(true));
}
Expand Down Expand Up @@ -532,5 +533,6 @@ class coro_http_server {
std::string passwd_;
bool use_ssl_ = false;
#endif
coro_http_router router_;
};
} // namespace cinatra

0 comments on commit 08d0c6d

Please sign in to comment.