diff --git a/include/ylt/standalone/cinatra/coro_http_client.hpp b/include/ylt/standalone/cinatra/coro_http_client.hpp index 00d31efc2..8a4bea234 100644 --- a/include/ylt/standalone/cinatra/coro_http_client.hpp +++ b/include/ylt/standalone/cinatra/coro_http_client.hpp @@ -144,11 +144,8 @@ class coro_http_client : public std::enable_shared_from_this { std::string proxy_auth_token; bool enable_tcp_no_delay; #ifdef CINATRA_ENABLE_SSL - bool use_ssl = false; - std::string base_path; - std::string cert_file; - int verify_mode; - std::string domain; + bool use_ssl = + false; // if set use_ssl true, cinatra will add https automaticlly. #endif }; @@ -414,6 +411,37 @@ class coro_http_client : public std::enable_shared_from_this { co_return co_await write_websocket(std::span(data), op); } + async_simple::coro::Lazy write_ws_frame(std::span msg, + websocket ws, opcode op, + resp_data &data, + bool eof = true) { + auto header = ws.encode_frame(msg, op, eof, true); + std::vector buffers{asio::buffer(header), + asio::buffer(msg)}; + + auto [ec, sz] = co_await async_write(buffers); + if (ec) { + data.net_err = ec; + data.status = 404; + } + } + +#ifdef CINATRA_ENABLE_GZIP + void gzip_decompress(std::string_view source, std::string &dest_buf, + std::span &span, resp_data &data) { + if (enable_ws_deflate_ && is_server_support_ws_deflate_) { + if (cinatra::gzip_codec::deflate(source, dest_buf)) { + span = dest_buf; + } + else { + CINATRA_LOG_ERROR << "compuress data error, data: " << source; + data.net_err = std::make_error_code(std::errc::protocol_error); + data.status = 404; + } + } + } +#endif + template async_simple::coro::Lazy write_websocket( Source source, opcode op = opcode::text) { @@ -429,92 +457,29 @@ class coro_http_client : public std::enable_shared_from_this { } } + std::span span{}; if constexpr (is_span_v) { + span = {source.data(), source.size()}; #ifdef CINATRA_ENABLE_GZIP - if (enable_ws_deflate_ && is_server_support_ws_deflate_) { - std::string dest_buf; - if (cinatra::gzip_codec::deflate({source.data(), source.size()}, - dest_buf)) { - std::span msg(dest_buf.data(), dest_buf.size()); - auto header = ws.encode_frame(msg, op, true, true); - std::vector buffers{asio::buffer(header), - asio::buffer(dest_buf)}; - - auto [ec, sz] = co_await async_write(buffers); - if (ec) { - data.net_err = ec; - data.status = 404; - } - } - else { - CINATRA_LOG_ERROR << "compuress data error, data: " - << std::string(source.begin(), source.end()); - data.net_err = std::make_error_code(std::errc::protocol_error); - data.status = 404; - } - } - else { -#endif - auto encode_header = ws.encode_frame(source, op, true); - std::vector buffers{ - asio::buffer(encode_header.data(), encode_header.size()), - asio::buffer(source.data(), source.size())}; - - auto [ec, _] = co_await async_write(buffers); - if (ec) { - data.net_err = ec; - data.status = 404; - } -#ifdef CINATRA_ENABLE_GZIP - } + std::string dest_buf; + gzip_decompress({source.data(), source.size()}, dest_buf, span, data); #endif + co_await write_ws_frame(span, ws, op, data); } else { while (true) { auto result = co_await source(); + span = {result.buf.data(), result.buf.size()}; #ifdef CINATRA_ENABLE_GZIP - if (enable_ws_deflate_ && is_server_support_ws_deflate_) { - std::string dest_buf; - if (cinatra::gzip_codec::deflate( - {result.buf.data(), result.buf.size()}, dest_buf)) { - std::span msg(dest_buf.data(), dest_buf.size()); - auto header = ws.encode_frame(msg, op, result.eof, true); - std::vector buffers{asio::buffer(header), - asio::buffer(dest_buf)}; - auto [ec, sz] = co_await async_write(buffers); - if (ec) { - data.net_err = ec; - data.status = 404; - } - } - else { - CINATRA_LOG_ERROR << "compuress data error, data: " - << std::string(result.buf.data()); - data.net_err = std::make_error_code(std::errc::protocol_error); - data.status = 404; - } - } - else { + std::string dest_buf; + gzip_decompress({result.buf.data(), result.buf.size()}, dest_buf, span, + data); #endif - std::span msg(result.buf.data(), result.buf.size()); - auto encode_header = ws.encode_frame(msg, op, result.eof); - std::vector buffers{ - asio::buffer(encode_header.data(), encode_header.size()), - asio::buffer(msg.data(), msg.size())}; + co_await write_ws_frame(span, ws, op, data, result.eof); - auto [ec, _] = co_await async_write(buffers); - if (ec) { - data.net_err = ec; - data.status = 404; - break; - } - - if (result.eof) { - break; - } -#ifdef CINATRA_ENABLE_GZIP + if (result.eof || data.status == 404) { + break; } -#endif } } @@ -1145,7 +1110,6 @@ class coro_http_client : public std::enable_shared_from_this { else { while (true) { auto result = co_await source(); - std::cout << result.buf.size() << std::endl; if (std::tie(ec, size) = co_await async_write(asio::buffer( result.buf.data(), std::min(content_length, result.buf.size()))); @@ -1574,6 +1538,7 @@ class coro_http_client : public std::enable_shared_from_this { // all be http proxy_request_uri_.append("http://") .append(u.get_host()) + .append(":") .append(u.get_port()); } proxy_request_uri_.append(u.get_path()); @@ -2139,11 +2104,6 @@ class coro_http_client : public std::enable_shared_from_this { .append(CRCF); } - std::error_code ec; - if (!std::filesystem::exists(part.filename, ec)) { - co_return resp_data{ - std::make_error_code(std::errc::no_such_file_or_directory), 404}; - } part_content_head.append(CRCF); } else { @@ -2242,16 +2202,8 @@ class coro_http_client : public std::enable_shared_from_this { } data_ptr = asio::buffer_cast(read_buf.data()); - if (is_close_frame) { - if (payload_len >= 2) { - payload_len -= 2; - data_ptr += sizeof(uint16_t); - } - } - #ifdef CINATRA_ENABLE_GZIP - if (!is_close_frame && is_server_support_ws_deflate_ && - enable_ws_deflate_) { + if (is_server_support_ws_deflate_ && enable_ws_deflate_) { inflate_str_.clear(); if (!cinatra::gzip_codec::inflate({data_ptr, payload_len}, inflate_str_)) { @@ -2260,17 +2212,19 @@ class coro_http_client : public std::enable_shared_from_this { data.net_err = std::make_error_code(std::errc::protocol_error); co_return data; } - data.status = 200; - data.resp_body = {inflate_str_.data(), inflate_str_.size()}; + data_ptr = inflate_str_.data(); + payload_len = inflate_str_.length(); } - else { #endif - - data.status = 200; - data.resp_body = {data_ptr, payload_len}; -#ifdef CINATRA_ENABLE_GZIP + if (is_close_frame) { + if (payload_len >= 2) { + payload_len -= 2; + data_ptr += sizeof(uint16_t); + } } -#endif + data.status = 200; + data.resp_body = {data_ptr, payload_len}; + read_buf.consume(read_buf.size()); if (is_close_frame) { diff --git a/include/ylt/standalone/cinatra/coro_http_connection.hpp b/include/ylt/standalone/cinatra/coro_http_connection.hpp index 4d4009617..d7308c90c 100644 --- a/include/ylt/standalone/cinatra/coro_http_connection.hpp +++ b/include/ylt/standalone/cinatra/coro_http_connection.hpp @@ -718,28 +718,19 @@ class coro_http_connection case cinatra::ws_frame_type::WS_TEXT_FRAME: case cinatra::ws_frame_type::WS_BINARY_FRAME: { #ifdef CINATRA_ENABLE_GZIP - if (is_client_ws_compressed_) { - inflate_str_.clear(); - if (!cinatra::gzip_codec::inflate( - {payload.data(), payload.size()}, inflate_str_)) { - CINATRA_LOG_ERROR << "uncompuress data error"; - result.ec = std::make_error_code(std::errc::protocol_error); - break; - } - result.eof = true; - result.data = {inflate_str_.data(), inflate_str_.size()}; + if (!gzip_compress(payload, result)) { break; } - else { #endif - result.eof = true; - result.data = {payload.data(), payload.size()}; - break; + result.eof = true; + result.data = {payload.data(), payload.size()}; + } break; + case cinatra::ws_frame_type::WS_CLOSE_FRAME: { #ifdef CINATRA_ENABLE_GZIP + if (!gzip_compress(payload, result)) { + break; } #endif - } break; - case cinatra::ws_frame_type::WS_CLOSE_FRAME: { close_frame close_frame = ws_.parse_close_payload(payload.data(), payload.size()); result.eof = true; @@ -790,6 +781,22 @@ class coro_http_connection co_return result; } +#ifdef CINATRA_ENABLE_GZIP + bool gzip_compress(std::span &payload, websocket_result &result) { + if (is_client_ws_compressed_) { + inflate_str_.clear(); + if (!cinatra::gzip_codec::inflate({payload.data(), payload.size()}, + inflate_str_)) { + CINATRA_LOG_ERROR << "uncompuress data error"; + result.ec = std::make_error_code(std::errc::protocol_error); + return false; + } + payload = inflate_str_; + } + return true; + } +#endif + auto &tcp_socket() { return socket_; } void set_quit_callback(std::function callback, diff --git a/src/coro_http/tests/test_cinatra.cpp b/src/coro_http/tests/test_cinatra.cpp index d3d0e7904..1da322242 100644 --- a/src/coro_http/tests/test_cinatra.cpp +++ b/src/coro_http/tests/test_cinatra.cpp @@ -414,6 +414,37 @@ TEST_CASE("test cinatra::string SSO to no SSO") { CHECK(s == sum); } +TEST_CASE("test config") { + coro_http_client client{}; + coro_http_client::config conf{}; + conf.sec_key = "s//GYHa/XO7Hd2F2eOGfyA=="; + conf.proxy_host = "http://example.com"; + conf.proxy_host = "9090"; + conf.max_single_part_size = 1024 * 1024; + conf.proxy_auth_username = "cinatra"; + conf.proxy_auth_token = "cinatra"; + conf.proxy_auth_passwd = "cinatra"; + conf.enable_tcp_no_delay = true; + client.init_config(conf); + + std::unordered_map req_headers{{"test", "ok"}}; + client.set_headers(req_headers); + const auto &headers = client.get_headers(); + CHECK(req_headers == headers); + + auto &executor = client.get_executor(); + auto name = executor.name(); + CHECK(!name.empty()); + + const auto &c = client.get_config(); + CHECK(c.enable_tcp_no_delay == conf.enable_tcp_no_delay); + CHECK(c.max_single_part_size == 1024 * 1024); + + auto ret = async_simple::coro::syncAwait(client.connect("http://##test.com")); + CHECK(ret.status != 200); + CHECK(ret.net_err.value() == (int)std::errc::protocol_error); +} + struct add_data { bool before(coro_http_request &req, coro_http_response &res) { req.set_aspect_data("hello world"); @@ -921,7 +952,7 @@ TEST_CASE("test request with out buffer") { std::string str; str.resize(10); std::string url = "http://127.0.0.1:8090/test"; - std::string url1 = "http://127.0.0.1:8090/test"; + std::string url1 = "http://127.0.0.1:8090/test1"; { coro_http_client client; @@ -945,6 +976,8 @@ TEST_CASE("test request with out buffer") { std::cout << result.resp_body << "\n"; CHECK(result.status == 200); CHECK(!client.is_body_in_out_buf()); + auto s = client.release_buf(); + CHECK(s == "it is a test string, more than 10 bytes"); } { @@ -1680,6 +1713,29 @@ TEST_CASE("test coro_http_client chunked upload and download") { }); server.async_start(); + { + coro_http_client client{}; + std::string uri = "http://###127.0.0.1:8090/chunked_upload"; + std::string filename = "test_chunked_upload.txt"; + auto lazy = client.async_upload_chunked(uri, http_method::PUT, filename); + auto result = async_simple::coro::syncAwait(lazy); + CHECK(result.status != 200); + + uri = "http://127.0.0.1:8090/chunked_upload"; + filename = "no_such.txt"; + auto lazy1 = client.async_upload_chunked(uri, http_method::PUT, filename); + result = async_simple::coro::syncAwait(lazy1); + CHECK(result.status != 200); + + std::shared_ptr file = nullptr; + uri = "http://127.0.0.1:8090/chunked_upload"; + auto lazy2 = client.async_upload_chunked(uri, http_method::PUT, file); + result = async_simple::coro::syncAwait(lazy2); + CHECK(result.status != 200); + + auto code = async_simple::coro::syncAwait(client.handle_shake()); + CHECK(code); + } auto sizes = {1024 * 1024, 2'000'000, 1024, 100, 0}; for (auto size : sizes) { std::string filename = "test_chunked_upload.txt"; @@ -1758,6 +1814,7 @@ TEST_CASE("test coro_http_client not exist domain and bad uri") { { coro_http_client client{}; + client.set_req_timeout(1s); auto r = async_simple::coro::syncAwait( client.async_get("http://www.baidu.com/><")); CHECK(r.net_err); @@ -1948,6 +2005,16 @@ TEST_CASE("test coro http proxy request") { result = async_simple::coro::syncAwait(client.async_get(uri)); if (!result.net_err) CHECK(result.status >= 200); + + client.set_proxy("106.14.255.124", "80"); + uri = "http://www.baidu.com:443"; + result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(result.status != 200); + + client.set_proxy("106.14.255.124", "80"); + uri = "http://www.baidu.com:12345"; + result = async_simple::coro::syncAwait(client.async_get(uri)); + CHECK(result.status != 200); } TEST_CASE("test coro http proxy request with port") { diff --git a/src/coro_http/tests/test_cinatra_websocket.cpp b/src/coro_http/tests/test_cinatra_websocket.cpp index 4b89623b4..402499ee7 100644 --- a/src/coro_http/tests/test_cinatra_websocket.cpp +++ b/src/coro_http/tests/test_cinatra_websocket.cpp @@ -73,6 +73,25 @@ async_simple::coro::Lazy test_websocket(coro_http_client &client) { CHECK(data.net_err == asio::error::eof); } +#ifdef CINATRA_ENABLE_GZIP +async_simple::coro::Lazy test_gzip_websocket(coro_http_client &client) { + auto r = co_await client.connect("ws://localhost:8090/ws"); + if (r.net_err) { + co_return; + } + + std::string str = "hello websocket"; + auto result = co_await client.write_websocket(str.data(), str.size()); + auto data = co_await client.read_websocket(); + CHECK(data.resp_body == "hello websocket"); + + 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); +} +#endif + TEST_CASE("test websocket") { cinatra::coro_http_server server(1, 8090); server.set_http_handler( @@ -102,6 +121,12 @@ TEST_CASE("test websocket") { async_simple::coro::syncAwait(test_websocket(client)); +#ifdef CINATRA_ENABLE_GZIP + coro_http_client client1{}; + client1.set_ws_deflate(true); + async_simple::coro::syncAwait(test_gzip_websocket(client1)); +#endif + std::this_thread::sleep_for(std::chrono::milliseconds(300)); // client->async_close();