// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "telegram-bot-api/Query.h" #include "telegram-bot-api/Stats.h" #include "td/actor/actor.h" #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/port/IPAddress.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Time.h" #include namespace telegram_bot_api { td::FlatHashMap> empty_parameters; Query::Query(td::vector &&container, td::Slice token, bool is_test_dc, td::MutableSlice method, td::vector> &&args, td::vector> &&headers, td::vector &&files, std::shared_ptr shared_data, const td::IPAddress &peer_ip_address, bool is_internal) : state_(State::Query) , shared_data_(shared_data) , peer_ip_address_(peer_ip_address) , container_(std::move(container)) , token_(token) , is_test_dc_(is_test_dc) , method_(method) , args_(std::move(args)) , headers_(std::move(headers)) , files_(std::move(files)) , is_internal_(is_internal) { if (method_.empty()) { method_ = arg("method"); } td::to_lower_inplace(method_); start_timestamp_ = td::Time::now(); LOG(INFO) << "Query " << this << ": " << *this; if (shared_data_) { shared_data_->query_count_.fetch_add(1, std::memory_order_relaxed); if (method_ != "getupdates") { shared_data_->query_list_size_.fetch_add(1, std::memory_order_relaxed); shared_data_->query_list_.put(this); } } } td::string Query::get_peer_ip_address() const { if (peer_ip_address_.is_valid() && !peer_ip_address_.is_reserved()) { // external connection return peer_ip_address_.get_ip_str().str(); } else { // invalid peer IP address or connection from the local network return get_header("x-real-ip").str(); } } td::int64 Query::query_size() const { return std::accumulate( container_.begin(), container_.end(), td::int64{0}, [](td::int64 acc, const td::BufferSlice &slice) { return static_cast(acc + slice.size()); }); } td::int64 Query::files_size() const { return std::accumulate(files_.begin(), files_.end(), td::int64{0}, [](td::int64 acc, const td::HttpFile &file) { return acc + file.size; }); } td::int64 Query::files_max_size() const { return std::accumulate(files_.begin(), files_.end(), td::int64{0}, [](td::int64 acc, const td::HttpFile &file) { return td::max(acc, file.size); }); } void Query::set_stat_actor(td::ActorId stat_actor) { stat_actor_ = stat_actor; send_request_stat(); } void Query::set_ok(td::BufferSlice result) { CHECK(state_ == State::Query); LOG(INFO) << "Query " << this << ": " << td::tag("method", method_) << td::tag("text", result.as_slice()); answer_ = std::move(result); state_ = State::OK; http_status_code_ = 200; send_response_stat(); } void Query::set_error(int http_status_code, td::BufferSlice result) { LOG(INFO) << "Query " << this << ": " << td::tag("method", method_) << td::tag("code", http_status_code) << td::tag("text", result.as_slice()); CHECK(state_ == State::Query); answer_ = std::move(result); state_ = State::Error; http_status_code_ = http_status_code; send_response_stat(); } void Query::set_retry_after_error(int retry_after) { retry_after_ = retry_after; td::FlatHashMap> parameters; parameters.emplace("retry_after", td::make_unique(retry_after)); set_error(429, td::json_encode( JsonQueryError(429, PSLICE() << "Too Many Requests: retry after " << retry_after, parameters))); } td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &query) { auto padded_time = td::lpad(PSTRING() << td::format::as_time(td::Time::now_cached() - query.start_timestamp()), 10, ' '); sb << "[bot" << td::rpad(query.token().str(), 46, ' ') << "][time:" << padded_time << ']' << td::tag("method", td::lpad(query.method().str(), 25, ' ')); if (!query.args().empty()) { sb << '{'; for (const auto &arg : query.args()) { sb << '['; if (arg.first.size() > 128) { sb << '<' << arg.first.size() << '>' << td::oneline(arg.first.substr(0, 128)) << "..."; } else { sb << td::oneline(arg.first); } sb << ':'; if (arg.second.size() > 4096) { sb << '<' << arg.second.size() << '>' << td::oneline(arg.second.substr(0, 4096)) << "..."; } else { sb << td::oneline(arg.second); } sb << ']'; } sb << '}'; } if (!query.files().empty()) { sb << query.files(); } return sb; } void Query::send_request_stat() const { if (stat_actor_.empty()) { return; } send_closure(stat_actor_, &BotStatActor::add_event, ServerBotStat::Request{query_size(), file_count(), files_size(), files_max_size()}, td::Time::now()); } void Query::send_response_stat() const { auto now = td::Time::now(); if (now - start_timestamp_ >= 100.0 && !is_internal_) { LOG(WARNING) << "Answer too old query with code " << http_status_code_ << " and answer size " << answer_.size() << ": " << *this; } if (stat_actor_.empty()) { return; } send_closure(stat_actor_, &BotStatActor::add_event, ServerBotStat::Response{state_ == State::OK, answer_.size(), file_count(), files_size()}, now); } } // namespace telegram_bot_api