Skip to content

Commit 322da07

Browse files
committed
Burl: HTTP proxy support
1 parent 61352a3 commit 322da07

File tree

1 file changed

+114
-23
lines changed

1 file changed

+114
-23
lines changed

example/client/burl/main.cpp

Lines changed: 114 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,22 @@ target(urls::url_view url) noexcept
105105
return url.encoded_target();
106106
}
107107

108+
core::string_view
109+
effective_port(urls::url_view url)
110+
{
111+
if(url.has_port())
112+
return url.port();
113+
114+
if(url.scheme() == "https")
115+
return "443";
116+
117+
if(url.scheme() == "http")
118+
return "80";
119+
120+
throw std::runtime_error{
121+
"Unsupported scheme" };
122+
}
123+
108124
struct is_redirect_result
109125
{
110126
bool is_redirect = false;
@@ -705,31 +721,97 @@ class message
705721
};
706722

707723
asio::awaitable<any_stream>
708-
connect(ssl::context& ssl_ctx, urls::url_view url)
724+
connect(
725+
const po::variables_map& vm,
726+
ssl::context& ssl_ctx,
727+
http_proto::context& http_proto_ctx,
728+
urls::url_view url)
709729
{
710730
auto executor = co_await asio::this_coro::executor;
711731
auto resolver = asio::ip::tcp::resolver{ executor };
712-
auto service = url.has_port() ? url.port() : url.scheme();
713-
auto rresults = co_await resolver.async_resolve(url.host(), service);
732+
auto stream = asio::ip::tcp::socket{ executor };
733+
734+
if(vm.count("proxy"))
735+
{
736+
auto proxy_url = urls::parse_uri(vm.at("proxy").as<std::string>());
737+
738+
if(proxy_url.has_error())
739+
throw system_error{ proxy_url.error(), "Failed to parse proxy" };
740+
741+
if(proxy_url->scheme() != "http")
742+
throw std::runtime_error{ "only HTTP proxies are supported" };
743+
744+
// Connect to the HTTP proxy server
745+
auto rr = co_await resolver.async_resolve(
746+
proxy_url->host(), effective_port(proxy_url.value()));
747+
co_await asio::async_connect(stream, rr);
748+
749+
{
750+
using http_proto::field;
751+
auto request = http_proto::request{};
752+
auto host = std::string{ url.encoded_host() };
753+
754+
host.push_back(':');
755+
host.append(effective_port(url));
756+
757+
request.set_method(http_proto::method::connect);
758+
request.set_target(host);
759+
request.set(field::host, host);
760+
request.set(field::proxy_connection, "keep-alive");
761+
762+
if(vm.count("user-agent"))
763+
{
764+
request.set(
765+
field::user_agent,
766+
vm.at("user-agent").as<std::string>());
767+
}
768+
else
769+
{
770+
request.set(field::user_agent, "Boost.Http.Io");
771+
}
772+
773+
// TODO
774+
// request.set(field::proxy_authorization, "");
775+
776+
auto serializer = http_proto::serializer{ http_proto_ctx };
777+
serializer.start(request);
778+
co_await http_io::async_write(stream, serializer);
779+
}
780+
781+
{
782+
auto parser = http_proto::response_parser{ http_proto_ctx };
783+
parser.reset();
784+
parser.start();
785+
co_await http_io::async_read_header(stream, parser);
786+
if(parser.get().status() != http_proto::status::ok)
787+
throw std::runtime_error{
788+
"Proxy server rejected the connection" };
789+
}
790+
}
791+
else // no proxy
792+
{
793+
auto rr = co_await resolver.async_resolve(
794+
url.host(), effective_port(url));
795+
co_await asio::async_connect(stream, rr);
796+
}
714797

715798
if(url.scheme() == "https")
716799
{
717-
auto stream = ssl::stream<asio::ip::tcp::socket>{ executor, ssl_ctx };
718-
co_await asio::async_connect(stream.lowest_layer(), rresults);
800+
auto ssl_stream = ssl::stream<asio::ip::tcp::socket>{
801+
std::move(stream), ssl_ctx };
719802

720-
if(auto host_s = std::string{ url.host() };
721-
!SSL_set_tlsext_host_name(stream.native_handle(), host_s.c_str()))
803+
auto host = std::string{ url.host() };
804+
if(!SSL_set_tlsext_host_name(
805+
ssl_stream.native_handle(), host.c_str()))
722806
{
723807
throw system_error{ static_cast<int>(::ERR_get_error()),
724808
asio::error::get_ssl_category() };
725809
}
726810

727-
co_await stream.async_handshake(ssl::stream_base::client);
728-
co_return stream;
811+
co_await ssl_stream.async_handshake(ssl::stream_base::client);
812+
co_return ssl_stream;
729813
}
730814

731-
auto stream = asio::ip::tcp::socket{ executor };
732-
co_await asio::async_connect(stream, rresults);
733815
co_return stream;
734816
}
735817

