src/detail/connection_pool.cpp

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