Skip to content

Instantly share code, notes, and snippets.

@madmongo1
Created November 27, 2020 10:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save madmongo1/cd62a5bebefb724ae45f0f60582c164e to your computer and use it in GitHub Desktop.
Save madmongo1/cd62a5bebefb724ae45f0f60582c164e to your computer and use it in GitHub Desktop.
std::string
deduce_port(std::string const &scheme, std::string port)
{
using boost::algorithm::iequals;
if (port.empty())
{
if (iequals(scheme, "ws") or iequals(scheme, "http"))
port = "http";
else if (iequals(scheme, "wss") or iequals(scheme, "https"))
port = "https";
else
throw system_error(net::error::invalid_argument,
"can't deduce port");
}
return port;
}
transport_type
deduce_transport(std::string const &scheme, std::string const &port)
{
using boost::algorithm::iequals;
if (scheme.empty())
{
if (port.empty())
return transport_type::tcp;
if (iequals(port, "http") or iequals(port, "ws") or
iequals(port, "80"))
return transport_type::tcp;
if (iequals(port, "https") or iequals(port, "wss") or
iequals(port, "443"))
return transport_type::tls;
throw system_error(net::error::invalid_argument,
"cannot deduce transport");
}
else
{
if (iequals(scheme, "http") or iequals(scheme, "ws"))
return transport_type::tcp;
if (iequals(scheme, "https") or iequals(scheme, "wss"))
return transport_type::tls;
throw system_error(net::error::invalid_argument,
"invalid scheme");
}
}
auto
set_sni(ssl_layer &stream, std::string const &host) -> void
{
if (not SSL_set_tlsext_host_name(stream.native_handle(),
host.c_str()))
throw system_error(
error_code(static_cast< int >(::ERR_get_error()),
net::error::get_ssl_category()));
}
std::string
build_target(std::string const &path,
std::string const &query,
std::string const &fragment)
{
std::string result;
if (path.empty())
result = "/";
else
result = path;
if (!query.empty())
result += "?" + query;
if (!fragment.empty())
result += "#" + fragment;
return result;
}
net::awaitable< net::ip::tcp::resolver::results_type >
resolve(std::string const &host, std::string const &port)
{
spdlog::debug("resolving [host {}][port {}]", host, port);
auto resolver =
net::ip::tcp::resolver(co_await net::this_coro::executor);
co_return co_await resolver.async_resolve(
host, port, net::use_awaitable);
}
net::awaitable< void >
connect_tcp(beast::tcp_stream & stream,
net::ip::tcp::resolver::results_type results)
{
spdlog::trace("connecting tcp");
stream.expires_after(30s);
auto ep =
co_await stream.async_connect(results, net::use_awaitable);
boost::ignore_unused(ep);
spdlog::trace("connected on {}", ep);
}
net::awaitable< void >
connect_tls(beast::ssl_stream< beast::tcp_stream > &stream,
std::string const & host)
{
spdlog::trace("tls handshake [host {}]", host);
set_sni(stream, host);
stream.next_layer().expires_after(30s);
co_await stream.async_handshake(net::ssl::stream_base::client,
net::use_awaitable);
}
auto
variant_websocket::connect(net::ssl::context & sslctx,
std::string url,
boost::beast::http::fields headers)
-> net::awaitable< void >
{
assert(this->empty());
static auto url_regex = std::regex(
R"regex((ws|wss|http|https)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\x3f?([^ #]*)#?([^ ]*))regex",
std::regex_constants::icase);
auto match = std::smatch();
if (not std::regex_match(url, match, url_regex))
throw system_error(net::error::invalid_argument, "invalid url");
auto &protocol = match[1];
auto &host = match[2];
auto &port_ind = match[3];
auto &path = match[4];
auto &query = match[5];
auto &fragment = match[6];
auto transport = deduce_transport(protocol, port_ind);
auto port = deduce_port(protocol, port_ind);
switch (transport)
{
case transport_type::tcp:
emplace_tcp(co_await net::this_coro::executor);
break;
case transport_type::tls:
emplace_tls(co_await net::this_coro::executor, sslctx);
break;
}
auto &tcp_layer = get_tcp();
// connect tcp
co_await connect_tcp(tcp_layer, co_await resolve(host.str(), port));
// tls handshake
if (auto tls = query_tls())
co_await connect_tls(*tls, host.str());
// websocket handshake
spdlog::trace("handshaking");
set_headers(headers);
beast::websocket::response_type response;
co_await client_handshake(
response, host.str(), build_target(path, query, fragment));
tcp_layer.expires_never();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment