Skip to content

Commit 4540ca5

Browse files
committed
burl: use http_proto::sink
1 parent 4a0d06d commit 4540ca5

File tree

4 files changed

+155
-59
lines changed

4 files changed

+155
-59
lines changed

example/client/burl/error.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#include "error.hpp"
2+
3+
namespace
4+
{
5+
const boost::system::error_category&
6+
error_category()
7+
{
8+
static const struct : boost::system::error_category
9+
{
10+
const char*
11+
name() const noexcept override
12+
{
13+
return "burl";
14+
}
15+
16+
std::string
17+
message(int ev) const override
18+
{
19+
switch(static_cast<error>(ev))
20+
{
21+
case error::binary_output_to_tty:
22+
return "binary output to tty";
23+
default:
24+
return "Unknown burl error";
25+
}
26+
}
27+
} category;
28+
29+
return category;
30+
};
31+
} // namespace
32+
33+
std::error_code
34+
make_error_code(error e)
35+
{
36+
return { static_cast<int>(e), error_category() };
37+
}

example/client/burl/error.hpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// Copyright (c) 2025 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/http_io
8+
//
9+
10+
#ifndef BURL_ERROR_HPP
11+
#define BURL_ERROR_HPP
12+
13+
#include <boost/system/error_code.hpp>
14+
15+
enum class error
16+
{
17+
binary_output_to_tty = 1,
18+
};
19+
20+
namespace boost
21+
{
22+
namespace system
23+
{
24+
template<>
25+
struct is_error_code_enum<error> : std::true_type
26+
{
27+
};
28+
} // namespace system
29+
} // namespace boost
30+
31+
std::error_code
32+
make_error_code(error e);
33+
34+
#endif

example/client/burl/main.cpp

Lines changed: 82 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "base64.hpp"
1313
#include "connect.hpp"
1414
#include "cookie.hpp"
15+
#include "error.hpp"
1516
#include "message.hpp"
1617
#include "progress_meter.hpp"
1718
#include "request.hpp"
@@ -23,7 +24,6 @@
2324
#include <boost/asio/bind_executor.hpp>
2425
#include <boost/asio/cancel_after.hpp>
2526
#include <boost/asio/co_spawn.hpp>
26-
#include <boost/asio/detached.hpp>
2727
#include <boost/asio/experimental/channel.hpp>
2828
#include <boost/asio/ip/tcp.hpp>
2929
#include <boost/asio/read.hpp>
@@ -123,13 +123,16 @@ can_reuse_connection(
123123
if(response.metadata().connection.close)
124124
return false;
125125

126+
if(response.payload() == http_proto::payload::size &&
127+
response.payload_size() > 1024 * 1204)
128+
return false;
129+
126130
return true;
127131
}
128132

129133
bool
130134
ignorebody(
131135
const operation_config& oc,
132-
http_proto::request_view request,
133136
http_proto::response_view response) noexcept
134137
{
135138
if(oc.resume_from && !response.count(http_proto::field::content_range))
@@ -278,6 +281,56 @@ create_request(
278281
return request;
279282
}
280283

284+
class sink : public http_proto::sink
285+
{
286+
progress_meter* pm_;
287+
any_ostream* os_;
288+
bool terminal_binary_ok_;
289+
290+
public:
291+
sink(progress_meter* pm, any_ostream* os, bool terminal_binary_ok)
292+
: pm_{ pm }
293+
, os_{ os }
294+
, terminal_binary_ok_{ terminal_binary_ok }
295+
{
296+
}
297+
298+
results
299+
on_write(buffers::const_buffer cb, bool) override
300+
{
301+
auto chunk =
302+
core::string_view(static_cast<const char*>(cb.data()), cb.size());
303+
304+
if(!terminal_binary_ok_ && os_->is_tty() && chunk.contains('\0'))
305+
return { error::binary_output_to_tty };
306+
307+
*os_ << chunk;
308+
pm_->update(cb.size());
309+
return { {}, cb.size() };
310+
}
311+
};
312+
313+
class null_sink : public http_proto::sink
314+
{
315+
std::uint64_t limit_;
316+
317+
public:
318+
null_sink(std::uint64_t limit)
319+
: limit_{ limit }
320+
{
321+
}
322+
323+
results
324+
on_write(buffers::const_buffer cb, bool) override
325+
{
326+
if(limit_ < cb.size())
327+
return { http_proto::error::body_too_large };
328+
329+
limit_ -= cb.size();
330+
return { {}, cb.size() };
331+
}
332+
};
333+
281334
asio::awaitable<http_proto::status>
282335
perform_request(
283336
operation_config oc,
@@ -395,8 +448,8 @@ perform_request(
395448
}
396449
});
397450

