#include #include #include #include #include #include #include #include #include namespace ranczo { using tcp = boost::asio::ip::tcp; namespace asio = boost::asio; namespace beast = boost::beast; namespace http = beast::http; using PmrCharAlloc = std::pmr::polymorphic_allocator< char >; using PmrFields = http::basic_fields< PmrCharAlloc >; using PmrStringBody = http::basic_string_body< char, std::char_traits< char >, PmrCharAlloc >; using PmrFlatBuffer = boost::beast::basic_flat_buffer< PmrCharAlloc >; using PmrResponse = http::response< PmrStringBody, PmrFields >; using PmrRequest = http::request< PmrStringBody, PmrFields >; expected< HttpGetClient::ParsedUrl > HttpGetClient::parse_url_generic(std::string_view url) { ParsedUrl out; namespace urls = boost::urls; auto r = urls::parse_uri(url); if(!r) { return unexpected(r.error()); } urls::url_view u = *r; out.scheme = std::string(u.scheme()); out.host = std::string(u.host()); // ścieżka out.target = std::string(u.encoded_path()); if(out.target.empty()) out.target = "/"; // query (bez fragmentu) if(!u.encoded_query().empty()) { out.target += "?"; out.target += std::string(u.encoded_query()); } // port if(!u.port().empty()) { out.port = u.port_number(); } else { if(out.scheme == "https") out.port = 443; else out.port = 80; } out.use_ssl = (out.scheme == "https"); return out; } awaitable_expected< std::pmr::string > HttpGetClient::async_get(std::string_view url, std::pmr::memory_resource * mr) { BOOST_ASSERT(mr); namespace ssl = boost::asio::ssl; PmrCharAlloc alloc{mr}; // 1. Parse URL auto parsed_exp = parse_url_generic(url); if(!parsed_exp) { co_return unexpected(parsed_exp.error()); } auto parsed = *parsed_exp; beast::error_code ec; // 2. Resolver tcp::resolver resolver{executor()}; auto results = co_await resolver.async_resolve(parsed.host, std::to_string(parsed.port), asio::redirect_error(asio::use_awaitable, ec)); if(ec) { co_return unexpected(ec); } // 3. Request na PMR http::request< PmrStringBody, PmrFields > req{http::verb::get, parsed.target, 11, PmrStringBody::value_type{alloc}, PmrFields{alloc}}; req.set(http::field::host, parsed.host); req.set(http::field::user_agent, "ranczo-http-client"); // 4. Wspólne rzeczy dla HTTP/HTTPS PmrFlatBuffer buffer{alloc}; PmrResponse res{std::piecewise_construct, std::make_tuple(PmrStringBody::value_type{alloc}), std::make_tuple(PmrFields{alloc})}; if(!parsed.use_ssl) { // ---------------------- HTTP (bez TLS) ---------------------- beast::tcp_stream stream{executor()}; stream.expires_after(_timeout); co_await stream.async_connect(results, asio::redirect_error(asio::use_awaitable, ec)); if(ec) { co_return unexpected(ec); } co_await http::async_write(stream, req, asio::redirect_error(asio::use_awaitable, ec)); if(ec) { co_return unexpected(ec); } co_await http::async_read(stream, buffer, res, asio::redirect_error(asio::use_awaitable, ec)); if(ec) { co_return unexpected(ec); } stream.socket().shutdown(tcp::socket::shutdown_both, ec); // błąd przy shutdown zwykle ignorujemy } else { // ---------------------- HTTPS (TLS) ---------------------- ssl::context ctx{ssl::context::tls_client}; // Uwaga: do prawdziwego użycia: // - ctx.set_default_verify_paths(); // - ctx.set_verify_mode(ssl::verify_peer); // Tu dla testów najprościej wyłączyć weryfikację: ctx.set_verify_mode(ssl::verify_none); beast::ssl_stream< beast::tcp_stream > stream{executor_, ctx}; auto & lowest = beast::get_lowest_layer(stream); lowest.expires_after(_timeout); co_await lowest.async_connect(results, asio::redirect_error(asio::use_awaitable, ec)); if(ec) { co_return unexpected(ec); } // (opcjonalnie) SNI – przydatne dla częsci serwerów if(!SSL_set_tlsext_host_name(stream.native_handle(), parsed.host.c_str())) { ec = beast::error_code(static_cast< int >(::ERR_get_error()), asio::error::get_ssl_category()); co_return unexpected(ec); } co_await stream.async_handshake(ssl::stream_base::client, asio::redirect_error(asio::use_awaitable, ec)); if(ec) { co_return unexpected(ec); } co_await http::async_write(stream, req, asio::redirect_error(asio::use_awaitable, ec)); if(ec) { co_return unexpected(ec); } co_await http::async_read(stream, buffer, res, asio::redirect_error(asio::use_awaitable, ec)); if(ec) { co_return unexpected(ec); } // shutdown TLS co_await stream.async_shutdown(asio::redirect_error(asio::use_awaitable, ec)); // tu też często dostaniesz EOF – można zignorować } // Opcjonalnie możesz sprawdzić status: // if (res.result() != http::status::ok) { ... } // Body korzysta z tego samego PMR std::pmr::string body{alloc}; body = res.body(); // kopia na tym samym allocatorze (bez dużych kosztów w mr) co_return body; } } // namespace ranczo