From 6ee8f04935cd15e7c716997eea6116ea01508896 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 14 Jan 2025 14:05:39 +0800 Subject: [PATCH] Fix and tests (#685) --- include/cinatra/coro_http_response.hpp | 1 + include/cinatra/uri.hpp | 28 --- tests/CMakeLists.txt | 3 + tests/test_cinatra.cpp | 19 +- tests/test_coro_http_server.cpp | 108 ++++++++- tests/test_http_parse.cpp | 304 +++++++++++++++++++++---- 6 files changed, 375 insertions(+), 88 deletions(-) diff --git a/include/cinatra/coro_http_response.hpp b/include/cinatra/coro_http_response.hpp index 0b106f52..b7eb761b 100644 --- a/include/cinatra/coro_http_response.hpp +++ b/include/cinatra/coro_http_response.hpp @@ -376,6 +376,7 @@ class coro_http_response { boundary_.clear(); has_set_content_ = false; cookies_.clear(); + need_date_ = true; } void set_shrink_to_fit(bool r) { need_shrink_every_time_ = r; } diff --git a/include/cinatra/uri.hpp b/include/cinatra/uri.hpp index 92e96077..75c1233c 100644 --- a/include/cinatra/uri.hpp +++ b/include/cinatra/uri.hpp @@ -261,34 +261,6 @@ class uri_t { std::string get_query() const { return std::string(query); } }; -inline std::string url_encode(const std::string &str) { - std::string new_str = ""; - char c; - int ic; - const char *chars = str.c_str(); - char buf_hex[10]; - size_t len = strlen(chars); - - for (size_t i = 0; i < len; i++) { - c = chars[i]; - ic = c; - // uncomment this if you want to encode spaces with + - /*if (c==' ') new_str += '+'; - else */ - if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') - new_str += c; - else { - sprintf(buf_hex, "%X", c); - if (ic < 16) - new_str += "%0"; - else - new_str += "%"; - new_str += buf_hex; - } - } - return new_str; -} - struct context { std::string host; std::string port; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 13facec0..8db3745a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -106,6 +106,9 @@ endif () add_executable(test_http_parse test_http_parse.cpp ) +if (UNIX) + target_link_libraries(test_http_parse ${ZLIB_LIBRARIES}) +endif() if (ENABLE_SIMD STREQUAL "AARCH64") if (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64") diff --git a/tests/test_cinatra.cpp b/tests/test_cinatra.cpp index 5e3f97c4..e71c2a78 100644 --- a/tests/test_cinatra.cpp +++ b/tests/test_cinatra.cpp @@ -722,7 +722,7 @@ TEST_CASE("test response") { server.set_http_handler( "/empty1", [&](coro_http_request &req, coro_http_response &resp) { resp.set_content_type<2>(); - CHECK(!resp.need_date()); + CHECK(resp.need_date()); resp.add_header_span({span.data(), span.size()}); resp.set_status_and_content_view(status_type::ok, ""); @@ -730,7 +730,7 @@ TEST_CASE("test response") { server.set_http_handler( "/empty2", [&](coro_http_request &req, coro_http_response &resp) { resp.set_content_type<2>(); - CHECK(!resp.need_date()); + CHECK(resp.need_date()); resp.add_header_span({span.data(), span.size()}); resp.set_status_and_content(status_type::ok, ""); @@ -1380,9 +1380,11 @@ TEST_CASE("test request with out buffer") { 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()); + if (ok && result.resp_body.size() <= 1024 * 64) { + std::string_view sv(str.data(), result.resp_body.size()); + // CHECK(result.resp_body == sv); + CHECK(client.is_body_in_out_buf()); + } } } @@ -1480,7 +1482,7 @@ TEST_CASE("test coro_http_client async_http_connect") { CHECK(r.status >= 200); r = async_simple::coro::syncAwait(client1.connect("http://cn.bing.com")); - CHECK(r.status == 200); + CHECK(r.status >= 200); } TEST_CASE("test collect all") { @@ -2501,9 +2503,8 @@ TEST_CASE("test multipart and chunked return error") { 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); + client.set_conn_timeout(1s); + client.get("http://www.baidu.com"); } TEST_CASE("test coro_http_client add header and url queries") { diff --git a/tests/test_coro_http_server.cpp b/tests/test_coro_http_server.cpp index f5a79a88..61b3b8b2 100644 --- a/tests/test_coro_http_server.cpp +++ b/tests/test_coro_http_server.cpp @@ -573,6 +573,14 @@ struct check_t { } }; +struct check_t1 { + bool before(coro_http_request &, coro_http_response &resp) { + std::cout << "check1 before" << std::endl; + resp.set_status_and_content(status_type::bad_request, "check failed"); + return false; + } +}; + struct get_data { bool before(coro_http_request &req, coro_http_response &res) { req.set_aspect_data("hello", "world"); @@ -607,8 +615,13 @@ TEST_CASE("test aspects") { co_return; }, get_data{}); + server.set_http_handler( + "/check1", + [](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "ok"); + }, + check_t1{}, log_t{}); server.async_start(); - std::this_thread::sleep_for(300ms); coro_http_client client{}; auto result = client.get("http://127.0.0.1:9001/"); @@ -636,6 +649,9 @@ TEST_CASE("test aspects") { result = client.get("http://127.0.0.1:9001/aspect"); CHECK(result.status == 200); + + result = client.get("http://127.0.0.1:9001/check1"); + CHECK(result.status == 400); } TEST_CASE("use out context") { @@ -696,9 +712,32 @@ TEST_CASE("delay reply, server stop, form-urlencode, qureies, throw") { throw std::invalid_argument("invalid arguments"); resp.set_status_and_content(status_type::ok, "ok"); }); + server.set_http_handler( + "/coro_throw", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + CHECK(req.get_boundary().empty()); + throw std::invalid_argument("invalid arguments"); + resp.set_status_and_content(status_type::ok, "ok"); + co_return; + }); + server.set_http_handler( + "/throw1", [](coro_http_request &req, coro_http_response &resp) { + CHECK(req.get_boundary().empty()); + throw 1; + resp.set_status_and_content(status_type::ok, "ok"); + }); + server.set_http_handler( + "/coro_throw1", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + CHECK(req.get_boundary().empty()); + throw 1; + resp.set_status_and_content(status_type::ok, "ok"); + co_return; + }); server.async_start(); - std::this_thread::sleep_for(200ms); resp_data result; coro_http_client client1{}; @@ -715,6 +754,15 @@ TEST_CASE("delay reply, server stop, form-urlencode, qureies, throw") { result = client1.get("http://127.0.0.1:9001/throw"); CHECK(result.status == 503); + result = client1.get("http://127.0.0.1:9001/coro_throw"); + CHECK(result.status == 503); + + result = client1.get("http://127.0.0.1:9001/throw1"); + CHECK(result.status == 503); + + result = client1.get("http://127.0.0.1:9001/coro_throw1"); + CHECK(result.status == 503); + server.stop(); std::cout << "ok\n"; } @@ -1269,13 +1317,63 @@ TEST_CASE("test restful api") { CHECK(req.matches_.str(2) == "200"); response.set_status_and_content(status_type::ok, "number regex ok"); }); + server.set_http_handler( + "/test4/{}", [](coro_http_request &req, coro_http_response &response) { + CHECK(req.matches_.str(1) == "100"); + 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"); + auto result = client.get("http://127.0.0.1:9001/test2/name/test3/test"); + result = client.get("http://127.0.0.1:9001/numbers/100/test/200"); + result = client.get("http://127.0.0.1:9001/test4/100"); + CHECK(result.status == 200); +} + +TEST_CASE("test response standalone") { + coro_http_response resp(nullptr); + resp.set_status_and_content(status_type::ok, "ok"); + CHECK(resp.content() == "ok"); + CHECK(resp.content_size() == 2); + CHECK(resp.need_date()); + resp.need_date_head(false); + CHECK(!resp.need_date()); + + std::string str; + resp.build_resp_str(str); + CHECK(!str.empty()); + CHECK(str.find("200") != std::string::npos); + resp.clear(); + str.clear(); + + resp.set_status_and_content(status_type::ok, ""); + std::vector v{{"hello", "world"}}; + resp.add_header_span(v); + resp.build_resp_str(str); + CHECK(str.find("200") != std::string::npos); + resp.clear(); + str.clear(); + + resp.set_keepalive(true); + resp.build_resp_str(str); + CHECK(str.find("501") != std::string::npos); + resp.set_format_type(format_type::chunked); + resp.build_resp_str(str); + CHECK(str.find("501") != std::string::npos); + resp.clear(); + str.clear(); + + std::string_view out = "hello"; + resp.set_status_and_content_view(status_type::ok, out); + resp.build_resp_str(str); + CHECK(str.find("200") != std::string::npos); + + std::vector buffers; + resp.set_content_type<4>(); + resp.build_resp_head(buffers); + CHECK(buffers.size() == 13); } TEST_CASE("test radix tree restful api") { diff --git a/tests/test_http_parse.cpp b/tests/test_http_parse.cpp index acee8e49..a8bf3c47 100644 --- a/tests/test_http_parse.cpp +++ b/tests/test_http_parse.cpp @@ -1,55 +1,267 @@ #define DOCTEST_CONFIG_IMPLEMENT -#include - -#include "cinatra/picohttpparser.h" +#include "cinatra/coro_http_server.hpp" #include "doctest/doctest.h" using namespace cinatra; -#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"); +std::string_view REQ = + "R(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)"; + +std::string_view multipart_str = + "R(POST / HTTP/1.1\r\n" + "User-Agent: PostmanRuntime/7.39.0\r\n" + "Accept: */*\r\n" + "Cache-Control: no-cache\r\n" + "Postman-Token: 33c25732-1648-42ed-a467-cc9f1eb1e961\r\n" + "Host: purecpp.cn\r\n" + "Accept-Encoding: gzip, deflate, br\r\n" + "Connection: keep-alive\r\n" + "Content-Type: multipart/form-data; " + "boundary=--------------------------559980232503017651158362\r\n" + "Cookie: CSESSIONID=87343c8a24f34e28be05efea55315aab\r\n" + "\r\n" + "----------------------------559980232503017651158362\r\n" + "Content-Disposition: form-data; name=\"test\"\r\n" + "tom\r\n" + "----------------------------559980232503017651158362--\r\n"; + +std::string_view bad_multipart_str = + "R(POST / HTTP/1.1\r\n" + "User-Agent: PostmanRuntime/7.39.0\r\n" + "Accept: */*\r\n" + "Cache-Control: no-cache\r\n" + "Postman-Token: 33c25732-1648-42ed-a467-cc9f1eb1e961\r\n" + "Host: purecpp.cn\r\n" + "Accept-Encoding: gzip, deflate, br\r\n" + "Connection: keep-alive\r\n" + "Content-Type: multipart/form-data; boundary=559980232503017651158362\r\n" + "Cookie: CSESSIONID=87343c8a24f34e28be05efea55315aab\r\n" + "\r\n" + "559980232503017651158362\r\n" + "Content-Disposition: form-data; name=\"test\"\r\n" + "tom\r\n" + "559980232503017651158362--\r\n"; + +std::string_view resp_str = + "R(HTTP/1.1 400 Bad Request\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 20\r\n" + "Host: cinatra\r\n" + "\r\n\r\n" + "the url is not right)"; + +TEST_CASE("http_parser test") { + http_parser parser{}; + parser.parse_request(REQ.data(), REQ.size(), 0); + CHECK(parser.body_len() == 0); + CHECK(parser.body_len() + parser.header_len() == parser.total_len()); + CHECK(parser.has_connection()); + + parser = {}; + std::string_view str(REQ.data(), 20); + int ret = parser.parse_request(str.data(), str.size(), 0); + CHECK(ret < 0); + + parser = {}; + ret = parser.parse_request(multipart_str.data(), multipart_str.size(), 0); + CHECK(ret > 0); + auto boundary = parser.get_boundary(); + CHECK(boundary == "--------------------------559980232503017651158362"); + + parser = {}; + ret = parser.parse_request(bad_multipart_str.data(), bad_multipart_str.size(), + 0); + CHECK(ret > 0); + auto bad_boundary = parser.get_boundary(); + CHECK(bad_boundary.empty()); + + parser = {}; + std::string_view part_resp(resp_str.data(), 20); + ret = parser.parse_response(part_resp.data(), part_resp.size(), 0); + CHECK(ret < 0); +} + +std::string_view req_str = + "R(GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg " + "HTTP/1.1\r\n" + "Content-Type: application/octet-stream" + "Host: cinatra\r\n" + "\r\n)"; + +std::string_view req_str1 = + "R(GET /ws " + "HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: cinatra\r\n" + "\r\n)"; + +std::string_view req_str2 = + "R(GET /ws " + "HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: websocket\r\n" + "\r\n)"; + +std::string_view req_str3 = + "R(GET /ws " + "HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Extensions: permessage-deflate\r\n" + "\r\n)"; + +std::string_view req_str4 = + "R(GET /ws " + "HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Content-Encoding: gzip\r\n" + "\r\n)"; + +std::string_view req_str5 = + "R(GET /ws " + "HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Content-Encoding: deflate\r\n" + "\r\n)"; + +std::string_view req_str6 = + "R(GET /ws " + "HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Content-Encoding: br\r\n" + "\r\n)"; + +std::string_view req_str7 = + "R(GET /ws " + "HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Content-Encoding: cinatra\r\n" + "\r\n)"; + +TEST_CASE("http_request test") { + http_parser parser{}; + int ret = parser.parse_request(req_str.data(), req_str.size(), 0); + CHECK(ret); + coro_http_request req(parser, nullptr); + CHECK(parser.msg().empty()); + + CHECK(req.get_accept_encoding().empty()); + CHECK(req.get_content_type() == content_type::octet_stream); + CHECK(req.get_boundary().empty()); + + req.set_aspect_data(std::string("test")); + CHECK(req.get_aspect_data().size() == 1); + req.set_aspect_data(std::vector{"test", "aspect"}); + CHECK(req.get_aspect_data().size() == 2); + CHECK(!req.is_support_compressed()); + CHECK(!req.is_upgrade()); + + parser = {}; + parser.parse_request(req_str2.data(), req_str2.size(), 0); + CHECK(!req.is_upgrade()); + + parser = {}; + parser.parse_request(req_str3.data(), req_str3.size(), 0); + CHECK(req.is_upgrade()); + CHECK(req.is_support_compressed()); + CHECK(req.get_encoding_type() == content_encoding::none); + + parser = {}; + parser.parse_request(req_str4.data(), req_str4.size(), 0); + CHECK(req.is_upgrade()); + CHECK(req.get_encoding_type() == content_encoding::gzip); + + parser = {}; + parser.parse_request(req_str5.data(), req_str5.size(), 0); + CHECK(req.is_upgrade()); + CHECK(req.get_encoding_type() == content_encoding::deflate); + + parser = {}; + parser.parse_request(req_str6.data(), req_str6.size(), 0); + CHECK(req.is_upgrade()); + CHECK(req.get_encoding_type() == content_encoding::br); + + parser = {}; + parser.parse_request(req_str7.data(), req_str7.size(), 0); + CHECK(req.is_upgrade()); + CHECK(req.get_encoding_type() == content_encoding::none); +} + +TEST_CASE("uri test") { + std::string uri = "https://example.com?name=tom"; + uri_t u; + bool r = u.parse_from(uri.data()); + CHECK(r); + CHECK(u.get_port() == "443"); + context c{u, http_method::GET}; + context c1{u, http_method::GET, "test"}; + CHECK(u.get_query() == "name=tom"); + + uri = "https://example.com:521?name=tom"; + r = u.parse_from(uri.data()); + CHECK(r); + CHECK(u.get_port() == "521"); + + uri = "#https://example.com?name=tom"; + r = u.parse_from(uri.data()); + CHECK(!r); + + uri = "https##://example.com?name=tom"; + r = u.parse_from(uri.data()); + CHECK(!r); + + uri = "https://^example.com?name=tom"; + r = u.parse_from(uri.data()); + CHECK(!r); + + uri = "https://example.com?^name=tom"; + r = u.parse_from(uri.data()); + CHECK(!r); + + uri = "http://username:password@example.com"; + r = u.parse_from(uri.data()); + CHECK(r); + CHECK(u.uinfo == "username:password"); + + uri = "http://example.com/data.csv#row=4"; + r = u.parse_from(uri.data()); + CHECK(r); + CHECK(u.fragment == "row=4"); + + uri = "https://example.com?name=tom$"; + r = u.parse_from(uri.data()); + CHECK(r); + + uri = "https://example.com?name=tom!"; + r = u.parse_from(uri.data()); + CHECK(r); } DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007)