src/detail/connection_pool.cpp
98.5% Lines (66/67)
100.0% List of functions (23/23)
88.9% Branches (24/27)
Functions (23)
Function
Calls
Lines
Branches
Blocks
boost::burl::detail::(anonymous namespace)::origin(boost::urls::url_view)
:46
68x
83.3%
100.0%
65.0%
boost::burl::detail::(anonymous namespace)::connect_tcp(boost::corosio::tcp_socket&, boost::capy::executor_ref, boost::burl::client::config const&, boost::urls::url_view)
:55
9x
100.0%
100.0%
44.0%
boost::burl::detail::(anonymous namespace)::tcp_connection::tcp_connection(boost::corosio::tcp_socket)
:81
4x
100.0%
–
100.0%
boost::burl::detail::(anonymous namespace)::tcp_connection::is_open() const
:87
12x
100.0%
–
100.0%
boost::burl::detail::(anonymous namespace)::tcp_connection::do_read_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>)
:101
8x
100.0%
100.0%
44.0%
boost::burl::detail::(anonymous namespace)::tcp_connection::do_write_some(std::span<boost::capy::const_buffer const, 18446744073709551615ul>)
:107
8x
100.0%
100.0%
44.0%
boost::burl::detail::(anonymous namespace)::tls_connection::tls_connection(boost::corosio::tcp_socket, boost::corosio::tls_context const&)
:119
2x
100.0%
100.0%
67.0%
boost::burl::detail::(anonymous namespace)::tls_connection::handshake()
:126
2x
100.0%
–
100.0%
boost::burl::detail::(anonymous namespace)::tls_connection::set_hostname(std::basic_string_view<char, std::char_traits<char> >)
:132
2x
100.0%
–
100.0%
boost::burl::detail::(anonymous namespace)::tls_connection::is_open() const
:138
3x
100.0%
–
100.0%
boost::burl::detail::(anonymous namespace)::tls_connection::do_read_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>)
:151
2x
100.0%
–
100.0%
boost::burl::detail::(anonymous namespace)::tls_connection::do_write_some(std::span<boost::capy::const_buffer const, 18446744073709551615ul>)
:157
2x
100.0%
–
100.0%
boost::burl::detail::(anonymous namespace)::stream_connection::stream_connection(boost::capy::any_stream)
:169
46x
100.0%
–
100.0%
boost::burl::detail::(anonymous namespace)::stream_connection::is_open() const
:175
37x
100.0%
–
100.0%
boost::burl::detail::(anonymous namespace)::stream_connection::do_read_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>)
:189
38x
100.0%
100.0%
44.0%
boost::burl::detail::(anonymous namespace)::stream_connection::do_write_some(std::span<boost::capy::const_buffer const, 18446744073709551615ul>)
:198
35x
100.0%
100.0%
44.0%
boost::burl::detail::connection::read_some_impl(boost::capy::detail::buffer_array<8ul, false>)
:210
236x
100.0%
100.0%
42.0%
boost::burl::detail::connection::write_some_impl(boost::capy::detail::buffer_array<8ul, true>)
:220
46x
100.0%
100.0%
42.0%
boost::burl::detail::connection_pool::connection_pool(boost::capy::executor_ref, boost::corosio::tls_context, boost::burl::client::config)
:229
54x
100.0%
–
100.0%
boost::burl::detail::connection_pool::acquire(boost::urls::url_view)
:240
68x
100.0%
100.0%
44.0%
boost::burl::detail::connection_pool::release(boost::burl::detail::pooled_connection)
:280
43x
100.0%
72.7%
81.0%
boost::burl::detail::connection_pool::connect(boost::urls::url_view) const
:294
58x
100.0%
100.0%
44.0%
boost::burl::detail::pooled_connection::return_to_pool()
:386
64x
100.0%
100.0%
71.0%
| Line | Branch | TLA | Hits | Source Code |
|---|---|---|---|---|
| 1 | // | |||
| 2 | // Copyright (c) 2026 Mohammad Nejati | |||
| 3 | // | |||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| 6 | // | |||
| 7 | // Official repository: https://github.com/cppalliance/burl | |||
| 8 | // | |||
| 9 | ||||
| 10 | #include "connection_pool.hpp" | |||
| 11 | ||||
| 12 | #include <boost/burl/error.hpp> | |||
| 13 | ||||
| 14 | #include "effective_port.hpp" | |||
| 15 | #include "http_tunnel.hpp" | |||
| 16 | #include "socks5_tunnel.hpp" | |||
| 17 | ||||
| 18 | #include <boost/capy/io/any_stream.hpp> | |||
| 19 | #include <boost/corosio/connect.hpp> | |||
| 20 | #include <boost/corosio/openssl_stream.hpp> | |||
| 21 | #include <boost/corosio/resolver.hpp> | |||
| 22 | #include <boost/corosio/shutdown_type.hpp> | |||
| 23 | #include <boost/corosio/socket_option.hpp> | |||
| 24 | #include <boost/corosio/tcp_socket.hpp> | |||
| 25 | #include <boost/corosio/timeout.hpp> | |||
| 26 | #include <boost/corosio/tls_stream.hpp> | |||
| 27 | #include <boost/url/scheme.hpp> | |||
| 28 | #include <boost/url/url_view.hpp> | |||
| 29 | ||||
| 30 | #include <memory> | |||
| 31 | #include <string> | |||
| 32 | #include <string_view> | |||
| 33 | #include <utility> | |||
| 34 | ||||
| 35 | namespace boost | |||
| 36 | { | |||
| 37 | namespace burl | |||
| 38 | { | |||
| 39 | namespace detail | |||
| 40 | { | |||
| 41 | ||||
| 42 | namespace | |||
| 43 | { | |||
| 44 | ||||
| 45 | std::string | |||
| 46 | 68x | origin(urls::url_view url) | ||
| 47 | { | |||
| 48 |
1/1✓ Branch 2 taken 68 times.
|
136x | std::string key{ url.scheme() }; | |
| 49 |
1/1✓ Branch 1 taken 68 times.
|
68x | key += "://"; | |
| 50 |
1/1✓ Branch 2 taken 68 times.
|
68x | key += url.encoded_host_and_port(); | |
| 51 | 68x | return key; | ||
| 52 | ✗ | } | ||
| 53 | ||||
| 54 | capy::io_task<> | |||
| 55 |
1/1✓ Branch 1 taken 9 times.
|
9x | connect_tcp( | |
| 56 | corosio::tcp_socket& socket, | |||
| 57 | capy::executor_ref exec, | |||
| 58 | const client::config& cfg, | |||
| 59 | urls::url_view url) | |||
| 60 | { | |||
| 61 | corosio::resolver resolver(exec); | |||
| 62 | auto [rec, eps] = co_await resolver.resolve( | |||
| 63 | url.encoded_host_address(), effective_port(url)); | |||
| 64 | if(rec) | |||
| 65 | co_return rec; | |||
| 66 | ||||
| 67 | if(auto [cec, ep] = co_await corosio::connect(socket, eps); cec) | |||
| 68 | co_return cec; | |||
| 69 | ||||
| 70 | if(cfg.tcp_nodelay) | |||
| 71 | socket.set_option(corosio::socket_option::no_delay(true)); | |||
| 72 | ||||
| 73 | co_return {}; | |||
| 74 | 18x | } | ||
| 75 | ||||
| 76 | class tcp_connection final : public connection | |||
| 77 | { | |||
| 78 | corosio::tcp_socket socket_; | |||
| 79 | ||||
| 80 | public: | |||
| 81 | 4x | explicit tcp_connection(corosio::tcp_socket socket) | ||
| 82 | 4x | : socket_(std::move(socket)) | ||
| 83 | { | |||
| 84 | 4x | } | ||
| 85 | ||||
| 86 | bool | |||
| 87 | 12x | is_open() const noexcept override | ||
| 88 | { | |||
| 89 | 12x | return socket_.is_open(); | ||
| 90 | } | |||
| 91 | ||||
| 92 | // capy::io_task<> | |||
| 93 | // shutdown() override | |||
| 94 | // { | |||
| 95 | // socket_.shutdown(corosio::shutdown_both); | |||
| 96 | // co_return {}; | |||
| 97 | // } | |||
| 98 | ||||
| 99 | private: | |||
| 100 | capy::io_task<std::size_t> | |||
| 101 |
1/1✓ Branch 1 taken 8 times.
|
8x | do_read_some(std::span<capy::mutable_buffer const> buffers) override | |
| 102 | { | |||
| 103 | co_return co_await socket_.read_some(buffers); | |||
| 104 | 16x | } | ||
| 105 | ||||
| 106 | capy::io_task<std::size_t> | |||
| 107 |
1/1✓ Branch 1 taken 8 times.
|
8x | do_write_some(std::span<capy::const_buffer const> buffers) override | |
| 108 | { | |||
| 109 | co_return co_await socket_.write_some(buffers); | |||
| 110 | 16x | } | ||
| 111 | }; | |||
| 112 | ||||
| 113 | class tls_connection final : public connection | |||
| 114 | { | |||
| 115 | corosio::tcp_socket socket_; | |||
| 116 | corosio::openssl_stream stream_; | |||
| 117 | ||||
| 118 | public: | |||
| 119 | 2x | tls_connection(corosio::tcp_socket socket, const corosio::tls_context& ctx) | ||
| 120 | 2x | : socket_(std::move(socket)) | ||
| 121 |
1/1✓ Branch 3 taken 2 times.
|
4x | , stream_(&socket_, ctx) | |
| 122 | { | |||
| 123 | 2x | } | ||
| 124 | ||||
| 125 | capy::io_task<> | |||
| 126 | 2x | handshake() | ||
| 127 | { | |||
| 128 | 2x | return stream_.handshake(corosio::tls_role::client); | ||
| 129 | } | |||
| 130 | ||||
| 131 | void | |||
| 132 | 2x | set_hostname(std::string_view hostname) | ||
| 133 | { | |||
| 134 | 2x | stream_.set_hostname(hostname); | ||
| 135 | 2x | } | ||
| 136 | ||||
| 137 | bool | |||
| 138 | 3x | is_open() const noexcept override | ||
| 139 | { | |||
| 140 | 3x | return socket_.is_open(); | ||
| 141 | } | |||
| 142 | ||||
| 143 | // capy::io_task<> | |||
| 144 | // shutdown() override | |||
| 145 | // { | |||
| 146 | // return stream_.shutdown(); | |||
| 147 | // } | |||
| 148 | ||||
| 149 | private: | |||
| 150 | capy::io_task<std::size_t> | |||
| 151 | 2x | do_read_some(std::span<capy::mutable_buffer const> buffers) override | ||
| 152 | { | |||
| 153 | 2x | return stream_.read_some(buffers); | ||
| 154 | } | |||
| 155 | ||||
| 156 | capy::io_task<std::size_t> | |||
| 157 | 2x | do_write_some(std::span<capy::const_buffer const> buffers) override | ||
| 158 | { | |||
| 159 | 2x | return stream_.write_some(buffers); | ||
| 160 | } | |||
| 161 | }; | |||
| 162 | ||||
| 163 | class stream_connection final : public connection | |||
| 164 | { | |||
| 165 | capy::any_stream stream_; | |||
| 166 | bool open_ = true; | |||
| 167 | ||||
| 168 | public: | |||
| 169 | 46x | explicit stream_connection(capy::any_stream stream) | ||
| 170 | 46x | : stream_(std::move(stream)) | ||
| 171 | { | |||
| 172 | 46x | } | ||
| 173 | ||||
| 174 | bool | |||
| 175 | 37x | is_open() const noexcept override | ||
| 176 | { | |||
| 177 | 37x | return open_; | ||
| 178 | } | |||
| 179 | ||||
| 180 | // capy::io_task<> | |||
| 181 | // shutdown() override | |||
| 182 | // { | |||
| 183 | // open_ = false; | |||
| 184 | // co_return {}; | |||
| 185 | // } | |||
| 186 | ||||
| 187 | private: | |||
| 188 | capy::io_task<std::size_t> | |||
| 189 |
1/1✓ Branch 1 taken 38 times.
|
38x | do_read_some(std::span<capy::mutable_buffer const> buffers) override | |
| 190 | { | |||
| 191 | auto [ec, n] = co_await stream_.read_some(buffers); | |||
| 192 | if(ec) | |||
| 193 | open_ = false; | |||
| 194 | co_return { ec, n }; | |||
| 195 | 76x | } | ||
| 196 | ||||
| 197 | capy::io_task<std::size_t> | |||
| 198 |
1/1✓ Branch 1 taken 35 times.
|
35x | do_write_some(std::span<capy::const_buffer const> buffers) override | |
| 199 | { | |||
| 200 | auto [ec, n] = co_await stream_.write_some(buffers); | |||
| 201 | if(ec) | |||
| 202 | open_ = false; | |||
| 203 | co_return { ec, n }; | |||
| 204 | 70x | } | ||
| 205 | }; | |||
| 206 | ||||
| 207 | } // namespace | |||
| 208 | ||||
| 209 | capy::io_task<std::size_t> | |||
| 210 |
1/1✓ Branch 1 taken 236 times.
|
236x | connection:: | |
| 211 | read_some_impl(capy::detail::mutable_buffer_array<8> rba) | |||
| 212 | { | |||
| 213 | if(io_timeout_) | |||
| 214 | co_return co_await corosio::timeout( | |||
| 215 | do_read_some(rba), *io_timeout_); | |||
| 216 | co_return co_await do_read_some(rba); | |||
| 217 | 472x | } | ||
| 218 | ||||
| 219 | capy::io_task<std::size_t> | |||
| 220 |
1/1✓ Branch 1 taken 46 times.
|
46x | connection:: | |
| 221 | write_some_impl(capy::detail::const_buffer_array<8> wba) | |||
| 222 | { | |||
| 223 | if(io_timeout_) | |||
| 224 | co_return co_await corosio::timeout( | |||
| 225 | do_write_some(wba), *io_timeout_); | |||
| 226 | co_return co_await do_write_some(wba); | |||
| 227 | 92x | } | ||
| 228 | ||||
| 229 | 54x | connection_pool::connection_pool( | ||
| 230 | capy::executor_ref exec, | |||
| 231 | corosio::tls_context tls_ctx, | |||
| 232 | 54x | config cfg) | ||
| 233 | 54x | : exec_(exec) | ||
| 234 | 54x | , tls_ctx_(std::move(tls_ctx)) | ||
| 235 | 108x | , config_(std::move(cfg)) | ||
| 236 | { | |||
| 237 | 54x | } | ||
| 238 | ||||
| 239 | capy::io_task<pooled_connection> | |||
| 240 |
1/1✓ Branch 1 taken 68 times.
|
68x | connection_pool::acquire(urls::url_view url) | |
| 241 | { | |||
| 242 | auto key = origin(url); | |||
| 243 | auto [it, last] = idle_.equal_range(key); | |||
| 244 | while(it != last) | |||
| 245 | { | |||
| 246 | auto entry = std::move(it->second); | |||
| 247 | it = idle_.erase(it); | |||
| 248 | ||||
| 249 | if(config::clock::now() - entry.idle_since >= config_.pool_idle_timeout) | |||
| 250 | continue; | |||
| 251 | ||||
| 252 | if(!entry.conn->is_open()) | |||
| 253 | continue; | |||
| 254 | ||||
| 255 | entry.conn->set_io_timeout(config_.io_timeout); | |||
| 256 | co_return { | |||
| 257 | {}, | |||
| 258 | { std::move(entry.conn), | |||
| 259 | weak_from_this(), | |||
| 260 | std::move(key) } | |||
| 261 | }; | |||
| 262 | } | |||
| 263 | ||||
| 264 | auto [ec, conn] = | |||
| 265 | co_await corosio::timeout( | |||
| 266 | connect(url), config_.connect_timeout); | |||
| 267 | if(ec) | |||
| 268 | co_return { ec, {} }; | |||
| 269 | ||||
| 270 | conn->set_io_timeout(config_.io_timeout); | |||
| 271 | co_return { | |||
| 272 | {}, | |||
| 273 | { std::move(conn), | |||
| 274 | weak_from_this(), | |||
| 275 | std::move(key) } | |||
| 276 | }; | |||
| 277 | 136x | } | ||
| 278 | ||||
| 279 | void | |||
| 280 | 43x | connection_pool::release(pooled_connection pc) | ||
| 281 | { | |||
| 282 |
5/6✓ Branch 1 taken 42 times.
✓ Branch 2 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 42 times.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 42 times.
|
43x | if(!pc.conn_ || !pc.conn_->is_open()) | |
| 283 | 1x | return; | ||
| 284 | ||||
| 285 |
2/2✓ Branch 1 taken 1 time.
✓ Branch 2 taken 41 times.
|
42x | if(idle_.count(pc.key_) >= config_.pool_max_idle_per_host) | |
| 286 | 1x | return; | ||
| 287 | ||||
| 288 |
1/1✓ Branch 1 taken 41 times.
|
41x | idle_.emplace( | |
| 289 | 41x | std::move(pc.key_), | ||
| 290 |
0/2✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
82x | idle_connection{ std::move(pc.conn_), config::clock::now() }); | |
| 291 | } | |||
| 292 | ||||
| 293 | capy::io_task<std::unique_ptr<connection>> | |||
| 294 |
1/1✓ Branch 1 taken 58 times.
|
58x | connection_pool::connect(urls::url_view url) const | |
| 295 | { | |||
| 296 | using urls::scheme; | |||
| 297 | ||||
| 298 | if(url.scheme_id() != scheme::http && url.scheme_id() != scheme::https) | |||
| 299 | co_return { error::unsupported_url_scheme, {} }; | |||
| 300 | ||||
| 301 | if(config_.connect_handler) | |||
| 302 | { | |||
| 303 | auto [ec, stream] = co_await config_.connect_handler(url); | |||
| 304 | if(ec) | |||
| 305 | co_return { ec, {} }; | |||
| 306 | co_return { | |||
| 307 | {}, std::make_unique<stream_connection>(std::move(stream)) }; | |||
| 308 | } | |||
| 309 | ||||
| 310 | corosio::tcp_socket socket(exec_); | |||
| 311 | ||||
| 312 | if(config_.proxy) | |||
| 313 | { | |||
| 314 | auto const& proxy = *config_.proxy; | |||
| 315 | if(effective_port(proxy).empty()) | |||
| 316 | co_return { error::unsupported_proxy_scheme, {} }; | |||
| 317 | ||||
| 318 | if(auto [ec] = co_await connect_tcp(socket, exec_, config_, proxy); ec) | |||
| 319 | co_return { ec, {} }; | |||
| 320 | ||||
| 321 | if(proxy.scheme() == "http") | |||
| 322 | { | |||
| 323 | auto [ec] = co_await open_http_tunnel( | |||
| 324 | capy::any_stream(&socket), url, proxy); | |||
| 325 | if(ec) | |||
| 326 | co_return { ec, {} }; | |||
| 327 | } | |||
| 328 | else if(proxy.scheme() == "socks5") | |||
| 329 | { | |||
| 330 | urls::url resolved; | |||
| 331 | ||||
| 332 | corosio::resolver resolver(exec_); | |||
| 333 | auto [rec, eps] = co_await resolver.resolve( | |||
| 334 | url.encoded_host_address(), effective_port(url)); | |||
| 335 | if(rec) | |||
| 336 | co_return { rec, {} }; | |||
| 337 | ||||
| 338 | auto const& ep = eps.front().get_endpoint(); | |||
| 339 | resolved.set_port_number(ep.port()); | |||
| 340 | if(ep.is_v4()) | |||
| 341 | resolved.set_host_ipv4( | |||
| 342 | urls::ipv4_address(ep.v4_address().to_bytes())); | |||
| 343 | else | |||
| 344 | resolved.set_host_ipv6( | |||
| 345 | urls::ipv6_address(ep.v6_address().to_bytes())); | |||
| 346 | ||||
| 347 | auto [ec] = co_await open_socks5_tunnel( | |||
| 348 | capy::any_stream(&socket), resolved, proxy); | |||
| 349 | if(ec) | |||
| 350 | co_return { ec, {} }; | |||
| 351 | } | |||
| 352 | else if(proxy.scheme() == "socks5h") | |||
| 353 | { | |||
| 354 | auto [ec] = co_await open_socks5_tunnel( | |||
| 355 | capy::any_stream(&socket), url, proxy); | |||
| 356 | if(ec) | |||
| 357 | co_return { ec, {} }; | |||
| 358 | } | |||
| 359 | else | |||
| 360 | { | |||
| 361 | co_return { error::unsupported_proxy_scheme, {} }; | |||
| 362 | } | |||
| 363 | } | |||
| 364 | else | |||
| 365 | { | |||
| 366 | if(auto [ec] = co_await connect_tcp(socket, exec_, config_, url); ec) | |||
| 367 | co_return { ec, {} }; | |||
| 368 | } | |||
| 369 | ||||
| 370 | if(url.scheme_id() == scheme::https) | |||
| 371 | { | |||
| 372 | auto conn = | |||
| 373 | std::make_unique<tls_connection>(std::move(socket), tls_ctx_); | |||
| 374 | conn->set_hostname(url.encoded_host()); | |||
| 375 | auto [hec] = co_await conn->handshake(); | |||
| 376 | if(hec) | |||
| 377 | co_return { hec, {} }; | |||
| 378 | ||||
| 379 | co_return { {}, std::move(conn) }; | |||
| 380 | } | |||
| 381 | ||||
| 382 | co_return { {}, std::make_unique<tcp_connection>(std::move(socket)) }; | |||
| 383 | 116x | } | ||
| 384 | ||||
| 385 | void | |||
| 386 | 64x | pooled_connection::return_to_pool() | ||
| 387 | { | |||
| 388 |
2/2✓ Branch 2 taken 21 times.
✓ Branch 3 taken 43 times.
|
64x | if(auto pool = pool_.lock()) | |
| 389 |
1/1✓ Branch 4 taken 21 times.
|
64x | pool->release(std::move(*this)); | |
| 390 | 64x | } | ||
| 391 | ||||
| 392 | } // namespace detail | |||
| 393 | } // namespace burl | |||
| 394 | } // namespace boost | |||
| 395 |