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 77x 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) :53 9x 100.0% 100.0% 44.0% boost::burl::detail::(anonymous namespace)::tcp_connection::tcp_connection(boost::corosio::tcp_socket) :79 4x 100.0% 100.0% boost::burl::detail::(anonymous namespace)::tcp_connection::is_open() const :85 12x 100.0% 100.0% boost::burl::detail::(anonymous namespace)::tcp_connection::do_read_some(std::span<boost::capy::mutable_buffer const, 18446744073709551615ul>) :99 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>) :105 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&) :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 63x 100.0% 100.0% boost::burl::detail::connection_pool::acquire(boost::urls::url_view) :212 77x 100.0% 100.0% 44.0% boost::burl::detail::connection_pool::release(boost::burl::detail::pooled_connection) :251 44x 100.0% 72.7% 81.0% boost::burl::detail::connection_pool::connect(boost::urls::url_view) const :265 67x 100.0% 100.0% 44.0% boost::burl::detail::pooled_connection::return_to_pool() :359 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 77x origin(urls::url_view url)
45 {
46
1/1
✓ Branch 2 taken 77 times.
154x std::string key{ url.scheme() };
47
1/1
✓ Branch 1 taken 77 times.
77x key += "://";
48
1/1
✓ Branch 2 taken 77 times.
77x key += url.encoded_host_and_port();
49 77x return key;
50 }
51
52 capy::io_task<>
53
1/1
✓ Branch 1 taken 9 times.
9x connect_tcp(
54 corosio::tcp_socket& socket,
55 capy::executor_ref exec,
56 const client::config& cfg,
57 urls::url_view url)
58 {
59 corosio::resolver resolver(exec);
60 auto [rec, eps] = co_await resolver.resolve(
61 url.encoded_host_address(), effective_port(url));
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 18x }
73
74 class tcp_connection final : public connection
75 {
76 corosio::tcp_socket socket_;
77
78 public:
79 4x explicit tcp_connection(corosio::tcp_socket socket)
80 4x : socket_(std::move(socket))
81 {
82 4x }
83
84 bool
85 12x is_open() const noexcept override
86 {
87 12x 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 8 times.
8x do_read_some(std::span<capy::mutable_buffer const> buffers) override
100 {
101 co_return co_await socket_.read_some(buffers);
102 16x }
103
104 capy::io_task<std::size_t>
105
1/1
✓ Branch 1 taken 8 times.
8x do_write_some(std::span<capy::const_buffer const> buffers) override
106 {
107 co_return co_await socket_.write_some(buffers);
108 16x }
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 63x connection_pool::connection_pool(
202 capy::executor_ref exec,
203 corosio::tls_context tls_ctx,
204 63x config cfg)
205 63x : exec_(exec)
206 63x , tls_ctx_(std::move(tls_ctx))
207 126x , config_(std::move(cfg))
208 {
209 63x }
210
211 capy::io_task<pooled_connection>
212
1/1
✓ Branch 1 taken 77 times.
77x 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 154x }
249
250 void
251 44x connection_pool::release(pooled_connection pc)
252 {
253
5/6
✓ Branch 1 taken 43 times.
✓ Branch 2 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 43 times.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 43 times.
44x if(!pc.conn_ || !pc.conn_->is_open())
254 1x return;
255
256
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 42 times.
43x if(idle_.count(pc.key_) >= config_.pool_max_idle_per_host)
257 1x return;
258
259
1/1
✓ Branch 1 taken 42 times.
42x idle_.emplace(
260 42x std::move(pc.key_),
261
0/2
✗ Branch 6 not taken.
✗ Branch 7 not taken.
84x 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 67 times.
67x connection_pool::connect(urls::url_view url) const
266 {
267 using urls::scheme;
268
269 if(url.scheme_id() != scheme::http && url.scheme_id() != scheme::https)
270 co_return { error::unsupported_url_scheme, {} };
271
272 if(config_.connect_handler)
273 {
274 auto [ec, stream] = co_await config_.connect_handler(url);
275 if(ec)
276 co_return { ec, {} };
277 co_return {
278 {}, std::make_unique<stream_connection>(std::move(stream)) };
279 }
280
281 corosio::tcp_socket socket(exec_);
282
283 if(config_.proxy)
284 {
285 auto const& proxy = *config_.proxy;
286 if(effective_port(proxy).empty())
287 co_return { error::unsupported_proxy_scheme, {} };
288
289 if(auto [ec] = co_await connect_tcp(socket, exec_, config_, proxy); ec)
290 co_return { ec, {} };
291
292 if(proxy.scheme() == "http")
293 {
294 auto [ec] = co_await open_http_tunnel(
295 capy::any_stream(&socket), url, proxy);
296 if(ec)
297 co_return { ec, {} };
298 }
299 else if(proxy.scheme() == "socks5")
300 {
301 urls::url resolved;
302
303 corosio::resolver resolver(exec_);
304 auto [rec, eps] = co_await resolver.resolve(
305 url.encoded_host_address(), effective_port(url));
306 if(rec)
307 co_return { rec, {} };
308
309 auto const& ep = eps.front().get_endpoint();
310 resolved.set_port_number(ep.port());
311 if(ep.is_v4())
312 resolved.set_host_ipv4(
313 urls::ipv4_address(ep.v4_address().to_bytes()));
314 else
315 resolved.set_host_ipv6(
316 urls::ipv6_address(ep.v6_address().to_bytes()));
317
318 auto [ec] = co_await open_socks5_tunnel(
319 capy::any_stream(&socket), resolved, proxy);
320 if(ec)
321 co_return { ec, {} };
322 }
323 else if(proxy.scheme() == "socks5h")
324 {
325 auto [ec] = co_await open_socks5_tunnel(
326 capy::any_stream(&socket), url, proxy);
327 if(ec)
328 co_return { ec, {} };
329 }
330 else
331 {
332 co_return { error::unsupported_proxy_scheme, {} };
333 }
334 }
335 else
336 {
337 if(auto [ec] = co_await connect_tcp(socket, exec_, config_, url); ec)
338 co_return { ec, {} };
339 }
340
341 if(url.scheme_id() == scheme::https)
342 {
343 auto tls_ctx = tls_ctx_;
344 tls_ctx.set_hostname(url.encoded_host());
345
346 auto conn =
347 std::make_unique<tls_connection>(std::move(socket), tls_ctx);
348 auto [hec] = co_await conn->handshake();
349 if(hec)
350 co_return { hec, {} };
351
352 co_return { {}, std::move(conn) };
353 }
354
355 co_return { {}, std::make_unique<tcp_connection>(std::move(socket)) };
356 134x }
357
358 void
359 65x pooled_connection::return_to_pool()
360 {
361
2/2
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 43 times.
65x if(auto pool = pool_.lock())
362
1/1
✓ Branch 4 taken 22 times.
65x pool->release(std::move(*this));
363 65x }
364
365 } // namespace detail
366 } // namespace burl
367 } // namespace boost
368