diff --git a/include/cinatra/coro_http_client.hpp b/include/cinatra/coro_http_client.hpp index e4413485..00a1db32 100644 --- a/include/cinatra/coro_http_client.hpp +++ b/include/cinatra/coro_http_client.hpp @@ -46,22 +46,6 @@ template class client_pool; } namespace cinatra { -#ifdef INJECT_FOR_HTTP_CLIENT_TEST -enum class ClientInjectAction { - none, - response_error, - header_error, - chunk_error, - write_failed, - read_failed, -}; -inline ClientInjectAction inject_response_valid = ClientInjectAction::none; -inline ClientInjectAction inject_header_valid = ClientInjectAction::none; -inline ClientInjectAction inject_chunk_valid = ClientInjectAction::none; -inline ClientInjectAction inject_write_failed = ClientInjectAction::none; -inline ClientInjectAction inject_read_failed = ClientInjectAction::none; -#endif - template struct is_stream : std::false_type {}; @@ -130,6 +114,8 @@ struct read_result { std::error_code err; }; +enum class upload_type_t { with_length, chunked, multipart }; + class coro_http_client : public std::enable_shared_from_this { public: struct config { @@ -144,11 +130,8 @@ class coro_http_client : public std::enable_shared_from_this { std::string proxy_auth_token; bool enable_tcp_no_delay; #ifdef CINATRA_ENABLE_SSL - bool use_ssl = false; - std::string base_path; - std::string cert_file; - int verify_mode; - std::string domain; + bool use_ssl = + false; // if set use_ssl true, cinatra will add https automaticlly. #endif }; @@ -414,6 +397,37 @@ class coro_http_client : public std::enable_shared_from_this { co_return co_await write_websocket(std::span(data), op); } + async_simple::coro::Lazy write_ws_frame(std::span msg, + websocket ws, opcode op, + resp_data &data, + bool eof = true) { + auto header = ws.encode_frame(msg, op, eof, true); + std::vector buffers{ + asio::buffer(header), asio::buffer(msg.data(), msg.size())}; + + auto [ec, sz] = co_await async_write(buffers); + if (ec) { + data.net_err = ec; + data.status = 404; + } + } + +#ifdef CINATRA_ENABLE_GZIP + void gzip_compress(std::string_view source, std::string &dest_buf, + std::span &span, resp_data &data) { + if (enable_ws_deflate_ && is_server_support_ws_deflate_) { + if (cinatra::gzip_codec::deflate(source, dest_buf)) { + span = dest_buf; + } + else { + CINATRA_LOG_ERROR << "compress data error, data: " << source; + data.net_err = std::make_error_code(std::errc::protocol_error); + data.status = 404; + } + } + } +#endif + template async_simple::coro::Lazy write_websocket( Source source, opcode op = opcode::text) { @@ -429,92 +443,29 @@ class coro_http_client : public std::enable_shared_from_this { } } + std::span span{}; if constexpr (is_span_v) { + span = {source.data(), source.size()}; #ifdef CINATRA_ENABLE_GZIP - if (enable_ws_deflate_ && is_server_support_ws_deflate_) { - std::string dest_buf; - if (cinatra::gzip_codec::deflate({source.data(), source.size()}, - dest_buf)) { - std::span msg(dest_buf.data(), dest_buf.size()); - auto header = ws.encode_frame(msg, op, true, true); - std::vector buffers{asio::buffer(header), - asio::buffer(dest_buf)}; - - auto [ec, sz] = co_await async_write(buffers); - if (ec) { - data.net_err = ec; - data.status = 404; - } - } - else { - CINATRA_LOG_ERROR << "compuress data error, data: " - << std::string(source.begin(), source.end()); - data.net_err = std::make_error_code(std::errc::protocol_error); - data.status = 404; - } - } - else { -#endif - auto encode_header = ws.encode_frame(source, op, true); - std::vector buffers{ - asio::buffer(encode_header.data(), encode_header.size()), - asio::buffer(source.data(), source.size())}; - - auto [ec, _] = co_await async_write(buffers); - if (ec) { - data.net_err = ec; - data.status = 404; - } -#ifdef CINATRA_ENABLE_GZIP - } + std::string dest_buf; + gzip_compress({source.data(), source.size()}, dest_buf, span, data); #endif + co_await write_ws_frame(span, ws, op, data); } else { while (true) { auto result = co_await source(); + span = {result.buf.data(), result.buf.size()}; #ifdef CINATRA_ENABLE_GZIP - if (enable_ws_deflate_ && is_server_support_ws_deflate_) { - std::string dest_buf; - if (cinatra::gzip_codec::deflate( - {result.buf.data(), result.buf.size()}, dest_buf)) { - std::span msg(dest_buf.data(), dest_buf.size()); - auto header = ws.encode_frame(msg, op, result.eof, true); - std::vector buffers{asio::buffer(header), - asio::buffer(dest_buf)}; - auto [ec, sz] = co_await async_write(buffers); - if (ec) { - data.net_err = ec; - data.status = 404; - } - } - else { - CINATRA_LOG_ERROR << "compuress data error, data: " - << std::string(result.buf.data()); - data.net_err = std::make_error_code(std::errc::protocol_error); - data.status = 404; - } - } - else { + std::string dest_buf; + gzip_compress({result.buf.data(), result.buf.size()}, dest_buf, span, + data); #endif - std::span msg(result.buf.data(), result.buf.size()); - auto encode_header = ws.encode_frame(msg, op, result.eof); - std::vector buffers{ - asio::buffer(encode_header.data(), encode_header.size()), - asio::buffer(msg.data(), msg.size())}; - - auto [ec, _] = co_await async_write(buffers); - if (ec) { - data.net_err = ec; - data.status = 404; - break; - } + co_await write_ws_frame(span, ws, op, data, result.eof); - if (result.eof) { - break; - } -#ifdef CINATRA_ENABLE_GZIP + if (result.eof || data.status == 404) { + break; } -#endif } } @@ -678,104 +629,6 @@ class coro_http_client : public std::enable_shared_from_this { coro_http_client *self; }; - async_simple::coro::Lazy async_upload_multipart(std::string uri) { - std::shared_ptr guard(nullptr, [this](auto) { - req_headers_.clear(); - form_data_.clear(); - }); - if (form_data_.empty()) { - CINATRA_LOG_WARNING << "no multipart"; - co_return resp_data{std::make_error_code(std::errc::invalid_argument), - 404}; - } - - req_context<> ctx{req_content_type::multipart, "", ""}; - resp_data data{}; - auto [ok, u] = handle_uri(data, uri); - if (!ok) { - co_return resp_data{std::make_error_code(std::errc::protocol_error), 404}; - } - - size_t content_len = multipart_content_len(); - - add_header("Content-Length", std::to_string(content_len)); - - std::string header_str = build_request_header(u, http_method::POST, ctx); - - std::error_code ec{}; - size_t size = 0; - - if (socket_->has_closed_) { - { - auto time_out_guard = - timer_guard(this, conn_timeout_duration_, "connect timer"); - data = co_await connect(u); - } - if (socket_->is_timeout_) { - co_return resp_data{std::make_error_code(std::errc::timed_out), 404}; - } - if (data.net_err) { - co_return data; - } - } - - auto time_out_guard = - timer_guard(this, req_timeout_duration_, "request timer"); - std::tie(ec, size) = co_await async_write(asio::buffer(header_str)); -#ifdef INJECT_FOR_HTTP_CLIENT_TEST - if (inject_write_failed == ClientInjectAction::write_failed) { - ec = std::make_error_code(std::errc::not_connected); - } -#endif - if (ec) { -#ifdef INJECT_FOR_HTTP_CLIENT_TEST - inject_write_failed = ClientInjectAction::none; -#endif - CINATRA_LOG_DEBUG << ec.message(); - co_return resp_data{ec, 404}; - } - - for (auto &[key, part] : form_data_) { - data = co_await send_single_part(key, part); - - if (data.net_err) { - if (socket_->is_timeout_) { - data.net_err = std::make_error_code(std::errc::timed_out); - } - co_return data; - } - } - - std::string last_part; - last_part.append("--").append(BOUNDARY).append("--").append(CRCF); - if (std::tie(ec, size) = co_await async_write(asio::buffer(last_part)); - ec) { - if (socket_->is_timeout_) { - ec = std::make_error_code(std::errc::timed_out); - } - co_return resp_data{ec, 404}; - } - - bool is_keep_alive = true; - data = co_await handle_read(ec, size, is_keep_alive, std::move(ctx), - http_method::POST); - if (socket_->is_timeout_) { - ec = std::make_error_code(std::errc::timed_out); - } - handle_result(data, ec, is_keep_alive); - co_return data; - } - - async_simple::coro::Lazy async_upload_multipart( - std::string uri, std::string name, std::string filename) { - if (!add_file_part(std::move(name), std::move(filename))) { - CINATRA_LOG_WARNING << "open file failed or duplicate test names"; - co_return resp_data{std::make_error_code(std::errc::invalid_argument), - 404}; - } - co_return co_await async_upload_multipart(std::move(uri)); - } - async_simple::coro::Lazy async_download(std::string uri, std::string filename, std::string range = "") { @@ -853,7 +706,7 @@ class coro_http_client : public std::enable_shared_from_this { std::string_view get_port() { return port_; } private: - async_simple::coro::Lazy send_file_chunked_with_copy( + async_simple::coro::Lazy send_file_copy_with_chunked( std::string_view source, std::error_code &ec) { std::string file_data; detail::resize(file_data, max_single_part_size_); @@ -877,7 +730,7 @@ class coro_http_client : public std::enable_shared_from_this { } } - async_simple::coro::Lazy send_file_no_chunked_with_copy( + async_simple::coro::Lazy send_file_copy_with_length( std::string_view source, std::error_code &ec, std::size_t length, std::size_t offset) { if (length <= 0) { @@ -887,11 +740,11 @@ class coro_http_client : public std::enable_shared_from_this { detail::resize(file_data, (std::min)(max_single_part_size_, length)); coro_io::coro_file file{}; file.open(source, std::ios::in); - file.seek(offset, std::ios::cur); if (!file.is_open()) { ec = std::make_error_code(std::errc::bad_file_descriptor); co_return; } + file.seek(offset, std::ios::cur); std::size_t size; while (length > 0) { if (std::tie(ec, size) = co_await file.async_read( @@ -923,7 +776,7 @@ class coro_http_client : public std::enable_shared_from_this { } } }; - async_simple::coro::Lazy send_file_without_copy( + async_simple::coro::Lazy send_file_no_copy_with_length( const std::filesystem::path &source, std::error_code &ec, std::size_t length, std::size_t offset) { fd_guard guard(source.c_str()); @@ -943,7 +796,7 @@ class coro_http_client : public std::enable_shared_from_this { co_return; } } - async_simple::coro::Lazy send_file_without_copy_chunked( + async_simple::coro::Lazy send_file_no_copy_with_chunked( const std::filesystem::path &source, std::error_code &ec) { fd_guard guard(source.c_str()); if (guard.fd < 0) [[unlikely]] { @@ -1007,42 +860,49 @@ class coro_http_client : public std::enable_shared_from_this { return remaining_bytes; } - public: - template - async_simple::coro::Lazy async_upload( - S uri, http_method method, Source source /* file */, - uint64_t offset = 0 /*file offset*/, - int64_t content_length = -1 /*upload size*/, - req_content_type content_type = req_content_type::text, - std::unordered_map headers = {}) { - std::error_code ec{}; - size_t size = 0; - bool is_keep_alive = true; - req_context<> ctx{content_type}; - resp_data data{}; - - std::shared_ptr guard(nullptr, [&, this](auto) { - if (!req_headers_.empty()) { - req_headers_.clear(); + template + void check_source(resp_data &data, Source &source) { + if constexpr (is_stream_ptr_v) { + if (!source) { + data = resp_data{ + std::make_error_code(std::errc::no_such_file_or_directory), 404}; } - handle_result(data, ec, is_keep_alive); - }); - - auto [ok, u] = handle_uri(data, uri); - if (!ok) { - co_return resp_data{std::make_error_code(std::errc::protocol_error), 404}; } - - constexpr bool is_stream_file = is_stream_ptr_v; - if constexpr (is_stream_file) { - if (!source) { - co_return resp_data{ + else if constexpr (std::is_same_v || + std::is_same_v) { + if (!std::filesystem::exists(source)) { + data = resp_data{ std::make_error_code(std::errc::no_such_file_or_directory), 404}; } } - // get the content_length + } + + void handle_upload_header_with_multipart() { + size_t content_len = multipart_content_len(); + add_header("Content-Length", std::to_string(content_len)); + } + + void handle_upload_header_with_chunked( + std::unordered_map &headers) { + if (!resp_chunk_str_.empty()) { + resp_chunk_str_.clear(); + } + + if (headers.empty()) { + add_header("Transfer-Encoding", "chunked"); + } + else { + headers.emplace("Transfer-Encoding", "chunked"); + } + } + + template + int64_t handle_upload_header_with_length( + resp_data &data, Source &source, + std::unordered_map &headers, uint64_t offset, + int64_t content_length) { if (content_length < 0) { - if constexpr (is_stream_file) { + if constexpr (is_stream_ptr_v) { content_length = getRemainingBytes(*source); } else if constexpr (std::is_same_v || @@ -1053,14 +913,16 @@ class coro_http_client : public std::enable_shared_from_this { CINATRA_LOG_ERROR << "user should set content-length before calling async_upload " "when source is user-defined function."; - co_return resp_data{std::make_error_code(std::errc::invalid_argument), - 404}; + data = + resp_data{std::make_error_code(std::errc::invalid_argument), 404}; + return content_length; } content_length -= offset; if (content_length < 0) { CINATRA_LOG_ERROR << "the offset is larger than the end of file"; - co_return resp_data{std::make_error_code(std::errc::invalid_argument), - 404}; + data = + resp_data{std::make_error_code(std::errc::invalid_argument), 404}; + return content_length; } } @@ -1073,173 +935,197 @@ class coro_http_client : public std::enable_shared_from_this { else { headers.emplace("Content-Length", std::string_view(buf, ptr - buf)); } + return content_length; + } - std::string header_str = - build_request_header(u, method, ctx, true, std::move(headers)); + async_simple::coro::Lazy send_fstream_with_multipart( + std::error_code &ec) { + resp_data data{}; + for (auto &[key, part] : form_data_) { + data = co_await send_single_part(key, part); - if (socket_->has_closed_) { - { - auto guard = timer_guard(this, conn_timeout_duration_, "connect timer"); - data = co_await connect(u); + if (data.net_err) { + ec = data.net_err; + co_return; } - if (socket_->is_timeout_) { - co_return resp_data{std::make_error_code(std::errc::timed_out), 404}; + } + + std::string last_part; + size_t size = 0; + last_part.append("--").append(BOUNDARY).append("--").append(CRCF); + if (std::tie(ec, size) = co_await async_write(asio::buffer(last_part)); + ec) { + co_return; + } + } + + template + async_simple::coro::Lazy send_fstream_with_chunked( + Source &source, std::error_code &ec) { + size_t size = 0; + std::string file_data; + detail::resize(file_data, max_single_part_size_); + while (!source->eof()) { + size_t rd_size = + source->read(file_data.data(), file_data.size()).gcount(); + std::vector bufs; + std::string size_str; + cinatra::to_chunked_buffers(bufs, size_str, {file_data.data(), rd_size}, + source->eof()); + if (std::tie(ec, size) = co_await async_write(bufs); ec) { + break; } - if (data.net_err) { - co_return data; + } + } + + template + async_simple::coro::Lazy send_fstream_with_length( + Source &source, std::error_code &ec, uint64_t offset, + int64_t content_length) { + size_t size = 0; + source->seekg(offset, std::ios::cur); + std::string file_data; + detail::resize(file_data, std::min(max_single_part_size_, + content_length)); + while (content_length > 0 && !source->eof()) { + size_t rd_size = + source + ->read(file_data.data(), + std::min(content_length, file_data.size())) + .gcount(); + if (std::tie(ec, size) = + co_await async_write(asio::buffer(file_data.data(), rd_size)); + ec) { + break; } + content_length -= rd_size; + } + if (!ec && content_length > 0) { + // bad request, file is smaller than content-length + ec = std::make_error_code(std::errc::invalid_argument); } + } - auto time_guard = timer_guard(this, req_timeout_duration_, "request timer"); - std::tie(ec, size) = co_await async_write(asio::buffer(header_str)); - if (ec) { - if (socket_->is_timeout_) { - ec = std::make_error_code(std::errc::timed_out); + template + async_simple::coro::Lazy send_sink_with_chunked(Source &source, + std::error_code &ec) { + size_t size = 0; + while (true) { + auto result = co_await source(); + std::vector bufs; + std::string size_str; + cinatra::to_chunked_buffers( + bufs, size_str, {result.buf.data(), result.buf.size()}, result.eof); + if (std::tie(ec, size) = co_await async_write(bufs); ec) { + break; + } + if (result.eof) { + break; } - co_return resp_data{ec, 404}; } + } - if constexpr (is_stream_file) { - source->seekg(offset, std::ios::cur); - std::string file_data; - detail::resize(file_data, std::min(max_single_part_size_, - content_length)); - while (content_length > 0 && !source->eof()) { - size_t rd_size = - source - ->read(file_data.data(), - std::min(content_length, file_data.size())) - .gcount(); - if (std::tie(ec, size) = - co_await async_write(asio::buffer(file_data.data(), rd_size)); - ec) { - break; - } - content_length -= rd_size; + template + async_simple::coro::Lazy send_sink_with_length(Source &source, + std::error_code &ec, + int64_t content_length) { + size_t size = 0; + while (true) { + auto result = co_await source(); + if (std::tie(ec, size) = co_await async_write(asio::buffer( + result.buf.data(), + std::min(content_length, result.buf.size()))); + ec) { + break; + } + content_length -= size; + if (content_length <= 0) { + break; } - if (!ec && content_length > 0) { + else if (result.eof) [[unlikely]] { // bad request, file is smaller than content-length ec = std::make_error_code(std::errc::invalid_argument); + break; } } - else if constexpr (std::is_same_v || - std::is_same_v) { -#ifdef __linux__ -#ifdef CINATRA_ENABLE_SSL - if (!has_init_ssl_) { -#endif - co_await send_file_without_copy(std::filesystem::path{source}, ec, - content_length, offset); -#ifdef CINATRA_ENABLE_SSL - } - else { - co_await send_file_no_chunked_with_copy(source, ec, content_length, - offset); - } -#endif -#else - co_await send_file_no_chunked_with_copy(source, ec, content_length, - offset); -#endif + } + + async_simple::coro::Lazy reconnect(resp_data &data, uri_t u) { + { + auto guard = timer_guard(this, conn_timeout_duration_, "connect timer"); + data = co_await connect(u); } - else { - while (true) { - auto result = co_await source(); - std::cout << result.buf.size() << std::endl; - if (std::tie(ec, size) = co_await async_write(asio::buffer( - result.buf.data(), - std::min(content_length, result.buf.size()))); - ec) { - break; - } - content_length -= size; - if (content_length <= 0) { - break; - } - else if (result.eof) [[unlikely]] { - // bad request, file is smaller than content-length - ec = std::make_error_code(std::errc::invalid_argument); - break; - } - } + if (socket_->is_timeout_) { + data = resp_data{std::make_error_code(std::errc::timed_out), 404}; } - if (ec) { - if (socket_->is_timeout_) { - ec = std::make_error_code(std::errc::timed_out); - } - co_return resp_data{ec, 404}; + if (data.net_err) { + co_return false; } - data = co_await handle_read(ec, size, is_keep_alive, std::move(ctx), - http_method::POST); - if (ec && socket_->is_timeout_) { + co_return true; + } + + void handle_upload_timeout_error(std::error_code &ec) { +#ifdef INJECT_FOR_HTTP_CLIENT_TEST + if (write_header_timeout_ || write_payload_timeout_ || read_timeout_) { + socket_->is_timeout_ = true; + } +#endif + if (socket_->is_timeout_) { ec = std::make_error_code(std::errc::timed_out); } - handle_result(data, ec, is_keep_alive); - co_return data; } - template - async_simple::coro::Lazy async_upload_chunked( - S uri, http_method method, Source source, + template + async_simple::coro::Lazy async_upload_impl( + S uri, http_method method, Source source /* file */, req_content_type content_type = req_content_type::text, - std::unordered_map headers = {}) { - req_context<> ctx{content_type}; - resp_data data{}; + std::unordered_map headers = {}, + uint64_t offset = 0 /*file offset*/, + int64_t content_length = -1 /*upload size*/) { std::error_code ec{}; size_t size = 0; bool is_keep_alive = true; + req_context<> ctx{content_type}; + resp_data data{}; std::shared_ptr guard(nullptr, [&, this](auto) { if (!req_headers_.empty()) { req_headers_.clear(); } - handle_result(data, ec, is_keep_alive); }); - if (!resp_chunk_str_.empty()) { - resp_chunk_str_.clear(); - } - auto [ok, u] = handle_uri(data, uri); if (!ok) { co_return resp_data{std::make_error_code(std::errc::protocol_error), 404}; } - constexpr bool is_stream_file = is_stream_ptr_v; - if constexpr (is_stream_file) { - if (!source) { - co_return resp_data{ - std::make_error_code(std::errc::no_such_file_or_directory), 404}; + if constexpr (upload_type != upload_type_t::multipart) { + check_source(data, source); + if (data.status != 0) { + co_return data; } } - else if constexpr (std::is_same_v || - std::is_same_v) { - if (!std::filesystem::exists(source)) { - co_return resp_data{ - std::make_error_code(std::errc::no_such_file_or_directory), 404}; + + if constexpr (upload_type == upload_type_t::with_length) { + content_length = handle_upload_header_with_length(data, source, headers, + offset, content_length); + if (data.status != 0) { + co_return data; } } - - if (headers.empty()) { - add_header("Transfer-Encoding", "chunked"); + else if constexpr (upload_type == upload_type_t::chunked) { + handle_upload_header_with_chunked(headers); } - else { - headers.emplace("Transfer-Encoding", "chunked"); + else if constexpr (upload_type == upload_type_t::multipart) { + handle_upload_header_with_multipart(); } std::string header_str = build_request_header(u, method, ctx, true, std::move(headers)); if (socket_->has_closed_) { - { - auto guard = timer_guard(this, conn_timeout_duration_, "connect timer"); - data = co_await connect(u); - } - if (socket_->is_timeout_) { - co_return resp_data{std::make_error_code(std::errc::timed_out), 404}; - } - if (data.net_err) { + if (bool r = co_await reconnect(data, u); !r) { co_return data; } } @@ -1247,76 +1133,127 @@ class coro_http_client : public std::enable_shared_from_this { auto time_guard = timer_guard(this, req_timeout_duration_, "request timer"); std::tie(ec, size) = co_await async_write(asio::buffer(header_str)); if (ec) { - if (socket_->is_timeout_) { - ec = std::make_error_code(std::errc::timed_out); - } + handle_upload_timeout_error(ec); co_return resp_data{ec, 404}; } + constexpr bool is_stream_file = is_stream_ptr_v; if constexpr (is_stream_file) { - std::string file_data; - detail::resize(file_data, max_single_part_size_); - while (!source->eof()) { - size_t rd_size = - source->read(file_data.data(), file_data.size()).gcount(); - std::vector bufs; - std::string size_str; - cinatra::to_chunked_buffers(bufs, size_str, {file_data.data(), rd_size}, - source->eof()); - if (std::tie(ec, size) = co_await async_write(bufs); ec) { - break; - } + if constexpr (upload_type == upload_type_t::with_length) { + co_await send_fstream_with_length(source, ec, offset, content_length); + } + else if constexpr (upload_type == upload_type_t::chunked) { + co_await send_fstream_with_chunked(source, ec); } } + else if constexpr (std::is_enum_v) { // only for multipart + co_await send_fstream_with_multipart(ec); + } else if constexpr (std::is_same_v || std::is_same_v) { #ifdef __linux__ #ifdef CINATRA_ENABLE_SSL if (!has_init_ssl_) { #endif - co_await send_file_without_copy_chunked(std::filesystem::path{source}, - ec); + if constexpr (upload_type == upload_type_t::with_length) { + co_await send_file_no_copy_with_length(std::filesystem::path{source}, + ec, content_length, offset); + } + else if constexpr (upload_type == upload_type_t::chunked) { + co_await send_file_no_copy_with_chunked(std::filesystem::path{source}, + ec); + } #ifdef CINATRA_ENABLE_SSL } else { - co_await send_file_chunked_with_copy(source, ec); + if constexpr (upload_type == upload_type_t::with_length) { + co_await send_file_copy_with_length(source, ec, content_length, + offset); + } + else if constexpr (upload_type == upload_type_t::chunked) { + co_await send_file_copy_with_chunked(source, ec); + } } #endif #else - co_await send_file_chunked_with_copy(source, ec); + if constexpr (upload_type == upload_type_t::with_length) { + co_await send_file_copy_with_length(source, ec, content_length, offset); + } + else if constexpr (upload_type == upload_type_t::chunked) { + co_await send_file_copy_with_chunked(source, ec); + } #endif } else { - while (true) { - auto result = co_await source(); - std::vector bufs; - std::string size_str; - cinatra::to_chunked_buffers( - bufs, size_str, {result.buf.data(), result.buf.size()}, result.eof); - if (std::tie(ec, size) = co_await async_write(bufs); ec) { - break; - } - if (result.eof) { - break; - } + if constexpr (upload_type == upload_type_t::with_length) { + co_await send_sink_with_length(source, ec, content_length); + } + else if constexpr (upload_type == upload_type_t::chunked) { + co_await send_sink_with_chunked(source, ec); } } if (ec) { - if (socket_->is_timeout_) { - ec = std::make_error_code(std::errc::timed_out); - } + handle_upload_timeout_error(ec); co_return resp_data{ec, 404}; } data = co_await handle_read(ec, size, is_keep_alive, std::move(ctx), http_method::POST); - if (ec && socket_->is_timeout_) { - ec = std::make_error_code(std::errc::timed_out); + if (ec) { + handle_upload_timeout_error(ec); } handle_result(data, ec, is_keep_alive); co_return data; } + public: + // send file with length + template + async_simple::coro::Lazy async_upload( + S uri, http_method method, Source source /* file */, + uint64_t offset = 0 /*file offset*/, + int64_t content_length = -1 /*upload size*/, + req_content_type content_type = req_content_type::text, + std::unordered_map headers = {}) { + return async_upload_impl( + std::move(uri), method, std::move(source), content_type, + std::move(headers), offset, content_length); + } + + // send file with chunked + template + async_simple::coro::Lazy async_upload_chunked( + S uri, http_method method, Source source, + req_content_type content_type = req_content_type::text, + std::unordered_map headers = {}) { + return async_upload_impl( + std::move(uri), method, std::move(source), content_type, + std::move(headers)); + } + + // send multipart data, should call add_file_part or add_str_part firstly. + async_simple::coro::Lazy async_upload_multipart(std::string uri) { + if (form_data_.empty()) { + CINATRA_LOG_WARNING << "no multipart"; + co_return resp_data{std::make_error_code(std::errc::invalid_argument), + 404}; + } + + co_return co_await async_upload_impl( + std::move(uri), http_method::POST, upload_type_t::multipart, + req_content_type::multipart); + } + + async_simple::coro::Lazy async_upload_multipart( + std::string uri, std::string name, std::string filename) { + if (!add_file_part(std::move(name), std::move(filename))) { + CINATRA_LOG_WARNING << "open file failed or duplicate test names"; + co_return resp_data{std::make_error_code(std::errc::invalid_argument), + 404}; + } + co_return co_await async_upload_multipart(std::move(uri)); + } + template async_simple::coro::Lazy async_request( S uri, http_method method, req_context ctx, @@ -1371,50 +1308,10 @@ class coro_http_client : public std::enable_shared_from_this { u.path = uri; } if (socket_->has_closed_) { - host_ = proxy_host_.empty() ? u.get_host() : proxy_host_; - port_ = proxy_port_.empty() ? u.get_port() : proxy_port_; - auto guard = timer_guard(this, conn_timeout_duration_, "connect timer"); - if (ec = co_await coro_io::async_connect(&executor_wrapper_, - socket_->impl_, host_, port_); - ec) { - break; - } - - if (socket_->is_timeout_) { - data.net_err = std::make_error_code(std::errc::timed_out); + data = co_await connect(u); + if (data.status != 0) { co_return data; } - - if (enable_tcp_no_delay_) { - socket_->impl_.set_option(asio::ip::tcp::no_delay(true), ec); - if (ec) { - break; - } - } - - if (u.is_ssl) { -#ifdef CINATRA_ENABLE_SSL - if (!has_init_ssl_) { - size_t pos = u.host.find("www."); - std::string host; - if (pos != std::string_view::npos) { - host = std::string{u.host.substr(pos + 4)}; - } - else { - host = std::string{u.host}; - } - bool r = init_ssl(asio::ssl::verify_none, "", host); - if (!r) { - data.net_err = std::make_error_code(std::errc::invalid_argument); - co_return data; - } - } -#endif - if (ec = co_await handle_shake(); ec) { - break; - } - } - socket_->has_closed_ = false; } std::vector vec; @@ -1574,6 +1471,7 @@ class coro_http_client : public std::enable_shared_from_this { // all be http proxy_request_uri_.append("http://") .append(u.get_host()) + .append(":") .append(u.get_port()); } proxy_request_uri_.append(u.get_path()); @@ -1583,7 +1481,7 @@ class coro_http_client : public std::enable_shared_from_this { std::string build_request_header( const uri_t &u, http_method method, const auto &ctx, - bool is_chunked = false, + bool already_has_len = false, std::unordered_map headers = {}) { std::string req_str(method_name(method)); @@ -1664,7 +1562,7 @@ class coro_http_client : public std::enable_shared_from_this { should_add_len = false; } - if (is_chunked) { + if (already_has_len) { should_add_len = false; } @@ -1687,14 +1585,11 @@ class coro_http_client : public std::enable_shared_from_this { int parse_ret = parser.parse_response(data_ptr, header_size, 0); #ifdef INJECT_FOR_HTTP_CLIENT_TEST - if (inject_response_valid == ClientInjectAction::response_error) { + if (parse_failed_forever_) { parse_ret = -1; } #endif if (parse_ret < 0) { -#ifdef INJECT_FOR_HTTP_CLIENT_TEST - inject_response_valid = ClientInjectAction::none; -#endif return std::make_error_code(std::errc::protocol_error); } head_buf_.consume(header_size); // header size @@ -1717,15 +1612,7 @@ class coro_http_client : public std::enable_shared_from_this { } ec = handle_header(data, parser_, size); -#ifdef INJECT_FOR_HTTP_CLIENT_TEST - if (inject_header_valid == ClientInjectAction::header_error) { - ec = std::make_error_code(std::errc::protocol_error); - } -#endif if (ec) { -#ifdef INJECT_FOR_HTTP_CLIENT_TEST - inject_header_valid = ClientInjectAction::none; -#endif break; } @@ -1998,16 +1885,6 @@ class coro_http_client : public std::enable_shared_from_this { break; } -#ifdef INJECT_FOR_HTTP_CLIENT_TEST - if (inject_read_failed == ClientInjectAction::read_failed) { - ec = std::make_error_code(std::errc::not_connected); - } - if (ec) { - inject_read_failed = ClientInjectAction::none; - break; - } -#endif - size_t buf_size = chunked_buf_.size(); size_t additional_size = buf_size - size; const char *data_ptr = @@ -2015,15 +1892,7 @@ class coro_http_client : public std::enable_shared_from_this { std::string_view size_str(data_ptr, size - CRCF.size()); auto chunk_size = hex_to_int(size_str); chunked_buf_.consume(size); -#ifdef INJECT_FOR_HTTP_CLIENT_TEST - if (inject_chunk_valid == ClientInjectAction::chunk_error) { - chunk_size = -1; - } -#endif if (chunk_size < 0) { -#ifdef INJECT_FOR_HTTP_CLIENT_TEST - inject_chunk_valid = ClientInjectAction::none; -#endif CINATRA_LOG_DEBUG << "bad chunked size"; ec = asio::error::make_error_code( asio::error::basic_errors::invalid_argument); @@ -2071,6 +1940,11 @@ class coro_http_client : public std::enable_shared_from_this { co_return resp_data{ec, 404}; } +#ifdef INJECT_FOR_HTTP_CLIENT_TEST + if (connect_timeout_forever_) { + socket_->is_timeout_ = true; + } +#endif if (socket_->is_timeout_) { auto ec = std::make_error_code(std::errc::timed_out); co_return resp_data{ec, 404}; @@ -2085,6 +1959,23 @@ class coro_http_client : public std::enable_shared_from_this { } if (u.is_ssl) { +#ifdef CINATRA_ENABLE_SSL + if (!has_init_ssl_) { + size_t pos = u.host.find("www."); + std::string host; + if (pos != std::string_view::npos) { + host = std::string{u.host.substr(pos + 4)}; + } + else { + host = std::string{u.host}; + } + bool r = init_ssl(asio::ssl::verify_none, "", host); + if (!r) { + co_return resp_data{ + std::make_error_code(std::errc::invalid_argument), 404}; + } + } +#endif if (auto ec = co_await handle_shake(); ec) { co_return resp_data{ec, 404}; } @@ -2139,11 +2030,6 @@ class coro_http_client : public std::enable_shared_from_this { .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 { @@ -2242,16 +2128,8 @@ class coro_http_client : public std::enable_shared_from_this { } data_ptr = asio::buffer_cast(read_buf.data()); - if (is_close_frame) { - if (payload_len >= 2) { - payload_len -= 2; - data_ptr += sizeof(uint16_t); - } - } - #ifdef CINATRA_ENABLE_GZIP - if (!is_close_frame && is_server_support_ws_deflate_ && - enable_ws_deflate_) { + if (is_server_support_ws_deflate_ && enable_ws_deflate_) { inflate_str_.clear(); if (!cinatra::gzip_codec::inflate({data_ptr, payload_len}, inflate_str_)) { @@ -2260,17 +2138,19 @@ class coro_http_client : public std::enable_shared_from_this { data.net_err = std::make_error_code(std::errc::protocol_error); co_return data; } - data.status = 200; - data.resp_body = {inflate_str_.data(), inflate_str_.size()}; + data_ptr = inflate_str_.data(); + payload_len = inflate_str_.length(); } - else { #endif - - data.status = 200; - data.resp_body = {data_ptr, payload_len}; -#ifdef CINATRA_ENABLE_GZIP + if (is_close_frame) { + if (payload_len >= 2) { + payload_len -= 2; + data_ptr += sizeof(uint16_t); + } } -#endif + data.status = 200; + data.resp_body = {data_ptr, payload_len}; + read_buf.consume(read_buf.size()); if (is_close_frame) { @@ -2328,6 +2208,11 @@ class coro_http_client : public std::enable_shared_from_this { template async_simple::coro::Lazy> async_read( AsioBuffer &&buffer, size_t size_to_read) noexcept { +#ifdef INJECT_FOR_HTTP_CLIENT_TEST + if (read_failed_forever_) { + return async_read_failed(); + } +#endif #ifdef CINATRA_ENABLE_SSL if (has_init_ssl_) { return coro_io::async_read(*socket_->ssl_stream_, buffer, size_to_read); @@ -2340,9 +2225,26 @@ class coro_http_client : public std::enable_shared_from_this { #endif } +#ifdef INJECT_FOR_HTTP_CLIENT_TEST + async_simple::coro::Lazy> + async_write_failed() { + co_return std::make_pair(std::make_error_code(std::errc::io_error), 0); + } + + async_simple::coro::Lazy> + async_read_failed() { + co_return std::make_pair(std::make_error_code(std::errc::io_error), 0); + } +#endif + template async_simple::coro::Lazy> async_write( AsioBuffer &&buffer) { +#ifdef INJECT_FOR_HTTP_CLIENT_TEST + if (write_failed_forever_) { + return async_write_failed(); + } +#endif #ifdef CINATRA_ENABLE_SSL if (has_init_ssl_) { return coro_io::async_write(*socket_->ssl_stream_, buffer); @@ -2358,6 +2260,11 @@ class coro_http_client : public std::enable_shared_from_this { template async_simple::coro::Lazy> async_read_until( AsioBuffer &buffer, asio::string_view delim) noexcept { +#ifdef INJECT_FOR_HTTP_CLIENT_TEST + if (read_failed_forever_) { + return async_read_failed(); + } +#endif #ifdef CINATRA_ENABLE_SSL if (has_init_ssl_) { return coro_io::async_read_until(*socket_->ssl_stream_, buffer, delim); @@ -2445,7 +2352,7 @@ class coro_http_client : public std::enable_shared_from_this { bool enable_follow_redirect_ = false; bool enable_timeout_ = false; std::chrono::steady_clock::duration conn_timeout_duration_ = - std::chrono::seconds(8); + std::chrono::seconds(30); std::chrono::steady_clock::duration req_timeout_duration_ = std::chrono::seconds(60); bool enable_tcp_no_delay_ = true; @@ -2469,6 +2376,16 @@ class coro_http_client : public std::enable_shared_from_this { bool stop_bench_ = false; size_t total_len_ = 0; #endif +#ifdef INJECT_FOR_HTTP_CLIENT_TEST + public: + bool write_failed_forever_ = false; + bool connect_timeout_forever_ = false; + bool parse_failed_forever_ = false; + bool read_failed_forever_ = false; + bool write_header_timeout_ = false; + bool write_payload_timeout_ = false; + bool read_timeout_ = false; +#endif }; } // namespace cinatra diff --git a/include/cinatra/coro_http_connection.hpp b/include/cinatra/coro_http_connection.hpp index cf66d459..8b2c24f7 100644 --- a/include/cinatra/coro_http_connection.hpp +++ b/include/cinatra/coro_http_connection.hpp @@ -721,28 +721,19 @@ class coro_http_connection case cinatra::ws_frame_type::WS_TEXT_FRAME: case cinatra::ws_frame_type::WS_BINARY_FRAME: { #ifdef CINATRA_ENABLE_GZIP - if (is_client_ws_compressed_) { - inflate_str_.clear(); - if (!cinatra::gzip_codec::inflate( - {payload.data(), payload.size()}, inflate_str_)) { - CINATRA_LOG_ERROR << "uncompuress data error"; - result.ec = std::make_error_code(std::errc::protocol_error); - break; - } - result.eof = true; - result.data = {inflate_str_.data(), inflate_str_.size()}; + if (!gzip_compress(payload, result)) { break; } - else { #endif - result.eof = true; - result.data = {payload.data(), payload.size()}; - break; + result.eof = true; + result.data = {payload.data(), payload.size()}; + } break; + case cinatra::ws_frame_type::WS_CLOSE_FRAME: { #ifdef CINATRA_ENABLE_GZIP + if (!gzip_compress(payload, result)) { + break; } #endif - } break; - case cinatra::ws_frame_type::WS_CLOSE_FRAME: { close_frame close_frame = ws_.parse_close_payload(payload.data(), payload.size()); result.eof = true; @@ -793,6 +784,22 @@ class coro_http_connection co_return result; } +#ifdef CINATRA_ENABLE_GZIP + bool gzip_compress(std::span &payload, websocket_result &result) { + if (is_client_ws_compressed_) { + inflate_str_.clear(); + if (!cinatra::gzip_codec::inflate({payload.data(), payload.size()}, + inflate_str_)) { + CINATRA_LOG_ERROR << "uncompuress data error"; + result.ec = std::make_error_code(std::errc::protocol_error); + return false; + } + payload = inflate_str_; + } + return true; + } +#endif + auto &tcp_socket() { return socket_; } void set_quit_callback(std::function callback, @@ -920,7 +927,7 @@ class coro_http_connection code_utils::base64_encode(accept_key, sha1buf, sizeof(sha1buf), 0); - response_.set_status_and_content(status_type::switching_protocols); + response_.set_status_and_content(status_type::switching_protocols, ""); response_.add_header("Upgrade", "WebSocket"); response_.add_header("Connection", "Upgrade"); diff --git a/include/cinatra/coro_http_request.hpp b/include/cinatra/coro_http_request.hpp index 21cfb23a..cb7ae9a8 100644 --- a/include/cinatra/coro_http_request.hpp +++ b/include/cinatra/coro_http_request.hpp @@ -141,10 +141,6 @@ class coro_http_request { 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(); } - std::string_view get_accept_encoding() { return get_header_value("Accept-Encoding"); } @@ -243,10 +239,6 @@ class coro_http_request { return false; } - void set_aspect_data(std::string data) { - aspect_data_.push_back(std::move(data)); - } - void set_aspect_data(std::vector data) { aspect_data_ = std::move(data); } diff --git a/include/cinatra/coro_http_response.hpp b/include/cinatra/coro_http_response.hpp index f4b76b64..0b106f52 100644 --- a/include/cinatra/coro_http_response.hpp +++ b/include/cinatra/coro_http_response.hpp @@ -54,7 +54,7 @@ class coro_http_response { has_set_content_ = true; } void set_status_and_content( - status_type status, std::string content = "", + status_type status, std::string content, content_encoding encoding = content_encoding::none, std::string_view client_encoding_type = "") { set_status_and_content_view(status, std::move(content), encoding, false, @@ -220,8 +220,10 @@ class coro_http_response { resp_str.append(TRANSFER_ENCODING_SV); } else { - if (!content_.empty()) { - auto [ptr, ec] = std::to_chars(buf_, buf_ + 32, content_.size()); + if (!content_.empty() || !content_view_.empty()) { + size_t content_size = + content_.empty() ? content_view_.size() : content_.size(); + auto [ptr, ec] = std::to_chars(buf_, buf_ + 32, content_size); resp_str.append(CONTENT_LENGTH_SV); resp_str.append(std::string_view(buf_, std::distance(buf_, ptr))); resp_str.append(CRCF); @@ -385,8 +387,8 @@ class coro_http_response { void redirect(const std::string &url, bool is_forever = false) { add_header("Location", url); is_forever == false - ? set_status_and_content(status_type::moved_temporarily) - : set_status_and_content(status_type::moved_permanently); + ? set_status_and_content(status_type::moved_temporarily, "") + : set_status_and_content(status_type::moved_permanently, ""); } private: diff --git a/include/cinatra/coro_http_server.hpp b/include/cinatra/coro_http_server.hpp index 1bc287b0..0403d109 100644 --- a/include/cinatra/coro_http_server.hpp +++ b/include/cinatra/coro_http_server.hpp @@ -604,7 +604,7 @@ class coro_http_server { private: std::error_code listen() { - CINATRA_LOG_INFO << "begin to listen"; + CINATRA_LOG_INFO << "begin to listen " << port_; using asio::ip::tcp; asio::error_code ec; diff --git a/include/cinatra/gzip.hpp b/include/cinatra/gzip.hpp index f8a09980..006874dd 100644 --- a/include/cinatra/gzip.hpp +++ b/include/cinatra/gzip.hpp @@ -177,7 +177,7 @@ inline bool inflate(std::string_view str_src, std::string &str_dest) { // generated output if (err == Z_STREAM_END) { // Finish up - int kerr = ::inflateEnd(&zs); + [[maybe_unused]] int kerr = ::inflateEnd(&zs); // Got a good result, set the size to the amount unzipped in this call // (including all recursive calls) @@ -202,7 +202,7 @@ inline bool inflate(std::string_view str_src, std::string &str_dest) { str_dest.append((const char *)bytes_out, OUTPUT_BUF_SIZE - zs.avail_out); - int kerr = ::inflateEnd(&zs); + [[maybe_unused]] int kerr = ::inflateEnd(&zs); break; } @@ -252,7 +252,7 @@ inline bool deflate(std::string_view str_src, std::string &str_dest) { // generated output if (err == Z_STREAM_END) { // Finish up - int kerr = ::deflateEnd(&zs); + [[maybe_unused]] int kerr = ::deflateEnd(&zs); // Got a good result, set the size to the amount unzipped in this call // (including all recursive calls) @@ -277,7 +277,7 @@ inline bool deflate(std::string_view str_src, std::string &str_dest) { str_dest.append((const char *)bytes_out, OUTPUT_BUF_SIZE - zs.avail_out); - int kerr = ::deflateEnd(&zs); + [[maybe_unused]] int kerr = ::deflateEnd(&zs); break; } diff --git a/include/cinatra/http_parser.hpp b/include/cinatra/http_parser.hpp index 76a967f4..e71d280f 100644 --- a/include/cinatra/http_parser.hpp +++ b/include/cinatra/http_parser.hpp @@ -48,10 +48,7 @@ class http_parser { if (header_len_ < 0) [[unlikely]] { CINATRA_LOG_WARNING << "parse http head failed"; if (num_headers_ == CINATRA_MAX_HTTP_HEADER_FIELD_SIZE) { - CINATRA_LOG_ERROR << "the field of http head is out of max limit " - << CINATRA_MAX_HTTP_HEADER_FIELD_SIZE - << ", you can define macro " - "CINATRA_MAX_HTTP_HEADER_FIELD_SIZE to expand it."; + output_error(); } } return header_len_; @@ -76,10 +73,7 @@ class http_parser { if (header_len_ < 0) [[unlikely]] { CINATRA_LOG_WARNING << "parse http head failed"; if (num_headers_ == CINATRA_MAX_HTTP_HEADER_FIELD_SIZE) { - CINATRA_LOG_ERROR << "the field of http head is out of max limit " - << CINATRA_MAX_HTTP_HEADER_FIELD_SIZE - << ", you can define macro " - "CINATRA_MAX_HTTP_HEADER_FIELD_SIZE to expand it."; + output_error(); } return header_len_; } @@ -173,11 +167,6 @@ class http_parser { return content_type.substr(pos + 1); } - bool is_req_ranges() const { - auto value = this->get_header_value("Range"sv); - return !value.empty(); - } - bool is_resp_ranges() const { auto value = this->get_header_value("Accept-Ranges"sv); return !value.empty(); @@ -248,11 +237,12 @@ class http_parser { } } - 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; + private: + void output_error() { + CINATRA_LOG_ERROR << "the field of http head is out of max limit " + << CINATRA_MAX_HTTP_HEADER_FIELD_SIZE + << ", you can define macro " + "CINATRA_MAX_HTTP_HEADER_FIELD_SIZE to expand it."; } private: diff --git a/include/cinatra/multipart.hpp b/include/cinatra/multipart.hpp index b85b5001..154d082c 100644 --- a/include/cinatra/multipart.hpp +++ b/include/cinatra/multipart.hpp @@ -25,7 +25,6 @@ class multipart_reader_t { 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, diff --git a/include/cinatra/picohttpparser.h b/include/cinatra/picohttpparser.h index d57377a5..479a8c23 100644 --- a/include/cinatra/picohttpparser.h +++ b/include/cinatra/picohttpparser.h @@ -233,44 +233,6 @@ static const char *findchar_fast(const char *buf, const char *buf_end, 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) { diff --git a/tests/test_cinatra.cpp b/tests/test_cinatra.cpp index ab8c5ca1..e0819b98 100644 --- a/tests/test_cinatra.cpp +++ b/tests/test_cinatra.cpp @@ -28,7 +28,6 @@ using namespace std::chrono_literals; using namespace cinatra; -#ifdef CINATRA_ENABLE_GZIP std::string_view get_header_value(auto &resp_headers, std::string_view key) { for (const auto &[k, v] : resp_headers) { if (k == key) @@ -37,25 +36,61 @@ std::string_view get_header_value(auto &resp_headers, std::string_view key) { return {}; } +#ifdef CINATRA_ENABLE_GZIP TEST_CASE("test for gzip") { coro_http_server server(1, 8090); server.set_http_handler( "/gzip", [](coro_http_request &req, coro_http_response &res) { CHECK(req.get_header_value("Content-Encoding") == "gzip"); + CHECK(req.get_encoding_type() == content_encoding::gzip); res.set_status_and_content(status_type::ok, "hello world", content_encoding::gzip); }); + server.set_http_handler( + "/deflate", [](coro_http_request &req, coro_http_response &res) { + CHECK(req.get_header_value("Content-Encoding") == "deflate"); + CHECK(req.get_encoding_type() == content_encoding::deflate); + res.set_status_and_content(status_type::ok, "hello world", + content_encoding::deflate); + }); + server.set_http_handler( + "/none", [](coro_http_request &req, coro_http_response &res) { + CHECK(req.get_header_value("Content-Encoding") == "none"); + CHECK(req.get_encoding_type() == content_encoding::none); + res.set_status_and_content(status_type::ok, "hello world", + content_encoding::none); + }); server.async_start(); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:8090/gzip"; + client.add_header("Content-Encoding", "gzip"); + auto result = async_simple::coro::syncAwait(client.async_get(uri)); + // auto content = get_header_value(result.resp_headers, "Content-Encoding"); + CHECK(get_header_value(result.resp_headers, "Content-Encoding") == "gzip"); + CHECK(result.resp_body == "hello world"); + } - coro_http_client client{}; - std::string uri = "http://127.0.0.1:8090/gzip"; - client.add_header("Content-Encoding", "gzip"); - auto result = async_simple::coro::syncAwait(client.async_get(uri)); - auto content = get_header_value(result.resp_headers, "Content-Encoding"); - CHECK(get_header_value(result.resp_headers, "Content-Encoding") == "gzip"); - CHECK(result.resp_body == "hello world"); + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:8090/deflate"; + client.add_header("Content-Encoding", "deflate"); + auto result = async_simple::coro::syncAwait(client.async_get(uri)); + // auto content = get_header_value(result.resp_headers, "Content-Encoding"); + CHECK(get_header_value(result.resp_headers, "Content-Encoding") == + "deflate"); + CHECK(result.resp_body == "hello world"); + } + + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:8090/none"; + client.add_header("Content-Encoding", "none"); + auto result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(get_header_value(result.resp_headers, "Content-Encoding").empty()); + CHECK(result.resp_body == "hello world"); + } server.stop(); } @@ -69,7 +104,7 @@ TEST_CASE("test encoding type") { if (encoding_type == content_encoding::gzip) { // only post request have this field std::string decode_str; - bool r = gzip_codec::uncompress(req.get_body(), decode_str); + gzip_codec::uncompress(req.get_body(), decode_str); CHECK(decode_str == "Hello World"); } resp.set_status_and_content(status_type::ok, "ok", content_encoding::gzip, @@ -100,6 +135,27 @@ TEST_CASE("test encoding type") { CHECK(resp.content() == "ok"); co_return; }); + std::string_view content = "ok"; + server.set_http_handler( + "/only_deflate_view", + [content](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_status_and_content_view(status_type::ok, content, + content_encoding::deflate, true, "ok"); + co_return; + }); + server.set_http_handler( + "/only_deflate", + [content](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_status_and_content(status_type::ok, "ok", + content_encoding::deflate, + req.get_accept_encoding()); + // client4 accept-encoding not allow gzip, response content no + // compression + CHECK(resp.content() == "ok"); + co_return; + }); server.async_start(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); @@ -133,6 +189,15 @@ TEST_CASE("test encoding type") { client4.async_get("http://127.0.0.1:9001/only_gzip")); CHECK(result.resp_body == "ok"); + coro_http_client client5{}; + result = async_simple::coro::syncAwait( + client5.async_get("http://127.0.0.1:9001/only_deflate_view")); + CHECK(result.resp_body == "ok"); + client5.add_header("Accept-Encoding", "gzip"); + result = async_simple::coro::syncAwait( + client5.async_get("http://127.0.0.1:9001/only_deflate")); + CHECK(result.resp_body == "ok"); + server.stop(); } #endif @@ -229,6 +294,7 @@ TEST_CASE("test ssl client") { coro_http_client client{}; client.enable_auto_redirect(true); bool ok = client.init_ssl(); + client.reset(); REQUIRE_MESSAGE(ok == true, "init ssl fail, please check ssl config"); auto result = client.get("https://www.bing.com"); CHECK(result.status >= 200); @@ -357,6 +423,237 @@ TEST_CASE("test cinatra::string SSO to no SSO") { CHECK(s == sum); } +TEST_CASE("test config") { + coro_http_client client{}; + coro_http_client::config conf{}; + conf.sec_key = "s//GYHa/XO7Hd2F2eOGfyA=="; + conf.proxy_host = "http://example.com"; + conf.proxy_host = "9090"; + conf.max_single_part_size = 1024 * 1024; + conf.proxy_auth_username = "cinatra"; + conf.proxy_auth_token = "cinatra"; + conf.proxy_auth_passwd = "cinatra"; + conf.enable_tcp_no_delay = true; + client.init_config(conf); + + std::unordered_map req_headers{{"test", "ok"}}; + client.set_headers(req_headers); + const auto &headers = client.get_headers(); + CHECK(req_headers == headers); + + auto &executor = client.get_executor(); + auto name = executor.name(); + CHECK(!name.empty()); + + const auto &c = client.get_config(); + CHECK(c.enable_tcp_no_delay == conf.enable_tcp_no_delay); + CHECK(c.max_single_part_size == 1024 * 1024); + + auto ret = async_simple::coro::syncAwait(client.connect("http://##test.com")); + CHECK(ret.status != 200); + CHECK(ret.net_err.value() == (int)std::errc::protocol_error); +} + +#ifndef CINATRA_ENABLE_SSL +TEST_CASE("test request https without init_ssl") { + coro_http_client client{}; + auto ret = client.get("https://baidu.com"); + CHECK(ret.status != 200); + + ret = async_simple::coro::syncAwait(client.connect("https://baidu.com")); + CHECK(ret.status != 200); +} +#endif + +struct add_data { + bool before(coro_http_request &req, coro_http_response &res) { + req.set_aspect_data("hello world"); + return true; + } +}; + +struct add_more_data { + bool before(coro_http_request &req, coro_http_response &res) { + req.set_aspect_data(std::vector{"test", "aspect"}); + return true; + } +}; + +struct auth_t { + bool before(coro_http_request &req, coro_http_response &res) { return true; } +}; + +struct dely_t { + bool before(coro_http_request &req, coro_http_response &res) { + res.set_status_and_content(status_type::unauthorized, "unauthorized"); + return false; + } +}; + +TEST_CASE("test aspect") { + coro_http_server server(1, 9001); + server.set_http_handler( + "/get", + [](coro_http_request &req, coro_http_response &resp) { + auto val = req.get_aspect_data(); + CHECK(val[0] == "hello world"); + resp.set_status_and_content(status_type::ok, "ok"); + }, + add_data{}); + server.set_http_handler( + "/get_more", + [](coro_http_request &req, coro_http_response &resp) { + auto val = req.get_aspect_data(); + CHECK(val[0] == "test"); + CHECK(val[1] == "aspect"); + CHECK(!req.is_upgrade()); + resp.set_status_and_content(status_type::ok, "ok"); + }, + add_more_data{}); + server.set_http_handler( + "/auth", + [](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "ok"); + }, + dely_t{}, auth_t{}); + server.set_http_handler( + "/exception", [](coro_http_request &req, coro_http_response &resp) { + throw std::invalid_argument("invalid argument"); + }); + server.set_http_handler( + "/throw", [](coro_http_request &req, coro_http_response &resp) { + throw 9; + }); + server.set_http_handler( + "/coro_exception", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + throw std::invalid_argument("invalid argument"); + co_return; + }); + server.set_http_handler( + "/coro_throw", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + throw 9; + co_return; + }); + + server.async_start(); + + coro_http_client client{}; + auto result = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:9001/get")); + CHECK(result.status == 200); + result = async_simple::coro::syncAwait(client.async_get("/get_more")); + CHECK(result.status == 200); + result = async_simple::coro::syncAwait(client.async_get("/auth")); + CHECK(result.status == 401); + CHECK(result.resp_body == "unauthorized"); + result = async_simple::coro::syncAwait(client.async_get("/exception")); + CHECK(result.status == 503); + result = async_simple::coro::syncAwait(client.async_get("/throw")); + CHECK(result.status == 503); + result = async_simple::coro::syncAwait(client.async_get("/coro_exception")); + CHECK(result.status == 503); + result = async_simple::coro::syncAwait(client.async_get("/coro_throw")); + CHECK(result.status == 503); +} + +TEST_CASE("test response") { + coro_http_server server(1, 9001); + server.set_http_handler( + "/get", [](coro_http_request &req, coro_http_response &resp) { + resp.get_conn()->set_multi_buf(false); + resp.set_status_and_content(status_type::ok, "ok"); + CHECK(resp.content_size() == 2); + CHECK(resp.need_date()); + }); + server.set_http_handler( + "/get2", [](coro_http_request &req, coro_http_response &resp) { + resp.get_conn()->set_multi_buf(false); + resp.set_status(status_type::ok); + }); + std::array span{{"hello", "span"}}; + server.set_http_handler( + "/get1", [&](coro_http_request &req, coro_http_response &resp) { + resp.get_conn()->set_multi_buf(false); + resp.need_date_head(false); + CHECK(!resp.need_date()); + resp.set_keepalive(true); + resp.add_header_span({span.data(), span.size()}); + + resp.set_status_and_content(status_type::ok, "ok"); + }); + std::string sv = "hello view"; + server.set_http_handler( + "/view", [&](coro_http_request &req, coro_http_response &resp) { + resp.get_conn()->set_multi_buf(false); + resp.need_date_head(false); + resp.set_content_type<2>(); + CHECK(!resp.need_date()); + resp.add_header_span({span.data(), span.size()}); + + resp.set_status_and_content_view( + status_type::ok, std::string_view(sv.data(), sv.size())); + }); + server.set_http_handler( + "/empty", [&](coro_http_request &req, coro_http_response &resp) { + resp.get_conn()->set_multi_buf(false); + resp.need_date_head(false); + resp.set_content_type<2>(); + CHECK(!resp.need_date()); + resp.add_header_span({span.data(), span.size()}); + + resp.set_status_and_content_view(status_type::ok, ""); + }); + server.set_http_handler( + "/empty1", [&](coro_http_request &req, coro_http_response &resp) { + resp.set_content_type<2>(); + CHECK(!resp.need_date()); + resp.add_header_span({span.data(), span.size()}); + + resp.set_status_and_content_view(status_type::ok, ""); + }); + server.set_http_handler( + "/empty2", [&](coro_http_request &req, coro_http_response &resp) { + resp.set_content_type<2>(); + CHECK(!resp.need_date()); + resp.add_header_span({span.data(), span.size()}); + + resp.set_status_and_content(status_type::ok, ""); + }); + server.async_start(); + coro_http_client client{}; + auto result = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:9001/get")); + CHECK(result.status == 200); + result = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:9001/get1")); + CHECK(result.status == 200); + CHECK(get_header_value(result.resp_headers, "hello") == "span"); + result = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:9001/get2")); + CHECK(result.status == 200); + CHECK(result.resp_body == "200 OK"); + result = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:9001/view")); + CHECK(result.status == 200); + CHECK(result.resp_body == "hello view"); + result = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:9001/empty")); + CHECK(result.status == 200); + CHECK(result.resp_body.empty()); + result = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:9001/empty1")); + CHECK(result.status == 200); + CHECK(result.resp_body.empty()); + result = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:9001/empty2")); + CHECK(result.status == 200); + CHECK(result.resp_body.empty()); +} + async_simple::coro::Lazy send_data(auto &ch, size_t count) { for (int i = 0; i < count; i++) { co_await coro_io::async_send(ch, i); @@ -400,15 +697,15 @@ TEST_CASE("test coro channel with multi thread") { TEST_CASE("test coro channel") { { - auto ch = coro_io::create_shared_channel(100); + auto ch = coro_io::create_channel(100); auto ec = async_simple::coro::syncAwait( - coro_io::async_send(*ch, std::string("test"))); + coro_io::async_send(ch, std::string("test"))); CHECK(!ec); std::string val; std::error_code err; std::tie(err, val) = - async_simple::coro::syncAwait(coro_io::async_receive(*ch)); + async_simple::coro::syncAwait(coro_io::async_receive(ch)); CHECK(!err); CHECK(val == "test"); } @@ -675,10 +972,11 @@ TEST_CASE("test request with out buffer") { std::string str; str.resize(10); std::string url = "http://127.0.0.1:8090/test"; - std::string url1 = "http://127.0.0.1:8090/test"; + std::string url1 = "http://127.0.0.1:8090/test1"; { coro_http_client client; + client.add_header("Host", "cinatra"); auto ret = client.async_request(url, http_method::GET, req_context<>{}, {}, std::span{str.data(), str.size()}); auto result = async_simple::coro::syncAwait(ret); @@ -699,6 +997,8 @@ TEST_CASE("test request with out buffer") { std::cout << result.resp_body << "\n"; CHECK(result.status == 200); CHECK(!client.is_body_in_out_buf()); + auto s = client.release_buf(); + CHECK(s == "it is a test string, more than 10 bytes"); } { @@ -713,6 +1013,20 @@ TEST_CASE("test request with out buffer") { CHECK(result.resp_body == sv); CHECK(client.is_body_in_out_buf()); } + + { + detail::resize(str, 1024 * 64); + coro_http_client client; + std::string dest = "http://www.baidu.com"; + auto ret = client.async_request(dest, http_method::GET, req_context<>{}, {}, + std::span{str.data(), str.size()}); + auto result = async_simple::coro::syncAwait(ret); + bool ok = result.status == 200 || result.status == 301; + CHECK(ok); + std::string_view sv(str.data(), result.resp_body.size()); + CHECK(result.resp_body == sv); + CHECK(client.is_body_in_out_buf()); + } } TEST_CASE("test pass path not entire uri") { @@ -741,7 +1055,8 @@ TEST_CASE("test coro_http_client connect/request timeout") { auto r = async_simple::coro::syncAwait(client.async_get("http://www.baidu.com")); std::cout << r.net_err.value() << ", " << r.net_err.message() << "\n"; - CHECK(r.net_err != std::errc{}); + if (r.status != 200) + CHECK(r.net_err != std::errc{}); #endif } @@ -1166,6 +1481,148 @@ TEST_CASE("test coro_http_client multipart upload") { CHECK(result.status == 200); } +#ifdef CINATRA_ENABLE_SSL +TEST_CASE("test ssl upload") { + coro_http_server server(1, 8091); + server.init_ssl("../openssl_files/server.crt", "../openssl_files/server.key", + "test"); + server.set_http_handler( + "/upload", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + std::string_view filename = req.get_header_value("filename"); + uint64_t sz; + auto oldpath = fs::current_path().append(filename); + std::string newpath = fs::current_path() + .append("server_" + std::string{filename}) + .string(); + std::ofstream file(newpath, std::ios::binary); + CHECK(file.is_open()); + file.write(req.get_body().data(), req.get_body().size()); + file.flush(); + file.close(); + + size_t offset = 0; + std::string offset_s = std::string{req.get_header_value("offset")}; + if (!offset_s.empty()) { + offset = stoull(offset_s); + } + + std::string filesize = std::string{req.get_header_value("filesize")}; + + if (!filesize.empty()) { + sz = stoull(filesize); + } + else { + sz = std::filesystem::file_size(oldpath); + sz -= offset; + } + + CHECK(!filename.empty()); + CHECK(sz == std::filesystem::file_size(newpath)); + std::ifstream ifs(oldpath); + ifs.seekg(offset, std::ios::cur); + std::string str; + str.resize(sz); + ifs.read(str.data(), sz); + CHECK(str == req.get_body()); + resp.set_status_and_content(status_type::ok, std::string(filename)); + co_return; + }); + server.async_start(); + + std::string filename = "test_ssl_upload.txt"; + create_file(filename, 10); + std::string uri = "https://127.0.0.1:8091/upload"; + + { + coro_http_client client{}; + bool r = client.init_ssl(); + CHECK(r); + r = client.init_ssl(); + CHECK(r); + client.add_header("filename", filename); + auto lazy = client.async_upload(uri, http_method::PUT, filename); + auto result = async_simple::coro::syncAwait(lazy); + CHECK(result.status == 200); + } + + { + coro_http_client client{}; + client.add_header("filename", filename); + auto lazy = client.async_upload(uri, http_method::PUT, filename); + auto result = async_simple::coro::syncAwait(lazy); + CHECK(result.status == 200); + } + + cinatra::coro_http_server server1(1, 9002); + server1.init_ssl("../openssl_files/server.crt", "../openssl_files/server.key", + "test"); + server1.set_http_handler( + "/chunked", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + assert(req.get_content_type() == content_type::chunked); + chunked_result result{}; + std::string content; + + while (true) { + result = co_await req.get_conn()->read_chunked(); + if (result.ec) { + co_return; + } + if (result.eof) { + break; + } + + content.append(result.data); + } + + std::cout << "content size: " << content.size() << "\n"; + std::cout << content << "\n"; + resp.set_format_type(format_type::chunked); + resp.set_status_and_content(status_type::ok, "chunked ok"); + }); + server1.async_start(); + + uri = "https://127.0.0.1:9002/chunked"; + { + coro_http_client client{}; + bool r = client.init_ssl(); + CHECK(r); + std::string_view file = "test_ssl_upload.txt"; + client.add_header("filename", filename); + auto lazy = client.async_upload_chunked(uri, http_method::PUT, file); + auto result = async_simple::coro::syncAwait(lazy); + CHECK(result.status == 200); + } + + { + coro_http_client client{}; + client.enable_sni_hostname(true); + bool r = client.init_ssl(); + CHECK(r); + std::unordered_map headers; + headers.emplace("filename", filename); + auto lazy = client.async_upload_chunked(uri, http_method::PUT, filename, + req_content_type::none, headers); + auto result = async_simple::coro::syncAwait(lazy); + CHECK(result.status == 200); + } + + { + coro_http_client client{}; + client.write_failed_forever_ = true; + bool r = client.init_ssl(); + CHECK(r); + client.add_header("filename", filename); + auto lazy = client.async_upload_chunked(uri, http_method::PUT, filename); + auto result = async_simple::coro::syncAwait(lazy); + CHECK(result.status != 200); + } +} +#endif + TEST_CASE("test coro_http_client upload") { auto test_upload_by_file_path = [](std::string filename, std::size_t offset = 0, @@ -1392,6 +1849,14 @@ TEST_CASE("test coro_http_client upload") { test_upload_by_stream(filename, offset, r_size, true); } } + { + filename = "some_test_file.txt"; + bool r = create_file(filename, 10); + CHECK(r); + test_upload_by_file_path(filename, 20, SIZE_MAX, true); + std::error_code ec{}; + fs::remove(filename, ec); + } } TEST_CASE("test coro_http_client chunked upload and download") { @@ -1434,8 +1899,31 @@ TEST_CASE("test coro_http_client chunked upload and download") { }); server.async_start(); + { + coro_http_client client{}; + std::string uri = "http://###127.0.0.1:8090/chunked_upload"; + std::string filename = "test_chunked_upload.txt"; + auto lazy = client.async_upload_chunked(uri, http_method::PUT, filename); + auto result = async_simple::coro::syncAwait(lazy); + CHECK(result.status != 200); + + uri = "http://127.0.0.1:8090/chunked_upload"; + filename = "no_such.txt"; + auto lazy1 = client.async_upload_chunked(uri, http_method::PUT, filename); + result = async_simple::coro::syncAwait(lazy1); + CHECK(result.status != 200); + + std::shared_ptr file = nullptr; + uri = "http://127.0.0.1:8090/chunked_upload"; + auto lazy2 = client.async_upload_chunked(uri, http_method::PUT, file); + result = async_simple::coro::syncAwait(lazy2); + CHECK(result.status != 200); + + auto code = async_simple::coro::syncAwait(client.handle_shake()); + CHECK(code); + } auto sizes = {1024 * 1024, 2'000'000, 1024, 100, 0}; - for (auto size : sizes) { + for ([[maybe_unused]] auto size : sizes) { std::string filename = "test_chunked_upload.txt"; std::error_code ec{}; fs::remove(filename, ec); @@ -1512,6 +2000,7 @@ TEST_CASE("test coro_http_client not exist domain and bad uri") { { coro_http_client client{}; + client.set_req_timeout(1s); auto r = async_simple::coro::syncAwait( client.async_get("http://www.baidu.com/><")); CHECK(r.net_err); @@ -1529,8 +2018,8 @@ TEST_CASE("test coro_http_client async_get") { auto r1 = async_simple::coro::syncAwait(client.async_get("http://www.baidu.com")); - CHECK(!r.net_err); - CHECK(r.status == 200); + CHECK(!r1.net_err); + CHECK(r1.status == 200); } TEST_CASE("test basic http request") { @@ -1635,55 +2124,93 @@ TEST_CASE("test coro_http_client request timeout") { #ifdef INJECT_FOR_HTTP_CLIENT_TEST TEST_CASE("test inject failed") { - // { - // coro_http_client client{}; - // inject_response_valid = ClientInjectAction::response_error; - // client.set_req_timeout(8s); - // auto result = client.get("http://purecpp.cn"); - // CHECK(result.net_err == std::errc::protocol_error); - - // inject_header_valid = ClientInjectAction::header_error; - // result = client.get("http://purecpp.cn"); - // CHECK(result.net_err == std::errc::protocol_error); - // } - - // { - // coro_http_client client{}; - // client.set_req_timeout(10s); - // std::string uri = - // "http://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx"; - // std::string filename = "test.jpg"; - // - // std::error_code ec{}; - // std::filesystem::remove(filename, ec); - // - // inject_read_failed = ClientInjectAction::read_failed; - // auto result = client.download(uri, filename); - // CHECK(result.net_err == std::make_error_code(std::errc::not_connected)); - // } - // - // { - // coro_http_client client{}; - // client.set_req_timeout(10s); - // std::string uri = - // "http://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx"; - // std::string filename = "test.jpg"; - // - // std::error_code ec{}; - // std::filesystem::remove(filename, ec); - // - // inject_chunk_valid = ClientInjectAction::chunk_error; - // auto result = client.download(uri, filename); - // CHECK(result.status == 404); - // } + coro_http_client client{}; + client.write_failed_forever_ = true; + auto ret = client.get("http://baidu.com"); + CHECK(ret.status != 200); + client.write_failed_forever_ = false; + + client.connect_timeout_forever_ = true; + ret = async_simple::coro::syncAwait(client.connect("http://baidu.com")); + CHECK(ret.status != 200); + + client.add_str_part("hello", "world"); + ret = async_simple::coro::syncAwait( + client.async_upload_multipart("http://baidu.com")); + CHECK(ret.status != 200); + client.connect_timeout_forever_ = false; + + client.parse_failed_forever_ = true; + ret = async_simple::coro::syncAwait( + client.async_upload_multipart("http://baidu.com")); + CHECK(ret.status != 200); + client.parse_failed_forever_ = false; + + coro_http_server server(1, 8090); + server.set_http_handler( + "/", [](coro_http_request &, coro_http_response &res) mutable { + std::string str(1024, 'a'); + res.set_status_and_content(status_type::ok, std::move(str)); + }); + server.async_start(); + std::string uri = "http://127.0.0.1:8090"; { - coro_http_client client{}; - client.add_str_part("hello", "world"); - inject_write_failed = ClientInjectAction::write_failed; - auto result = async_simple::coro::syncAwait( - client.async_upload_multipart("https://www.bing.com")); - CHECK(result.status == 404); + coro_http_client client1{}; + client1.read_failed_forever_ = true; + ret = client1.get(uri); + CHECK(ret.status != 200); + + client1.close(); + std::string out; + out.resize(2024); + ret = async_simple::coro::syncAwait( + client1.async_request(uri, http_method::GET, req_context<>{}, {}, + std::span{out.data(), out.size()})); + CHECK(ret.status != 200); + client1.read_failed_forever_ = false; + } + + { + coro_http_client client1{}; + client1.add_str_part("hello", "test"); + client1.write_failed_forever_ = true; + client1.write_header_timeout_ = true; + ret = async_simple::coro::syncAwait( + client1.async_upload_multipart("http://baidu.com")); + CHECK(ret.status != 200); + client1.write_failed_forever_ = false; + client1.write_header_timeout_ = false; + } + + { + coro_http_client client1{}; + client1.add_str_part("hello", "test"); + client1.write_failed_forever_ = true; + client1.write_payload_timeout_ = true; + ret = async_simple::coro::syncAwait( + client1.async_upload_multipart("http://baidu.com")); + CHECK(ret.status != 200); + } + + { + coro_http_client client1{}; + client1.add_str_part("hello", "test"); + client1.read_failed_forever_ = true; + client1.read_timeout_ = true; + ret = async_simple::coro::syncAwait( + client1.async_upload_multipart("http://baidu.com")); + CHECK(ret.status != 200); + } + + { + coro_http_client client1{}; + client1.write_failed_forever_ = true; + ret = async_simple::coro::syncAwait(client1.connect("http://baidu.com")); + if (!ret.net_err) { + ret = async_simple::coro::syncAwait(client1.write_websocket("test")); + CHECK(ret.status != 200); + } } } #endif @@ -1702,6 +2229,16 @@ TEST_CASE("test coro http proxy request") { result = async_simple::coro::syncAwait(client.async_get(uri)); if (!result.net_err) CHECK(result.status >= 200); + + client.set_proxy("106.14.255.124", "80"); + uri = "http://www.baidu.com:443"; + result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(result.status != 200); + + client.set_proxy("106.14.255.124", "80"); + uri = "http://www.baidu.com:12345"; + result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(result.status != 200); } TEST_CASE("test coro http proxy request with port") { @@ -1740,13 +2277,13 @@ TEST_CASE("test coro http redirect request") { resp_data result = async_simple::coro::syncAwait(client.async_get(uri)); if (result.status != 404 && !result.net_err) { CHECK(!result.net_err); - if (result.status != 502) + if (result.status < 500) CHECK(result.status == 302); if (client.is_redirect(result)) { std::string redirect_uri = client.get_redirect_uri(); result = async_simple::coro::syncAwait(client.async_get(redirect_uri)); - if (result.status != 502 && result.status != 404) + if (result.status < 400) CHECK(result.status == 200); } diff --git a/tests/test_coro_http_server.cpp b/tests/test_coro_http_server.cpp index 6c49fc78..be6caaa2 100644 --- a/tests/test_coro_http_server.cpp +++ b/tests/test_coro_http_server.cpp @@ -639,6 +639,35 @@ TEST_CASE("use out context") { thd.join(); } +TEST_CASE("use metric") { + asio::io_context out_ctx; + auto work = std::make_unique(out_ctx); + std::thread thd([&] { + out_ctx.run(); + }); + + cinatra::coro_http_server server(out_ctx, "0.0.0.0:9007"); + server.set_no_delay(true); + auto addr = server.address(); + auto port = server.port(); + CHECK(addr == "0.0.0.0"); + CHECK(port == 9007); + server.use_metrics(); + server.async_start(); + + { + coro_http_client client1{}; + auto result = client1.get("http://127.0.0.1:9007/metrics"); + CHECK(result.status == 200); + CHECK(!result.resp_body.empty()); + } + + server.stop(); + + work.reset(); + thd.join(); +} + TEST_CASE("delay reply, server stop, form-urlencode, qureies, throw") { cinatra::coro_http_server server(1, 9001); @@ -913,7 +942,11 @@ TEST_CASE("test websocket") { auto lazy = []() -> async_simple::coro::Lazy { coro_http_client client{}; - co_await client.connect("ws://127.0.0.1:9001/ws_echo"); + auto ret = co_await client.connect("ws://127.0.0.1:9001/ws_echo"); + if (ret.status != 101) { + std::cout << ret.net_err.message() << "\n"; + } + CHECK(ret.status == 101); co_await client.write_websocket(std::string_view("test2fdsaf"), opcode::binary); auto data = co_await client.read_websocket(); @@ -1078,7 +1111,7 @@ TEST_CASE("check connecton timeout") { } TEST_CASE("test websocket with different message size") { - cinatra::coro_http_server server(1, 9001); + cinatra::coro_http_server server(1, 9003); server.set_http_handler( "/ws_echo1", [](cinatra::coro_http_request &req, @@ -1107,11 +1140,17 @@ TEST_CASE("test websocket with different message size") { }); server.async_start(); - auto lazy = [](std::string &str) -> async_simple::coro::Lazy { + auto lazy = [](std::string str) -> async_simple::coro::Lazy { coro_http_client client{}; - co_await client.connect("ws://127.0.0.1:9001/ws_echo1"); + auto ret = co_await client.connect("ws://127.0.0.1:9003/ws_echo1"); + if (ret.status != 101) { + std::cout << ret.net_err.message() << "\n"; + } + + CHECK(ret.status == 101); co_await client.write_websocket(str); auto data = co_await client.read_websocket(); + CHECK(data.status == 200); CHECK(data.resp_body.size() == str.size()); co_await client.write_websocket_close(); data = co_await client.read_websocket(); @@ -1134,14 +1173,15 @@ TEST_CASE("test websocket with different message size") { } server.stop(); + std::cout << "server stop" << std::endl; } #ifdef CINATRA_ENABLE_SSL TEST_CASE("test ssl server") { cinatra::coro_http_server server(1, 9001); - - server.init_ssl("../../include/cinatra/server.crt", - "../../include/cinatra/server.key", "test"); + std::cout << std::filesystem::current_path() << "\n"; + server.init_ssl("../openssl_files/server.crt", "../openssl_files/server.key", + "test"); server.set_http_handler( "/ssl", [](coro_http_request &req, coro_http_response &resp) { resp.set_status_and_content(status_type::ok, "ssl"); @@ -1151,8 +1191,7 @@ TEST_CASE("test ssl server") { std::this_thread::sleep_for(200ms); coro_http_client client{}; - [[maybe_unused]] auto r = client.init_ssl(asio::ssl::verify_peer, - "../../include/cinatra/server.crt"); + [[maybe_unused]] auto r = client.init_ssl(); auto result = client.get("https://127.0.0.1:9001/ssl"); CHECK(result.status == 200);