From f1013776338ceb1b8b766f9d2f35c6e216415b08 Mon Sep 17 00:00:00 2001 From: helintong Date: Mon, 10 Jun 2024 21:05:40 +0800 Subject: [PATCH] feat: add compress api for coro client 1. add multiple encoding type support 2. add encoding for coro client 3. coro server add accept-encoding check --- include/cinatra/coro_http_client.hpp | 36 +++++++++++- include/cinatra/coro_http_request.hpp | 19 +++++++ include/cinatra/coro_http_response.hpp | 57 +++++++++++++++---- include/cinatra/define.h | 1 + include/cinatra/response_cv.hpp | 1 - tests/test_cinatra.cpp | 77 ++++++++++++++++++++++++++ 6 files changed, 179 insertions(+), 12 deletions(-) diff --git a/include/cinatra/coro_http_client.hpp b/include/cinatra/coro_http_client.hpp index 5f90831b..858f3d64 100644 --- a/include/cinatra/coro_http_client.hpp +++ b/include/cinatra/coro_http_client.hpp @@ -1436,6 +1436,20 @@ class coro_http_client : public std::enable_shared_from_this { if (is_redirect) redirect_uri_ = parser_.get_header_value("Location"); +#ifdef CINATRA_ENABLE_GZIP + if (!parser_.get_header_value("Content-Encoding").empty()) { + if (parser_.get_header_value("Content-Encoding").find("gzip") != + std::string_view::npos) + encoding_type_ = content_encoding::gzip; + else if (parser_.get_header_value("Content-Encoding").find("deflate") != + std::string_view::npos) + encoding_type_ = content_encoding::deflate; + } + else { + encoding_type_ = content_encoding::none; + } +#endif + size_t content_len = (size_t)parser_.body_len(); #ifdef BENCHMARK_TEST total_len_ = parser_.total_len(); @@ -1541,7 +1555,26 @@ class coro_http_client : public std::enable_shared_from_this { } std::string_view reply(data_ptr, content_len); - data.resp_body = reply; +#ifdef CINATRA_ENABLE_GZIP + if (encoding_type_ == content_encoding::gzip) { + std::string unziped_str; + bool r = gzip_codec::uncompress(reply, unziped_str); + if (r) + data.resp_body = unziped_str; + else + data.resp_body = reply; + } + else if (encoding_type_ == content_encoding::deflate) { + std::string inflate_str; + bool r = gzip_codec::inflate(reply, inflate_str); + if (r) + data.resp_body = inflate_str; + else + data.resp_body = reply; + } + else +#endif + data.resp_body = reply; head_buf_.consume(content_len); } @@ -2066,6 +2099,7 @@ class coro_http_client : public std::enable_shared_from_this { bool enable_ws_deflate_ = false; bool is_server_support_ws_deflate_ = false; std::string inflate_str_; + content_encoding encoding_type_ = content_encoding::none; #endif #ifdef BENCHMARK_TEST diff --git a/include/cinatra/coro_http_request.hpp b/include/cinatra/coro_http_request.hpp index a41df18e..5b643c17 100644 --- a/include/cinatra/coro_http_request.hpp +++ b/include/cinatra/coro_http_request.hpp @@ -145,6 +145,25 @@ class coro_http_request { bool is_req_ranges() { return parser_.is_req_ranges(); } + std::string get_accept_encoding() { + return std::string(get_header_value("Accept-Encoding")); + } + + content_encoding get_encoding_type() { + auto encoding_type = get_header_value("Content-Encoding"); + if (!encoding_type.empty()) { + if (encoding_type.find("gzip") != std::string_view::npos) + return content_encoding::gzip; + else if (encoding_type.find("deflate") != std::string_view::npos) + return content_encoding::deflate; + else + return content_encoding::none; + } + else { + return content_encoding::none; + } + } + content_type get_content_type() { if (is_chunked()) return content_type::chunked; diff --git a/include/cinatra/coro_http_response.hpp b/include/cinatra/coro_http_response.hpp index b3a55e46..93c28284 100644 --- a/include/cinatra/coro_http_response.hpp +++ b/include/cinatra/coro_http_response.hpp @@ -52,26 +52,63 @@ class coro_http_response { } void set_status_and_content( status_type status, std::string content = "", - content_encoding encoding = content_encoding::none) { - set_status_and_content_view(status, std::move(content), encoding, false); + content_encoding encoding = content_encoding::none, + std::string client_encoding_type = "") { + set_status_and_content_view(status, std::move(content), encoding, false, + client_encoding_type); } template void set_status_and_content_view( status_type status, String content = "", - content_encoding encoding = content_encoding::none, bool is_view = true) { + content_encoding encoding = content_encoding::none, bool is_view = true, + std::string_view client_encoding_type = "") { status_ = status; #ifdef CINATRA_ENABLE_GZIP if (encoding == content_encoding::gzip) { - std::string encode_str; - bool r = gzip_codec::compress(content, encode_str, true); - if (!r) { - set_status_and_content(status_type::internal_server_error, - "gzip compress error"); + if (client_encoding_type.empty() || + client_encoding_type.find("gzip") != std::string_view::npos) { + std::string encode_str; + bool r = gzip_codec::compress(content, encode_str, true); + if (!r) { + set_status_and_content(status_type::internal_server_error, + "gzip compress error"); + } + else { + add_header("Content-Encoding", "gzip"); + set_content(std::move(encode_str)); + } + } + else { + if (is_view) { + content_view_ = content; + } + else { + content_ = std::move(content); + } + } + } + else if (encoding == content_encoding::deflate) { + if (client_encoding_type.empty() || + client_encoding_type.find("deflate") != std::string_view::npos) { + std::string deflate_str; + bool r = gzip_codec::deflate(content, deflate_str); + if (!r) { + set_status_and_content(status_type::internal_server_error, + "deflate compress error"); + } + else { + add_header("Content-Encoding", "deflate"); + set_content(std::move(deflate_str)); + } } else { - add_header("Content-Encoding", "gzip"); - set_content(std::move(encode_str)); + if (is_view) { + content_view_ = content; + } + else { + content_ = std::move(content); + } } } else diff --git a/include/cinatra/define.h b/include/cinatra/define.h index d663c406..2ce1c4c5 100644 --- a/include/cinatra/define.h +++ b/include/cinatra/define.h @@ -19,6 +19,7 @@ enum class http_method { OPTIONS, DEL, }; +enum class content_encoding { gzip, deflate, none }; constexpr inline auto GET = http_method::GET; constexpr inline auto POST = http_method::POST; constexpr inline auto DEL = http_method::DEL; diff --git a/include/cinatra/response_cv.hpp b/include/cinatra/response_cv.hpp index a9333daf..bd6e4f04 100644 --- a/include/cinatra/response_cv.hpp +++ b/include/cinatra/response_cv.hpp @@ -4,7 +4,6 @@ #include "define.h" namespace cinatra { -enum class content_encoding { gzip, none }; enum class status_type { init, diff --git a/tests/test_cinatra.cpp b/tests/test_cinatra.cpp index 24b64eb4..96eb12a8 100644 --- a/tests/test_cinatra.cpp +++ b/tests/test_cinatra.cpp @@ -56,6 +56,83 @@ TEST_CASE("test for gzip") { CHECK(decompress_data == "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_SSL