diff --git a/.github/workflows/linux_llvm_cov.yml b/.github/workflows/linux_llvm_cov.yml index b76716e9df..c012e1d48f 100644 --- a/.github/workflows/linux_llvm_cov.yml +++ b/.github/workflows/linux_llvm_cov.yml @@ -47,8 +47,9 @@ jobs: ./metric_test ./struct_pb_test ./reflection_test + ./coro_http_test llvm-profdata merge -sparse test_ylt-*.profraw -o test_ylt.profdata - llvm-cov show -object coro_io_test -object coro_rpc_test -object easylog_test -object struct_pack_test -object struct_pack_test_with_optimize -object metric_test -object struct_pb_test -object reflection_test -instr-profile=test_ylt.profdata -format=html -output-dir=../../.coverage_llvm_cov -ignore-filename-regex="thirdparty|src|template_switch" -show-instantiations=false + llvm-cov show -object coro_io_test -object coro_rpc_test -object easylog_test -object struct_pack_test -object struct_pack_test_with_optimize -object metric_test -object struct_pb_test -object reflection_test -object coro_http_test -instr-profile=test_ylt.profdata -format=html -output-dir=../../.coverage_llvm_cov -ignore-filename-regex="thirdparty|src|template_switch" -show-instantiations=false echo "Done!" - name: Upload Coverage Results @@ -63,7 +64,7 @@ jobs: echo "Code Coverage Report" > tmp.log echo "for detail, [goto summary](https://github.com/${{ github.repository_owner }}/${{ github.event.repository.name }}/actions/runs/${{github.run_id}}) download Artifacts `llvm-cov`" >> tmp.log echo "\`\`\`" >> tmp.log - llvm-cov report -object coro_io_test -object coro_rpc_test -object easylog_test -object struct_pack_test -object struct_pack_test_with_optimize -object metric_test -object struct_pb_test -object reflection_test -instr-profile=test_ylt.profdata -ignore-filename-regex="thirdparty|src|template_switch" -show-region-summary=false >> tmp.log + llvm-cov report -object coro_io_test -object coro_rpc_test -object easylog_test -object struct_pack_test -object struct_pack_test_with_optimize -object metric_test -object struct_pb_test -object reflection_test -object coro_http_test -instr-profile=test_ylt.profdata -ignore-filename-regex="thirdparty|src|template_switch" -show-region-summary=false >> tmp.log echo "\`\`\`" >> tmp.log - name: Create Comment diff --git a/include/ylt/coro_http/coro_http_client.hpp b/include/ylt/coro_http/coro_http_client.hpp index e6fc026fd9..d9ceee2caf 100644 --- a/include/ylt/coro_http/coro_http_client.hpp +++ b/include/ylt/coro_http/coro_http_client.hpp @@ -15,14 +15,26 @@ */ #pragma once #ifdef YLT_ENABLE_SSL +#ifndef CINATRA_ENABLE_SSL #define CINATRA_ENABLE_SSL #endif +#endif #include +#ifndef CINATRA_LOG_ERROR #define CINATRA_LOG_ERROR ELOG_ERROR +#endif +#ifndef CINATRA_LOG_WARNING #define CINATRA_LOG_WARNING ELOG_WARN +#endif +#ifndef CINATRA_LOG_INFO #define CINATRA_LOG_INFO ELOG_INFO +#endif +#ifndef CINATRA_LOG_DEBUG #define CINATRA_LOG_DEBUG ELOG_DEBUG +#endif +#ifndef CINATRA_LOG_TRACE #define CINATRA_LOG_TRACE ELOG_TRACE +#endif #include diff --git a/include/ylt/coro_http/coro_http_server.hpp b/include/ylt/coro_http/coro_http_server.hpp index a4bb1ecf49..16ee9059f1 100644 --- a/include/ylt/coro_http/coro_http_server.hpp +++ b/include/ylt/coro_http/coro_http_server.hpp @@ -15,14 +15,26 @@ */ #pragma once #ifdef YLT_ENABLE_SSL +#ifndef CINATRA_ENABLE_SSL #define CINATRA_ENABLE_SSL #endif +#endif #include +#ifndef CINATRA_LOG_ERROR #define CINATRA_LOG_ERROR ELOG_ERROR +#endif +#ifndef CINATRA_LOG_WARNING #define CINATRA_LOG_WARNING ELOG_WARN +#endif +#ifndef CINATRA_LOG_INFO #define CINATRA_LOG_INFO ELOG_INFO +#endif +#ifndef CINATRA_LOG_DEBUG #define CINATRA_LOG_DEBUG ELOG_DEBUG +#endif +#ifndef CINATRA_LOG_TRACE #define CINATRA_LOG_TRACE ELOG_TRACE +#endif #include diff --git a/include/ylt/coro_io/coro_io.hpp b/include/ylt/coro_io/coro_io.hpp index 2f4b7900c8..3992116ca0 100644 --- a/include/ylt/coro_io/coro_io.hpp +++ b/include/ylt/coro_io/coro_io.hpp @@ -379,7 +379,7 @@ struct channel : public asio::experimental::channel { }; template -inline channel create_load_blancer( +inline channel create_channel( size_t capacity, coro_io::ExecutorWrapper<> *executor = coro_io::get_global_executor()) { return channel(executor, capacity); diff --git a/src/coro_http/examples/example.cpp b/src/coro_http/examples/example.cpp index 1256feed73..f4deb8321c 100644 --- a/src/coro_http/examples/example.cpp +++ b/src/coro_http/examples/example.cpp @@ -629,8 +629,8 @@ void http_proxy() { assert(!resp_random.resp_body.empty()); } -void coro_load_blancer() { - auto ch = coro_io::create_load_blancer(10000); +void coro_channel() { + auto ch = coro_io::create_channel(10000); auto ec = async_simple::coro::syncAwait(coro_io::async_send(ch, 41)); assert(!ec); ec = async_simple::coro::syncAwait(coro_io::async_send(ch, 42)); @@ -661,6 +661,6 @@ int main() { test_gzip(); #endif http_proxy(); - coro_load_blancer(); + coro_channel(); return 0; } \ No newline at end of file diff --git a/src/coro_http/tests/CMakeLists.txt b/src/coro_http/tests/CMakeLists.txt new file mode 100644 index 0000000000..eadb6cd4fe --- /dev/null +++ b/src/coro_http/tests/CMakeLists.txt @@ -0,0 +1,46 @@ +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/tests) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +find_package(Threads REQUIRED) +link_libraries(Threads::Threads) + +include_directories(include) +include_directories(include/ylt/thirdparty) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines") + #-ftree-slp-vectorize with coroutine cause link error. disable it util gcc fix. + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-tree-slp-vectorize") +endif() + +add_executable(coro_http_test + test_coro_http_server.cpp + test_cinatra.cpp + test_cinatra_websocket.cpp + test_http_parse.cpp + main.cpp + ) + +add_custom_command( + TARGET coro_http_test POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/openssl_files + ${CMAKE_BINARY_DIR}/src/coro_http/openssl_files) +add_custom_command( + TARGET coro_http_test POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/openssl_files + ${CMAKE_BINARY_DIR}/output/openssl_files) + +add_test(NAME coro_http_test COMMAND coro_http_test) +# target_compile_definitions(easylog_test PRIVATE STRUCT_PACK_ENABLE_UNPORTABLE_TYPE) +if (YLT_ENABLE_SSL) + message(STATUS "Use SSL") + find_package(OpenSSL REQUIRED) + add_definitions(-DCINATRA_ENABLE_SSL) + target_link_libraries(coro_http_test OpenSSL::SSL OpenSSL::Crypto) +endif () diff --git a/src/coro_http/tests/main.cpp b/src/coro_http/tests/main.cpp new file mode 100644 index 0000000000..aca0a714de --- /dev/null +++ b/src/coro_http/tests/main.cpp @@ -0,0 +1,7 @@ +#define DOCTEST_CONFIG_IMPLEMENT + +#include "doctest.h" + +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 diff --git a/src/coro_http/tests/openssl_files/server.crt b/src/coro_http/tests/openssl_files/server.crt new file mode 100644 index 0000000000..aca31a7e3c --- /dev/null +++ b/src/coro_http/tests/openssl_files/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDKDCCAhACCQDHu0UVVUEr4DANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBh +bnkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjIxMDI1MDM1NzMwWhcNMzIx +MDIyMDM1NzMwWjBWMQswCQYDVQQGEwJDTjEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5 +MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRIwEAYDVQQDDAlsb2NhbGhv +c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCr6iWgRRYJ9QfKSUPT +nbw2rKZRlSBqnLeLdPam+s8RUA1p+YPoH2HJqIdxcfYmToz5t6G5OX8TFhAssShw +PalRlQm5QHp4pL7nqPV79auB3PYKv6TgOumwDUpoBxcu0l9di9fjYbC2LmpVJeVz +WQxCo+XO/g5YjXN1nPPeBgmZVkRvXLIYCTKshLlUa0nW7hj7Sl8CAV8OBNMBFkf1 +2vgcTqhs3yW9gnIwIoCFZvsdAsSbwR6zF1z96MeAYDIZWeyzUXkoZa4OCWwAhqzo ++0JWukuNuHhsQhIJDvIZWHEblT0GlentP8HPXjFnJHYGUAjx3Fj1mH8mFG0fEXXN +06qlAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGbKTy1mfSlJF012jKuIue2valI2 +CKz8X619jmxxIzk0k7wcmAlUUrUSFIzdIddZj92wYbBC1YNOWQ4AG5zpFo3NAQaZ +kYGnlt+d2pNHLaT4IV9JM4iwTqPyi+FsOwTjUGHgaOr+tfK8fZmPbDmAE46OlC/a +VVqNPmjaJiM2c/pJOs+HV9PvEOFmV9p5Yjjz4eV3jwqHdOcxZuLJl28/oqz65uCu +LQiivkdVCuwc1IlpRFejkrbkrk28XCCJwokLt03EQj4xs0sjoTKgd92fpjls/tt+ +rw+7ILsAsuoWPIdiuCArCU1LXJDz3FDHafX/dxzdVBzpfVgP0rNpS050Mls= +-----END CERTIFICATE----- diff --git a/src/coro_http/tests/openssl_files/server.key b/src/coro_http/tests/openssl_files/server.key new file mode 100644 index 0000000000..9aef5f5755 --- /dev/null +++ b/src/coro_http/tests/openssl_files/server.key @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,D920B8941C56ADDC + +I2lW3QsAG/xubjtXpXh3wQ5Ru3VZiMkPNjc+G6/2JjjVr1sD+fzCWvvwdqdxGuNJ +gKdpPBHLuQfTTzGETE4NKDkYzmiPTVbZPJ77DyfL2cK1dcZtAY46RsHf+VMI5N8l +Be1jQSB5xvUa88dSIeowPTc2XSnTIoSFWCa38XuqYF7i0a3lv96eAyXpqB7Tm2r8 +SoYlm0n7/uzRpk6HWST65qnVv/j+37LuvSy6ehyh44+KDS4x9FUOZc5xwJ/37Jnl +SDC10+9zLc+jOTk6XgUuBSmG+xfZdcOrbknQ1Xj1YtseYH0plYAEWi4PsnMQkHzC +GGvK08Lgqxd7cGEKFh2MRZ/TEwriN5ud5HGm4yIHIj45rbedtRSQwl2EyHdWeW0J +rFltDy+SXnnkJaOcnBYXUD1jEwyy2lLamWRiu83VFbCv6yhOYuR6JejM6dctjgZ+ +Qf0PzH6L1bVpHKEl/GLByJ6GWYrQJqw83LAXlR+NNCC3nN7WAAaTuzA9LpgW9Vk0 +khRRs7rJGxwwwE4TfG9FbQxwuOsjKV9pRohB1x1nFMMm5IJ9SON2KjizsVdLbt7t +Gb/5M7RcSnnGvIWWXalXpFGKgciwYd8F1v0TJ+FMooZxgUp7Pmp5YKIHkBjMrnnW +rKuoxmA5oPgSNUtr4ddMJ1sTIQPhqI27+CrySTzWKH1ls45okBvsiCejpcJwfrZW +KLSkz/FsPoWm44uomBSDOikry8axrKQLB9tOVPKCx/z0VP060P9N81mu4h67bixr +xu+odIONqGhRZT/BYHL2NjDfWlFmTJQy8Drn1a7IEhp8FV7l2aY/hisrMN7MQVza +FGB0hMbVHGeFOCD9QNQwRU2wLtwpE7LT/lGNmKadQadXxeAqOWBckXrpwnrxZDEP +a8AYr2J55h/IE4Oi2DyibSEZdB+7334OJHMmr14q53eIpeit19BYVhWyu9AtORJp +As61C7s82AO+E5gOswsq05jwWV/GIIkgZ8/vswEffiihmDEf6AUZsVGW3BlpFlyU +i3g4e8HFTJ+s9Z3sTgZ1EWOP6Wd2OzyQYVA4ggBR/g/IC9s5em1wvAkVwIZaPvj7 +21BIQXyiGrw52T+vTUrAUG0l7yoHGCgVYJ+aEm+f103AiBYuReUbo39GEIY2GHLu +r3oUehtt4of0ootmPCmjrRUyY6LPeD+d+i1jJUSYFKezsVRpaiF5+J8YLGMcOPiI +8qRRNgXDMMvttwyhoxyr5+667OMv+XWr2VQj7i9MWCFwTMwNzdUoZI3PWDhXbXDO +lQJS6v3iAPw+KvLJywODe+C4shUqYdrRdUSKE0FfuB8Ajzh86+FmjJcZM+BSxM4J +hC2yjv114jDlsgjFSxQE2K1iotLUY9mfmW8QWVMO3L4LlNpr4ypNLYX0Ph2wgqzQ +kszXTFN11RFKFLUhF0Mi5m4ffMLPD5YyoqO9grpyC1Nt7vxaPPvcvPD86jK3ksqJ +MwucZGgm9HtUuAjGOSljUr0d+d+4pySJbcpH2YDIBHGVsCScYPVg8XZ1CYko3mq/ +d6jDUgydraEmQvIPiKMpTE18rW+jierv2FlB8AGcwxm2VWxuM25wQ40J2YuZLY7k +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/src/coro_http/tests/test_cinatra.cpp b/src/coro_http/tests/test_cinatra.cpp new file mode 100644 index 0000000000..859af4cf6e --- /dev/null +++ b/src/coro_http/tests/test_cinatra.cpp @@ -0,0 +1,2334 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "doctest.h" +#include "ylt/coro_http/coro_http_client.hpp" +#include "ylt/coro_http/coro_http_server.hpp" + +using namespace std::chrono_literals; + +using namespace coro_http; + +#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) + return v; + } + return {}; +} + +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"); + res.set_status_and_content(status_type::ok, "hello world", + content_encoding::gzip); + }); + 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"); + server.stop(); +} + +TEST_CASE("test encoding type") { + coro_http_server server(1, 9001); + + server.set_http_handler("/get", [](coro_http_request &req, + coro_http_response &resp) { + auto encoding_type = req.get_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); + CHECK(decode_str == "Hello World"); + } + resp.set_status_and_content(status_type::ok, "ok", content_encoding::gzip, + req.get_accept_encoding()); + CHECK(resp.content() != "ok"); + }); + + server.set_http_handler( + "/coro", + [](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()); + CHECK(resp.content() != "ok"); + co_return; + }); + + server.set_http_handler( + "/only_gzip", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_status_and_content(status_type::ok, "ok", + content_encoding::gzip, + 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)); + + coro_http_client client1{}; + client1.add_header("Accept-Encoding", "gzip, deflate"); + auto result = async_simple::coro::syncAwait( + client1.async_get("http://127.0.0.1:9001/get")); + CHECK(result.resp_body == "ok"); + + coro_http_client client2{}; + client2.add_header("Accept-Encoding", "gzip, deflate"); + result = async_simple::coro::syncAwait( + client2.async_get("http://127.0.0.1:9001/coro")); + CHECK(result.resp_body == "ok"); + + coro_http_client client3{}; + std::unordered_map headers = { + {"Content-Encoding", "gzip"}, + }; + std::string ziped_str; + std::string_view data = "Hello World"; + gzip_codec::compress(data, ziped_str); + result = async_simple::coro::syncAwait(client3.async_post( + "http://127.0.0.1:9001/get", ziped_str, req_content_type::none, headers)); + CHECK(result.resp_body == "ok"); + + coro_http_client client4{}; + client4.add_header("Accept-Encoding", "deflate"); + result = async_simple::coro::syncAwait( + client4.async_get("http://127.0.0.1:9001/only_gzip")); + CHECK(result.resp_body == "ok"); + + server.stop(); +} +#endif + +#ifdef CINATRA_ENABLE_BROTLI +TEST_CASE("test brotli type") { + coro_http_server server(1, 9001); + + server.set_http_handler( + "/get", [](coro_http_request &req, coro_http_response &resp) { + auto encoding_type = req.get_encoding_type(); + + if (encoding_type == content_encoding::br) { + std::string decode_str; + bool r = br_codec::brotli_decompress(req.get_body(), decode_str); + CHECK(decode_str == "Hello World"); + } + resp.set_status_and_content(status_type::ok, "ok", content_encoding::br, + req.get_accept_encoding()); + }); + + server.async_start(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + coro_http_client client{}; + std::unordered_map headers = { + {"Content-Encoding", "br"}, + }; + std::string ziped_str; + std::string_view data = "Hello World"; + bool r = br_codec::brotli_compress(data, ziped_str); + + auto result = async_simple::coro::syncAwait(client.async_post( + "http://127.0.0.1:9001/get", ziped_str, req_content_type::none, headers)); + CHECK(result.resp_body == "ok"); + server.stop(); +} +#endif + +#ifdef CINATRA_ENABLE_SSL +TEST_CASE("test ssl client") { + { + coro_http_client client4{}; + client4.set_ssl_schema(true); + auto result = client4.get("www.baidu.com"); + assert(result.status == 200); + + auto lazy = []() -> async_simple::coro::Lazy { + coro_http_client client5{}; + client5.set_ssl_schema(true); + co_await client5.connect("www.baidu.com"); + auto result = co_await client5.async_get("/"); + assert(result.status == 200); + }; + async_simple::coro::syncAwait(lazy()); + } + { + coro_http_client client{}; + auto result = client.get("https://www.bing.com"); + CHECK(result.status >= 200); + } + + { + coro_http_client client{}; + auto r = + async_simple::coro::syncAwait(client.connect("https://www.baidu.com")); + if (r.status == 200) { + auto result = client.get("/"); + CHECK(result.status >= 200); + } + } + + { + coro_http_client client{}; + auto result = client.get("http://www.bing.com"); + CHECK(result.status >= 200); + } + + { + coro_http_client client{}; + client.set_ssl_schema(true); + auto result = client.get("www.bing.com"); + CHECK(result.status >= 200); + } + + { + coro_http_client client{}; + client.set_ssl_schema(false); + auto result = client.get("https://www.bing.com"); + CHECK(result.status >= 200); + } + + { + coro_http_client client{}; + client.enable_auto_redirect(true); + bool ok = client.init_ssl(); + REQUIRE_MESSAGE(ok == true, "init ssl fail, please check ssl config"); + auto result = client.get("https://www.bing.com"); + CHECK(result.status >= 200); + } + + { + coro_http_client client{}; + client.set_req_timeout(8s); + client.enable_auto_redirect(true); + std::string uri = "http://www.bing.com"; + // Make sure the host and port are matching with your proxy server + client.set_proxy("106.14.255.124", "80"); + resp_data result = async_simple::coro::syncAwait(client.async_get(uri)); + if (!result.net_err) + CHECK(result.status >= 200); + } + + { + coro_http_client client{}; + bool ok = client.init_ssl(); + REQUIRE_MESSAGE(ok == true, "init ssl fail, please check ssl config"); + auto result = client.get("https://www.bing.com"); + CHECK(result.status >= 200); + } +} + +TEST_CASE("test ssl client") { + coro_http_client client{}; + bool ok = client.init_ssl(); + REQUIRE_MESSAGE(ok == true, "init ssl fail, please check ssl config"); + // client.set_sni_hostname("https://www.bing.com"); + auto result = client.get("https://www.bing.com"); + CHECK(result.status >= 200); +} +#endif + +bool create_file(std::string_view filename, size_t file_size = 1024) { + std::ofstream out(filename.data(), std::ios::binary); + if (!out.is_open()) { + return false; + } + + std::string str; + for (int i = 0; i < file_size; ++i) { + str.push_back(rand() % 26 + 'A'); + } + out.write(str.data(), str.size()); + return true; +} + +TEST_CASE("test cinatra::string with SSO") { + std::string s = "HelloHi"; + auto oldlen = s.length(); + s.reserve(10); + memset(s.data() + oldlen + 1, 'A', 3); + cinatra::detail::resize(s, 10); + CHECK(s[10] == '\0'); + memcpy(s.data() + oldlen, "233", 3); + CHECK(strlen(s.data()) == 10); + CHECK(s == "HelloHi233"); +} + +TEST_CASE("test parse query") { + { + http_parser parser{}; + parser.parse_query("="); + parser.parse_query("&a"); + parser.parse_query("&b="); + parser.parse_query("&c=&d"); + parser.parse_query("&e=&f=1"); + parser.parse_query("&g=1&h=1"); + auto map = parser.queries(); + CHECK(map["a"].empty()); + CHECK(map["b"].empty()); + CHECK(map["c"].empty()); + CHECK(map["d"].empty()); + CHECK(map["e"].empty()); + CHECK(map["f"] == "1"); + CHECK(map["g"] == "1"); + CHECK(map["h"] == "1"); + } + { + http_parser parser{}; + parser.parse_query("test"); + parser.parse_query("test1="); + parser.parse_query("test2=&"); + parser.parse_query("test3&"); + parser.parse_query("test4&a"); + parser.parse_query("test5&b=2"); + parser.parse_query("test6=1&c=2"); + parser.parse_query("test7=1&d"); + parser.parse_query("test8=1&e="); + parser.parse_query("test9=1&f"); + parser.parse_query("test10=1&g=10&h&i=3&j"); + auto map = parser.queries(); + CHECK(map["test"].empty()); + CHECK(map.size() == 21); + } +} + +TEST_CASE("test cinatra::string without SSO") { + std::string s(1000, 'A'); + std::string s2(5000, 'B'); + std::string sum = s + s2; + auto oldlen = s.length(); + s.reserve(6000); + memset(s.data() + oldlen + 1, 'A', 5000); + cinatra::detail::resize(s, 6000); + CHECK(s[6000] == '\0'); + memcpy(s.data() + oldlen, s2.data(), s2.length()); + CHECK(strlen(s.data()) == 6000); + CHECK(s == sum); +} + +TEST_CASE("test cinatra::string SSO to no SSO") { + std::string s(10, 'A'); + std::string s2(5000, 'B'); + std::string sum = s + s2; + auto oldlen = s.length(); + s.reserve(5010); + memset(s.data() + oldlen + 1, 'A', 5000); + cinatra::detail::resize(s, 5010); + CHECK(s[5010] == '\0'); + memcpy(s.data() + oldlen, s2.data(), s2.length()); + CHECK(strlen(s.data()) == 5010); + CHECK(s == sum); +} + +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); + } +} + +async_simple::coro::Lazy recieve_data(auto &ch, auto &vec, size_t count) { + while (true) { + if (vec.size() == count) { + std::cout << std::this_thread::get_id() << "\n"; + break; + } + + auto [ec, i] = co_await coro_io::async_receive(ch); + vec.push_back(i); + } +} + +TEST_CASE("test coro channel with multi thread") { + size_t count = 10000; + auto ch = coro_io::create_channel(count); + send_data(ch, count).via(ch.get_executor()).start([](auto &&) { + }); + + std::vector vec; + std::vector group; + for (int i = 0; i < 10; i++) { + group.emplace_back(std::thread([&]() { + async_simple::coro::syncAwait( + recieve_data(ch, vec, count).via(ch.get_executor())); + })); + } + for (auto &thd : group) { + thd.join(); + } + + for (int i = 0; i < count; i++) { + CHECK(vec.at(i) == i); + } +} + +TEST_CASE("test coro channel") { + { + auto ch = coro_io::create_channel(100); + auto ec = async_simple::coro::syncAwait( + 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)); + CHECK(!err); + CHECK(val == "test"); + } + auto ch = coro_io::create_channel(1000); + auto ec = async_simple::coro::syncAwait(coro_io::async_send(ch, 41)); + CHECK(!ec); + ec = async_simple::coro::syncAwait(coro_io::async_send(ch, 42)); + CHECK(!ec); + + std::error_code err; + int val; + std::tie(err, val) = + async_simple::coro::syncAwait(coro_io::async_receive(ch)); + CHECK(!err); + CHECK(val == 41); + + std::tie(err, val) = + async_simple::coro::syncAwait(coro_io::async_receive(ch)); + CHECK(!err); + CHECK(val == 42); +} + +async_simple::coro::Lazy test_select_channel() { + using namespace coro_io; + using namespace async_simple; + using namespace async_simple::coro; + + auto ch1 = coro_io::create_channel(1000); + auto ch2 = coro_io::create_channel(1000); + + co_await async_send(ch1, 41); + co_await async_send(ch2, 42); + + std::array arr{41, 42}; + int val; + + size_t index = + co_await select(std::pair{async_receive(ch1), + [&val](auto value) { + auto [ec, r] = value.value(); + val = r; + }}, + std::pair{async_receive(ch2), [&val](auto value) { + auto [ec, r] = value.value(); + val = r; + }}); + + CHECK(val == arr[index]); + + co_await async_send(ch1, 41); + co_await async_send(ch2, 42); + + std::vector>> vec; + vec.push_back(async_receive(ch1)); + vec.push_back(async_receive(ch2)); + + index = co_await select(std::move(vec), [&](size_t i, auto result) { + val = result.value().second; + }); + CHECK(val == arr[index]); + + period_timer timer1(coro_io::get_global_executor()); + timer1.expires_after(100ms); + period_timer timer2(coro_io::get_global_executor()); + timer2.expires_after(200ms); + + int val1; + index = co_await select(std::pair{timer1.async_await(), + [&](auto val) { + CHECK(val.value()); + val1 = 0; + }}, + std::pair{timer2.async_await(), [&](auto val) { + CHECK(val.value()); + val1 = 0; + }}); + CHECK(index == val1); + + int val2; + index = co_await select(std::pair{coro_io::post([] { + }), + [&](auto) { + std::cout << "post1\n"; + val2 = 0; + }}, + std::pair{coro_io::post([] { + }), + [&](auto) { + std::cout << "post2\n"; + val2 = 1; + }}); + CHECK(index == val2); + + co_await async_send(ch1, 43); + auto lazy = coro_io::post([] { + }); + + int val3 = -1; + index = co_await select(std::pair{async_receive(ch1), + [&](auto result) { + val3 = result.value().second; + }}, + std::pair{std::move(lazy), [&](auto) { + val3 = 0; + }}); + + if (index == 0) { + CHECK(val3 == 43); + } + else if (index == 1) { + CHECK(val3 == 0); + } +} + +TEST_CASE("test select coro channel") { + using namespace coro_io; + async_simple::coro::syncAwait(test_select_channel()); + + auto ch = coro_io::create_channel(1000); + + async_simple::coro::syncAwait(coro_io::async_send(ch, 41)); + async_simple::coro::syncAwait(coro_io::async_send(ch, 42)); + + std::error_code ec; + int val; + std::tie(ec, val) = async_simple::coro::syncAwait(coro_io::async_receive(ch)); + CHECK(val == 41); + + std::tie(ec, val) = async_simple::coro::syncAwait(coro_io::async_receive(ch)); + CHECK(val == 42); +} + +TEST_CASE("test bad address") { + { + coro_http_server server(1, 9001, "127.0.0.1"); + server.async_start(); + auto ec = server.get_errc(); + CHECK(!ec); + } + { + coro_http_server server(1, 9001, "localhost"); + server.async_start(); + auto ec = server.get_errc(); + CHECK(!ec); + } + { + coro_http_server server(1, 9001, "0.0.0.0"); + server.async_start(); + auto ec = server.get_errc(); + CHECK(!ec); + } + { + coro_http_server server(1, 9001); + server.async_start(); + auto ec = server.get_errc(); + CHECK(!ec); + } + { + coro_http_server server(1, "0.0.0.0:9001"); + server.async_start(); + auto ec = server.get_errc(); + CHECK(!ec); + } + { + coro_http_server server(1, "127.0.0.1:9001"); + server.async_start(); + auto ec = server.get_errc(); + CHECK(!ec); + } + { + coro_http_server server(1, "localhost:9001"); + server.async_start(); + auto ec = server.get_errc(); + CHECK(!ec); + } + + { + coro_http_server server(1, 9001, "x.x.x.x"); + server.async_start(); + auto ec = server.get_errc(); + CHECK(ec); + } + { + coro_http_server server(1, "localhost:aaa"); + server.async_start(); + auto ec = server.get_errc(); + CHECK(ec); + } +} + +async_simple::coro::Lazy test_collect_all() { + asio::io_context ioc; + std::thread thd([&] { + ioc.run(); + }); + std::vector> v; + std::vector> futures; + for (int i = 0; i < 2; ++i) { + auto client = std::make_shared(); + v.push_back(client); + futures.push_back(client->async_get("http://www.baidu.com/")); + } + + auto out = co_await async_simple::coro::collectAll(std::move(futures)); + + for (auto &item : out) { + auto result = item.value(); + CHECK(result.status >= 200); + } + thd.join(); +} + +TEST_CASE("test default http handler") { + coro_http_server server(1, 9001); + server.set_default_handler( + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_status_and_content(status_type::ok, + "It is from default handler"); + co_return; + }); + server.set_http_handler( + "/view", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_delay(true); + resp.set_status_and_content_view(status_type::ok, + req.get_body()); // no copy + co_await resp.get_conn()->reply(); + }); + server.async_start(); + + for (int i = 0; i < 5; i++) { + coro_http_client client{}; + async_simple::coro::syncAwait(client.connect("http://127.0.0.1:9001")); + auto data = client.get("/test"); + CHECK(data.resp_body == "It is from default handler"); + data = client.get("/test_again"); + CHECK(data.resp_body == "It is from default handler"); + data = client.get("/any"); + CHECK(data.resp_body == "It is from default handler"); + data = async_simple::coro::syncAwait( + client.async_post("/view", "post string", req_content_type::string)); + CHECK(data.status == 200); + CHECK(data.resp_body == "post string"); + } +} + +TEST_CASE("test request with out buffer") { + coro_http_server server(1, 8090); + server.set_http_handler( + "/test", [](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, + "it is a test string, more than 10 bytes"); + }); + server.set_http_handler( + "/test1", [](coro_http_request &req, coro_http_response &resp) { + resp.set_format_type(format_type::chunked); + resp.set_status_and_content(status_type::ok, + "it is a test string, more than 10 bytes"); + }); + server.async_start(); + + 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"; + + { + coro_http_client client; + auto ret = client.async_request(url, http_method::GET, req_context<>{}, {}, + std::span{str.data(), str.size()}); + auto result = async_simple::coro::syncAwait(ret); + std::cout << result.status << "\n"; + std::cout << result.net_err.message() << "\n"; + std::cout << result.resp_body << "\n"; + CHECK(result.status == 200); + CHECK(!client.is_body_in_out_buf()); + } + + { + coro_http_client client; + auto ret = client.async_request(url1, http_method::GET, req_context<>{}, {}, + std::span{str.data(), str.size()}); + auto result = async_simple::coro::syncAwait(ret); + std::cout << result.status << "\n"; + std::cout << result.net_err.message() << "\n"; + std::cout << result.resp_body << "\n"; + CHECK(result.status == 200); + CHECK(!client.is_body_in_out_buf()); + } + + { + detail::resize(str, 1024); + coro_http_client client; + auto ret = client.async_request(url, 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") { + coro_http_client client{}; + auto r = + async_simple::coro::syncAwait(client.async_get("http://www.baidu.com")); + std::cout << r.resp_body.size() << "\n"; + auto buf = client.release_buf(); + std::cout << strlen(buf.data()) << "\n"; + std::cout << buf << "\n"; + CHECK(r.status >= 200); + + r = async_simple::coro::syncAwait(client.async_get("http://www.baidu.com")); + CHECK(r.status >= 200); + + r = async_simple::coro::syncAwait(client.async_get("/")); + CHECK(r.status >= 200); +} + +TEST_CASE("test coro_http_client connect/request timeout") { + { +#if !defined(_MSC_VER) + coro_http_client client{}; + cinatra::coro_http_client::config conf{.conn_timeout_duration = 1ms}; + client.init_config(conf); + 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{}); +#endif + } + + { + coro_http_client client{}; + cinatra::coro_http_client::config conf{.conn_timeout_duration = 10s, + .req_timeout_duration = 1ms}; + client.init_config(conf); + auto r = + async_simple::coro::syncAwait(client.async_get("http://www.baidu.com")); + std::cout << r.net_err.message() << "\n"; + CHECK(r.net_err != std::errc{}); + } +} + +TEST_CASE("test coro_http_client async_http_connect") { + coro_http_client client{}; + cinatra::coro_http_client::config conf{.req_timeout_duration = 60s}; + client.init_config(conf); + auto r = async_simple::coro::syncAwait( + client.async_http_connect("http://www.baidu.com")); + CHECK(r.status >= 200); + for (auto [k, v] : r.resp_headers) { + std::cout << k << ", " << v << "\n"; + } + + coro_http_client client1{}; + r = async_simple::coro::syncAwait( + client1.async_http_connect("http//www.badurl.com")); + CHECK(r.status != 200); + + r = async_simple::coro::syncAwait(client1.connect("http://cn.bing.com")); + CHECK(client1.get_host() == "cn.bing.com"); + CHECK(client1.get_port() == "80"); + CHECK(r.status >= 200); + + r = async_simple::coro::syncAwait(client1.connect("http://www.baidu.com")); + + CHECK(r.status >= 200); + r = async_simple::coro::syncAwait(client1.connect("http://cn.bing.com")); + CHECK(r.status == 200); +} + +TEST_CASE("test collect all") { + async_simple::coro::syncAwait(test_collect_all()); +} + +TEST_CASE("test head put and some other request") { + coro_http_server server(1, 8090); + server.set_http_handler( + "/headers", [](coro_http_request &req, coro_http_response &resp) { + resp.add_header("Content-Type", "application/json"); + resp.add_header("Content-Length", "117"); + resp.set_status_and_content(status_type::ok, ""); + }); + server.set_http_handler( + "/", [](coro_http_request &req, coro_http_response &resp) { + resp.set_status(status_type::method_not_allowed); + }); + server.set_http_handler( + "/", [](coro_http_request &req, coro_http_response &resp) { + resp.add_header("Allow", "HEAD, OPTIONS, GET, POST, PUT"); + resp.set_status_and_content(status_type::ok, ""); + }); + server.set_http_handler( + "/put/json", [](coro_http_request &req, coro_http_response &resp) { + auto json_str = req.get_body(); + std::ofstream file("json.txt", std::ios::binary); + file.write(json_str.data(), json_str.size()); + file.close(); + resp.set_status_and_content(status_type::ok, ""); + }); + server.set_http_handler( + "/delete/:name", [](coro_http_request &req, coro_http_response &resp) { + auto &filename = req.params_["name"]; + std::error_code ec; + fs::remove(filename, ec); + std::string result = ec ? "delete failed" : "ok"; + resp.set_status_and_content(status_type::ok, result); + }); + server.set_http_handler( + "/delete/:name", [](coro_http_request &req, coro_http_response &resp) { + auto &filename = req.params_["name"]; + std::error_code ec; + fs::remove(filename, ec); + std::string result = ec ? "delete failed" : "delete ok"; + resp.set_status_and_content(status_type::ok, result); + }); + + server.async_start(); + std::this_thread::sleep_for(300ms); + + coro_http_client client{}; + + auto result = async_simple::coro::syncAwait( + client.async_head("http://127.0.0.1:8090/headers")); + CHECK(result.status == 200); + + result = async_simple::coro::syncAwait( + client.async_patch("http://127.0.0.1:8090/")); + CHECK(result.status == 405); + + result = async_simple::coro::syncAwait( + client.async_trace("http://127.0.0.1:8090/")); + CHECK(result.status == 405); + + result = async_simple::coro::syncAwait( + client.async_options("http://127.0.0.1:8090/")); + CHECK(result.status == 200); + + std::string json = R"({ + "Id": 12345, + "Customer": "John Smith", + "Quantity": 1, + "Price": 10.00 + })"; + + coro_http_client client1{}; + result = async_simple::coro::syncAwait(client1.async_put( + "http://127.0.0.1:8090/put/json", json, req_content_type::json)); + CHECK(result.status == 200); + + result = async_simple::coro::syncAwait(client1.async_post( + "http://127.0.0.1:8090/delete/json.txt", json, req_content_type::json)); + + CHECK(result.status == 404); + + result = async_simple::coro::syncAwait(client1.async_delete( + "http://127.0.0.1:8090/delete/json.txt", json, req_content_type::json)); + + CHECK(result.status == 200); +} + +TEST_CASE("test upload file") { + coro_http_server server(1, 8090); + server.set_http_handler( + "/multipart", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + assert(req.get_content_type() == content_type::multipart); + auto boundary = req.get_boundary(); + multipart_reader_t multipart(req.get_conn()); + while (true) { + auto part_head = co_await multipart.read_part_head(boundary); + if (part_head.ec) { + co_return; + } + + std::cout << part_head.name << "\n"; + std::cout << part_head.filename << "\n"; + + std::shared_ptr file; + std::string filename; + if (!part_head.filename.empty()) { + file = std::make_shared(); + filename = std::to_string( + std::chrono::system_clock::now().time_since_epoch().count()); + + size_t pos = part_head.filename.rfind('.'); + if (pos != std::string::npos) { + auto extent = part_head.filename.substr(pos); + filename += extent; + } + + std::cout << filename << "\n"; + file->open(filename, std::ios::trunc | std::ios::out); + if (!file->is_open()) { + resp.set_status_and_content(status_type::internal_server_error, + "file open failed"); + co_return; + } + } + + auto part_body = co_await multipart.read_part_body(boundary); + if (part_body.ec) { + co_return; + } + + if (!filename.empty()) { + auto [ec, sz] = co_await file->async_write(part_body.data); + if (ec) { + co_return; + } + + file->close(); + CHECK(fs::file_size(filename) == 2 * 1024 * 1024); + } + else { + std::cout << part_body.data << "\n"; + } + + if (part_body.eof) { + break; + } + } + + resp.set_status_and_content(status_type::ok, "multipart finished"); + co_return; + }); + + 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/multipart"; + resp_data result = + async_simple::coro::syncAwait(client.async_upload_multipart(uri)); + CHECK(result.status == 404); + + client.add_str_part("hello", "world"); + client.add_str_part("key", "value"); + CHECK(!client.add_file_part("key", "value")); + result = async_simple::coro::syncAwait(client.async_upload_multipart(uri)); + CHECK(!client.is_redirect(result)); + CHECK(result.resp_body == "multipart finished"); + + client.add_str_part("hello", "world"); + result = async_simple::coro::syncAwait( + client.async_upload_multipart("http//badurl.com")); + CHECK(result.status == 404); + + client.set_max_single_part_size(1024); + std::string test_file_name = "test1.txt"; + std::ofstream test_file; + test_file.open(test_file_name, + std::ios::binary | std::ios::out | std::ios::trunc); + std::vector test_file_data(2 * 1024 * 1024, '0'); + test_file.write(test_file_data.data(), test_file_data.size()); + test_file.close(); + result = async_simple::coro::syncAwait( + client.async_upload_multipart(uri, "test", test_file_name)); + + CHECK(result.resp_body == "multipart finished"); + + std::filesystem::remove(std::filesystem::path(test_file_name)); + + std::string not_exist_file = "notexist.txt"; + result = async_simple::coro::syncAwait(client.async_upload_multipart( + uri, "test_not_exist_file", not_exist_file)); + CHECK(result.status == 404); + + result = async_simple::coro::syncAwait(client.async_upload_multipart( + "http//badurl.com", "test_not_exist_file", not_exist_file)); + CHECK(result.status == 404); + + client.close(); + + server.stop(); +} + +TEST_CASE("test bad uri") { + coro_http_client client{}; + CHECK(client.add_header("hello", "cinatra")); + CHECK(client.add_header("hello", "cinatra")); + CHECK(!client.add_header("", "cinatra")); + client.add_str_part("hello", "world"); + auto result = async_simple::coro::syncAwait( + client.async_upload_multipart("http://www.badurlrandom.org")); + CHECK(result.status == 404); +} + +TEST_CASE("test multiple ranges download") { + coro_http_client client{}; + std::string uri = "http://uniquegoodshiningmelody.neverssl.com/favicon.ico"; + + std::string filename = "test1.txt"; + std::error_code ec{}; + std::filesystem::remove(filename, ec); + resp_data result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "1-16")); + if (result.status == 206) { + CHECK(std::filesystem::file_size(filename) == 16); + } +} + +TEST_CASE("test ranges download") { + create_file("test_range.txt", 64); + coro_http_server server(1, 8090); + server.set_static_res_dir("", ""); + server.async_start(); + + coro_http_client client{}; + client.set_req_timeout(std::chrono::seconds(8)); + std::string uri = "http://127.0.0.1:8090/test_range.txt"; + + std::string filename = "test1.txt"; + std::error_code ec{}; + std::filesystem::remove(filename, ec); + resp_data result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "1-10")); + CHECK(result.status == 206); + CHECK(std::filesystem::file_size(filename) == 10); + + filename = "test2.txt"; + std::filesystem::remove(filename, ec); + result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "10-15")); + CHECK(result.status == 206); + CHECK(std::filesystem::file_size(filename) == 6); +} + +TEST_CASE("test ranges download with a bad filename and multiple ranges") { + create_file("test_multiple_range.txt", 64); + coro_http_server server(1, 8090); + server.set_static_res_dir("", ""); + server.async_start(); + + coro_http_client client{}; + std::string uri = "http://127.0.0.1:8090/test_multiple_range.txt"; + + std::string filename = ""; + std::error_code ec{}; + std::filesystem::remove(filename, ec); + resp_data result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "1-10,11-16")); + CHECK(result.status == 404); + CHECK(result.net_err == + std::make_error_code(std::errc::no_such_file_or_directory)); + + client.add_header("Range", "bytes=1-10,20-30"); + result = client.get(uri); + CHECK(result.status == 206); + CHECK(result.resp_body.size() == 21); + + filename = "test_ranges.txt"; + client.add_header("Range", "bytes=0-10,21-30"); + result = client.download(uri, filename); + CHECK(result.status == 206); + CHECK(fs::file_size(filename) == 21); +} + +TEST_CASE("test coro_http_client quit") { + std::promise promise; + [&] { + { coro_http_client client{}; } + promise.set_value(true); + }(); + + CHECK(promise.get_future().get()); +} + +TEST_CASE("test coro_http_client multipart upload") { + coro_http_server server(1, 8090); + server.set_http_handler( + "/multipart_upload", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + assert(req.get_content_type() == content_type::multipart); + auto boundary = req.get_boundary(); + multipart_reader_t multipart(req.get_conn()); + while (true) { + auto part_head = co_await multipart.read_part_head(boundary); + if (part_head.ec) { + co_return; + } + + std::cout << part_head.name << "\n"; + std::cout << part_head.filename << "\n"; + + std::shared_ptr file; + std::string filename; + if (!part_head.filename.empty()) { + file = std::make_shared(); + filename = std::to_string( + std::chrono::system_clock::now().time_since_epoch().count()); + + size_t pos = part_head.filename.rfind('.'); + if (pos != std::string::npos) { + auto extent = part_head.filename.substr(pos); + filename += extent; + } + + std::cout << filename << "\n"; + file->open(filename, std::ios::trunc | std::ios::out); + if (!file->is_open()) { + resp.set_status_and_content(status_type::internal_server_error, + "file open failed"); + co_return; + } + } + + auto part_body = co_await multipart.read_part_body(boundary); + if (part_body.ec) { + co_return; + } + + if (!filename.empty()) { + auto [ec, sz] = co_await file->async_write(part_body.data); + if (ec) { + co_return; + } + + file->close(); + CHECK(fs::file_size(filename) == 1024); + } + else { + std::cout << part_body.data << "\n"; + } + + if (part_body.eof) { + break; + } + } + + resp.set_status_and_content(status_type::ok, "ok"); + co_return; + }); + + server.async_start(); + + std::string filename = "test_1024.txt"; + create_file(filename); + + coro_http_client client{}; + std::string uri = "http://127.0.0.1:8090/multipart_upload"; + client.add_str_part("test", "test value"); + client.add_file_part("test file", filename); + auto result = + async_simple::coro::syncAwait(client.async_upload_multipart(uri)); + CHECK(result.status == 200); +} + +TEST_CASE("test coro_http_client upload") { + auto test_upload_by_file_path = [](std::string filename, + std::size_t offset = 0, + std::size_t r_size = SIZE_MAX, + bool should_failed = false) { + coro_http_client client{}; + client.add_header("filename", filename); + client.add_header("offset", std::to_string(offset)); + if (r_size != SIZE_MAX) + client.add_header("filesize", std::to_string(r_size)); + std::string uri = "http://127.0.0.1:8090/upload"; + cinatra::resp_data result; + if (r_size != SIZE_MAX) { + auto lazy = + client.async_upload(uri, http_method::PUT, filename, offset, r_size); + result = async_simple::coro::syncAwait(lazy); + } + else { + auto lazy = client.async_upload(uri, http_method::PUT, filename, offset); + result = async_simple::coro::syncAwait(lazy); + } + CHECK(((result.status == 200) ^ should_failed)); + }; + auto test_upload_by_stream = [](std::string filename, std::size_t offset = 0, + std::size_t r_size = SIZE_MAX, + bool should_failed = false) { + coro_http_client client{}; + client.add_header("filename", filename); + client.add_header("offset", std::to_string(offset)); + if (r_size != SIZE_MAX) + client.add_header("filesize", std::to_string(r_size)); + std::string uri = "http://127.0.0.1:8090/upload"; + std::ifstream ifs(filename, std::ios::binary); + cinatra::resp_data result; + if (r_size != SIZE_MAX) { + auto lazy = + client.async_upload(uri, http_method::PUT, filename, offset, r_size); + result = async_simple::coro::syncAwait(lazy); + } + else { + auto lazy = client.async_upload(uri, http_method::PUT, filename, offset); + result = async_simple::coro::syncAwait(lazy); + } + CHECK(((result.status == 200) ^ should_failed)); + }; + auto test_upload_by_coro = [](std::string filename, + std::size_t r_size = SIZE_MAX, + bool should_failed = false) { + coro_http_client client{}; + client.add_header("filename", filename); + client.add_header("offset", "0"); + if (r_size != SIZE_MAX) + client.add_header("filesize", std::to_string(r_size)); + std::string uri = "http://127.0.0.1:8090/upload"; + coro_io::coro_file file; + file.open(filename, std::ios::in); + CHECK(file.is_open()); + std::string buf; + buf.resize(1'000'000); + auto async_read = + [&file, &buf]() -> async_simple::coro::Lazy { + auto [ec, size] = co_await file.async_read(buf.data(), buf.size()); + co_return read_result{{buf.data(), size}, file.eof(), ec}; + }; + cinatra::resp_data result; + if (r_size == SIZE_MAX) { + auto lazy = client.async_upload(uri, http_method::PUT, async_read); + result = async_simple::coro::syncAwait(lazy); + CHECK(result.status != 200); + } + else { + auto lazy = + client.async_upload(uri, http_method::PUT, async_read, 0, r_size); + result = async_simple::coro::syncAwait(lazy); + CHECK(((result.status == 200) ^ should_failed)); + } + }; + coro_http_server server(1, 8090); + 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_upload.txt"; + // upload without size + { + auto sizes = {1024 * 1024, 2'000'000, 1024, 100, 0}; + for (auto size : sizes) { + std::error_code ec{}; + fs::remove(filename, ec); + if (ec) { + std::cout << ec << "\n"; + } + bool r = create_file(filename, size); + CHECK(r); + test_upload_by_file_path(filename); + test_upload_by_stream(filename); + test_upload_by_coro(filename); + } + } + // upload with size + { + auto sizes = {std::pair{1024 * 1024, 1'000'000}, + std::pair{2'000'000, 1'999'999}, std::pair{200, 1}, + std::pair{100, 0}, std::pair{0, 0}}; + for (auto [size, r_size] : sizes) { + std::error_code ec{}; + fs::remove(filename, ec); + if (ec) { + std::cout << ec << "\n"; + } + bool r = create_file(filename, size); + CHECK(r); + test_upload_by_file_path(filename, 0, r_size); + test_upload_by_stream(filename, 0, r_size); + test_upload_by_coro(filename, r_size); + } + } + // upload with too large size + { + auto sizes = {std::pair{1024 * 1024, 1024 * 1024 + 2}, + std::pair{2'000'000, 2'000'001}, std::pair{200, 502}, + std::pair{0, 1}}; + for (auto [size, r_size] : sizes) { + std::error_code ec{}; + fs::remove(filename, ec); + if (ec) { + std::cout << ec << "\n"; + } + bool r = create_file(filename, size); + CHECK(r); + test_upload_by_file_path(filename, 0, r_size, true); + test_upload_by_stream(filename, 0, r_size, true); + test_upload_by_coro(filename, r_size, true); + } + } + // upload with offset + { + auto sizes = {std::pair{1024 * 1024, 1'000'000}, + std::pair{2'000'000, 1'999'999}, std::pair{200, 1}, + std::pair{100, 0}, std::pair{0, 0}}; + for (auto [size, offset] : sizes) { + std::error_code ec{}; + fs::remove(filename, ec); + if (ec) { + std::cout << ec << "\n"; + } + bool r = create_file(filename, size); + CHECK(r); + test_upload_by_file_path(filename, offset); + test_upload_by_stream(filename, offset); + } + } + // upload with size & offset + { + auto sizes = {std::tuple{1024 * 1024, 500'000, 500'000}, + std::tuple{2'000'000, 1'999'999, 1}, std::tuple{200, 1, 199}, + std::tuple{100, 100, 0}}; + for (auto [size, offset, r_size] : sizes) { + std::error_code ec{}; + fs::remove(filename, ec); + if (ec) { + std::cout << ec << "\n"; + } + bool r = create_file(filename, size); + CHECK(r); + test_upload_by_file_path(filename, offset, r_size); + test_upload_by_stream(filename, offset, r_size); + } + } + // upload with too large size & offset + { + auto sizes = {std::tuple{1024 * 1024, 1'000'000, 50'000}, + std::tuple{2'000'000, 1'999'999, 2}, std::tuple{200, 1, 200}, + std::tuple{100, 100, 1}}; + for (auto [size, offset, r_size] : sizes) { + std::error_code ec{}; + fs::remove(filename, ec); + if (ec) { + std::cout << ec << "\n"; + } + bool r = create_file(filename, size); + CHECK(r); + test_upload_by_file_path(filename, offset, r_size, true); + test_upload_by_stream(filename, offset, r_size, true); + } + } +} + +TEST_CASE("test coro_http_client chunked upload and download") { + { + coro_http_server server(1, 8090); + server.set_http_handler( + "/chunked_upload", + [](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_view filename = req.get_header_value("filename"); + + CHECK(!filename.empty()); + + 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()); + + while (true) { + result = co_await req.get_conn()->read_chunked(); + if (result.ec) { + co_return; + } + + file.write(result.data.data(), result.data.size()); + + if (result.eof) { + break; + } + } + file.flush(); + file.close(); + auto sz = std::filesystem::file_size(oldpath); + CHECK(sz == std::filesystem::file_size(newpath)); + resp.set_status_and_content(status_type::ok, std::string(filename)); + }); + + server.async_start(); + auto sizes = {1024 * 1024, 2'000'000, 1024, 100, 0}; + for (auto size : sizes) { + std::string filename = "test_chunked_upload.txt"; + std::error_code ec{}; + fs::remove(filename, ec); + if (ec) { + std::cout << ec << "\n"; + } + bool r = create_file(filename, 1024 * 1024 * 8); + CHECK(r); + coro_http_client client{}; + client.add_header("filename", filename); + std::string uri = "http://127.0.0.1:8090/chunked_upload"; + auto lazy = client.async_upload_chunked(uri, http_method::PUT, filename); + auto result = async_simple::coro::syncAwait(lazy); + CHECK(result.status == 200); + } + } + + { + // chunked download, not in cache + create_file("test_102.txt", 102); + create_file("test_static.txt", 1024); + coro_http_server server(1, 8090); + server.set_static_res_dir("download", ""); + server.set_max_size_of_cache_files(100); + server.set_transfer_chunked_size(100); + server.async_start(); + + coro_http_client client{}; + + std::string download_url = "http://127.0.0.1:8090/download/test_static.txt"; + std::string download_name = "test1.txt"; + auto r = client.download(download_url, download_name); + CHECK(r.status == 200); + CHECK(std::filesystem::file_size(download_name) == 1024); + + download_url = "http://127.0.0.1:8090/download/test_102.txt"; + download_name = "test2.txt"; + r = client.download(download_url, download_name); + CHECK(r.status == 200); + CHECK(std::filesystem::file_size(download_name) == 102); + } +} + +TEST_CASE("test coro_http_client get") { + coro_http_client client{}; + auto r = client.get("http://www.baidu.com"); + CHECK(!r.net_err); + CHECK(r.status < 400); +} + +TEST_CASE("test coro_http_client add header and url queries") { + coro_http_client client{}; + client.add_header("Connection", "keep-alive"); + auto r = + async_simple::coro::syncAwait(client.async_get("http://www.baidu.cn")); + CHECK(!r.net_err); + CHECK(r.status < 400); + + auto r2 = async_simple::coro::syncAwait( + client.async_get("http://www.baidu.com?name='tom'&age=20")); + CHECK(!r2.net_err); + CHECK(r2.status < 400); +} + +TEST_CASE("test coro_http_client not exist domain and bad uri") { + { + coro_http_client client{}; + auto r = async_simple::coro::syncAwait( + client.async_get("http://www.notexistwebsit.com")); + CHECK(r.net_err); + CHECK(r.status != 200); + CHECK(client.has_closed()); + } + + { + coro_http_client client{}; + auto r = async_simple::coro::syncAwait( + client.async_get("http://www.baidu.com/><")); + CHECK(r.net_err); + CHECK(r.status != 200); + CHECK(client.has_closed()); + } +} + +TEST_CASE("test coro_http_client async_get") { + coro_http_client client{}; + auto r = + async_simple::coro::syncAwait(client.async_get("http://www.baidu.com")); + CHECK(!r.net_err); + CHECK(r.status < 400); + + auto r1 = + async_simple::coro::syncAwait(client.async_get("http://www.baidu.com")); + CHECK(!r.net_err); + CHECK(r.status == 200); +} + +TEST_CASE("test basic http request") { + coro_http_server server(1, 8090); + // Setting up GET and POST handlers + server.set_http_handler( + "/", [&server](coro_http_request &, coro_http_response &res) mutable { + res.set_status_and_content(status_type::ok, "hello world"); + }); + server.set_http_handler( + "/", [&server](coro_http_request &req, coro_http_response &res) mutable { + std::string str(req.get_body()); + str.append(" reply from post"); + res.set_status_and_content(status_type::ok, std::move(str)); + }); + + // Setting up PUT handler + server.set_http_handler( + "/", [&server](coro_http_request &req, coro_http_response &res) mutable { + std::string str(req.get_body()); + str.append(" put successfully"); + res.set_status_and_content(status_type::ok, std::move(str)); + }); + + // Setting up DELETE handler + server.set_http_handler( + "/", [&server](coro_http_request &, coro_http_response &res) mutable { + res.set_status_and_content(status_type::ok, "data deleted"); + }); + + 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"; + + // Testing PUT method + resp_data result = async_simple::coro::syncAwait(client.async_request( + uri, http_method::PUT, + req_context{.content = "data for put"})); + CHECK(result.resp_body == "data for put put successfully"); + + // Testing DELETE method + result = async_simple::coro::syncAwait(client.async_request( + uri, http_method::DEL, req_context{})); + CHECK(result.resp_body == "data deleted"); + + // Testing GET method again after DELETE + result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(result.resp_body == "hello world"); + + size_t size = result.resp_body.size(); + auto buf = client.release_buf(); + CHECK(size == strlen(buf.data())); + CHECK(buf == "hello world"); + + // Rest of the POST tests + result = async_simple::coro::syncAwait(client.async_post( + uri, "async post hello coro_http_client", req_content_type::string)); + CHECK(result.resp_body == + "async post hello coro_http_client reply from post"); + + result = client.post(uri, "sync post hello coro_http_client", + req_content_type::string); + CHECK(result.resp_body == "sync post hello coro_http_client reply from post"); + + std::string_view uri1 = "http://127.0.0.1:8090"; + std::string_view post_str = "post hello coro_http_client"; + + result = async_simple::coro::syncAwait( + client.async_request(uri, http_method::POST, + req_context{.content = post_str})); + CHECK(result.resp_body == "post hello coro_http_client reply from post"); + + result = async_simple::coro::syncAwait( + client.async_request(uri1, http_method::POST, + req_context{.content = post_str})); + CHECK(result.resp_body == "post hello coro_http_client reply from post"); + + result = client.post(uri, "", req_content_type::string); + CHECK(result.status == 200); + + server.stop(); +} + +TEST_CASE("test coro_http_client request timeout") { + coro_http_client client{}; + cinatra::coro_http_client::config conf{.conn_timeout_duration = 10s, + .req_timeout_duration = 1ms}; + client.init_config(conf); + auto r = + async_simple::coro::syncAwait(client.connect("http://www.baidu.com")); + std::cout << r.net_err.message() << "\n"; + if (!r.net_err) { + r = async_simple::coro::syncAwait(client.async_get("/")); + if (r.net_err) { + CHECK(r.net_err == std::errc::timed_out); + } + } +} + +#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.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); + } +} +#endif + +TEST_CASE("test coro http proxy request") { + coro_http_client client{}; + client.set_req_timeout(8s); + std::string uri = "http://www.baidu.com"; + // Make sure the host and port are matching with your proxy server + client.set_proxy("106.14.255.124", "80"); + resp_data 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"); + result = async_simple::coro::syncAwait(client.async_get(uri)); + if (!result.net_err) + CHECK(result.status >= 200); +} + +TEST_CASE("test coro http proxy request with port") { + coro_http_client client{}; + client.set_req_timeout(8s); + std::string uri = "http://www.baidu.com:80"; + // Make sure the host and port are matching with your proxy server + client.set_proxy("106.14.255.124", "80"); + resp_data result = async_simple::coro::syncAwait(client.async_get(uri)); + if (!result.net_err) + CHECK(result.status >= 200); // maybe return 500 from that host. +} + +// TEST_CASE("test coro http basic auth request") { +// coro_http_client client{}; +// std::string uri = "http://www.purecpp.cn"; +// client.set_proxy_basic_auth("user", "pass"); +// resp_data result = async_simple::coro::syncAwait(client.async_get(uri)); +// CHECK(!result.net_err); +// CHECK(result.status == 200); +// } + +TEST_CASE("test coro http bearer token auth request") { + coro_http_client client{}; + std::string uri = "http://www.baidu.com"; + client.set_proxy_bearer_token_auth("password"); + resp_data result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(!result.net_err); + CHECK(result.status < 400); +} + +TEST_CASE("test coro http redirect request") { + coro_http_client client{}; + client.set_req_timeout(8s); + std::string uri = "http://httpbin.org/redirect-to?url=http://httpbin.org/get"; + 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) + 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) + CHECK(result.status == 200); + } + + client.enable_auto_redirect(true); + result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(result.status >= 200); + } +} + +TEST_CASE("test coro http request timeout") { + coro_http_server server(1, 8090); + server.set_http_handler( + "/", [&server](coro_http_request &, coro_http_response &res) mutable { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + res.set_status_and_content(status_type::ok, "hello world"); + }); + + 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"; + + resp_data result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(result.status == 200); + + client.set_req_timeout(500ms); + result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(result.net_err == std::errc::timed_out); + + // after timeout, the socket in client has been closed, so use a new client + // to test. + coro_http_client client1{}; + result = async_simple::coro::syncAwait(client1.async_post( + uri, "async post hello coro_http_client", req_content_type::string)); + CHECK(!result.net_err); + + server.stop(); +} + +TEST_CASE("test coro_http_client using external io_context") { + asio::io_context io_context; + std::promise promise; + auto future = promise.get_future(); + auto work = std::make_unique(io_context); + std::thread io_thd([&io_context, &promise] { + promise.set_value(); + io_context.run(); + }); + future.wait(); + + coro_http_client client(io_context.get_executor()); + auto r = + async_simple::coro::syncAwait(client.async_get("http://www.baidu.com")); + CHECK(!r.net_err); + CHECK(r.status < 400); + work.reset(); + io_context.run(); + io_thd.join(); +} + +async_simple::coro::Lazy simulate_self_join() { + coro_http_client client{}; + co_return co_await client.async_get("http://www.baidu.com"); +} + +TEST_CASE("test coro_http_client dealing with self join") { + auto r = async_simple::coro::syncAwait(simulate_self_join()); + CHECK(!r.net_err); + CHECK(r.status < 400); +} + +TEST_CASE("test coro_http_client no scheme still send request check") { + coro_http_server server(1, 8090); + server.set_http_handler( + "/", [&server](coro_http_request &, coro_http_response &res) mutable { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + res.set_status_and_content(status_type::ok, "hello world"); + }); + + server.async_start(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::string uri = "http://127.0.0.1:8090"; + + coro_http_client client{}; + auto resp = async_simple::coro::syncAwait(client.async_get("127.0.0.1:8090")); + CHECK(!resp.net_err); + CHECK(resp.status == 200); + resp = async_simple::coro::syncAwait( + client.async_get("127.0.0.1:8090/ref='http://www.baidu.com'")); + CHECK(resp.status == 404); + + server.stop(); +} + +#ifdef DSKIP_TIME_TEST +TEST_CASE("test conversion between unix time and gmt time, http format") { + std::chrono::microseconds time_cost{0}; + std::ifstream file("../../tests/files_for_test_time_parse/http_times.txt"); + if (!file) { + std::cout << "open file failed" << std::endl; + } + std::string line; + while (std::getline(file, line)) { + std::istringstream iss(line); + std::string time_to_parse; + std::string timestamp; + if (std::getline(iss, time_to_parse, '#') && std::getline(iss, timestamp)) { + std::pair result; + auto start = std::chrono::system_clock::now(); + for (int i = 0; i < 100; i++) { + result = get_timestamp(time_to_parse); + } + auto end = std::chrono::system_clock::now(); + auto duration = duration_cast(end - start); + time_cost += duration; + if (result.first == true) { + CHECK(timestamp != "invalid"); + if (timestamp != "invalid") { + CHECK(result.second == std::stoll(timestamp)); + } + } + else { + CHECK(timestamp == "invalid"); + } + } + } + file.close(); + std::cout << double(time_cost.count()) * + std::chrono::microseconds::period::num / + std::chrono::microseconds::period::den + << "s" << std::endl; +} + +TEST_CASE("test conversion between unix time and gmt time, utc format") { + std::chrono::microseconds time_cost{0}; + std::ifstream file("../../tests/files_for_test_time_parse/utc_times.txt"); + if (!file) { + std::cout << "open file failed" << std::endl; + } + std::string line; + while (std::getline(file, line)) { + std::istringstream iss(line); + std::string time_to_parse; + std::string timestamp; + if (std::getline(iss, time_to_parse, '#') && std::getline(iss, timestamp)) { + std::pair result; + auto start = std::chrono::system_clock::now(); + for (int i = 0; i < 100; i++) { + result = get_timestamp(time_to_parse); + } + auto end = std::chrono::system_clock::now(); + auto duration = duration_cast(end - start); + time_cost += duration; + if (result.first == true) { + CHECK(timestamp != "invalid"); + if (timestamp != "invalid") { + CHECK(result.second == std::stoll(timestamp)); + } + } + else { + CHECK(timestamp == "invalid"); + } + } + } + file.close(); + std::cout << double(time_cost.count()) * + std::chrono::microseconds::period::num / + std::chrono::microseconds::period::den + << "s" << std::endl; +} + +TEST_CASE( + "test conversion between unix time and gmt time, utc without punctuation " + "format") { + std::chrono::microseconds time_cost{0}; + std::ifstream file( + "../../tests/files_for_test_time_parse/" + "utc_without_punctuation_times.txt"); + if (!file) { + std::cout << "open file failed" << std::endl; + } + std::string line; + while (std::getline(file, line)) { + std::istringstream iss(line); + std::string time_to_parse; + std::string timestamp; + if (std::getline(iss, time_to_parse, '#') && std::getline(iss, timestamp)) { + std::pair result; + auto start = std::chrono::system_clock::now(); + for (int i = 0; i < 100; i++) { + result = get_timestamp( + time_to_parse); + } + auto end = std::chrono::system_clock::now(); + auto duration = duration_cast(end - start); + time_cost += duration; + if (result.first == true) { + CHECK(timestamp != "invalid"); + if (timestamp != "invalid") { + CHECK(result.second == std::stoll(timestamp)); + } + } + else { + CHECK(timestamp == "invalid"); + } + } + } + file.close(); + std::cout << double(time_cost.count()) * + std::chrono::microseconds::period::num / + std::chrono::microseconds::period::den + << "s" << std::endl; +} +#endif + +TEST_CASE("Testing get_content_type_str function") { + SUBCASE("Test HTML content type") { + CHECK(get_content_type_str(req_content_type::html) == + "text/html; charset=UTF-8"); + } + + SUBCASE("Test JSON content type") { + CHECK(get_content_type_str(req_content_type::json) == + "application/json; charset=UTF-8"); + } + + SUBCASE("Test String content type") { + CHECK(get_content_type_str(req_content_type::string) == + "text/html; charset=UTF-8"); + } + + SUBCASE("Test Multipart content type") { + std::string result = get_content_type_str(req_content_type::multipart); + std::string expectedPrefix = "multipart/form-data; boundary="; + CHECK(result.find(expectedPrefix) == + 0); // Check if the result starts with the expected prefix + + // Check if there is something after the prefix, + // this test failed. + /*CHECK(result.length() > expectedPrefix.length());*/ + } + + SUBCASE("Test Octet Stream content type") { + CHECK(get_content_type_str(req_content_type::octet_stream) == + "application/octet-stream"); + } + + SUBCASE("Test XML content type") { + CHECK(get_content_type_str(req_content_type::xml) == "application/xml"); + } +} + +TEST_CASE("test get_local_time_str with_month") { + char buf[32]; + std::string_view format = "%Y-%m-%d %H:%M:%S"; // This format includes '%m' + std::time_t t = std::time(nullptr); + + std::string_view result = cinatra::get_local_time_str(buf, t, format); + std::cout << "Local time with month: " << result << "\n"; + + // Perform a basic check + CHECK(!result.empty()); +} + +TEST_CASE("Testing base64_encode function") { + SUBCASE("Base64 encoding of an empty string") { + CHECK(base64_encode("") == ""); + } + + SUBCASE("Base64 encoding of 'Hello'") { + CHECK(base64_encode("Hello") == "SGVsbG8="); + } + + SUBCASE("Base64 encoding of a binary data") { + std::string binaryData = "\x01\x02\x03"; // Example binary data + CHECK(base64_encode(binaryData) == "AQID"); + } +} + +TEST_CASE("Testing is_valid_utf8 function") { + SUBCASE("Valid UTF-8 string") { + auto validUtf8 = std::u8string(u8"Hello, 世界"); + std::string validUtf8Converted(validUtf8.begin(), validUtf8.end()); + CHECK(is_valid_utf8((unsigned char *)validUtf8.c_str(), validUtf8.size()) == + true); + } + + SUBCASE("Invalid UTF-8 string with wrong continuation bytes") { + std::string invalidUtf8 = "Hello, \x80\x80"; // wrong continuation bytes + CHECK(is_valid_utf8((unsigned char *)invalidUtf8.c_str(), + invalidUtf8.size()) == false); + } + + SUBCASE("Empty string") { + std::string empty; + CHECK(is_valid_utf8((unsigned char *)empty.c_str(), empty.size()) == true); + } +} + +TEST_CASE("test transfer cookie to string") { + cookie cookie("name", "value"); + CHECK(cookie.get_name() == "name"); + CHECK(cookie.get_value() == "value"); + CHECK(cookie.to_string() == "name=value"); + cookie.set_path("/"); + CHECK(cookie.to_string() == "name=value; path=/"); + cookie.set_comment("comment"); + CHECK(cookie.to_string() == "name=value; path=/"); + cookie.set_domain("baidu.com"); + CHECK(cookie.to_string() == "name=value; domain=baidu.com; path=/"); + cookie.set_secure(true); + CHECK(cookie.to_string() == "name=value; domain=baidu.com; path=/; secure"); + cookie.set_http_only(true); + CHECK(cookie.to_string() == + "name=value; domain=baidu.com; path=/; secure; HttpOnly"); + cookie.set_priority("Low"); + CHECK(cookie.to_string() == + "name=value; domain=baidu.com; path=/; Priority=Low; secure; HttpOnly"); + cookie.set_priority("Medium"); + CHECK(cookie.to_string() == + "name=value; domain=baidu.com; path=/; Priority=Medium; secure; " + "HttpOnly"); + cookie.set_priority("High"); + CHECK( + cookie.to_string() == + "name=value; domain=baidu.com; path=/; Priority=High; secure; HttpOnly"); + cookie.set_priority(""); + cookie.set_http_only(false); + + cookie.set_version(1); + CHECK(cookie.to_string() == + "name=\"value\"; Comment=\"comment\"; Domain=\"baidu.com\"; " + "Path=\"/\"; secure; Version=\"1\""); + + cookie.set_secure(false); + cookie.set_max_age(100); + CHECK(cookie.to_string() == + "name=\"value\"; Comment=\"comment\"; Domain=\"baidu.com\"; " + "Path=\"/\"; Max-Age=\"100\"; Version=\"1\""); + + cookie.set_http_only(true); + CHECK(cookie.to_string() == + "name=\"value\"; Comment=\"comment\"; Domain=\"baidu.com\"; " + "Path=\"/\"; Max-Age=\"100\"; HttpOnly; Version=\"1\""); + + cookie.set_priority("Low"); + CHECK( + cookie.to_string() == + "name=\"value\"; Comment=\"comment\"; Domain=\"baidu.com\"; Path=\"/\"; " + "Priority=\"Low\"; Max-Age=\"100\"; HttpOnly; Version=\"1\""); + cookie.set_priority("Medium"); + CHECK( + cookie.to_string() == + "name=\"value\"; Comment=\"comment\"; Domain=\"baidu.com\"; Path=\"/\"; " + "Priority=\"Medium\"; Max-Age=\"100\"; HttpOnly; Version=\"1\""); + cookie.set_priority("High"); + CHECK( + cookie.to_string() == + "name=\"value\"; Comment=\"comment\"; Domain=\"baidu.com\"; Path=\"/\"; " + "Priority=\"High\"; Max-Age=\"100\"; HttpOnly; Version=\"1\""); +} + +std::vector get_header_values( + std::span &resp_headers, std::string_view key) { + std::vector values{}; + for (const auto &p : resp_headers) { + if (p.name == key) + values.push_back(p.value); + } + return values; +} + +std::string cookie_str1 = ""; +std::string cookie_str2 = ""; + +TEST_CASE("test cookie") { + coro_http_server server(5, 8090); + server.set_http_handler( + "/construct_cookies", + [](coro_http_request &req, coro_http_response &res) { + auto session = req.get_session(); + session->get_session_cookie().set_path("/"); + cookie_str1 = session->get_session_cookie().to_string(); + + cookie another_cookie("test", "cookie"); + another_cookie.set_http_only(true); + another_cookie.set_domain("baidu.com"); + res.add_cookie(another_cookie); + cookie_str2 = another_cookie.to_string(); + + res.set_status_and_content(status_type::ok, session->get_session_id()); + }); + + server.set_http_handler( + "/check_session_cookie", + [](coro_http_request &req, coro_http_response &res) { + auto session_id = req.get_header_value("Cookie"); + CHECK(session_id == + CSESSIONID + "=" + req.get_session()->get_session_id()); + res.set_status(status_type::ok); + }); + + server.async_start(); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + coro_http_client client{}; + auto r1 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/construct_cookies")); + auto cookie_strs = get_header_values(r1.resp_headers, "Set-Cookie"); + CHECK(cookie_strs.size() == 2); + bool check1 = + (cookie_strs[0] == cookie_str1 && cookie_strs[1] == cookie_str2); + bool check2 = + (cookie_strs[1] == cookie_str1 && cookie_strs[0] == cookie_str2); + CHECK((check1 || check2)); + CHECK(r1.status == 200); + + std::string session_cookie = + CSESSIONID + "=" + std::string(r1.resp_body.data(), r1.resp_body.size()); + + client.add_header("Cookie", session_cookie); + auto r2 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/check_session_cookie")); + CHECK(r2.status == 200); + + server.stop(); +} + +std::string session_id_login = ""; +std::string session_id_logout = ""; +std::string session_id_check_login = ""; +std::string session_id_check_logout = ""; + +TEST_CASE("test session") { + coro_http_server server(5, 8090); + server.set_http_handler( + "/login", [](coro_http_request &req, coro_http_response &res) { + auto session = req.get_session(); + session_id_login = session->get_session_id(); + session->set_data("login", true); + res.set_status(status_type::ok); + }); + server.set_http_handler( + "/logout", [](coro_http_request &req, coro_http_response &res) { + auto session = req.get_session(); + session_id_logout = session->get_session_id(); + session->remove_data("login"); + res.set_status(status_type::ok); + }); + server.set_http_handler( + "/check_login", [](coro_http_request &req, coro_http_response &res) { + auto session = req.get_session(); + session_id_check_login = session->get_session_id(); + bool login = session->get_data("login").value_or(false); + CHECK(login == true); + res.set_status(status_type::ok); + }); + server.set_http_handler( + "/check_logout", [](coro_http_request &req, coro_http_response &res) { + auto session = req.get_session(); + session_id_check_logout = session->get_session_id(); + bool login = session->get_data("login").value_or(false); + CHECK(login == false); + res.set_status(status_type::ok); + }); + server.async_start(); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + coro_http_client client{}; + auto r1 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/check_logout")); + CHECK(r1.status == 200); + + auto r2 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/login")); + CHECK(r2.status == 200); + CHECK(session_id_login != session_id_check_logout); + + std::string session_cookie = CSESSIONID + "=" + session_id_login; + + client.add_header("Cookie", session_cookie); + auto r3 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/check_login")); + CHECK(r3.status == 200); + CHECK(session_id_login == session_id_check_login); + + client.add_header("Cookie", session_cookie); + auto r4 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/logout")); + CHECK(r4.status == 200); + CHECK(session_id_login == session_id_logout); + + client.add_header("Cookie", session_cookie); + auto r5 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/check_logout")); + CHECK(r5.status == 200); + CHECK(session_id_login == session_id_check_logout); + + server.stop(); +} + +std::string session_id = ""; +TEST_CASE("test session timeout") { + coro_http_server server(5, 8090); + + server.set_http_handler( + "/construct_session", + [](coro_http_request &req, coro_http_response &res) { + auto session = req.get_session(); + session_id = session->get_session_id(); + session->set_session_timeout(1); + res.set_status(status_type::ok); + }); + + server.set_http_handler("/no_sleep", [](coro_http_request &req, + coro_http_response &res) { + CHECK(session_manager::get().check_session_existence(session_id) == true); + res.set_status(status_type::ok); + }); + + server.set_http_handler("/after_sleep_2s", [](coro_http_request &req, + coro_http_response &res) { + CHECK(session_manager::get().check_session_existence(session_id) == false); + res.set_status(status_type::ok); + }); + + session_manager::get().set_check_session_duration(10ms); + server.async_start(); + + coro_http_client client{}; + auto r1 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/construct_session")); + CHECK(r1.status == 200); + + auto r2 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/no_sleep")); + CHECK(r2.status == 200); + + std::this_thread::sleep_for(2s); + auto r3 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/after_sleep_2s")); + CHECK(r3.status == 200); + + server.stop(); +} + +TEST_CASE("test session validate") { + coro_http_server server(5, 8090); + + server.set_http_handler( + "/construct_session", + [](coro_http_request &req, coro_http_response &res) { + auto session = req.get_session(); + session_id = session->get_session_id(); + res.set_status(status_type::ok); + }); + + server.set_http_handler( + "/invalidate_session", + [](coro_http_request &req, coro_http_response &res) { + CHECK(session_manager::get().check_session_existence(session_id) == + true); + session_manager::get().get_session(session_id)->invalidate(); + res.set_status(status_type::ok); + }); + + server.set_http_handler("/after_sleep_2s", [](coro_http_request &req, + coro_http_response &res) { + CHECK(session_manager::get().check_session_existence(session_id) == false); + res.set_status(status_type::ok); + }); + + session_manager::get().set_check_session_duration(10ms); + server.async_start(); + + coro_http_client client{}; + auto r1 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/construct_session")); + CHECK(r1.status == 200); + + auto r2 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/invalidate_session")); + CHECK(r2.status == 200); + + std::this_thread::sleep_for(2s); + auto r3 = async_simple::coro::syncAwait( + client.async_get("http://127.0.0.1:8090/after_sleep_2s")); + CHECK(r3.status == 200); + + server.stop(); +} \ No newline at end of file diff --git a/src/coro_http/tests/test_cinatra_websocket.cpp b/src/coro_http/tests/test_cinatra_websocket.cpp new file mode 100644 index 0000000000..4b89623b4a --- /dev/null +++ b/src/coro_http/tests/test_cinatra_websocket.cpp @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include + +#include "doctest.h" +#include "ylt/coro_http/coro_http_client.hpp" +#include "ylt/coro_http/coro_http_server.hpp" + +using namespace std::chrono_literals; + +using namespace coro_http; + +#ifdef CINATRA_ENABLE_SSL +TEST_CASE("test wss client") { + cinatra::coro_http_server server(1, 9001); + server.init_ssl("../openssl_files/server.crt", "../openssl_files/server.key", + "test"); + server.set_http_handler( + "/", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + CHECK(req.get_content_type() == content_type::websocket); + websocket_result result{}; + while (true) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + auto ec = co_await req.get_conn()->write_websocket(result.data); + if (ec) { + break; + } + } + }); + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client{}; + bool ok = + client.init_ssl(asio::ssl::verify_peer, "../openssl_files/server.crt"); + REQUIRE_MESSAGE(ok == true, "init ssl fail, please check ssl config"); + + async_simple::coro::syncAwait(client.connect("wss://localhost:9001")); + + async_simple::coro::syncAwait(client.write_websocket("hello")); + auto data = async_simple::coro::syncAwait(client.read_websocket()); + CHECK(data.resp_body == "hello"); + + client.close(); + + server.stop(); +} +#endif + +async_simple::coro::Lazy test_websocket(coro_http_client &client) { + auto r = co_await client.connect("ws://localhost:8090/ws"); + if (r.net_err) { + co_return; + } + + auto result = co_await client.write_websocket("hello websocket"); + auto data = co_await client.read_websocket(); + CHECK(data.resp_body == "hello websocket"); + co_await client.write_websocket("test again"); + data = co_await client.read_websocket(); + CHECK(data.resp_body == "test again"); + co_await client.write_websocket_close("ws close"); + data = co_await client.read_websocket(); + CHECK(data.resp_body == "ws close"); + CHECK(data.net_err == asio::error::eof); +} + +TEST_CASE("test websocket") { + cinatra::coro_http_server server(1, 8090); + server.set_http_handler( + "/ws", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + CHECK(req.get_content_type() == content_type::websocket); + websocket_result result{}; + while (true) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + auto ec = co_await req.get_conn()->write_websocket(result.data); + if (ec) { + break; + } + } + }); + server.async_start(); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + coro_http_client client{}; + client.set_ws_sec_key("s//GYHa/XO7Hd2F2eOGfyA=="); + + async_simple::coro::syncAwait(test_websocket(client)); + + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + // client->async_close(); +} + +void test_websocket_content(size_t len) { + cinatra::coro_http_server server(1, 8090); + server.set_http_handler( + "/", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + CHECK(req.get_content_type() == content_type::websocket); + websocket_result result{}; + while (true) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + auto ec = co_await req.get_conn()->write_websocket(result.data); + if (ec) { + break; + } + } + }); + server.async_start(); + + auto lazy = [len]() -> async_simple::coro::Lazy { + coro_http_client client{}; + co_await client.connect("ws://localhost:8090"); + std::string send_str(len, 'a'); + co_await client.write_websocket(std::string(send_str)); + auto data = co_await client.read_websocket(); + REQUIRE(data.resp_body.size() == send_str.size()); + CHECK(data.resp_body == send_str); + }; + + async_simple::coro::syncAwait(lazy()); + + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + server.stop(); +} + +TEST_CASE("test websocket content lt 126") { + test_websocket_content(1); + test_websocket_content(125); +} + +TEST_CASE("test websocket content ge 126") { + test_websocket_content(126); + test_websocket_content(127); +} + +TEST_CASE("test websocket content ge 65535") { + test_websocket_content(65535); + test_websocket_content(65536); +} + +TEST_CASE("test send after server stop") { + cinatra::coro_http_server server(1, 8090); + server.async_start(); + + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + coro_http_client client{}; + async_simple::coro::syncAwait(client.connect("ws://127.0.0.1:8090")); + + server.stop(); + + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + async_simple::coro::syncAwait(client.write_websocket("")); + auto data = async_simple::coro::syncAwait(client.read_websocket()); + CHECK(data.net_err); +} + +TEST_CASE("test read write in different threads") { + cinatra::coro_http_server server(1, 8090); + server.set_http_handler( + "/", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + CHECK(req.get_content_type() == content_type::websocket); + websocket_result result{}; + while (true) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + auto ec = co_await req.get_conn()->write_websocket(result.data); + if (ec) { + break; + } + } + }); + server.async_start(); + + auto client = std::make_shared(); + std::string send_str(100, 'a'); + std::weak_ptr weak = client; + auto another_thread_lazy = [client, + send_str]() -> async_simple::coro::Lazy { + for (int i = 0; i < 100; i++) { + auto data = co_await client->read_websocket(); + if (data.net_err) { + co_return; + } + REQUIRE(data.resp_body.size() == send_str.size()); + CHECK(data.resp_body == send_str); + } + }; + another_thread_lazy().via(coro_io::get_global_executor()).start([](auto &&) { + }); + + auto lazy = [client, weak, &send_str]() -> async_simple::coro::Lazy { + co_await client->connect("ws://localhost:8090"); + for (int i = 0; i < 100; i++) { + auto data = co_await client->write_websocket(std::string(send_str)); + if (data.net_err) { + co_return; + } + } + }; + + async_simple::coro::syncAwait(lazy()); + + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + server.stop(); +} + +async_simple::coro::Lazy test_websocket() { + coro_http_client client{}; + auto r = co_await client.connect("ws://127.0.0.1:8089/ws_echo"); + if (r.net_err) { + co_return; + } + + co_await client.write_websocket(std::string_view("test2fdsaf"), + opcode::binary); + auto data = co_await client.read_websocket(); + CHECK(data.resp_body == "test2fdsaf"); + + co_await client.write_websocket_close("ws close"); + data = co_await client.read_websocket(); + CHECK(data.net_err == asio::error::eof); + CHECK(data.resp_body == "ws close"); +} + +TEST_CASE("test client quit after send msg") { + coro_http_server server(1, 8089); + server.set_http_handler( + "/ws_echo", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + websocket_result result{}; + + while (true) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + if (result.type == ws_frame_type::WS_CLOSE_FRAME) { + break; + } + + co_await resp.get_conn()->write_websocket(result.data); + } + }); + server.async_start(); + + async_simple::coro::syncAwait(test_websocket()); +} + +#ifdef CINATRA_ENABLE_GZIP +TEST_CASE("test websocket permessage defalte") { + coro_http_server server(1, 8090); + server.set_http_handler( + "/ws_extesion", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + websocket_result result{}; + while (true) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + if (result.type == ws_frame_type::WS_CLOSE_FRAME) { + std::cout << "close frame\n"; + break; + } + + if (result.type == ws_frame_type::WS_TEXT_FRAME || + result.type == ws_frame_type::WS_BINARY_FRAME) { + CHECK(result.data == "test"); + } + else 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; + } + } + }); + + server.async_start(); + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + coro_http_client client{}; + client.set_ws_deflate(true); + async_simple::coro::syncAwait( + client.connect("ws://localhost:8090/ws_extesion")); + + std::string send_str("test"); + + async_simple::coro::syncAwait(client.write_websocket(send_str)); + auto data = async_simple::coro::syncAwait(client.read_websocket()); + CHECK(data.resp_body == "test"); + + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + server.stop(); + client.close(); +} +#endif diff --git a/src/coro_http/tests/test_coro_http_server.cpp b/src/coro_http/tests/test_coro_http_server.cpp new file mode 100644 index 0000000000..1960fbabdc --- /dev/null +++ b/src/coro_http/tests/test_coro_http_server.cpp @@ -0,0 +1,1540 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "doctest.h" +#include "ylt/coro_http/coro_http_client.hpp" +#include "ylt/coro_http/coro_http_server.hpp" + +using namespace coro_http; + +using namespace std::chrono_literals; + +TEST_CASE("test parse ranges") { + bool is_valid = true; + auto vec = parse_ranges("200-999", 10000, is_valid); + CHECK(is_valid); + CHECK(vec == std::vector>{{200, 999}}); + + vec = parse_ranges("-", 10000, is_valid); + CHECK(is_valid); + CHECK(vec == std::vector>{{0, 9999}}); + + vec = parse_ranges("-a", 10000, is_valid); + 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()); + + is_valid = true; + vec = parse_ranges("-900", 10000, is_valid); + CHECK(is_valid); + CHECK(vec == std::vector>{{9100, 9999}}); + + vec = parse_ranges("900", 10000, is_valid); + CHECK(is_valid); + CHECK(vec == std::vector>{{900, 9999}}); + + vec = parse_ranges("200-999, 2000-2499", 10000, is_valid); + CHECK(is_valid); + CHECK(vec == std::vector>{{200, 999}, {2000, 2499}}); + + vec = parse_ranges("200-999, 2000-2499, 9500-", 10000, is_valid); + CHECK(is_valid); + CHECK(vec == std::vector>{ + {200, 999}, {2000, 2499}, {9500, 9999}}); + + vec = parse_ranges("", 10000, is_valid); + CHECK(is_valid); + CHECK(vec == std::vector>{{0, 9999}}); +} + +TEST_CASE("coro_io post") { + auto t1 = async_simple::coro::syncAwait(coro_io::post([] { + })); + CHECK(!t1.hasError()); + auto t2 = async_simple::coro::syncAwait(coro_io::post([] { + throw std::invalid_argument("e"); + })); + CHECK(t2.hasError()); + + auto t3 = async_simple::coro::syncAwait(coro_io::post([] { + return 1; + })); + int r3 = t3.value(); + CHECK(r3 == 1); + + auto t4 = async_simple::coro::syncAwait(coro_io::post([] { + throw std::invalid_argument("e"); + return 1; + })); + CHECK(t4.hasError()); + + try { + std::rethrow_exception(t4.getException()); + } catch (const std::exception &e) { + CHECK(e.what() == std::string("e")); + std::cout << e.what() << "\n"; + } +} + +TEST_CASE("coro_server example, will block") { + return; // remove this line when you run the coro server. + cinatra::coro_http_server server(std::thread::hardware_concurrency(), 9001); + server.set_http_handler( + "/", [](coro_http_request &req, coro_http_response &resp) { + // response in io thread. + std::this_thread::sleep_for(10ms); + resp.set_status_and_content(cinatra::status_type::ok, "hello world"); + }); + + server.set_http_handler( + "/coro", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + co_await coro_io::post([&]() { + // coroutine in other thread. + std::this_thread::sleep_for(10ms); + resp.set_status_and_content(cinatra::status_type::ok, "hello world"); + }); + co_return; + }); + + server.set_http_handler( + "/echo", [](coro_http_request &req, coro_http_response &resp) { + // response in io thread. + resp.set_status_and_content(cinatra::status_type::ok, "hello world"); + }); + server.sync_start(); + CHECK(server.port() > 0); +} + +template +bool create_file(View filename, size_t file_size = 1024) { + std::cout << "begin to open file: " << filename << "\n"; + std::ofstream out(filename, std::ios::binary); + if (!out.is_open()) { + std::cout << "open file: " << filename << " failed\n"; + return false; + } + std::cout << "open file: " << filename << " ok\n"; + std::string str(file_size, 'A'); + out.write(str.data(), str.size()); + return true; +} + +TEST_CASE("test redirect") { + coro_http_server server(1, 9001); + server.set_http_handler( + "/", [](coro_http_request &req, coro_http_response &resp) { + resp.redirect("/test"); + }); + + server.set_http_handler( + "/test", [](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "redirect ok"); + }); + + server.async_start(); + + coro_http_client client{}; + auto result = client.get("http://127.0.0.1:9001/"); + CHECK(result.status == 302); + for (auto [k, v] : result.resp_headers) { + if (k == "Location") { + auto r = client.get(std::string(v)); + CHECK(r.resp_body == "redirect ok"); + break; + } + } +} + +TEST_CASE("test post") { + cinatra::coro_http_server server(1, 9001); + server.set_http_handler( + "/echo", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_status_and_content(status_type::ok, + std::string(req.get_body())); + co_return; + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client{}; + std::string str = "test"; + auto r = + client.post("http://127.0.0.1:9001/echo", str, req_content_type::text); + CHECK(r.status == 200); + CHECK(r.resp_body == "test"); + + r = client.post("/echo", "", req_content_type::text); + CHECK(r.status == 200); + CHECK(r.resp_body == ""); +} + +TEST_CASE("test multiple download") { + coro_http_server server(1, 9001); + server.set_http_handler( + "/", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + // multipart_reader_t multipart(resp.get_conn()); + bool ok; + if (ok = co_await resp.get_conn()->begin_multipart(); !ok) { + co_return; + } + + std::vector vec{"hello", " world", " ok"}; + + for (auto &str : vec) { + if (ok = co_await resp.get_conn()->write_multipart(str, "text/plain"); + !ok) { + co_return; + } + } + + ok = co_await resp.get_conn()->end_multipart(); + }); + + server.async_start(); + + coro_http_client client{}; + auto result = client.get("http://127.0.0.1:9001/"); + CHECK(result.status == 200); + CHECK(result.resp_body == "hello world ok"); +} + +TEST_CASE("test range download") { + create_file("range_test.txt", 64); +#ifdef ASIO_WINDOWS +#else + create_file("中文测试.txt", 64); + create_file(fs::path(u8"utf8中文.txt").string(), 64); +#endif + std::cout << fs::current_path() << "\n"; + coro_http_server server(1, 9001); + server.set_static_res_dir("", ""); + server.set_file_resp_format_type(file_resp_format_type::range); + server.async_start(); + std::this_thread::sleep_for(300ms); + +#ifdef ASIO_WINDOWS +#else + { + // test Chinese file name + coro_http_client client{}; + std::string local_filename = "temp.txt"; + + std::string base_uri = "http://127.0.0.1:9001/"; + std::string path = code_utils::url_encode("中文测试.txt"); + auto result = client.download(base_uri + path, local_filename); + CHECK(result.status == 200); + CHECK(fs::file_size(local_filename) == 64); + } + + { + coro_http_client client{}; + std::string local_filename = "temp1.txt"; + std::string base_uri = "http://127.0.0.1:9001/"; + std::string path = + 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); + } +#endif + + coro_http_client client{}; + std::string filename = "test1.txt"; + std::error_code ec{}; + std::filesystem::remove(filename, ec); + + std::string uri = "http://127.0.0.1:9001/range_test.txt"; + resp_data result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "1-16")); + CHECK(result.status == 206); + CHECK(fs::file_size(filename) == 16); + + filename = "test2.txt"; + result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "0-63")); + CHECK(result.status == 200); + CHECK(fs::file_size(filename) == 64); + + filename = "test2.txt"; + result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "-10")); + CHECK(result.status == 206); + CHECK(fs::file_size(filename) == 10); + + filename = "test2.txt"; + result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "0-200")); + CHECK(result.status == 200); + CHECK(fs::file_size(filename) == 64); + + filename = "test3.txt"; + result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "100-200")); + CHECK(result.status == 416); + + result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "aaa-200")); + CHECK(result.status == 416); +} + +class my_object { + public: + void normal(coro_http_request &req, coro_http_response &response) { + response.set_status_and_content(status_type::ok, "ok"); + } + + async_simple::coro::Lazy lazy(coro_http_request &req, + coro_http_response &response) { + response.set_status_and_content(status_type::ok, "ok lazy"); + co_return; + } +}; + +TEST_CASE("set http handler") { + cinatra::coro_http_server server(1, 9001); + auto &router = server.get_router(); + auto &handlers = router.get_handlers(); + + server.set_http_handler( + "/", [](coro_http_request &req, coro_http_response &response) { + response.set_status_and_content(status_type::ok, "ok"); + }); + CHECK(handlers.size() == 1); + server.set_http_handler( + "/", [](coro_http_request &req, coro_http_response &response) { + response.set_status_and_content(status_type::ok, "ok"); + }); + CHECK(handlers.size() == 1); + server.set_http_handler( + "/aa", [](coro_http_request &req, coro_http_response &response) { + response.set_status_and_content(status_type::ok, "ok"); + }); + CHECK(handlers.size() == 2); + + server.set_http_handler( + "/bb", [](coro_http_request &req, coro_http_response &response) { + response.set_status_and_content(status_type::ok, "ok"); + }); + CHECK(handlers.size() == 4); + + cinatra::coro_http_server server2(1, 9001); + server2.set_http_handler( + "/", [](coro_http_request &req, coro_http_response &response) { + response.set_status_and_content(status_type::ok, "ok"); + }); + + auto &handlers2 = server2.get_router().get_handlers(); + CHECK(handlers2.size() == 1); + + my_object o{}; + // member function + server2.set_http_handler("/test", &my_object::normal, o); + server2.set_http_handler("/test_lazy", &my_object::lazy, o); + CHECK(handlers2.size() == 2); + + auto coro_func = + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + response.set_status_and_content(status_type::ok, "ok"); + co_return; + }; + + auto &coro_handlers = router.get_coro_handlers(); + server.set_http_handler("/", coro_func); + CHECK(coro_handlers.size() == 1); + server.set_http_handler("/", coro_func); + CHECK(coro_handlers.size() == 1); + server.set_http_handler("/aa", coro_func); + CHECK(coro_handlers.size() == 2); + + server.set_http_handler("/bb", coro_func); + CHECK(coro_handlers.size() == 4); +} + +TEST_CASE("test server start and stop") { + cinatra::coro_http_server server(1, 9000); + auto future = server.async_start(); + + cinatra::coro_http_server server2(1, 9000); + auto future2 = server2.async_start(); + future2.wait(); + auto ec = future2.value(); + CHECK(ec == asio::error::address_in_use); +} + +TEST_CASE("test server sync_start and stop") { + cinatra::coro_http_server server(1, 0); + + std::promise promise; + std::error_code ec; + std::thread thd([&] { + promise.set_value(); + ec = server.sync_start(); + }); + promise.get_future().wait(); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + server.stop(); + thd.join(); + CHECK(server.port() > 0); + CHECK(ec == asio::error::operation_aborted); +} + +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"); + CHECK(!value.empty()); + + auto value1 = req.get_header_value("connection1"); + CHECK(value1.empty()); + + auto value2 = req.get_query_value("aa"); + CHECK(value2 == "1"); + + auto value3 = req.get_query_value("bb"); + CHECK(value3 == "test"); + + auto value4 = req.get_query_value("cc"); + CHECK(value4.empty()); + + auto headers = req.get_headers(); + CHECK(!headers.empty()); + + auto queries = req.get_queries(); + CHECK(!queries.empty()); + + resp.set_keepalive(true); + resp.set_status_and_content(cinatra::status_type::ok, "hello world"); + }); + + server.set_http_handler( + "/test1", [](coro_http_request &req, coro_http_response &resp) { + CHECK(req.get_method() == "POST"); + CHECK(req.get_url() == "/test1"); + CHECK(req.get_conn()->local_address() == "127.0.0.1:9001"); + CHECK(req.get_conn()->remote_address().find("127.0.0.1:") != + std::string::npos); + resp.add_header("Host", "Cinatra"); + resp.set_status_and_content(cinatra::status_type::ok, "hello world"); + }); + + server.set_http_handler( + "/test_coro", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + co_await coro_io::post([&] { + resp.set_status(cinatra::status_type::ok); + resp.set_content("hello world in coro"); + }); + }); + + server.set_http_handler( + "/empty", [](coro_http_request &req, coro_http_response &resp) { + resp.add_header("Host", "Cinatra"); + resp.set_status_and_content(cinatra::status_type::ok, ""); + }); + + server.set_http_handler( + "/close", [](coro_http_request &req, coro_http_response &resp) { + resp.set_keepalive(false); + resp.set_status_and_content(cinatra::status_type::ok, "hello"); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client{}; + resp_data result; + result = client.get("http://127.0.0.1:9001/test?aa=1&bb=test"); + CHECK(result.status == 200); + CHECK(result.resp_body == "hello world"); + + result = + client.post("http://127.0.0.1:9001/test1", "", req_content_type::text); + CHECK(result.status == 200); + CHECK(result.resp_body == "hello world"); + + result = client.get("http://127.0.0.1:9001/test_coro"); + CHECK(result.status == 200); + CHECK(result.resp_body == "hello world in coro"); + + result = client.get("http://127.0.0.1:9001/not_exist"); + CHECK(result.status == 404); + + result = client.get("http://127.0.0.1:9001/empty"); + CHECK(result.status == 200); + auto &headers = result.resp_headers; + auto it = + std::find_if(headers.begin(), headers.end(), [](http_header &header) { + return header.name == "Host" && header.value == "Cinatra"; + }); + CHECK(it != headers.end()); + CHECK(result.resp_body.empty()); + + client.add_header("Connection", "close"); + result = client.get("http://127.0.0.1:9001/close"); + CHECK(result.status == 200); + + server.stop(); +} + +TEST_CASE("test alias") { + http_server server(1, 9001); + server.set_http_handler("/", [](request &req, response &resp) { + resp.set_status_and_content(status_type::ok, "ok"); + }); + server.async_start(); + std::this_thread::sleep_for(300ms); + + coro_http_client client{}; + auto result = client.get("http://127.0.0.1:9001/"); + CHECK(result.resp_body == "ok"); +} + +struct log_t { + bool before(coro_http_request &, coro_http_response &) { + std::cout << "before log" << std::endl; + return true; + } + + bool after(coro_http_request &, coro_http_response &res) { + std::cout << "after log" << std::endl; + res.add_header("aaaa", "bbcc"); + return true; + } +}; + +struct check_t { + bool before(coro_http_request &, coro_http_response &) { + std::cout << "check before" << std::endl; + return true; + } +}; + +struct get_data { + bool before(coro_http_request &req, coro_http_response &res) { + req.set_aspect_data("hello", "world"); + return true; + } +}; + +TEST_CASE("test aspects") { + coro_http_server server(1, 9001); + 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 + + server.set_static_res_dir("", "", log_t{}, check_t{}); + server.set_http_handler( + "/", + [](coro_http_request &req, coro_http_response &resp) { + resp.add_header("aaaa", "bbcc"); + resp.set_status_and_content(status_type::ok, "ok"); + }, + log_t{}, check_t{}); + + server.set_http_handler( + "/aspect", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + auto &val = req.get_aspect_data(); + CHECK(val[0] == "hello"); + CHECK(val[1] == "world"); + resp.set_status_and_content(status_type::ok, "ok"); + co_return; + }, + get_data{}); + server.async_start(); + std::this_thread::sleep_for(300ms); + + coro_http_client client{}; + auto result = client.get("http://127.0.0.1:9001/"); + + auto check = [](auto &result) { + bool has_str = false; + for (auto [k, v] : result.resp_headers) { + if (k == "aaaa") { + if (v == "bbcc") { + has_str = true; + } + break; + } + } + CHECK(has_str); + }; + + check(result); + + 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); + + result = client.get("http://127.0.0.1:9001/aspect"); + CHECK(result.status == 200); +} + +TEST_CASE("use out context") { + 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, 9001); + server.set_http_handler( + "/out_ctx", [](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "use out ctx"); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + { + coro_http_client client1{}; + auto result = client1.get("http://127.0.0.1:9001/out_ctx"); + CHECK(result.status == 200); + CHECK(result.resp_body == "use out ctx"); + } + + server.stop(); + + work.reset(); + thd.join(); +} + +TEST_CASE("delay reply, server stop, form-urlencode, qureies, throw") { + cinatra::coro_http_server server(1, 9001); + + server.set_http_handler( + "/delay2", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_delay(true); + std::this_thread::sleep_for(200ms); + resp.set_status_and_content(status_type::ok, "delay reply in coro"); + co_await resp.get_conn()->reply(); + }); + + server.set_http_handler( + "/form-urlencode", [](coro_http_request &req, coro_http_response &resp) { + 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"); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + resp_data result; + coro_http_client client1{}; + result = client1.get("http://127.0.0.1:9001/delay2"); + CHECK(result.status == 200); + CHECK(result.resp_body == "delay reply in coro"); + + result = client1.post("http://127.0.0.1:9001/form-urlencode", + "theCityName=58367&aa=%22bbb%22", + req_content_type::form_url_encode); + CHECK(result.status == 200); + CHECK(result.resp_body == "form-urlencode"); + + result = client1.get("http://127.0.0.1:9001/throw"); + CHECK(result.status == 503); + + server.stop(); + std::cout << "ok\n"; +} + +async_simple::coro::Lazy chunked_upload1(coro_http_client &client) { + std::string filename = "test.txt"; + create_file(filename, 1010); + + coro_io::coro_file file{}; + file.open(filename, std::ios::in); + + std::string buf; + detail::resize(buf, 100); + + auto fn = [&file, &buf]() -> async_simple::coro::Lazy { + auto [ec, size] = co_await file.async_read(buf.data(), buf.size()); + co_return read_result{{buf.data(), buf.size()}, file.eof(), ec}; + }; + + auto result = co_await client.async_upload_chunked( + "http://127.0.0.1:9001/chunked"sv, http_method::POST, std::move(fn)); + co_return result; +} + +TEST_CASE("chunked request") { + cinatra::coro_http_server server(1, 9001); + server.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"); + }); + + server.set_http_handler( + "/write_chunked", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_format_type(format_type::chunked); + bool ok; + if (ok = co_await resp.get_conn()->begin_chunked(); !ok) { + co_return; + } + + std::vector vec{"hello", " world", " ok"}; + + for (auto &str : vec) { + if (ok = co_await resp.get_conn()->write_chunked(str); !ok) { + co_return; + } + } + + ok = co_await resp.get_conn()->end_chunked(); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client{}; + auto r = async_simple::coro::syncAwait(chunked_upload1(client)); + CHECK(r.status == 200); + CHECK(r.resp_body == "chunked ok"); + + auto ss = std::make_shared(); + *ss << "hello world"; + auto result = async_simple::coro::syncAwait(client.async_upload_chunked( + "http://127.0.0.1:9001/chunked"sv, http_method::POST, ss)); + CHECK(result.status == 200); + CHECK(result.resp_body == "chunked ok"); + + result = client.get("http://127.0.0.1:9001/write_chunked"); + CHECK(result.status == 200); + CHECK(result.resp_body == "hello world ok"); +} + +TEST_CASE("test websocket with chunked") { + int ws_chunk_size = 100; + cinatra::coro_http_server server(1, 9001); + server.set_http_handler( + "/ws_source", + [ws_chunk_size](coro_http_request &req, coro_http_response &resp) + -> async_simple::coro::Lazy { + CHECK(req.get_content_type() == content_type::websocket); + std::string out_str; + websocket_result result{}; + while (!result.eof) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + if (result.type == ws_frame_type::WS_CLOSE_FRAME) { + std::cout << "close frame\n"; + CHECK(result.data.empty()); + break; + } + + std::cout << result.data.size() << "\n"; + + if (result.data.size() < ws_chunk_size) { + CHECK(result.data.size() == 24); + CHECK(result.eof); + } + else { + CHECK(result.data.size() == ws_chunk_size); + CHECK(!result.eof); + } + out_str.append(result.data); + + auto ec = co_await req.get_conn()->write_websocket(result.data); + if (ec) { + continue; + } + } + + CHECK(out_str.size() == 1024); + std::cout << out_str << "\n"; + }); + server.async_start(); + + coro_http_client client{}; + async_simple::coro::syncAwait( + client.connect("ws://127.0.0.1:9001/ws_source")); + + std::string filename = "test.tmp"; + create_file(filename); + std::ifstream in(filename, std::ios::binary); + + std::string str; + str.resize(ws_chunk_size); + + auto source_fn = [&]() -> async_simple::coro::Lazy { + size_t size = in.read(str.data(), str.size()).gcount(); + bool eof = in.eof(); + co_return read_result{{str.data(), size}, eof}; + }; + + async_simple::coro::syncAwait( + client.write_websocket(std::move(source_fn), opcode::binary)); + + auto data = async_simple::coro::syncAwait(client.read_websocket()); + if (data.net_err) { + std::cout << "ws_msg net error " << data.net_err.message() << "\n"; + return; + } + + size_t msg_len = data.resp_body.size(); + + std::cout << "ws msg len: " << msg_len << std::endl; + CHECK(!data.resp_body.empty()); + std::this_thread::sleep_for(300ms); + server.stop(); +} + +TEST_CASE("test websocket") { + cinatra::coro_http_server server(1, 9001); + server.set_http_handler( + "/ws_echo", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + CHECK(req.get_content_type() == content_type::websocket); + std::ofstream out_file("test.temp", std::ios::binary); + websocket_result result{}; + while (true) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + out_file.close(); + break; + } + + if (result.type == ws_frame_type::WS_CLOSE_FRAME) { + std::cout << "close frame\n"; + out_file.close(); + break; + } + + 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"; + 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) { + break; + } + } + }); + server.async_start(); + std::this_thread::sleep_for(200ms); // wait for server handle all messages + + auto lazy = []() -> async_simple::coro::Lazy { + coro_http_client client{}; + co_await client.connect("ws://127.0.0.1:9001/ws_echo"); + co_await client.write_websocket(std::string_view("test2fdsaf"), + opcode::binary); + auto data = co_await client.read_websocket(); + CHECK(data.resp_body == "test2fdsaf"); + co_await client.write_websocket("test_ws"); + data = co_await client.read_websocket(); + CHECK(data.resp_body == "test_ws"); + co_await client.write_websocket("PING", opcode::ping); + data = co_await client.read_websocket(); + CHECK(data.resp_body == "pong"); + co_await client.write_websocket("PONG", opcode::pong); + data = co_await client.read_websocket(); + CHECK(data.resp_body == "ping"); + co_await client.write_websocket_close("normal close"); + data = co_await client.read_websocket(); + CHECK(data.resp_body == "normal close"); + CHECK(data.net_err == asio::error::eof); + }; + + async_simple::coro::syncAwait(lazy()); +} + +TEST_CASE("check small ws file") { + std::string filename = "test.temp"; + std::error_code ec; + size_t file_size = std::filesystem::file_size(filename, ec); + if (ec) { + return; + } + std::ifstream file(filename, std::ios::binary); + if (!file) { + return; + } + std::string str; + str.resize(file_size); + + file.read(str.data(), str.size()); + CHECK(str == "test2fdsaftest_ws"); + std::filesystem::remove(filename, ec); +} + +TEST_CASE("test websocket binary data") { + cinatra::coro_http_server server(1, 9001); + server.set_http_handler( + "/short_binary", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + CHECK(req.get_content_type() == content_type::websocket); + websocket_result result{}; + while (true) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + if (result.type == ws_frame_type::WS_CLOSE_FRAME) { + std::cout << "close frame\n"; + CHECK(result.data.empty()); + break; + } + + if (result.type == ws_frame_type::WS_BINARY_FRAME) { + CHECK(result.data.size() == 127); + } + } + }); + server.set_http_handler( + "/medium_binary", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + CHECK(req.get_content_type() == content_type::websocket); + websocket_result result{}; + while (true) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + if (result.type == ws_frame_type::WS_CLOSE_FRAME) { + std::cout << "close frame\n"; + CHECK(result.data.empty()); + break; + } + + if (result.type == ws_frame_type::WS_BINARY_FRAME) { + CHECK(result.data.size() == 65535); + } + } + }); + server.set_http_handler( + "/long_binary", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + CHECK(req.get_content_type() == content_type::websocket); + websocket_result result{}; + while (true) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + if (result.type == ws_frame_type::WS_CLOSE_FRAME) { + std::cout << "close frame\n"; + CHECK(result.data.empty()); + break; + } + + if (result.type == ws_frame_type::WS_BINARY_FRAME) { + CHECK(result.data.size() == 65536); + } + } + }); + server.async_start(); + + auto client1 = std::make_shared(); + async_simple::coro::syncAwait( + client1->connect("ws://127.0.0.1:9001/short_binary")); + + std::string short_str(127, 'A'); + async_simple::coro::syncAwait( + client1->write_websocket(std::move(short_str), opcode::binary)); + + auto client2 = std::make_shared(); + async_simple::coro::syncAwait( + client2->connect("ws://127.0.0.1:9001/medium_binary")); + + std::string medium_str(65535, 'A'); + async_simple::coro::syncAwait( + client2->write_websocket(std::move(medium_str), opcode::binary)); + + auto client3 = std::make_shared(); + async_simple::coro::syncAwait( + client3->connect("ws://127.0.0.1:9001/long_binary")); + + std::string long_str(65536, 'A'); + async_simple::coro::syncAwait( + client3->write_websocket(std::move(long_str), opcode::binary)); + + async_simple::coro::syncAwait(client1->write_websocket_close()); + async_simple::coro::syncAwait(client2->write_websocket_close()); + async_simple::coro::syncAwait(client3->write_websocket_close()); +} + +TEST_CASE("check connecton timeout") { + cinatra::coro_http_server server(1, 9001); + server.set_check_duration(std::chrono::microseconds(600)); + server.set_timeout_duration(std::chrono::microseconds(500)); + server.set_http_handler( + "/", [](coro_http_request &req, coro_http_response &response) { + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client; + client.get("http://127.0.0.1:9001/"); + + // wait for timeout, the timeout connections will be removed by server. + std::this_thread::sleep_for(std::chrono::seconds(1)); + CHECK(server.connection_count() == 0); +} + +TEST_CASE("test websocket with different message size") { + cinatra::coro_http_server server(1, 9001); + server.set_http_handler( + "/ws_echo1", + [](cinatra::coro_http_request &req, + cinatra::coro_http_response &resp) -> async_simple::coro::Lazy { + REQUIRE(req.get_content_type() == cinatra::content_type::websocket); + cinatra::websocket_result result{}; + + while (true) { + req.get_conn()->set_ws_max_size( + 70000); // default max size 8M, you can control the size. + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + if (result.type == cinatra::ws_frame_type::WS_CLOSE_FRAME) { + REQUIRE(result.data.empty()); + break; + } + + auto ec = co_await req.get_conn()->write_websocket(result.data); + if (ec) { + break; + } + } + }); + server.async_start(); + + 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"); + co_await client.write_websocket(str); + auto data = co_await client.read_websocket(); + CHECK(data.resp_body.size() == str.size()); + co_await client.write_websocket_close(); + data = co_await client.read_websocket(); + CHECK(data.resp_body.empty()); + CHECK(data.net_err == asio::error::eof); + }; + + SUBCASE("medium message - 16 bit length") { + std::string medium_message( + 65535, 'x'); // 65,535 'x' characters for the medium message test. + + async_simple::coro::syncAwait(lazy(medium_message)); + } + + SUBCASE("large message - 64 bit length") { + std::string large_message( + 70000, 'x'); // 70,000 'x' characters for the large message test. + + async_simple::coro::syncAwait(lazy(large_message)); + } + + server.stop(); +} + +#ifdef CINATRA_ENABLE_SSL +TEST_CASE("test ssl server") { + cinatra::coro_http_server server(1, 9001); + 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"); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client{}; + [[maybe_unused]] auto r = client.init_ssl(); + + auto result = client.get("https://127.0.0.1:9001/ssl"); + CHECK(result.status == 200); + CHECK(result.resp_body == "ssl"); + std::cout << "ssl ok\n"; +} +#endif + +TEST_CASE("test http download server") { + cinatra::coro_http_server server(1, 9001); + std::string filename = "test_download.txt"; + create_file(filename, 1010); + + // curl http://127.0.0.1:9001/download/test_download.txt will download + // test_download.txt file + server.set_transfer_chunked_size(100); + server.set_static_res_dir("download", ""); + server.async_start(); + std::this_thread::sleep_for(200ms); + + { + coro_http_client client{}; + auto result = async_simple::coro::syncAwait(client.async_download( + "http://127.0.0.1:9001/download/test_download.txt", "download.txt")); + + CHECK(result.status == 200); + std::string download_file = fs::absolute("download.txt").string(); + std::ifstream ifs(download_file, std::ios::binary); + std::string content((std::istreambuf_iterator(ifs)), + (std::istreambuf_iterator())); + CHECK(content.size() == 1010); + CHECK(content[0] == 'A'); + } + + { + coro_http_client client{}; + auto result = async_simple::coro::syncAwait(client.async_download( + "http://127.0.0.1:9001/download/test_download.txt", "download1.txt", + "0-")); + + CHECK(result.status == 200); + std::string download_file = fs::absolute("download1.txt").string(); + std::ifstream ifs(download_file, std::ios::binary); + std::string content((std::istreambuf_iterator(ifs)), + (std::istreambuf_iterator())); + CHECK(content.size() == 1010); + CHECK(content[0] == 'A'); + } +} + +TEST_CASE("test restful api") { + cinatra::coro_http_server server(1, 9001); + + server.set_http_handler( + "/test2/{}/test3/{}", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + co_await coro_io::post([&]() { + // coroutine in other thread. + CHECK(req.matches_.str(1) == "name"); + CHECK(req.matches_.str(2) == "test"); + resp.set_status_and_content(cinatra::status_type::ok, "hello world"); + }); + co_return; + }); + + server.set_http_handler( + R"(/numbers/(\d+)/test/(\d+))", + [](coro_http_request &req, coro_http_response &response) { + CHECK(req.matches_.str(1) == "100"); + CHECK(req.matches_.str(2) == "200"); + response.set_status_and_content(status_type::ok, "number regex ok"); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client; + client.get("http://127.0.0.1:9001/test2/name/test3/test"); + client.get("http://127.0.0.1:9001/numbers/100/test/200"); +} + +TEST_CASE("test radix tree restful api") { + cinatra::coro_http_server server(1, 9001); + + server.set_http_handler( + "/", [](coro_http_request &req, coro_http_response &response) { + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/user/:id", [](coro_http_request &req, coro_http_response &response) { + CHECK(req.params_["id"] == "cinatra"); + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/user/:id/subscriptions", + [](coro_http_request &req, coro_http_response &response) { + CHECK(req.params_["id"] == "subid"); + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/users/:userid/subscriptions/:subid", + [](coro_http_request &req, coro_http_response &response) { + CHECK(req.params_["userid"] == "ultramarines"); + CHECK(req.params_["subid"] == "guilliman"); + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/values/:x/:y/:z", + [](coro_http_request &req, coro_http_response &response) { + CHECK(req.params_["x"] == "guilliman"); + CHECK(req.params_["y"] == "cawl"); + CHECK(req.params_["z"] == "yvraine"); + response.set_status_and_content(status_type::ok, "ok"); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client; + client.get("http://127.0.0.1:9001/user/cinatra"); + client.get("http://127.0.0.1:9001/user/subid/subscriptions"); + client.get("http://127.0.0.1:9001/user/ultramarines/subscriptions/guilliman"); + client.get("http://127.0.0.1:9001/value/guilliman/cawl/yvraine"); + + client.post("http://127.0.0.1:9001/user/cinatra", "hello", + req_content_type::string); + client.post("http://127.0.0.1:9001/user/subid/subscriptions", "hello", + req_content_type::string); + client.post("http://127.0.0.1:9001/user/ultramarines/subscriptions/guilliman", + "hello", req_content_type::string); + client.post("http://127.0.0.1:9001/value/guilliman/cawl/yvraine", "hello", + req_content_type::string); +} + +TEST_CASE("test coro radix tree restful api") { + cinatra::coro_http_server server(1, 9001); + + server.set_http_handler( + "/", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&]() { + response.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.set_http_handler( + "/user/:id", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&]() { + CHECK(req.params_["id"] == "cinatra"); + response.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.set_http_handler( + "/user/:id/subscriptions", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&] { + CHECK(req.params_["id"] == "subid"); + response.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.set_http_handler( + "/users/:userid/subscriptions/:subid", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&] { + CHECK(req.params_["userid"] == "ultramarines"); + CHECK(req.params_["subid"] == "guilliman"); + response.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.set_http_handler( + "/values/:x/:y/:z", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&] { + CHECK(req.params_["x"] == "guilliman"); + CHECK(req.params_["y"] == "cawl"); + CHECK(req.params_["z"] == "yvraine"); + response.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.set_http_handler( + "/ai/robot/:messages", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&]() { + CHECK(req.params_["messages"] == "android"); + response.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client; + client.get("http://127.0.0.1:9001/user/cinatra"); + client.get("http://127.0.0.1:9001/user/subid/subscriptions"); + client.get("http://127.0.0.1:9001/user/ultramarines/subscriptions/guilliman"); + client.get("http://127.0.0.1:9001/value/guilliman/cawl/yvraine"); + + client.post("http://127.0.0.1:9001/user/cinatra", "hello", + req_content_type::string); + client.post("http://127.0.0.1:9001/user/subid/subscriptions", "hello", + req_content_type::string); + client.post("http://127.0.0.1:9001/user/ultramarines/subscriptions/guilliman", + "hello", req_content_type::string); + client.post("http://127.0.0.1:9001/value/guilliman/cawl/yvraine", "hello", + req_content_type::string); + client.post("http://127.0.0.1:9001/ai/robot/android", "hello", + req_content_type::string); +} + +TEST_CASE("test reverse proxy") { + SUBCASE( + "exception tests: empty hosts, empty weights test or count not equal") { + cinatra::coro_http_server server(1, 9002); + CHECK_THROWS_AS(server.set_http_proxy_handler( + "/", {}, coro_io::load_blance_algorithm::WRR, {2, 1}), + std::invalid_argument); + + CHECK_THROWS_AS(server.set_http_proxy_handler( + "/", {"127.0.0.1:8801", "127.0.0.1:8802"}, + coro_io::load_blance_algorithm::WRR), + std::invalid_argument); + + CHECK_THROWS_AS(server.set_http_proxy_handler( + "/", {"127.0.0.1:8801", "127.0.0.1:8802"}, + coro_io::load_blance_algorithm::WRR, {1}), + std::invalid_argument); + + CHECK_THROWS_AS( + server.set_http_proxy_handler("/", {}), + std::invalid_argument); + } + + cinatra::coro_http_server web_one(1, 9001); + + web_one.set_http_handler( + "/", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&]() { + response.set_status_and_content(status_type::ok, "web1"); + }); + }); + + web_one.async_start(); + + cinatra::coro_http_server web_two(1, 9002); + + web_two.set_http_handler( + "/", + [](coro_http_request &req, + coro_http_response &response) -> async_simple::coro::Lazy { + co_await coro_io::post([&]() { + response.set_status_and_content(status_type::ok, "web2"); + }); + }); + + web_two.async_start(); + + cinatra::coro_http_server web_three(1, 9003); + + web_three.set_http_handler( + "/", [](coro_http_request &req, coro_http_response &response) { + response.set_status_and_content(status_type::ok, "web3"); + }); + + web_three.async_start(); + + std::this_thread::sleep_for(200ms); + + coro_http_server proxy_wrr(2, 8090); + proxy_wrr.set_http_proxy_handler( + "/", {"127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"}, + coro_io::load_blance_algorithm::WRR, {10, 5, 5}, log_t{}, check_t{}); + + coro_http_server proxy_rr(2, 8091); + proxy_rr.set_http_proxy_handler( + "/", {"127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"}, + coro_io::load_blance_algorithm::RR, {}, log_t{}); + + coro_http_server proxy_random(2, 8092); + proxy_random.set_http_proxy_handler( + "/", {"127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"}); + + coro_http_server proxy_all(2, 8093); + proxy_all.set_http_proxy_handler( + "/", {"127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"}); + + proxy_wrr.async_start(); + proxy_rr.async_start(); + proxy_random.async_start(); + proxy_all.async_start(); + + std::this_thread::sleep_for(200ms); + + coro_http_client client_rr; + resp_data resp_rr = client_rr.get("http://127.0.0.1:8091/"); + CHECK(resp_rr.resp_body == "web1"); + resp_rr = client_rr.get("http://127.0.0.1:8091/"); + CHECK(resp_rr.resp_body == "web2"); + resp_rr = client_rr.get("http://127.0.0.1:8091/"); + CHECK(resp_rr.resp_body == "web3"); + resp_rr = client_rr.get("http://127.0.0.1:8091/"); + CHECK(resp_rr.resp_body == "web1"); + resp_rr = client_rr.get("http://127.0.0.1:8091/"); + CHECK(resp_rr.resp_body == "web2"); + resp_rr = client_rr.post("http://127.0.0.1:8091/", "test content", + req_content_type::text); + CHECK(resp_rr.resp_body == "web3"); + + coro_http_client client_wrr; + resp_data resp = client_wrr.get("http://127.0.0.1:8090/"); + CHECK(resp.resp_body == "web1"); + resp = client_wrr.get("http://127.0.0.1:8090/"); + CHECK(resp.resp_body == "web1"); + resp = client_wrr.get("http://127.0.0.1:8090/"); + CHECK(resp.resp_body == "web2"); + resp = client_wrr.get("http://127.0.0.1:8090/"); + CHECK(resp.resp_body == "web3"); + + coro_http_client client_random; + resp_data resp_random = client_random.get("http://127.0.0.1:8092/"); + std::cout << resp_random.resp_body << "\n"; + CHECK(!resp_random.resp_body.empty()); + + coro_http_client client_all; + resp_random = client_all.post("http://127.0.0.1:8093/", "test content", + req_content_type::text); + std::cout << resp_random.resp_body << "\n"; + CHECK(!resp_random.resp_body.empty()); +} + +TEST_CASE("test reverse proxy websocket") { + coro_http_server server(1, 9001); + server.set_http_handler( + "/ws_echo", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + CHECK(req.get_content_type() == content_type::websocket); + websocket_result result{}; + while (true) { + result = co_await req.get_conn()->read_websocket(); + if (result.ec) { + break; + } + + auto ec = co_await req.get_conn()->write_websocket(result.data); + if (ec) { + break; + } + } + }); + server.async_start(); + + coro_http_server proxy_server(1, 9002); + proxy_server.set_websocket_proxy_handler("/ws_echo", + {"ws://127.0.0.1:9001/ws_echo"}); + proxy_server.async_start(); + std::this_thread::sleep_for(200ms); + + coro_http_client client{}; + auto r = async_simple::coro::syncAwait( + client.connect("ws://127.0.0.1:9002/ws_echo")); + CHECK(!r.net_err); + for (int i = 0; i < 10; i++) { + async_simple::coro::syncAwait(client.write_websocket("test websocket")); + auto data = async_simple::coro::syncAwait(client.read_websocket()); + std::cout << data.resp_body << "\n"; + CHECK(data.resp_body == "test websocket"); + } +} diff --git a/src/coro_http/tests/test_http_parse.cpp b/src/coro_http/tests/test_http_parse.cpp new file mode 100644 index 0000000000..aa9a203cc0 --- /dev/null +++ b/src/coro_http/tests/test_http_parse.cpp @@ -0,0 +1,52 @@ +#include + +#include "doctest.h" +#include "ylt/coro_http/coro_http_client.hpp" +#include "ylt/coro_http/coro_http_server.hpp" + +using namespace coro_http; + +#define REQ \ + "GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg " \ + "HTTP/1.1\r\n" \ + "Host: www.kittyhell.com\r\n" \ + "User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; " \ + "rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 " \ + "Pathtraq/0.9\r\n" \ + "Accept: " \ + "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" \ + "Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n" \ + "Accept-Encoding: gzip,deflate\r\n" \ + "Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n" \ + "Keep-Alive: 115\r\n" \ + "Connection: keep-alive\r\n" \ + "Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; " \ + "__utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; " \ + "__utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor." \ + "com|utmcct=/reader/|utmcmd=referral\r\n" \ + "\r\n" + +TEST_CASE("http parser test") { + const char *method; + size_t method_len; + const char *path; + size_t path_len; + int minor_version; + cinatra::http_header headers[64]; + size_t num_headers; + int i, ret; + bool has_connection, has_close, has_upgrade, has_query; + + num_headers = sizeof(headers) / sizeof(headers[0]); + ret = cinatra::detail::phr_parse_request( + REQ, sizeof(REQ) - 1, &method, &method_len, &path, &path_len, + &minor_version, headers, &num_headers, 0, has_connection, has_close, + has_upgrade, has_query); + CHECK(ret == 703); + CHECK(strncmp(method, "GET", method_len) == 0); + CHECK(minor_version == 1); + std::string name(headers[0].name); + std::string value(headers[0].value); + CHECK(name == "Host"); + CHECK(value == "www.kittyhell.com"); +} diff --git a/src/coro_io/tests/test_coro_channel.cpp b/src/coro_io/tests/test_coro_channel.cpp index fa5135dce1..a130b35ad5 100644 --- a/src/coro_io/tests/test_coro_channel.cpp +++ b/src/coro_io/tests/test_coro_channel.cpp @@ -20,7 +20,7 @@ using namespace std::chrono_literals; #endif async_simple::coro::Lazy test_channel() { - auto ch = coro_io::create_load_blancer(1000); + auto ch = coro_io::create_channel(1000); co_await coro_io::async_send(ch, 41); co_await coro_io::async_send(ch, 42); @@ -37,8 +37,8 @@ async_simple::coro::Lazy test_channel() { async_simple::coro::Lazy test_select_channel() { using namespace coro_io; using namespace async_simple::coro; - auto ch1 = coro_io::create_load_blancer(1000); - auto ch2 = coro_io::create_load_blancer(1000); + auto ch1 = coro_io::create_channel(1000); + auto ch2 = coro_io::create_channel(1000); co_await async_send(ch1, 41); co_await async_send(ch2, 42);