1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-02-12 19:22:37 +00:00
ton/http/http-proxy.cpp
Alex Melman bab4c1637e Automatically integrates git build information into executables.
Usage:
func.exe -V
adnl-pong -V
validator-engine -V
and so on.
Result will be shown in the following format:
Func build information: [ Commit: d8b751d7a5, Date: 2021-02-27 14:34:41 +0200]
2021-02-27 14:34:43 +03:00

309 lines
10 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
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 <algorithm>
#include <list>
#include "git.h"
#if TD_DARWIN || TD_LINUX
#include <unistd.h>
#endif
class HttpProxy;
class HttpRemote : public td::actor::Actor {
public:
struct Query {
std::unique_ptr<ton::http::HttpRequest> request;
std::shared_ptr<ton::http::HttpPayload> payload;
td::Timestamp timeout;
td::Promise<std::pair<std::unique_ptr<ton::http::HttpResponse>, std::shared_ptr<ton::http::HttpPayload>>> promise;
};
HttpRemote(std::string domain, td::actor::ActorId<HttpProxy> proxy) : domain_(std::move(domain)), proxy_(proxy) {
}
void start_up() override {
class Cb : public ton::http::HttpClient::Callback {
public:
Cb(td::actor::ActorId<HttpRemote> 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<HttpRemote> id_;
};
client_ = ton::http::HttpClient::create_multi(domain_, td::IPAddress(), 1, 1, std::make_shared<Cb>(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<ton::http::HttpRequest> request, std::shared_ptr<ton::http::HttpPayload> payload,
td::Promise<std::pair<std::unique_ptr<ton::http::HttpResponse>, std::shared_ptr<ton::http::HttpPayload>>>
promise) {
bool keep = request->keep_alive();
auto P = td::PromiseCreator::lambda(
[promise = std::move(promise),
keep](td::Result<std::pair<std::unique_ptr<ton::http::HttpResponse>, std::shared_ptr<ton::http::HttpPayload>>>
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<ton::http::HttpClient> client_;
std::list<Query> list_;
td::actor::ActorId<HttpProxy> 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<HttpProxy> proxy) : proxy_(proxy) {
}
void receive_request(
std::unique_ptr<ton::http::HttpRequest> request, std::shared_ptr<ton::http::HttpPayload> payload,
td::Promise<std::pair<std::unique_ptr<ton::http::HttpResponse>, std::shared_ptr<ton::http::HttpPayload>>>
promise) override {
td::actor::send_closure(proxy_, &HttpProxy::receive_request, std::move(request), std::move(payload),
std::move(promise));
}
private:
td::actor::ActorId<HttpProxy> proxy_;
};
server_ = ton::http::HttpServer::create(port_, std::make_shared<Cb>(actor_id(this)));
}
void receive_request(
std::unique_ptr<ton::http::HttpRequest> request, std::shared_ptr<ton::http::HttpPayload> payload,
td::Promise<std::pair<std::unique_ptr<ton::http::HttpResponse>, std::shared_ptr<ton::http::HttpPayload>>>
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<HttpRemote>("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<ton::http::HttpServer> server_;
std::map<std::string, td::actor::ActorOwn<HttpRemote>> 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<HttpProxy> x;
td::unique_ptr<td::LogInterface> 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<int>(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<td::uint16>(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<HttpProxy>("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;
}