src/detail/connection_pool.cpp

98.5% Lines (66/67) 100.0% List of functions (23/23) 88.9% Branches (24/27)
connection_pool.cpp
f(x) 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