From 80562e1c244b284ebfdf517503ca3089d4da4b92 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Wed, 17 Jan 2024 16:44:31 +0800 Subject: [PATCH] add unit tests and remove some code (#496) --- example/main.cpp | 12 +++++ include/cinatra/coro_http_client.hpp | 6 ++- include/cinatra/coro_http_connection.hpp | 12 +++-- include/cinatra/define.h | 10 ---- include/cinatra/url_encode_decode.hpp | 14 ----- include/cinatra/utils.hpp | 59 ++------------------ include/cinatra/websocket.hpp | 47 ---------------- tests/CMakeLists.txt | 5 +- tests/test_coro_http_server.cpp | 68 +++++++++++++++++------- 9 files changed, 77 insertions(+), 156 deletions(-) diff --git a/example/main.cpp b/example/main.cpp index eab0f4ce..e3ab4589 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -174,6 +174,18 @@ async_simple::coro::Lazy use_websocket() { std::cout << result.data << "\n"; } + if (result.type == ws_frame_type::WS_PING_FRAME || + result.type == ws_frame_type::WS_PONG_FRAME) { + // ping pong frame just need to continue, no need echo anything, + // because framework has reply ping/pong msg to client + // automatically. + continue; + } + else { + // error frame + break; + } + auto ec = co_await req.get_conn()->write_websocket(result.data); if (ec) { break; diff --git a/include/cinatra/coro_http_client.hpp b/include/cinatra/coro_http_client.hpp index 8e0795a9..3d8b46b2 100644 --- a/include/cinatra/coro_http_client.hpp +++ b/include/cinatra/coro_http_client.hpp @@ -1828,8 +1828,10 @@ class coro_http_client : public std::enable_shared_from_this { data_ptr = asio::buffer_cast(read_buf.data()); if (is_close_frame) { - payload_len -= 2; - data_ptr += sizeof(uint16_t); + if (payload_len >= 2) { + payload_len -= 2; + data_ptr += sizeof(uint16_t); + } } data.status = 200; diff --git a/include/cinatra/coro_http_connection.hpp b/include/cinatra/coro_http_connection.hpp index 87aacfed..8c47cdbb 100644 --- a/include/cinatra/coro_http_connection.hpp +++ b/include/cinatra/coro_http_connection.hpp @@ -541,6 +541,7 @@ class coro_http_connection case cinatra::ws_frame_type::WS_CLOSE_FRAME: { close_frame close_frame = ws_.parse_close_payload(payload.data(), payload.size()); + result.eof = true; result.data = {close_frame.message, close_frame.length}; std::string close_msg = ws_.format_close_payload( @@ -551,16 +552,16 @@ class coro_http_connection close(); } break; case cinatra::ws_frame_type::WS_PING_FRAME: { - auto ec = co_await write_websocket({payload.data(), payload.size()}, - opcode::pong); + result.data = {payload.data(), payload.size()}; + auto ec = co_await write_websocket("pong", opcode::pong); if (ec) { close(); result.ec = ec; } } break; case cinatra::ws_frame_type::WS_PONG_FRAME: { + result.data = {payload.data(), payload.size()}; auto ec = co_await write_websocket("ping", opcode::ping); - close(); result.ec = ec; } break; default: @@ -599,7 +600,10 @@ class coro_http_connection void set_ws_max_size(uint64_t max_size) { max_part_size_ = max_size; } - void set_shrink_to_fit(bool r) { need_shrink_every_time_ = r; } + void set_shrink_to_fit(bool r) { + need_shrink_every_time_ = r; + response_.set_shrink_to_fit(r); + } template async_simple::coro::Lazy> async_read( diff --git a/include/cinatra/define.h b/include/cinatra/define.h index bca137af..46907cf9 100644 --- a/include/cinatra/define.h +++ b/include/cinatra/define.h @@ -35,34 +35,24 @@ inline constexpr std::string_view method_name(http_method mthd) { switch (mthd) { case cinatra::http_method::DEL: return "DELETE"sv; - break; case cinatra::http_method::GET: return "GET"sv; - break; case cinatra::http_method::HEAD: return "HEAD"sv; - break; case cinatra::http_method::POST: return "POST"sv; - break; case cinatra::http_method::PUT: return "PUT"sv; - break; case cinatra::http_method::PATCH: return "PATCH"sv; - break; case cinatra::http_method::CONNECT: return "CONNECT"sv; - break; case cinatra::http_method::OPTIONS: return "OPTIONS"sv; - break; case cinatra::http_method::TRACE: return "TRACE"sv; - break; default: return "UNKONWN"sv; - break; } } diff --git a/include/cinatra/url_encode_decode.hpp b/include/cinatra/url_encode_decode.hpp index 75efacef..b39a558d 100644 --- a/include/cinatra/url_encode_decode.hpp +++ b/include/cinatra/url_encode_decode.hpp @@ -115,23 +115,9 @@ inline size_t base64_encode(char *_dst, const void *_src, size_t len, return dst - _dst; } -inline static std::string u8wstring_to_string(const std::wstring &wstr) { - std::wstring_convert> conv; - return conv.to_bytes(wstr); -} - -inline static std::wstring u8string_to_wstring(const std::string &str) { - std::wstring_convert> conv; - return conv.from_bytes(str); -} - inline static std::string get_string_by_urldecode(std::string_view content) { return url_decode(std::string(content.data(), content.size())); } -inline static bool is_url_encode(std::string_view str) { - return str.find("%") != std::string_view::npos || - str.find("+") != std::string_view::npos; -} } // namespace code_utils #endif // CPPWEBSERVER_URL_ENCODE_DECODE_HPP \ No newline at end of file diff --git a/include/cinatra/utils.hpp b/include/cinatra/utils.hpp index 8e7bdd83..4daf2ea9 100644 --- a/include/cinatra/utils.hpp +++ b/include/cinatra/utils.hpp @@ -34,12 +34,6 @@ struct ci_less { } }; - bool operator()(const std::string &s1, const std::string &s2) const { - return std::lexicographical_compare(s1.begin(), s1.end(), // source range - s2.begin(), s2.end(), // dest range - nocase_compare()); // comparison - } - bool operator()(std::string_view s1, std::string_view s2) const { return std::lexicographical_compare(s1.begin(), s1.end(), // source range s2.begin(), s2.end(), // dest range @@ -173,10 +167,6 @@ static const std::string base64_chars = "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; -static inline bool is_base64(char c) { - return (isalnum(c) || (c == '+') || (c == '/')); -} - inline std::string base64_encode(const std::string &str) { std::string ret; int i = 0; @@ -220,51 +210,10 @@ inline std::string base64_encode(const std::string &str) { } // from h2o -inline const char *MAP = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - -inline const char *MAP_URL_ENCODED = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789-_"; -inline size_t base64_encode(char *_dst, const void *_src, size_t len, - int url_encoded) { - char *dst = _dst; - const uint8_t *src = reinterpret_cast(_src); - const char *map = url_encoded ? MAP_URL_ENCODED : MAP; - uint32_t quad; - - for (; len >= 3; src += 3, len -= 3) { - quad = ((uint32_t)src[0] << 16) | ((uint32_t)src[1] << 8) | src[2]; - *dst++ = map[quad >> 18]; - *dst++ = map[(quad >> 12) & 63]; - *dst++ = map[(quad >> 6) & 63]; - *dst++ = map[quad & 63]; - } - if (len != 0) { - quad = (uint32_t)src[0] << 16; - *dst++ = map[quad >> 18]; - if (len == 2) { - quad |= (uint32_t)src[1] << 8; - *dst++ = map[(quad >> 12) & 63]; - *dst++ = map[(quad >> 6) & 63]; - if (!url_encoded) - *dst++ = '='; - } - else { - *dst++ = map[(quad >> 12) & 63]; - if (!url_encoded) { - *dst++ = '='; - *dst++ = '='; - } - } - } - - *dst = '\0'; - return dst - _dst; -} +// inline const char *MAP = +// "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +// "abcdefghijklmnopqrstuvwxyz" +// "0123456789+/"; inline bool is_valid_utf8(unsigned char *s, size_t length) { for (unsigned char *e = s + length; s != e;) { diff --git a/include/cinatra/websocket.hpp b/include/cinatra/websocket.hpp index bb0509e2..8327ef2e 100644 --- a/include/cinatra/websocket.hpp +++ b/include/cinatra/websocket.hpp @@ -121,58 +121,11 @@ class websocket { return ws_frame_type::WS_BINARY_FRAME; } - ws_frame_type parse_payload(const char *buf, size_t size, - std::string &outbuf) { - const unsigned char *inp = (const unsigned char *)(buf); - if (payload_length_ > size) - return ws_frame_type::WS_INCOMPLETE_FRAME; - - if (payload_length_ > outbuf.size()) { - outbuf.resize((size_t)payload_length_); - } - - if (*(uint32_t *)mask_ == 0) { - memcpy(&outbuf[0], (void *)(inp), payload_length_); - } - else { - // unmask data: - for (size_t i = 0; i < payload_length_; i++) { - outbuf[i] = inp[i] ^ mask_[i % 4]; - } - } - - if (msg_opcode_ == 0x0) - return (msg_fin_) - ? ws_frame_type::WS_TEXT_FRAME - : ws_frame_type::WS_INCOMPLETE_TEXT_FRAME; // continuation - // frame ? - if (msg_opcode_ == 0x1) - return (msg_fin_) ? ws_frame_type::WS_TEXT_FRAME - : ws_frame_type::WS_INCOMPLETE_TEXT_FRAME; - if (msg_opcode_ == 0x2) - return (msg_fin_) ? ws_frame_type::WS_BINARY_FRAME - : ws_frame_type::WS_INCOMPLETE_BINARY_FRAME; - if (msg_opcode_ == 0x8) - return ws_frame_type::WS_CLOSE_FRAME; - if (msg_opcode_ == 0x9) - return ws_frame_type::WS_PING_FRAME; - if (msg_opcode_ == 0xA) - return ws_frame_type::WS_PONG_FRAME; - return ws_frame_type::WS_BINARY_FRAME; - } - std::string format_header(size_t length, opcode code) { size_t header_length = encode_header(length, code); return {msg_header_, header_length}; } - std::vector format_message(const char *src, size_t length, - opcode code) { - size_t header_length = encode_header(length, code); - return {asio::buffer(msg_header_, header_length), - asio::buffer(src, length)}; - } - std::string encode_frame(std::span &data, opcode op, bool need_mask, bool eof = true) { std::string header; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7597d28a..9334cfcd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) set(project_name test_cinatra) add_executable(${project_name} + test_coro_http_server.cpp test_cinatra.cpp test_cinatra_websocket.cpp test_cmdline.cpp @@ -58,9 +59,6 @@ add_executable(test_time_util ) add_test(NAME test_time_util COMMAND test_time_util) -add_executable(test_coro_http_server test_coro_http_server.cpp) -add_test(NAME test_coro_http_server COMMAND test_coro_http_server) - option(CINATRA_ENABLE_SSL "Enable ssl support" OFF) if (CINATRA_ENABLE_SSL) message(STATUS "Use SSL") @@ -68,7 +66,6 @@ if (CINATRA_ENABLE_SSL) add_definitions(-DCINATRA_ENABLE_SSL) target_link_libraries(test_cinatra OpenSSL::SSL OpenSSL::Crypto) target_link_libraries(test_corofile PRIVATE OpenSSL::SSL OpenSSL::Crypto) - target_link_libraries(test_coro_http_server OpenSSL::SSL OpenSSL::Crypto) endif () add_executable(test_http_parse diff --git a/tests/test_coro_http_server.cpp b/tests/test_coro_http_server.cpp index 0a5647e9..d02a4e43 100644 --- a/tests/test_coro_http_server.cpp +++ b/tests/test_coro_http_server.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -12,17 +13,14 @@ #include "async_simple/coro/Lazy.h" #include "async_simple/coro/SyncAwait.h" +#include "cinatra/coro_http_client.hpp" #include "cinatra/coro_http_connection.hpp" +#include "cinatra/coro_http_server.hpp" #include "cinatra/define.h" #include "cinatra/response_cv.hpp" #include "cinatra/utils.hpp" #include "cinatra/ylt/coro_io/coro_io.hpp" #include "cinatra/ylt/coro_io/io_context_pool.hpp" -#define DOCTEST_CONFIG_IMPLEMENT -#include - -#include "cinatra/coro_http_client.hpp" -#include "cinatra/coro_http_server.hpp" #include "doctest/doctest.h" using namespace cinatra; @@ -43,6 +41,10 @@ TEST_CASE("test parse ranges") { CHECK(!is_valid); CHECK(vec.empty()); + vec = parse_ranges("--100", 10000, is_valid); + CHECK(!is_valid); + CHECK(vec.empty()); + vec = parse_ranges("abc", 10000, is_valid); CHECK(!is_valid); CHECK(vec.empty()); @@ -181,7 +183,7 @@ TEST_CASE("test range download") { #ifdef ASIO_WINDOWS #else create_file("中文测试.txt", 64); - create_file(fs::u8path("utf8中文.txt").string(), 64); + create_file(fs::path(u8"utf8中文.txt").string(), 64); #endif std::cout << fs::current_path() << "\n"; coro_http_server server(1, 9001); @@ -209,7 +211,7 @@ TEST_CASE("test range download") { std::string local_filename = "temp1.txt"; std::string base_uri = "http://127.0.0.1:9001/"; std::string path = - code_utils::url_encode(fs::u8path("utf8中文.txt").string()); + code_utils::url_encode(fs::path(u8"utf8中文.txt").string()); auto result = client.download(base_uri + path, local_filename); CHECK(result.status == 200); CHECK(fs::file_size(local_filename) == 64); @@ -270,7 +272,6 @@ class my_object { TEST_CASE("set http handler") { cinatra::coro_http_server server(1, 9001); - auto &router = server.get_router(); auto &handlers = router.get_handlers(); @@ -360,6 +361,7 @@ TEST_CASE("test server sync_start and stop") { TEST_CASE("get post") { cinatra::coro_http_server server(1, 9001); + server.set_shrink_to_fit(true); server.set_http_handler( "/test", [](coro_http_request &req, coro_http_response &resp) { auto value = req.get_header_value("connection"); @@ -480,7 +482,11 @@ struct check_t : public base_aspect { TEST_CASE("test aspects") { coro_http_server server(1, 9001); - create_file("test_aspect.txt", 64); + server.set_static_res_dir("", ""); + server.set_max_size_of_cache_files(100); + create_file("test_aspect.txt", 64); // in cache + create_file("test_file.txt", 200); // not in cache + std::vector> aspects = { std::make_shared(), std::make_shared()}; server.set_static_res_dir("", "", aspects); @@ -524,6 +530,9 @@ TEST_CASE("test aspects") { result = client.get("http://127.0.0.1:9001/test_aspect.txt"); CHECK(result.status == 200); + + result = client.get("http://127.0.0.1:9001/test_file.txt"); + CHECK(result.status == 200); } TEST_CASE("use out context") { @@ -573,11 +582,14 @@ TEST_CASE("delay reply, server stop, form-urlencode, qureies, throw") { CHECK(req.get_body() == "theCityName=58367&aa=%22bbb%22"); CHECK(req.get_query_value("theCityName") == "58367"); CHECK(req.get_decode_query_value("aa") == "\"bbb\""); + CHECK(req.get_decode_query_value("no_such-key").empty()); + CHECK(!req.is_upgrade()); resp.set_status_and_content(status_type::ok, "form-urlencode"); }); server.set_http_handler( "/throw", [](coro_http_request &req, coro_http_response &resp) { + CHECK(req.get_boundary().empty()); throw std::invalid_argument("invalid arguments"); resp.set_status_and_content(status_type::ok, "ok"); }); @@ -795,19 +807,30 @@ TEST_CASE("test websocket") { if (result.type == ws_frame_type::WS_CLOSE_FRAME) { std::cout << "close frame\n"; - CHECK(result.data.empty()); out_file.close(); break; } - if (result.type == ws_frame_type::WS_TEXT_FRAME) { + if (result.type == ws_frame_type::WS_TEXT_FRAME || + result.type == ws_frame_type::WS_BINARY_FRAME) { CHECK(!result.data.empty()); std::cout << result.data << "\n"; - } - - if (result.type == ws_frame_type::WS_BINARY_FRAME) { out_file << result.data; } + else { + std::cout << result.data << "\n"; + if (result.type == ws_frame_type::WS_PING_FRAME || + result.type == ws_frame_type::WS_PONG_FRAME) { + std::cout << "ping or pong msg\n"; + // ping pong frame just need to continue, no need echo anything, + // because framework has reply ping/pong to client automatically. + continue; + } + else { + // error frame + break; + } + } auto ec = co_await req.get_conn()->write_websocket(result.data); if (ec) { @@ -818,6 +841,9 @@ TEST_CASE("test websocket") { server.async_start(); auto client = std::make_shared(); + client->on_ws_close([](std::string_view reason) { + std::cout << "normal close, reason: " << reason << "\n"; + }); client->on_ws_msg([](resp_data data) { if (data.net_err) { std::cout << "ws_msg net error " << data.net_err.message() << "\n"; @@ -826,6 +852,7 @@ TEST_CASE("test websocket") { std::cout << "ws msg len: " << data.resp_body.size() << std::endl; CHECK(!data.resp_body.empty()); + std::cout << "recieve msg from server: " << data.resp_body << "\n"; }); async_simple::coro::syncAwait( @@ -833,8 +860,13 @@ TEST_CASE("test websocket") { async_simple::coro::syncAwait( client->async_send_ws("test2fdsaf", true, opcode::binary)); async_simple::coro::syncAwait(client->async_send_ws("test_ws")); + async_simple::coro::syncAwait( + client->async_send_ws("PING", false, opcode::ping)); + async_simple::coro::syncAwait( + client->async_send_ws("PONG", false, opcode::pong)); - async_simple::coro::syncAwait(client->async_send_ws_close()); + async_simple::coro::syncAwait(client->async_send_ws_close("normal close")); + std::this_thread::sleep_for(300ms); // wait for server handle all messages } TEST_CASE("check small ws file") { @@ -852,7 +884,7 @@ TEST_CASE("check small ws file") { str.resize(file_size); file.read(str.data(), str.size()); - CHECK(str == "test2fdsaf"); + CHECK(str == "test2fdsaftest_ws"); std::filesystem::remove(filename, ec); } @@ -1344,7 +1376,3 @@ TEST_CASE("test coro radix tree restful api") { client.get("http://127.0.0.1:9001/user/ultramarines/subscriptions/guilliman"); client.get("http://127.0.0.1:9001/value/guilliman/cawl/yvraine"); } - -DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) -int main(int argc, char **argv) { return doctest::Context(argc, argv).run(); } -DOCTEST_MSVC_SUPPRESS_WARNING_POP \ No newline at end of file