384 net::any_io_executor executor,
385 ssl::context& context,
390 : strand_(net::make_strand(std::move(executor))), resolver_(strand_), resolve_timer_(strand_),
391 admission_timer_(strand_), context_(context), host_(std::move(host)),
392 port_(std::move(port)), options_(options) {
394 limiter_.emplace(*options_.rate_limit_);
407 return limiter_ ? &*limiter_ :
nullptr;
418 return limiter_ ? &*limiter_ :
nullptr;
445 template <
typename CompletionToken>
448 [self = shared_from_this(),
449 request_value = std::move(request_value)](
auto&& handler)
mutable {
451 std::move(request_value),
CompletionHandler(std::forward<
decltype(handler)>(handler))
453 net::post(self->strand_, [self, pending = std::move(pending)]()
mutable {
454 self->enqueue(std::move(pending));
466 net::post(strand_, [self = shared_from_this()] {
467 self->stopped_ =
true;
468 self->resolver_.cancel();
469 self->resolve_timer_.cancel();
470 self->admission_timer_.cancel();
475 error.
message_ =
"HTTP client closed";
481 while (!self->queue_.empty()) {
482 auto pending = std::move(self->queue_.front());
483 self->queue_.pop_front();
503 HttpRequest request_;
505 bool retried_{
false};
509 bool refreshed_{
false};
521 std::optional<std::chrono::steady_clock::time_point> waiting_since_;
524 : request_(std::move(request_value)), handler_(std::move(completion)) {}
527 net::strand<net::any_io_executor> strand_;
528 tcp::resolver resolver_;
529 net::steady_timer resolve_timer_;
550 explicit CountedTimer(net::strand<net::any_io_executor>& strand) : timer_(strand) {}
553 template <
typename Rep,
typename Period>
554 void expires_after(
const std::chrono::duration<Rep, Period>& delay) {
555 timer_.expires_after(delay);
561 template <
typename Handler>
562 void async_wait(Handler&& handler) {
563 timer_.async_wait(std::forward<Handler>(handler));
567 std::size_t cancel() {
return timer_.cancel(); }
570 std::uint64_t arms() const noexcept {
return arms_; }
573 net::steady_timer timer_;
574 std::uint64_t arms_{0};
577 CountedTimer admission_timer_;
578 ssl::context& context_;
579 std::unique_ptr<beast::ssl_stream<beast::tcp_stream>> stream_;
580 beast::flat_buffer buffer_;
583 HttpClientOptions options_;
584 State state_{State::DISCONNECTED};
585 bool stopped_{
false};
586 bool resolve_timed_out_{
false};
587 std::deque<Pending> queue_;
588 std::optional<RateLimitController> limiter_;
589 std::optional<http::request<http::string_body>> active_request_;
590 std::optional<http::response_parser<http::string_body>> parser_;
592 std::optional<std::chrono::steady_clock::time_point> idle_since_;
594 void enqueue(Pending pending) {
599 error.reached_broker_ =
false;
600 error.broker_code_ =
"enqueue";
601 error.message_ =
"HTTP client is closed";
602 complete(std::move(pending.handler_), Result<HttpResponse>(std::move(error)));
605 if (queue_.size() >= options_.queue_limit_) {
607 limiter_->record_shed(pending.request_.group_);
614 error.reached_broker_ =
false;
615 error.broker_code_ =
"queue_full";
616 error.message_ =
"HTTP request queue limit exceeded";
617 complete(std::move(pending.handler_), Result<HttpResponse>(std::move(error)));
620 queue_.insert(insertion_point(pending.request_.group_), std::move(pending));
632 std::deque<Pending>::iterator insertion_point(
RateLimitGroup group) {
633 auto position = queue_.begin();
634 if (front_in_flight() && position != queue_.end()) {
638 while (position != queue_.end() &&
646 bool front_in_flight() const noexcept {
651 case State::DISCONNECTED:
652 case State::RESOLVING:
653 case State::CONNECTING:
654 case State::HANDSHAKING:
662 if (stopped_ || queue_.empty()) {
665 if (state_ == State::DISCONNECTED) {
669 if (state_ != State::READY) {
675 if (queue_.front().request_.method_ != http::verb::get && !queue_.front().refreshed_ && idle_since_ &&
676 std::chrono::steady_clock::now() - *idle_since_ > options_.unreplayed_idle_limit_) {
677 queue_.front().refreshed_ =
true;
680 queue_.front().waiting_since_.reset();
692 const auto group = queue_.front().request_.group_;
693 const auto delay = limiter_->delay_until(group);
694 if (delay > RateLimitController::Duration::zero()) {
698 if (delay > std::chrono::duration_cast<RateLimitController::Duration>(options_.timeout_)) {
699 limiter_->record_shed(group);
700 auto pending = std::move(queue_.front());
707 error.reached_broker_ = pending.sent_;
708 error.broker_code_ =
"admission_deadline";
709 error.message_ =
"request would wait for rate-limit admission beyond its deadline";
710 complete(std::move(pending.handler_), Result<HttpResponse>(std::move(error)));
714 if (!queue_.front().waiting_since_) {
715 queue_.front().waiting_since_ = std::chrono::steady_clock::now();
717 admission_timer_.expires_after(delay);
718 admission_timer_.async_wait([self = shared_from_this()](beast::error_code ec) {
727 if (
auto& waiting_since = queue_.front().waiting_since_) {
728 limiter_->record_delay(
730 std::chrono::duration_cast<RateLimitController::Duration>(
731 std::chrono::steady_clock::now() - *waiting_since
734 waiting_since.reset();
736 admission_timer_.cancel();
737 limiter_->acquire(group);
741 void start_resolve() {
742 stream_ = std::make_unique<beast::ssl_stream<beast::tcp_stream>>(strand_, context_);
743 state_ = State::RESOLVING;
744 resolve_timed_out_ =
false;
745 resolve_timer_.expires_after(options_.timeout_);
746 resolve_timer_.async_wait([self = shared_from_this()](beast::error_code ec) {
747 if (!ec && self->state_ == State::RESOLVING) {
748 self->resolve_timed_out_ =
true;
749 self->resolver_.cancel();
752 resolver_.async_resolve(
755 [self = shared_from_this()](beast::error_code ec, tcp::resolver::results_type endpoints) {
756 self->resolve_timer_.cancel();
759 if (self->stopped_ || !self->stream_) {
763 if (self->resolve_timed_out_) {
764 ec = beast::error_code(net::error::timed_out);
766 self->fail(
"resolve", ec);
769 self->state_ = State::CONNECTING;
770 beast::get_lowest_layer(*self->stream_).expires_after(self->options_.timeout_);
771 beast::get_lowest_layer(*self->stream_)
774 [self](beast::error_code connect_ec, tcp::resolver::results_type::endpoint_type) {
775 if (self->stopped_ || !self->stream_) {
779 self->fail(
"connect", connect_ec);
782 self->start_handshake();
789 void start_handshake() {
790 if (!SSL_set_tlsext_host_name(stream_->native_handle(), host_.c_str())) {
793 beast::error_code(
static_cast<int>(::ERR_get_error()), net::error::get_ssl_category())
797 stream_->set_verify_callback(ssl::host_name_verification(host_));
798 state_ = State::HANDSHAKING;
799 beast::get_lowest_layer(*stream_).expires_after(options_.timeout_);
800 stream_->async_handshake(
801 ssl::stream_base::client, [self = shared_from_this()](beast::error_code ec) {
802 if (self->stopped_ || !self->stream_) {
806 self->fail(
"tls_handshake", ec);
809 self->state_ = State::READY;
812 self->idle_since_ = std::chrono::steady_clock::now();
821 queue_.front().sent_ =
true;
822 const auto& request_value = queue_.front().request_;
823 active_request_.emplace(request_value.method_, request_value.target_, request_value.version_);
824 active_request_->set(http::field::host, host_header());
825 active_request_->set(http::field::user_agent,
"algotradelib");
826 active_request_->keep_alive(
true);
827 for (
const auto& header : request_value.headers_) {
828 active_request_->set(header.first, header.second);
830 active_request_->body() = request_value.body_;
831 if (!request_value.body_.empty()) {
832 active_request_->prepare_payload();
835 state_ = State::WRITING;
836 beast::get_lowest_layer(*stream_).expires_after(options_.timeout_);
838 *stream_, *active_request_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
839 if (self->stopped_ || self->queue_.empty()) {
843 self->fail(
"write", ec);
853 parser_->body_limit(options_.body_limit_);
854 state_ = State::READING;
855 beast::get_lowest_layer(*stream_).expires_after(options_.timeout_);
857 *stream_, buffer_, *parser_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
858 if (self->stopped_ || self->queue_.empty()) {
862 self->fail(
"read", ec);
865 auto response = self->parser_->release();
866 const bool keep_alive = response.keep_alive() && !response.need_eof();
867 self->parser_.reset();
868 self->active_request_.reset();
869 auto pending = std::move(self->queue_.front());
870 self->queue_.pop_front();
872 self->state_ = State::READY;
873 self->idle_since_ = std::chrono::steady_clock::now();
877 self->complete(std::move(pending.handler_), Result<HttpResponse>(std::move(response)));
883 void fail(
const char* stage, beast::error_code ec) {
892 if (queue_.empty()) {
895 auto& pending = queue_.front();
897 pending.request_.method_ == http::verb::get && !pending.retried_ &&
898 ec != net::error::operation_aborted
900 pending.retried_ =
true;
904 auto completed = std::move(pending);
907 error.reached_broker_ = completed.sent_;
908 complete(std::move(completed.handler_), Result<HttpResponse>(std::move(error)));
912 void disconnect() noexcept {
913 resolve_timer_.cancel();
918 buffer_.consume(buffer_.size());
920 active_request_.reset();
921 state_ = State::DISCONNECTED;
928 auto executor = net::get_associated_executor(handler, strand_);
929 net::dispatch(executor, [handler = std::move(handler), result = std::move(result)]()
mutable {
930 std::move(handler)(std::move(result));