398-
auto connect_to = [&](any_stream& stream, const urls::url_view& url)
399-
-> asio::awaitable<void>
451+
auto connect_to = [&](any_stream& stream,
452+
const urls::url_view& url) -> asio::awaitable<void>
400453
{
401454
// clean shutdown
402455
if(oc.proxy.empty())
@@ -485,17 +538,16 @@ perform_request(
485538

486539
if(can_reuse_connection(parser.get(), referer, url))
487540
{
488-
// Discard the body
489-
// TODO: drop the connection if body is large
490-
while(!parser.is_complete())
491-
{
492-
parser.consume_body(
493-
buffers::buffer_size(parser.pull_body()));
494-
co_await http_io::async_read_some(stream, parser);
495-
}
541+
// read and discard bodies smaller than 1MB
542+
parser.set_body<null_sink>(1024 * 1024);
543+
auto [ec, _] =
544+
co_await http_io::async_read(stream, parser, asio::as_tuple);
545+
if(ec)
546+
goto reconnect;
496547
}
497548
else
498549
{
550+
reconnect:
499551
co_await connect_to(stream, url);
500552
parser.reset();
501553
}
@@ -564,59 +616,25 @@ perform_request(
564616
"HTTP server doesn't seem to support byte ranges. Cannot resume.");
565617
}
566618

567-
auto stream_body = [&](progress_meter& pm) -> asio::awaitable<void>
568-
{
569-
for(;;)
570-
{
571-
for(auto cb : parser.pull_body())
572-
{
573-
auto chunk = core::string_view(
574-
static_cast<const char*>(cb.data()), cb.size());
575-
576-
if(output.is_tty() && !oc.terminal_binary_ok &&
577-
chunk.contains('\0'))
578-
{
579-
throw std::runtime_error(
580-
"Binary output can mess up your terminal.\n"
581-
"Use \"--output -\" to tell burl to output it to your "
582-
"terminal anyway, or\n"
583-
"consider \"--output <FILE>\" to save to a file.");
584-
}
585-
586-
output << chunk;
587-
parser.consume_body(cb.size());
588-
pm.update(cb.size());
589-
}
590-
591-
if(parser.is_complete())
592-
break;
593-
594-
auto [ec, _] = co_await http_io::async_read_some(
595-
stream, parser, asio::as_tuple);
596-
if(ec && ec != http_proto::condition::need_more_input)
597-
throw system_error{ ec };
598-
}
599-
};
600-
601-
if(!ignorebody(oc, request, parser.get()))
619+
if(!ignorebody(oc, parser.get()))
602620
{
603621
auto pm = progress_meter{ body_size(parser.get()) };
622+
parser.set_body<sink>(&pm, &output, oc.terminal_binary_ok);
604623

605624
if(output.is_tty() || oc.parallel_max > 1 || oc.noprogress)
606625
{
607-
co_await stream_body(pm);
626+
co_await http_io::async_read(stream, parser);
608627
}
609628
else
610629
{
611-
auto [order, ep1, ep2] =
630+
auto [order, ec, n, ep] =
612631
co_await asio::experimental::make_parallel_group(
613-
co_spawn(executor, stream_body(pm)),
632+
http_io::async_read(stream, parser),
614633
co_spawn(executor, report_progress(pm)))
615634
.async_wait(
616635
asio::experimental::wait_for_one{}, asio::deferred);
617-
618-
if(ep1)
619-
std::rethrow_exception(ep1);
636+
if(ec)
637+
throw system_error{ ec };
620638
}
621639
}
622640

@@ -688,6 +706,17 @@ retry(
688706
}
689707
catch(const system_error& e)
690708
{
709+
if(e.code() == error::binary_output_to_tty)
710+
{
711+
// clang-format off
712+
std::cerr <<
713+
"Binary output can mess up your terminal.\n"
714+
"Use \"--output -\" to tell burl to output it to your terminal anyway, or\n"
715+
"consider \"--output <FILE>\" to save to a file." << std::endl;
716+
// clang-format on
717+
co_return;
718+
}
719+
691720
std::cerr << e.what() << std::endl;
692721
if(!can_retry(e.code()))
693722
throw;

include/boost/http_io/impl/read.hpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ class read_body_op
138138
{
139139
BOOST_ASIO_CORO_REENTER(*this)
140140
{
141-
if(pr_.is_complete())
141+
pr_.parse(ec);
142+
if(ec != http_proto::error::need_data)
142143
{
143144
BOOST_ASIO_CORO_YIELD
144145
{
@@ -152,11 +153,6 @@ class read_body_op
152153
ec,
153154
0));
154155
}
155-
// If the body was just set,
156-
// this will transfer the
157-
// body data. Otherwise,
158-
// it is a no-op.
159-
pr_.parse(ec);
160156
goto upcall;
161157
}
162158
for(;;)

0 commit comments

Comments
 (0)