/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. TON Blockchain is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. You must obey the GNU General Public License in all respects for all of the code used other than OpenSSL. If you modify file(s) with this exception, you may extend this exception to your version of the file(s), but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. Copyright 2019-2020 Telegram Systems LLP */ #include "http/http-server.h" #include "http/http-client.h" #include "td/utils/port/signals.h" #include "td/utils/OptionParser.h" #include "td/utils/FileLog.h" #include #include #include "git.h" #if TD_DARWIN || TD_LINUX #include #endif class HttpProxy; class HttpRemote : public td::actor::Actor { public: struct Query { std::unique_ptr request; std::shared_ptr payload; td::Timestamp timeout; td::Promise, std::shared_ptr>> promise; }; HttpRemote(std::string domain, td::actor::ActorId proxy) : domain_(std::move(domain)), proxy_(proxy) { } void start_up() override { class Cb : public ton::http::HttpClient::Callback { public: Cb(td::actor::ActorId id) : id_(id) { } void on_ready() override { td::actor::send_closure(id_, &HttpRemote::set_ready, true); } void on_stop_ready() override { td::actor::send_closure(id_, &HttpRemote::set_ready, false); } private: td::actor::ActorId id_; }; client_ = ton::http::HttpClient::create_multi(domain_, td::IPAddress(), 1, 1, std::make_shared(actor_id(this))); fail_at_ = td::Timestamp::in(10.0); close_at_ = td::Timestamp::in(60.0); } void set_ready(bool ready) { if (ready == ready_) { return; } ready_ = ready; if (!ready) { fail_at_ = td::Timestamp::in(10.0); alarm_timestamp().relax(fail_at_); } else { fail_at_ = td::Timestamp::never(); while (list_.size() > 0) { auto q = std::move(list_.front()); list_.pop_front(); td::actor::send_closure(client_, &ton::http::HttpClient::send_request, std::move(q.request), std::move(q.payload), q.timeout, std::move(q.promise)); close_at_ = td::Timestamp::in(60.0); } } } void receive_request( std::unique_ptr request, std::shared_ptr payload, td::Promise, std::shared_ptr>> promise) { bool keep = request->keep_alive(); auto P = td::PromiseCreator::lambda( [promise = std::move(promise), keep](td::Result, std::shared_ptr>> R) mutable { if (R.is_error()) { promise.set_error(R.move_as_error()); } else { auto v = R.move_as_ok(); v.first->set_keep_alive(keep); if (v.second->payload_type() != ton::http::HttpPayload::PayloadType::pt_empty && !v.first->found_content_length() && !v.first->found_transfer_encoding()) { v.first->add_header(ton::http::HttpHeader{"Transfer-Encoding", "Chunked"}); } promise.set_value(std::move(v)); } }); if (ready_) { td::actor::send_closure(client_, &ton::http::HttpClient::send_request, std::move(request), std::move(payload), td::Timestamp::in(3.0), std::move(P)); close_at_ = td::Timestamp::in(60.0); } else { list_.push_back(Query{std::move(request), std::move(payload), td::Timestamp::in(3.0), std::move(P)}); } } void alarm() override; private: std::string domain_; bool ready_ = false; td::Timestamp fail_at_; td::Timestamp close_at_; td::actor::ActorOwn client_; std::list list_; td::actor::ActorId proxy_; }; class HttpProxy : public td::actor::Actor { public: HttpProxy() { } void set_port(td::uint16 port) { if (port_ != 0) { LOG(ERROR) << "duplicate port"; std::_Exit(2); } port_ = port; } void run() { if (port_ == 0) { LOG(ERROR) << "no port specified"; std::_Exit(2); } class Cb : public ton::http::HttpServer::Callback { public: Cb(td::actor::ActorId proxy) : proxy_(proxy) { } void receive_request( std::unique_ptr request, std::shared_ptr payload, td::Promise, std::shared_ptr>> promise) override { td::actor::send_closure(proxy_, &HttpProxy::receive_request, std::move(request), std::move(payload), std::move(promise)); } private: td::actor::ActorId proxy_; }; server_ = ton::http::HttpServer::create(port_, std::make_shared(actor_id(this))); } void receive_request( std::unique_ptr request, std::shared_ptr payload, td::Promise, std::shared_ptr>> promise) { auto host = request->host(); if (host.size() == 0) { host = request->url(); if (host.size() >= 7 && host.substr(0, 7) == "http://") { host = host.substr(7); } else if (host.size() >= 8 && host.substr(0, 8) == "https://") { host = host.substr(7); } auto p = host.find('/'); if (p != std::string::npos) { host = host.substr(0, p); } } else { if (host.size() >= 7 && host.substr(0, 7) == "http://") { host = host.substr(7); } else if (host.size() >= 8 && host.substr(0, 8) == "https://") { host = host.substr(7); } auto p = host.find('/'); if (p != std::string::npos) { host = host.substr(0, p); } } if (host.find(':') == std::string::npos) { host = host + ":80"; } std::transform(host.begin(), host.end(), host.begin(), [](unsigned char c) { return std::tolower(c); }); auto it = clients_.find(host); if (it == clients_.end()) { auto id = td::actor::create_actor("remote", host, actor_id(this)); it = clients_.emplace(host, std::move(id)).first; } td::actor::send_closure(it->second, &HttpRemote::receive_request, std::move(request), std::move(payload), std::move(promise)); } void close_client(std::string host) { auto it = clients_.find(host); CHECK(it != clients_.end()); clients_.erase(it); } private: td::uint16 port_; td::actor::ActorOwn server_; std::map> clients_; }; void HttpRemote::alarm() { if (!ready_) { if (fail_at_ && fail_at_.is_in_past()) { LOG(INFO) << "closing outbound HTTP connection because of upper level request timeout"; td::actor::send_closure(proxy_, &HttpProxy::close_client, domain_); stop(); return; } else { alarm_timestamp().relax(fail_at_); } } if (close_at_ && close_at_.is_in_past()) { LOG(INFO) << "closing outbound HTTP connection because of idle timeout"; td::actor::send_closure(proxy_, &HttpProxy::close_client, domain_); stop(); return; } alarm_timestamp().relax(close_at_); } int main(int argc, char *argv[]) { SET_VERBOSITY_LEVEL(verbosity_DEBUG); td::set_default_failure_signal_handler().ensure(); td::actor::ActorOwn x; td::unique_ptr logger_; SCOPE_EXIT { td::log_interface = td::default_log_interface; }; td::OptionParser p; p.set_description("simple http proxy"); p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) { int v = VERBOSITY_NAME(FATAL) + (td::to_integer(arg)); SET_VERBOSITY_LEVEL(v); }); p.add_option('V', "version", "shows http-proxy build version", [&]() { std::cout << "http-proxy build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n"; std::exit(0); }); p.add_option('h', "help", "prints_help", [&]() { char b[10240]; td::StringBuilder sb(td::MutableSlice{b, 10000}); sb << p; std::cout << sb.as_cslice().c_str(); std::exit(2); }); p.add_checked_option('p', "port", "sets listening port", [&](td::Slice arg) -> td::Status { TRY_RESULT(port, td::to_integer_safe(arg)); td::actor::send_closure(x, &HttpProxy::set_port, port); return td::Status::OK(); }); p.add_option('d', "daemonize", "set SIGHUP", [&]() { td::set_signal_handler(td::SignalType::HangUp, [](int sig) { #if TD_DARWIN || TD_LINUX close(0); setsid(); #endif }).ensure(); }); #if TD_DARWIN || TD_LINUX p.add_option('l', "logname", "log to file", [&](td::Slice fname) { logger_ = td::FileLog::create(fname.str()).move_as_ok(); td::log_interface = logger_.get(); }); #endif td::actor::Scheduler scheduler({7}); scheduler.run_in_context([&] { x = td::actor::create_actor("proxymain"); }); scheduler.run_in_context([&] { p.run(argc, argv).ensure(); }); scheduler.run_in_context([&] { td::actor::send_closure(x, &HttpProxy::run); }); while (scheduler.run(1)) { } return 0; }