|
12 | 12 | #include "base64.hpp" |
13 | 13 | #include "connect.hpp" |
14 | 14 | #include "cookie.hpp" |
| 15 | +#include "error.hpp" |
15 | 16 | #include "message.hpp" |
16 | 17 | #include "progress_meter.hpp" |
17 | 18 | #include "request.hpp" |
|
23 | 24 | #include <boost/asio/bind_executor.hpp> |
24 | 25 | #include <boost/asio/cancel_after.hpp> |
25 | 26 | #include <boost/asio/co_spawn.hpp> |
26 | | -#include <boost/asio/detached.hpp> |
27 | 27 | #include <boost/asio/experimental/channel.hpp> |
28 | 28 | #include <boost/asio/ip/tcp.hpp> |
29 | 29 | #include <boost/asio/read.hpp> |
@@ -123,13 +123,16 @@ can_reuse_connection( |
123 | 123 | if(response.metadata().connection.close) |
124 | 124 | return false; |
125 | 125 |
|
| 126 | + if(response.payload() == http_proto::payload::size && |
| 127 | + response.payload_size() > 1024 * 1204) |
| 128 | + return false; |
| 129 | + |
126 | 130 | return true; |
127 | 131 | } |
128 | 132 |
|
129 | 133 | bool |
130 | 134 | ignorebody( |
131 | 135 | const operation_config& oc, |
132 | | - http_proto::request_view request, |
133 | 136 | http_proto::response_view response) noexcept |
134 | 137 | { |
135 | 138 | if(oc.resume_from && !response.count(http_proto::field::content_range)) |
@@ -278,6 +281,56 @@ create_request( |
278 | 281 | return request; |
279 | 282 | } |
280 | 283 |
|
| 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 | + |
281 | 334 | asio::awaitable<http_proto::status> |
282 | 335 | perform_request( |
283 | 336 | operation_config oc, |
@@ -395,8 +448,8 @@ perform_request( |
395 | 448 | } |
396 | 449 | }); |
397 | 450 |
|
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> |
400 | 453 | { |
401 | 454 | // clean shutdown |
402 | 455 | if(oc.proxy.empty()) |
@@ -485,17 +538,16 @@ perform_request( |
485 | 538 |
|
486 | 539 | if(can_reuse_connection(parser.get(), referer, url)) |
487 | 540 | { |
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; |
496 | 547 | } |
497 | 548 | else |
498 | 549 | { |
| 550 | + reconnect: |
499 | 551 | co_await connect_to(stream, url); |
500 | 552 | parser.reset(); |
501 | 553 | } |
@@ -564,59 +616,25 @@ perform_request( |
564 | 616 | "HTTP server doesn't seem to support byte ranges. Cannot resume."); |
565 | 617 | } |
566 | 618 |
|
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())) |
602 | 620 | { |
603 | 621 | auto pm = progress_meter{ body_size(parser.get()) }; |
| 622 | + parser.set_body<sink>(&pm, &output, oc.terminal_binary_ok); |
604 | 623 |
|
605 | 624 | if(output.is_tty() || oc.parallel_max > 1 || oc.noprogress) |
606 | 625 | { |
607 | | - co_await stream_body(pm); |
| 626 | + co_await http_io::async_read(stream, parser); |
608 | 627 | } |
609 | 628 | else |
610 | 629 | { |
611 | | - auto [order, ep1, ep2] = |
| 630 | + auto [order, ec, n, ep] = |
612 | 631 | co_await asio::experimental::make_parallel_group( |
613 | | - co_spawn(executor, stream_body(pm)), |
| 632 | + http_io::async_read(stream, parser), |
614 | 633 | co_spawn(executor, report_progress(pm))) |
615 | 634 | .async_wait( |
616 | 635 | asio::experimental::wait_for_one{}, asio::deferred); |
617 | | - |
618 | | - if(ep1) |
619 | | - std::rethrow_exception(ep1); |
| 636 | + if(ec) |
| 637 | + throw system_error{ ec }; |
620 | 638 | } |
621 | 639 | } |
622 | 640 |
|
@@ -688,6 +706,17 @@ retry( |
688 | 706 | } |
689 | 707 | catch(const system_error& e) |
690 | 708 | { |
| 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 | + |
691 | 720 | std::cerr << e.what() << std::endl; |
692 | 721 | if(!can_retry(e.code())) |
693 | 722 | throw; |
|
0 commit comments