@@ -813,7 +895,7 @@ request(
813895
http_proto::request request,
814896
urls::url_view url)
815897
{
816-
auto stream = co_await connect(ssl_ctx, url);
898+
auto stream = co_await connect(vm, ssl_ctx, http_proto_ctx, url);
817899
auto parser = http_proto::response_parser{ http_proto_ctx };
818900
auto serializer = http_proto::serializer{ http_proto_ctx };
819901

@@ -838,15 +920,18 @@ request(
838920
if(auto it = response.find(http_proto::field::location);
839921
it != response.end())
840922
{
841-
auto redirect = urls::parse_uri(it->value).value();
923+
auto location = urls::parse_uri(it->value).value();
842924

843925
// Consume the body
844926
co_await http_io::async_read(stream, parser);
845927

846-
if(!can_reuse_connection(response, referer, redirect))
928+
if(!can_reuse_connection(response, referer, location))
847929
{
848-
co_await stream.async_shutdown(asio::as_tuple);
849-
stream = co_await connect(ssl_ctx, redirect);
930+
if(!vm.count("proxy"))
931+
co_await stream.async_shutdown(asio::as_tuple);
932+
933+
stream = co_await connect(
934+
vm, ssl_ctx, http_proto_ctx, location);
850935
}
851936

852937
// Change the method according to RFC 9110, Section 15.4.4.
@@ -857,11 +942,11 @@ request(
857942
request.erase(http_proto::field::content_type);
858943
msg = {}; // drop the body
859944
}
860-
request.set_target(target(redirect));
861-
request.set(http_proto::field::host, redirect.host());
862-
request.set(http_proto::field::referer, redirect);
945+
request.set_target(target(location));
946+
request.set(http_proto::field::host, location.host());
947+
request.set(http_proto::field::referer, location);
863948

864-
referer = redirect;
949+
referer = location;
865950

866951
serializer.reset();
867952
msg.start_serializer(serializer, request);
@@ -904,9 +989,12 @@ request(
904989
}
905990

906991
// clean shutdown
907-
auto [ec] = co_await stream.async_shutdown(asio::as_tuple);
908-
if(ec && ec != ssl::error::stream_truncated)
909-
throw system_error{ ec };
992+
if(!vm.count("proxy"))
993+
{
994+
auto [ec] = co_await stream.async_shutdown(asio::as_tuple);
995+
if(ec && ec != ssl::error::stream_truncated)
996+
throw system_error{ ec };
997+
}
910998
};
911999

9121000
int
@@ -936,6 +1024,9 @@ main(int argc, char* argv[])
9361024
("output,o",
9371025
po::value<std::string>()->value_name("<file>"),
9381026
"Write to file instead of stdout")
1027+
("proxy,x",
1028+
po::value<std::string>()->value_name("<url>"),
1029+
"Use this proxy")
9391030
("range,r",
9401031
po::value<std::string>()->value_name("<range>"),
9411032
"Retrieve only the bytes within range")

0 commit comments

Comments
 (0)