From 0f05b4fb350fed0a3696a8f79d48c218a1d2941f Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 23 Apr 2024 10:40:34 +0800 Subject: [PATCH] [coro_http][fix][feat]fix and support ws deflate (#662) --- .../standalone/cinatra/coro_http_client.hpp | 163 +++++++++++++++--- .../cinatra/coro_http_connection.hpp | 121 +++++++++---- .../standalone/cinatra/coro_http_request.hpp | 8 + .../standalone/cinatra/coro_http_response.hpp | 11 +- include/ylt/standalone/cinatra/gzip.hpp | 159 +++++++++++++++++ include/ylt/standalone/cinatra/utils.hpp | 14 +- include/ylt/standalone/cinatra/websocket.hpp | 18 +- src/coro_http/examples/example.cpp | 3 + 8 files changed, 419 insertions(+), 78 deletions(-) diff --git a/include/ylt/standalone/cinatra/coro_http_client.hpp b/include/ylt/standalone/cinatra/coro_http_client.hpp index 2eaea7456..7dcb41579 100644 --- a/include/ylt/standalone/cinatra/coro_http_client.hpp +++ b/include/ylt/standalone/cinatra/coro_http_client.hpp @@ -21,6 +21,9 @@ #include "async_simple/Unit.h" #include "async_simple/coro/FutureAwaiter.h" #include "async_simple/coro/Lazy.h" +#ifdef CINATRA_ENABLE_GZIP +#include "gzip.hpp" +#endif #include "cinatra_log_wrapper.hpp" #include "http_parser.hpp" #include "multipart.hpp" @@ -273,6 +276,12 @@ class coro_http_client : public std::enable_shared_from_this { return std::move(body_); } +#ifdef CINATRA_ENABLE_GZIP + void set_ws_deflate(bool enable_ws_deflate) { + enable_ws_deflate_ = enable_ws_deflate; + } +#endif + // only make socket connet(or handshake) to the host async_simple::coro::Lazy connect(std::string uri) { resp_data data{}; @@ -298,10 +307,30 @@ class coro_http_client : public std::enable_shared_from_this { } add_header("Sec-WebSocket-Key", ws_sec_key_); add_header("Sec-WebSocket-Version", "13"); - +#ifdef CINATRA_ENABLE_GZIP + if (enable_ws_deflate_) + add_header("Sec-WebSocket-Extensions", + "permessage-deflate; client_max_window_bits"); +#endif req_context<> ctx{}; data = co_await async_request(std::move(uri), http_method::GET, std::move(ctx)); + +#ifdef CINATRA_ENABLE_GZIP + if (enable_ws_deflate_) { + for (auto c : data.resp_headers) { + if (c.name == "Sec-WebSocket-Extensions") { + if (c.value.find("permessage-deflate;") != std::string::npos) { + is_server_support_ws_deflate_ = true; + } + else { + is_server_support_ws_deflate_ = false; + } + break; + } + } + } +#endif co_return data; } data = co_await connect(u); @@ -382,37 +411,91 @@ class coro_http_client : public std::enable_shared_from_this { } if constexpr (is_span_v) { - std::string 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 + 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 { - while (true) { - auto result = co_await source(); - - std::span msg(result.buf.data(), result.buf.size()); - std::string encode_header = ws.encode_frame(msg, op, result.eof); + else { +#endif + std::string encode_header = ws.encode_frame(source, op, true); std::vector buffers{ asio::buffer(encode_header.data(), encode_header.size()), - asio::buffer(msg.data(), msg.size())}; + asio::buffer(source.data(), source.size())}; auto [ec, _] = co_await async_write(buffers); if (ec) { data.net_err = ec; data.status = 404; - break; } +#ifdef CINATRA_ENABLE_GZIP + } +#endif + } + else { + while (true) { + auto result = co_await source(); +#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()); + std::string 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 { +#endif + std::span msg(result.buf.data(), result.buf.size()); + std::string 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())}; - if (result.eof) { - break; + 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 } +#endif } } @@ -903,7 +986,8 @@ class coro_http_client : public std::enable_shared_from_this { size_t rd_size = source->read(file_data.data(), file_data.size()).gcount(); std::vector bufs; - cinatra::to_chunked_buffers(bufs, {file_data.data(), rd_size}, + std::string size_str; + cinatra::to_chunked_buffers(bufs, size_str, {file_data.data(), rd_size}, source->eof()); if (std::tie(ec, size) = co_await async_write(bufs); ec) { break; @@ -922,7 +1006,8 @@ class coro_http_client : public std::enable_shared_from_this { auto [rd_ec, rd_size] = co_await file.async_read(file_data.data(), file_data.size()); std::vector bufs; - cinatra::to_chunked_buffers(bufs, {file_data.data(), rd_size}, + std::string size_str; + cinatra::to_chunked_buffers(bufs, size_str, {file_data.data(), rd_size}, file.eof()); if (std::tie(ec, size) = co_await async_write(bufs); ec) { break; @@ -933,8 +1018,9 @@ class coro_http_client : public std::enable_shared_from_this { while (true) { auto result = co_await source(); std::vector bufs; + std::string size_str; cinatra::to_chunked_buffers( - bufs, {result.buf.data(), result.buf.size()}, result.eof); + bufs, size_str, {result.buf.data(), result.buf.size()}, result.eof); if (std::tie(ec, size) = co_await async_write(bufs); ec) { break; } @@ -1839,9 +1925,28 @@ class coro_http_client : public std::enable_shared_from_this { } } - data.status = 200; - data.resp_body = {data_ptr, payload_len}; +#ifdef CINATRA_ENABLE_GZIP + if (!is_close_frame && is_server_support_ws_deflate_ && + enable_ws_deflate_) { + inflate_str_.clear(); + if (!cinatra::gzip_codec::inflate({data_ptr, payload_len}, + inflate_str_)) { + CINATRA_LOG_ERROR << "uncompuress data error"; + data.status = 404; + 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()}; + } + else { +#endif + data.status = 200; + data.resp_body = {data_ptr, payload_len}; +#ifdef CINATRA_ENABLE_GZIP + } +#endif read_buf.consume(read_buf.size()); header_size = 2; @@ -2024,6 +2129,12 @@ class coro_http_client : public std::enable_shared_from_this { std::string resp_chunk_str_; std::span out_buf_; +#ifdef CINATRA_ENABLE_GZIP + bool enable_ws_deflate_ = false; + bool is_server_support_ws_deflate_ = false; + std::string inflate_str_; +#endif + #ifdef BENCHMARK_TEST std::string req_str_; bool stop_bench_ = false; diff --git a/include/ylt/standalone/cinatra/coro_http_connection.hpp b/include/ylt/standalone/cinatra/coro_http_connection.hpp index f70550c34..749f6d634 100644 --- a/include/ylt/standalone/cinatra/coro_http_connection.hpp +++ b/include/ylt/standalone/cinatra/coro_http_connection.hpp @@ -21,6 +21,9 @@ #include "sha1.hpp" #include "string_resize.hpp" #include "websocket.hpp" +#ifdef CINATRA_ENABLE_GZIP +#include "gzip.hpp" +#endif #include "ylt/coro_io/coro_file.hpp" #include "ylt/coro_io/coro_io.hpp" @@ -132,6 +135,14 @@ class coro_http_connection if (body_len == 0) { if (parser_.method() == "GET"sv) { if (request_.is_upgrade()) { +#ifdef CINATRA_ENABLE_GZIP + if (request_.is_support_compressed()) { + is_client_ws_compressed_ = true; + } + else { + is_client_ws_compressed_ = false; + } +#endif // websocket build_ws_handshake_head(); bool ok = co_await reply(true); // response ws handshake @@ -356,7 +367,7 @@ class coro_http_connection size_t size; if (multi_buf_) { if (need_to_bufffer) { - response_.to_buffers(buffers_); + response_.to_buffers(buffers_, chunk_size_str_); } std::tie(ec, size) = co_await async_write(buffers_); } @@ -381,35 +392,9 @@ class coro_http_connection co_return true; } - std::string local_address() { - if (has_closed_) { - return ""; - } + std::string local_address() { return get_address_impl(false); } - std::stringstream ss; - std::error_code ec; - ss << socket_.local_endpoint(ec); - if (ec) { - return ""; - } - return ss.str(); - } - - std::string remote_address() { - static std::string remote_addr; - if (has_closed_) { - return remote_addr; - } - - std::stringstream ss; - std::error_code ec; - ss << socket_.remote_endpoint(ec); - if (ec) { - return remote_addr; - } - remote_addr = ss.str(); - return ss.str(); - } + std::string remote_address() { return get_address_impl(); } void set_multi_buf(bool r) { multi_buf_ = r; } @@ -448,7 +433,7 @@ class coro_http_connection bool eof = false) { response_.set_delay(true); buffers_.clear(); - to_chunked_buffers(buffers_, chunked_data, eof); + to_chunked_buffers(buffers_, chunk_size_str_, chunked_data, eof); co_return co_await reply(false); } @@ -562,11 +547,28 @@ class coro_http_connection async_simple::coro::Lazy write_websocket( std::string_view msg, opcode op = opcode::text) { - auto header = ws_.format_header(msg.length(), op); std::vector buffers; - buffers.push_back(asio::buffer(header)); - buffers.push_back(asio::buffer(msg)); + std::string header; +#ifdef CINATRA_ENABLE_GZIP + std::string dest_buf; + if (is_client_ws_compressed_ && msg.size() > 0) { + if (!cinatra::gzip_codec::deflate(msg, dest_buf)) { + CINATRA_LOG_ERROR << "compuress data error, data: " << msg; + co_return std::make_error_code(std::errc::protocol_error); + } + header = ws_.format_header(dest_buf.length(), op, true); + buffers.push_back(asio::buffer(header)); + buffers.push_back(asio::buffer(dest_buf)); + } + else { +#endif + header = ws_.format_header(msg.length(), op); + buffers.push_back(asio::buffer(header)); + buffers.push_back(asio::buffer(msg)); +#ifdef CINATRA_ENABLE_GZIP + } +#endif auto [ec, sz] = co_await async_write(buffers); co_return ec; } @@ -623,8 +625,27 @@ class coro_http_connection break; case cinatra::ws_frame_type::WS_TEXT_FRAME: case cinatra::ws_frame_type::WS_BINARY_FRAME: { - result.eof = true; - result.data = {payload.data(), payload.size()}; +#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()}; + break; + } + else { +#endif + result.eof = true; + result.data = {payload.data(), payload.size()}; + break; +#ifdef CINATRA_ENABLE_GZIP + } +#endif } break; case cinatra::ws_frame_type::WS_CLOSE_FRAME: { close_frame close_frame = @@ -811,11 +832,35 @@ class coro_http_connection response_.add_header("Connection", "Upgrade"); response_.add_header("Sec-WebSocket-Accept", std::string(accept_key, 28)); auto protocal_str = request_.get_header_value("sec-websocket-protocol"); +#ifdef CINATRA_ENABLE_GZIP + if (is_client_ws_compressed_) { + response_.add_header("Sec-WebSocket-Extensions", + "permessage-deflate; client_no_context_takeover"); + } +#endif if (!protocal_str.empty()) { response_.add_header("Sec-WebSocket-Protocol", std::string(protocal_str)); } } + std::string get_address_impl(bool remote = true) { + if (has_closed_) { + return ""; + } + + std::error_code ec; + auto pt = remote ? socket_.remote_endpoint(ec) : socket_.local_endpoint(ec); + if (ec) { + return ""; + } + auto addr = pt.address().to_string(ec); + if (ec) { + return ""; + } + addr.append(":").append(std::to_string(pt.port())); + return addr; + } + private: friend class multipart_reader_t; async_simple::Executor *executor_; @@ -837,6 +882,11 @@ class coro_http_connection uint64_t max_part_size_ = 8 * 1024 * 1024; std::string resp_str_; +#ifdef CINATRA_ENABLE_GZIP + bool is_client_ws_compressed_ = false; + std::string inflate_str_; +#endif + websocket ws_; #ifdef CINATRA_ENABLE_SSL std::unique_ptr ssl_ctx_ = nullptr; @@ -847,5 +897,6 @@ class coro_http_connection bool multi_buf_ = true; std::function default_handler_ = nullptr; + std::string chunk_size_str_; }; } // namespace cinatra diff --git a/include/ylt/standalone/cinatra/coro_http_request.hpp b/include/ylt/standalone/cinatra/coro_http_request.hpp index 36309a128..ea6574fd8 100644 --- a/include/ylt/standalone/cinatra/coro_http_request.hpp +++ b/include/ylt/standalone/cinatra/coro_http_request.hpp @@ -208,6 +208,14 @@ class coro_http_request { return true; } + bool is_support_compressed() { + auto extension_str = get_header_value("Sec-WebSocket-Extensions"); + if (extension_str.find("permessage-deflate") != std::string::npos) { + return true; + } + return false; + } + void set_aspect_data(std::string data) { aspect_data_.push_back(std::move(data)); } diff --git a/include/ylt/standalone/cinatra/coro_http_response.hpp b/include/ylt/standalone/cinatra/coro_http_response.hpp index 0d1d61acc..9cf13c936 100644 --- a/include/ylt/standalone/cinatra/coro_http_response.hpp +++ b/include/ylt/standalone/cinatra/coro_http_response.hpp @@ -109,14 +109,15 @@ class coro_http_response { std::string_view get_boundary() { return boundary_; } - void to_buffers(std::vector &buffers) { + void to_buffers(std::vector &buffers, + std::string &size_str) { buffers.push_back(asio::buffer(to_http_status_string(status_))); build_resp_head(buffers); if (!content_.empty()) { - handle_content(buffers, content_); + handle_content(buffers, size_str, content_); } else if (!content_view_.empty()) { - handle_content(buffers, content_view_); + handle_content(buffers, size_str, content_view_); } } @@ -293,9 +294,9 @@ class coro_http_response { private: void handle_content(std::vector &buffers, - std::string_view content) { + std::string &size_str, std::string_view content) { if (fmt_type_ == format_type::chunked) { - to_chunked_buffers(buffers, content, true); + to_chunked_buffers(buffers, size_str, content, true); } else { buffers.push_back(asio::buffer(content)); diff --git a/include/ylt/standalone/cinatra/gzip.hpp b/include/ylt/standalone/cinatra/gzip.hpp index 400ce6ff0..f8a099803 100644 --- a/include/ylt/standalone/cinatra/gzip.hpp +++ b/include/ylt/standalone/cinatra/gzip.hpp @@ -140,4 +140,163 @@ inline int uncompress_file(const char *src_file, const char *out_file_name) { return 0; } + +inline bool inflate(std::string_view str_src, std::string &str_dest) { + int err = Z_DATA_ERROR; + // Create stream + z_stream zs = {0}; + // Set output data streams, do this here to avoid overwriting on recursive + // calls + const int OUTPUT_BUF_SIZE = 8192; + Bytef bytes_out[OUTPUT_BUF_SIZE] = {0}; + + // Initialise the z_stream + err = ::inflateInit2(&zs, -15); + if (err != Z_OK) { + return false; + } + + // Use whatever input is provided + zs.next_in = (Bytef *)(str_src.data()); + zs.avail_in = str_src.length(); + + do { + try { + // Initialise stream values + // zs->zalloc = (alloc_func)0; + // zs->zfree = (free_func)0; + // zs->opaque = (voidpf)0; + + zs.next_out = bytes_out; + zs.avail_out = OUTPUT_BUF_SIZE; + + // Try to unzip the data + err = ::inflate(&zs, Z_SYNC_FLUSH); + + // Is zip finished reading all currently available input and writing all + // generated output + if (err == Z_STREAM_END) { + // Finish up + int kerr = ::inflateEnd(&zs); + + // Got a good result, set the size to the amount unzipped in this call + // (including all recursive calls) + + str_dest.append((const char *)bytes_out, + OUTPUT_BUF_SIZE - zs.avail_out); + return true; + } + else if ((err == Z_OK) && (zs.avail_out == 0) && (zs.avail_in != 0)) { + // Output array was not big enough, call recursively until there is + // enough space + + str_dest.append((const char *)bytes_out, + OUTPUT_BUF_SIZE - zs.avail_out); + + continue; + } + else if ((err == Z_OK) && (zs.avail_in == 0)) { + // All available input has been processed, everything ok. + // Set the size to the amount unzipped in this call (including all + // recursive calls) + str_dest.append((const char *)bytes_out, + OUTPUT_BUF_SIZE - zs.avail_out); + + int kerr = ::inflateEnd(&zs); + + break; + } + else { + return false; + } + } catch (...) { + return false; + } + } while (true); + + return err == Z_OK; +} + +inline bool deflate(std::string_view str_src, std::string &str_dest) { + int err = Z_DATA_ERROR; + // Create stream + z_stream zs = {0}; + // Set output data streams, do this here to avoid overwriting on recursive + // calls + const int OUTPUT_BUF_SIZE = 8192; + Bytef bytes_out[OUTPUT_BUF_SIZE] = {0}; + + // Initialise the z_stream + err = ::deflateInit2(&zs, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + if (err != Z_OK) { + return false; + } + // Use whatever input is provided + zs.next_in = (Bytef *)(str_src.data()); + zs.avail_in = str_src.length(); + + do { + try { + // Initialise stream values + // zs->zalloc = (alloc_func)0; + // zs->zfree = (free_func)0; + // zs->opaque = (voidpf)0; + + zs.next_out = bytes_out; + zs.avail_out = OUTPUT_BUF_SIZE; + + // Try to unzip the data + err = ::deflate(&zs, Z_SYNC_FLUSH); + + // Is zip finished reading all currently available input and writing all + // generated output + if (err == Z_STREAM_END) { + // Finish up + int kerr = ::deflateEnd(&zs); + + // Got a good result, set the size to the amount unzipped in this call + // (including all recursive calls) + + str_dest.append((const char *)bytes_out, + OUTPUT_BUF_SIZE - zs.avail_out); + return true; + } + else if ((err == Z_OK) && (zs.avail_out == 0) && (zs.avail_in != 0)) { + // Output array was not big enough, call recursively until there is + // enough space + + str_dest.append((const char *)bytes_out, + OUTPUT_BUF_SIZE - zs.avail_out); + + continue; + } + else if ((err == Z_OK) && (zs.avail_in == 0)) { + // All available input has been processed, everything ok. + // Set the size to the amount unzipped in this call (including all + // recursive calls) + str_dest.append((const char *)bytes_out, + OUTPUT_BUF_SIZE - zs.avail_out); + + int kerr = ::deflateEnd(&zs); + + break; + } + else { + return false; + } + } catch (...) { + return false; + } + } while (true); + + if (err == Z_OK) { + // subtract 4 to remove the extra 00 00 ff ff added to the end of the deflat + // function + str_dest = str_dest.substr(0, str_dest.length() - 4); + return true; + } + + return false; +} + } // namespace cinatra::gzip_codec \ No newline at end of file diff --git a/include/ylt/standalone/cinatra/utils.hpp b/include/ylt/standalone/cinatra/utils.hpp index befea49ed..44b099049 100644 --- a/include/ylt/standalone/cinatra/utils.hpp +++ b/include/ylt/standalone/cinatra/utils.hpp @@ -24,6 +24,7 @@ #include "define.h" #include "response_cv.hpp" +#include "string_resize.hpp" namespace cinatra { struct ci_less { @@ -135,18 +136,17 @@ inline std::string_view trim_sv(std::string_view v) { return v; } -inline std::string_view to_hex_string(size_t val) { - static char buf[20]; - auto [ptr, ec] = std::to_chars(std::begin(buf), std::end(buf), val, 16); - return std::string_view{buf, size_t(std::distance(buf, ptr))}; -} - inline void to_chunked_buffers(std::vector &buffers, + std::string &size_str, std::string_view chunk_data, bool eof) { size_t length = chunk_data.size(); if (length > 0) { // convert bytes transferred count to a hex string. - auto chunk_size = to_hex_string(length); + detail::resize(size_str, 20); + auto [ptr, ec] = + std::to_chars(size_str.data(), size_str.data() + 20, length, 16); + std::string_view chunk_size{size_str.data(), + size_t(std::distance(size_str.data(), ptr))}; // Construct chunk based on rfc2616 section 3.6.1 buffers.push_back(asio::buffer(chunk_size)); diff --git a/include/ylt/standalone/cinatra/websocket.hpp b/include/ylt/standalone/cinatra/websocket.hpp index a605eaf69..02dfac4b6 100644 --- a/include/ylt/standalone/cinatra/websocket.hpp +++ b/include/ylt/standalone/cinatra/websocket.hpp @@ -121,18 +121,23 @@ class websocket { return ws_frame_type::WS_BINARY_FRAME; } - std::string format_header(size_t length, opcode code) { - size_t header_length = encode_header(length, code); + std::string format_header(size_t length, opcode code, + bool is_compressed = false) { + size_t header_length = encode_header(length, code, is_compressed); return {msg_header_, header_length}; } - std::string encode_frame(std::span &data, opcode op, bool eof) { + std::string encode_frame(std::span &data, opcode op, bool eof, + bool need_compression = false) { std::string header; /// Base header. frame_header hdr{}; hdr.fin = eof; hdr.rsv1 = 0; - hdr.rsv2 = 0; + if (need_compression) + hdr.rsv2 = 1; + else + hdr.rsv2 = 0; hdr.rsv3 = 0; hdr.opcode = static_cast(op); hdr.mask = 1; @@ -224,7 +229,7 @@ class websocket { opcode get_opcode() { return (opcode)msg_opcode_; } private: - size_t encode_header(size_t length, opcode code) { + size_t encode_header(size_t length, opcode code, bool is_compressed = false) { size_t header_length; if (length < 126) { @@ -248,6 +253,9 @@ class websocket { msg_header_[0] |= code; } + if (is_compressed) + msg_header_[0] |= 0x40; + return header_length; } diff --git a/src/coro_http/examples/example.cpp b/src/coro_http/examples/example.cpp index b528d24ef..36a4bf035 100644 --- a/src/coro_http/examples/example.cpp +++ b/src/coro_http/examples/example.cpp @@ -317,6 +317,9 @@ async_simple::coro::Lazy basic_usage() { server.set_http_handler( "/post", [](coro_http_request &req, coro_http_response &resp) { + assert(resp.get_conn()->remote_address().find("127.0.0.1") != + std::string::npos); + assert(resp.get_conn()->local_address() == "127.0.0.1:9001"); auto req_body = req.get_body(); resp.set_status_and_content(status_type::ok, std::string{req_body}); });