1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

updated tonlib

- updated tonlib
- updated validator
- updated documentation
- first version of http over rldp proxy
This commit is contained in:
ton 2020-02-06 21:56:46 +04:00
parent 53ec9684bd
commit 77842f9b63
128 changed files with 10555 additions and 2285 deletions

25
http/CMakeLists.txt Normal file
View file

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
set(HTTP_SOURCE
http.h
http.cpp
http-connection.h
http-connection.cpp
http-inbound-connection.h
http-inbound-connection.cpp
http-outbound-connection.h
http-outbound-connection.cpp
http-server.h
http-server.cpp
http-client.h
http-client.hpp
http-client.cpp
)
add_library(tonhttp STATIC ${HTTP_SOURCE})
target_include_directories(tonhttp PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
target_link_libraries(tonhttp PUBLIC tdactor ton_crypto tl_api tdnet )
add_executable(http-proxy http-proxy.cpp)
target_include_directories(http-proxy PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
target_link_libraries(http-proxy PRIVATE tonhttp)

124
http/http-client.cpp Normal file
View file

@ -0,0 +1,124 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#include "http-client.hpp"
#include "td/utils/Random.h"
namespace ton {
namespace http {
void HttpClientImpl::create_connection() {
alarm_timestamp().relax(td::Timestamp::in(td::Random::fast(10.0, 20.0)));
if (domain_.size() > 0) {
auto S = addr_.init_host_port(domain_);
if (S.is_error()) {
LOG(INFO) << "failed domain '" << domain_ << "': " << S;
return;
}
}
auto fd = td::SocketFd::open(addr_);
if (fd.is_error()) {
LOG(INFO) << "failed to connect to " << addr_ << ": " << fd.move_as_error();
return;
}
class Cb : public HttpClient::Callback {
public:
Cb(td::actor::ActorId<HttpClientImpl> id) : id_(id) {
}
void on_ready() override {
td::actor::send_closure(id_, &HttpClientImpl::client_ready, true);
}
void on_stop_ready() override {
td::actor::send_closure(id_, &HttpClientImpl::client_ready, false);
}
private:
td::actor::ActorId<HttpClientImpl> id_;
};
conn_ = td::actor::create_actor<HttpOutboundConnection>(td::actor::ActorOptions().with_name("outconn").with_poll(),
fd.move_as_ok(), std::make_shared<Cb>(actor_id(this)));
}
void HttpClientImpl::send_request(
std::unique_ptr<HttpRequest> request, std::shared_ptr<HttpPayload> payload, td::Timestamp timeout,
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise) {
td::actor::send_closure(conn_, &HttpOutboundConnection::send_query, std::move(request), std::move(payload), timeout,
std::move(promise));
}
void HttpMultiClientImpl::send_request(
std::unique_ptr<HttpRequest> request, std::shared_ptr<HttpPayload> payload, td::Timestamp timeout,
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise) {
if (domain_.size() > 0) {
auto S = addr_.init_host_port(domain_);
if (S.is_error()) {
return answer_error(HttpStatusCode::status_bad_gateway, "", std::move(promise));
}
}
auto fd = td::SocketFd::open(addr_);
if (fd.is_error()) {
return answer_error(HttpStatusCode::status_bad_gateway, "", std::move(promise));
}
class Cb : public HttpClient::Callback {
public:
Cb(td::actor::ActorId<HttpMultiClientImpl> id) : id_(id) {
}
void on_ready() override {
}
void on_stop_ready() override {
}
private:
td::actor::ActorId<HttpMultiClientImpl> id_;
};
auto conn =
td::actor::create_actor<HttpOutboundConnection>(td::actor::ActorOptions().with_name("outconn").with_poll(),
fd.move_as_ok(), std::make_shared<Cb>(actor_id(this)))
.release();
request->set_keep_alive(false);
td::actor::send_closure(conn, &HttpOutboundConnection::send_query, std::move(request), std::move(payload), timeout,
std::move(promise));
}
td::actor::ActorOwn<HttpClient> HttpClient::create(std::string domain, td::IPAddress addr,
std::shared_ptr<HttpClient::Callback> callback) {
return td::actor::create_actor<HttpClientImpl>("httpclient", std::move(domain), addr, std::move(callback));
}
td::actor::ActorOwn<HttpClient> HttpClient::create_multi(std::string domain, td::IPAddress addr,
td::uint32 max_connections,
td::uint32 max_requests_per_connect,
std::shared_ptr<Callback> callback) {
return td::actor::create_actor<HttpMultiClientImpl>("httpmclient", std::move(domain), addr, max_connections,
max_requests_per_connect, std::move(callback));
}
} // namespace http
} // namespace ton

56
http/http-client.h Normal file
View file

@ -0,0 +1,56 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#pragma once
#include "http.h"
#include "td/utils/port/IPAddress.h"
#include "td/actor/actor.h"
namespace ton {
namespace http {
class HttpOutboundConnection;
class HttpClient : public td::actor::Actor {
public:
class Callback {
public:
virtual ~Callback() = default;
virtual void on_ready() = 0;
virtual void on_stop_ready() = 0;
};
virtual void check_ready(td::Promise<td::Unit> promise) = 0;
virtual void send_request(
std::unique_ptr<HttpRequest> request, std::shared_ptr<HttpPayload> payload, td::Timestamp timeout,
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise) = 0;
static td::actor::ActorOwn<HttpClient> create(std::string domain, td::IPAddress addr,
std::shared_ptr<Callback> callback);
static td::actor::ActorOwn<HttpClient> create_multi(std::string domain, td::IPAddress addr,
td::uint32 max_connections, td::uint32 max_requests_per_connect,
std::shared_ptr<Callback> callback);
};
} // namespace http
} // namespace ton

118
http/http-client.hpp Normal file
View file

@ -0,0 +1,118 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#include "http-client.h"
#include "http-outbound-connection.h"
#include "td/utils/Random.h"
namespace ton {
namespace http {
class HttpClientImpl : public HttpClient {
public:
HttpClientImpl(std::string domain, td::IPAddress addr, std::shared_ptr<Callback> callback)
: domain_(std::move(domain)), addr_(addr), callback_(std::move(callback)) {
}
void start_up() override {
create_connection();
}
void check_ready(td::Promise<td::Unit> promise) override {
if (ready_) {
promise.set_value(td::Unit());
} else {
promise.set_error(td::Status::Error(ErrorCode::notready, "connection not ready"));
}
}
void client_ready(bool value) {
if (ready_ == value) {
return;
}
ready_ = value;
if (ready_) {
callback_->on_ready();
} else {
callback_->on_stop_ready();
conn_.reset();
if (next_create_at_.is_in_past()) {
create_connection();
} else {
alarm_timestamp().relax(next_create_at_);
}
}
}
void alarm() override {
create_connection();
}
void send_request(
std::unique_ptr<HttpRequest> request, std::shared_ptr<HttpPayload> payload, td::Timestamp timeout,
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise) override;
void create_connection();
private:
bool ready_ = false;
std::string domain_;
td::IPAddress addr_;
td::Timestamp next_create_at_;
std::shared_ptr<Callback> callback_;
td::actor::ActorOwn<HttpOutboundConnection> conn_;
};
class HttpMultiClientImpl : public HttpClient {
public:
HttpMultiClientImpl(std::string domain, td::IPAddress addr, td::uint32 max_connections,
td::uint32 max_requests_per_connect, std::shared_ptr<Callback> callback)
: domain_(std::move(domain))
, addr_(addr)
, max_connections_(max_connections)
, max_requests_per_connect_(max_requests_per_connect)
, callback_(std::move(callback)) {
}
void start_up() override {
callback_->on_ready();
}
void check_ready(td::Promise<td::Unit> promise) override {
promise.set_value(td::Unit());
}
void send_request(
std::unique_ptr<HttpRequest> request, std::shared_ptr<HttpPayload> payload, td::Timestamp timeout,
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise) override;
private:
std::string domain_;
td::IPAddress addr_;
size_t max_connections_;
td::uint32 max_requests_per_connect_;
td::Timestamp next_create_at_;
std::shared_ptr<Callback> callback_;
};
} // namespace http
} // namespace ton

256
http/http-connection.cpp Normal file
View file

@ -0,0 +1,256 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#include "http-connection.h"
namespace ton {
namespace http {
void HttpConnection::loop() {
if (in_loop_) {
return;
}
in_loop_ = true;
auto status = [&] {
while (true) {
LOG(DEBUG) << "loop(): in=" << buffered_fd_.left_unread() << " out=" << buffered_fd_.left_unwritten();
bool is_eof = td::can_close(buffered_fd_);
bool read_eof = false;
bool written = false;
bool read = false;
if (is_eof || buffered_fd_.left_unread() <= fd_low_watermark()) {
allow_read_ = true;
}
if (allow_read_ && buffered_fd_.left_unread() < fd_high_watermark()) {
TRY_RESULT(r, buffered_fd_.flush_read(fd_high_watermark() - buffered_fd_.left_unread()));
if (r == 0 && is_eof) {
read_eof = true;
}
}
if (buffered_fd_.left_unread() >= fd_high_watermark()) {
allow_read_ = false;
}
{
auto &input = buffered_fd_.input_buffer();
auto s = input.size();
TRY_STATUS(receive(input));
read = input.size() < s;
}
if (buffered_fd_.left_unread() == 0 && read_eof) {
TRY_STATUS(receive_eof());
}
TRY_STATUS(buffered_fd_.flush_write());
if (writing_payload_ && buffered_fd_.left_unwritten() < fd_high_watermark()) {
auto w = buffered_fd_.left_unwritten();
continue_payload_write();
written = buffered_fd_.left_unwritten() > w;
}
if (close_after_write_ && !writing_payload_ && !buffered_fd_.left_unwritten()) {
LOG(INFO) << "close after write";
stop();
break;
}
if (close_after_read_ && !reading_payload_ && !buffered_fd_.left_unread()) {
LOG(INFO) << "close after read";
stop();
break;
}
if (!written && !read) {
break;
}
}
return td::Status::OK();
}();
in_loop_ = false;
if (status.is_error()) {
LOG(ERROR) << "loop() failed: " << status;
stop();
} else {
send_ready();
}
}
void HttpConnection::send_error(std::unique_ptr<HttpResponse> response) {
CHECK(!writing_payload_);
auto payload = response->create_empty_payload().move_as_ok();
CHECK(payload->parse_completed());
send_response(std::move(response), std::move(payload));
}
void HttpConnection::send_request(std::unique_ptr<HttpRequest> request, std::shared_ptr<HttpPayload> payload) {
CHECK(!writing_payload_);
request->store_http(buffered_fd_.output_buffer());
write_payload(std::move(payload));
}
void HttpConnection::send_response(std::unique_ptr<HttpResponse> response, std::shared_ptr<HttpPayload> payload) {
CHECK(!writing_payload_);
response->store_http(buffered_fd_.output_buffer());
write_payload(std::move(payload));
}
void HttpConnection::write_payload(std::shared_ptr<HttpPayload> payload) {
CHECK(!writing_payload_);
writing_payload_ = std::move(payload);
if (writing_payload_->parse_completed()) {
continue_payload_write();
return;
}
class Cb : public HttpPayload::Callback {
public:
Cb(td::actor::ActorId<HttpConnection> conn) : conn_(conn) {
}
void run(size_t ready_bytes) override {
if (!reached_ && ready_bytes >= watermark_) {
td::actor::send_closure(conn_, &HttpConnection::loop);
reached_ = true;
} else if (reached_ && ready_bytes < watermark_) {
reached_ = false;
}
}
void completed() override {
td::actor::send_closure(conn_, &HttpConnection::loop);
}
private:
size_t watermark_ = chunk_size();
bool reached_ = false;
td::actor::ActorId<HttpConnection> conn_;
};
writing_payload_->add_callback(std::make_unique<Cb>(actor_id(this)));
continue_payload_write();
}
void HttpConnection::continue_payload_write() {
if (!writing_payload_) {
return;
}
auto t = writing_payload_->payload_type();
if (t == HttpPayload::PayloadType::pt_eof) {
t = HttpPayload::PayloadType::pt_chunked;
}
while (!writing_payload_->written()) {
if (buffered_fd_.left_unwritten() > fd_high_watermark()) {
return;
}
if (!writing_payload_->parse_completed() && writing_payload_->ready_bytes() < chunk_size()) {
return;
}
writing_payload_->store_http(buffered_fd_.output_buffer(), chunk_size(), t);
}
if (writing_payload_->parse_completed() && writing_payload_->written()) {
payload_written();
return;
}
}
td::Status HttpConnection::read_payload(HttpResponse *response) {
CHECK(!reading_payload_);
if (!response->keep_alive()) {
close_after_read_ = true;
}
return read_payload(response->create_empty_payload().move_as_ok());
}
td::Status HttpConnection::read_payload(HttpRequest *request) {
CHECK(!reading_payload_);
return read_payload(request->create_empty_payload().move_as_ok());
}
td::Status HttpConnection::read_payload(std::shared_ptr<HttpPayload> payload) {
CHECK(!reading_payload_);
reading_payload_ = std::move(payload);
if (reading_payload_->parse_completed()) {
payload_read();
return td::Status::OK();
}
class Cb : public HttpPayload::Callback {
public:
Cb(td::actor::ActorId<HttpConnection> conn) : conn_(conn) {
}
void run(size_t ready_bytes) override {
if (!reached_ && ready_bytes < watermark_) {
reached_ = true;
td::actor::send_closure(conn_, &HttpConnection::loop);
} else if (reached_ && ready_bytes >= watermark_) {
reached_ = false;
}
}
void completed() override {
td::actor::send_closure(conn_, &HttpConnection::loop);
}
private:
size_t watermark_ = HttpRequest::low_watermark();
bool reached_ = false;
td::actor::ActorId<HttpConnection> conn_;
};
reading_payload_->add_callback(std::make_unique<Cb>(actor_id(this)));
auto &input = buffered_fd_.input_buffer();
return continue_payload_read(input);
}
td::Status HttpConnection::continue_payload_read(td::ChainBufferReader &input) {
if (!reading_payload_) {
return td::Status::OK();
}
while (!reading_payload_->parse_completed()) {
if (reading_payload_->ready_bytes() > fd_high_watermark()) {
return td::Status::OK();
}
auto s = input.size();
TRY_STATUS(reading_payload_->parse(input));
if (input.size() == s) {
return td::Status::OK();
}
}
if (reading_payload_->parse_completed()) {
payload_read();
return td::Status::OK();
}
return td::Status::OK();
}
td::Status HttpConnection::receive_payload(td::ChainBufferReader &input) {
CHECK(reading_payload_);
continue_payload_read(input);
return td::Status::OK();
}
} // namespace http
} // namespace ton

141
http/http-connection.h Normal file
View file

@ -0,0 +1,141 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#pragma once
#include "td/actor/actor.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/buffer.h"
#include "td/utils/BufferedFd.h"
#include "common/errorcode.h"
#include "http.h"
namespace ton {
namespace http {
class HttpConnection : public td::actor::Actor, public td::ObserverBase {
public:
class Callback {
public:
virtual ~Callback() = default;
virtual void on_close(td::actor::ActorId<HttpConnection> conn) = 0;
virtual void on_ready(td::actor::ActorId<HttpConnection> conn) = 0;
};
HttpConnection(td::SocketFd fd, std::unique_ptr<Callback> callback, bool is_client)
: buffered_fd_(std::move(fd)), callback_(std::move(callback)), is_client_(is_client) {
}
virtual td::Status receive(td::ChainBufferReader &input) = 0;
virtual td::Status receive_eof() = 0;
td::Status receive_payload(td::ChainBufferReader &input);
bool check_ready() const {
return !td::can_close(buffered_fd_);
}
void check_ready_async(td::Promise<td::Unit> promise) {
if (check_ready()) {
promise.set_value(td::Unit());
} else {
promise.set_error(td::Status::Error(ErrorCode::notready, "not ready"));
}
}
void send_ready() {
if (check_ready() && !sent_ready_ && callback_) {
callback_->on_ready(actor_id(this));
sent_ready_ = true;
}
}
void send_error(std::unique_ptr<HttpResponse> response);
void send_request(std::unique_ptr<HttpRequest> request, std::shared_ptr<HttpPayload> payload);
void send_response(std::unique_ptr<HttpResponse> response, std::shared_ptr<HttpPayload> payload);
void write_payload(std::shared_ptr<HttpPayload> payload);
void continue_payload_write();
td::Status receive_request();
td::Status receive_response();
td::Status read_payload(HttpRequest *request);
td::Status read_payload(HttpResponse *response);
td::Status read_payload(std::shared_ptr<HttpPayload> payload);
td::Status continue_payload_read(td::ChainBufferReader &input);
virtual void payload_read() = 0;
virtual void payload_written() = 0;
virtual ~HttpConnection() = default;
protected:
td::BufferedFd<td::SocketFd> buffered_fd_;
td::actor::ActorId<HttpConnection> self_;
std::unique_ptr<Callback> callback_;
bool sent_ready_ = false;
bool is_client_;
bool close_after_write_ = false;
bool close_after_read_ = false;
bool found_eof_ = false;
bool in_loop_ = false;
bool allow_read_ = true;
std::shared_ptr<HttpPayload> reading_payload_;
std::shared_ptr<HttpPayload> writing_payload_;
void notify() override {
// NB: Interface will be changed
td::actor::send_closure_later(self_, &HttpConnection::on_net);
}
void start_up() override {
self_ = actor_id(this);
// Subscribe for socket updates
// NB: Interface will be changed
td::actor::SchedulerContext::get()->get_poll().subscribe(buffered_fd_.get_poll_info().extract_pollable_fd(this),
td::PollFlags::ReadWrite() | td::PollFlags::Close());
notify();
}
void loop() override;
private:
static constexpr size_t fd_low_watermark() {
return 1 << 14;
}
static constexpr size_t fd_high_watermark() {
return 1 << 16;
}
static constexpr size_t chunk_size() {
return 1 << 10;
}
void on_net() {
loop();
}
void tear_down() override {
if (callback_) {
callback_->on_close(actor_id(this));
callback_ = nullptr;
}
// unsubscribe from socket updates
// nb: interface will be changed
td::actor::SchedulerContext::get()->get_poll().unsubscribe(buffered_fd_.get_poll_info().get_pollable_fd_ref());
}
};
} // namespace http
} // namespace ton

View file

@ -0,0 +1,99 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#include "http-inbound-connection.h"
#include "td/utils/misc.h"
namespace ton {
namespace http {
void HttpInboundConnection::send_client_error() {
static const auto s =
"HTTP/1.0 400 Bad Request\r\n"
"Connection: Close\r\n"
"\r\n";
buffered_fd_.output_buffer().append(td::Slice(s, strlen(s)));
close_after_write_ = true;
}
void HttpInboundConnection::send_server_error() {
static const auto s =
"HTTP/1.1 502 Bad Gateway\r\n"
"Connection: keep-alive\r\n"
"\r\n";
buffered_fd_.output_buffer().append(td::Slice(s, strlen(s)));
}
void HttpInboundConnection::send_proxy_error() {
static const auto s =
"HTTP/1.1 502 Bad Gateway\r\n"
"Connection: keep-alive\r\n"
"\r\n";
buffered_fd_.output_buffer().append(td::Slice(s, strlen(s)));
}
td::Status HttpInboundConnection::receive(td::ChainBufferReader &input) {
if (reading_payload_) {
return receive_payload(input);
}
if (!cur_request_ && !read_next_request_) {
return td::Status::OK();
}
while (!cur_request_ || !cur_request_->check_parse_header_completed()) {
bool exit_loop;
auto R = HttpRequest::parse(std::move(cur_request_), cur_line_, exit_loop, input);
if (R.is_error()) {
send_client_error();
return td::Status::OK();
}
if (exit_loop) {
return td::Status::OK();
}
cur_request_ = R.move_as_ok();
}
auto payload = cur_request_->create_empty_payload().move_as_ok();
auto P = td::PromiseCreator::lambda(
[SelfId = actor_id(this)](td::Result<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> R) {
if (R.is_ok()) {
auto a = R.move_as_ok();
td::actor::send_closure(SelfId, &HttpInboundConnection::send_answer, std::move(a.first), std::move(a.second));
} else {
td::actor::send_closure(SelfId, &HttpInboundConnection::send_proxy_error);
}
});
http_callback_->receive_request(std::move(cur_request_), payload, std::move(P));
read_payload(std::move(payload));
return td::Status::OK();
}
void HttpInboundConnection::send_answer(std::unique_ptr<HttpResponse> response, std::shared_ptr<HttpPayload> payload) {
CHECK(payload);
response->store_http(buffered_fd_.output_buffer());
write_payload(std::move(payload));
loop();
}
} // namespace http
} // namespace ton

View file

@ -0,0 +1,81 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#pragma once
#include "http.h"
#include "http-connection.h"
#include "http-server.h"
namespace ton {
namespace http {
class HttpInboundConnection : public HttpConnection {
public:
HttpInboundConnection(td::SocketFd fd, std::shared_ptr<HttpServer::Callback> http_callback)
: HttpConnection(std::move(fd), nullptr, false), http_callback_(std::move(http_callback)) {
}
td::Status receive_eof() override {
if (reading_payload_) {
if (reading_payload_->payload_type() != HttpPayload::PayloadType::pt_eof) {
return td::Status::Error("unexpected EOF");
} else {
reading_payload_->complete_parse();
payload_read();
return td::Status::OK();
}
} else {
return td::Status::OK();
}
}
void send_client_error();
void send_server_error();
void send_proxy_error();
void payload_written() override {
writing_payload_ = nullptr;
if (!close_after_write_) {
read_next_request_ = true;
}
}
void payload_read() override {
reading_payload_ = nullptr;
read_next_request_ = false;
}
td::Status receive(td::ChainBufferReader &input) override;
void send_answer(std::unique_ptr<HttpResponse> response, std::shared_ptr<HttpPayload> payload);
private:
static constexpr size_t chunk_size() {
return 1 << 14;
}
bool read_next_request_ = true;
std::shared_ptr<HttpServer::Callback> http_callback_;
std::unique_ptr<HttpRequest> cur_request_;
std::string cur_line_;
};
} // namespace http
} // namespace ton

View file

@ -0,0 +1,110 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#include "http-outbound-connection.h"
#include "td/utils/port/StdStreams.h"
namespace ton {
namespace http {
td::Status HttpOutboundConnection::receive(td::ChainBufferReader &input) {
if (input.size() == 0) {
return td::Status::OK();
}
if (reading_payload_) {
return receive_payload(input);
}
if (!promise_) {
return td::Status::Error("unexpected data");
}
while (!cur_response_ || !cur_response_->check_parse_header_completed()) {
bool exit_loop;
auto R = HttpResponse::parse(std::move(cur_response_), cur_line_, force_no_payload_, keep_alive_, exit_loop, input);
if (R.is_error()) {
answer_error(HttpStatusCode::status_bad_request, "", std::move(promise_));
return td::Status::OK();
}
if (exit_loop) {
return td::Status::OK();
}
cur_response_ = R.move_as_ok();
}
if (cur_response_->code() == 100) {
cur_response_ = nullptr;
return td::Status::OK();
}
close_after_read_ = !cur_response_->keep_alive() || !keep_alive_;
auto payload = cur_response_->create_empty_payload().move_as_ok();
promise_.set_value(std::make_pair(std::move(cur_response_), payload));
read_payload(std::move(payload));
if (!reading_payload_) {
return td::Status::OK();
}
return receive_payload(input);
}
void HttpOutboundConnection::send_query(
std::unique_ptr<HttpRequest> request, std::shared_ptr<HttpPayload> payload, td::Timestamp timeout,
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise) {
CHECK(request);
CHECK(payload);
if (promise_) {
LOG(INFO) << "delaying send of HTTP request";
next_.push_back(Query{std::move(request), std::move(payload), timeout, std::move(promise)});
return;
}
LOG(INFO) << "sending HTTP request";
keep_alive_ = request->keep_alive();
force_no_payload_ = request->no_payload_in_answer();
request->store_http(buffered_fd_.output_buffer());
write_payload(std::move(payload));
promise_ = std::move(promise);
alarm_timestamp() = timeout;
loop();
}
void HttpOutboundConnection::send_next_query() {
if (next_.size() == 0) {
return;
}
LOG(INFO) << "sending delayed HTTP request";
auto p = std::move(next_.front());
next_.pop_front();
keep_alive_ = p.request->keep_alive();
force_no_payload_ = p.request->no_payload_in_answer();
p.request->store_http(buffered_fd_.output_buffer());
write_payload(std::move(p.payload));
alarm_timestamp() = p.timeout;
promise_ = std::move(p.promise);
loop();
}
} // namespace http
} // namespace ton

View file

@ -0,0 +1,125 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#pragma once
#include "http.h"
#include "http-connection.h"
#include "http-client.h"
#include <list>
namespace ton {
namespace http {
class HttpOutboundConnection : public HttpConnection {
public:
struct Query {
std::unique_ptr<HttpRequest> request;
std::shared_ptr<HttpPayload> payload;
td::Timestamp timeout;
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise;
};
HttpOutboundConnection(td::SocketFd fd, std::shared_ptr<HttpClient::Callback> http_callback)
: HttpConnection(std::move(fd), nullptr, false), http_callback_(std::move(http_callback)) {
}
td::Status receive_eof() override {
if (reading_payload_) {
if (reading_payload_->payload_type() != HttpPayload::PayloadType::pt_eof) {
return td::Status::Error("unexpected EOF");
} else {
LOG(INFO) << "stopping (EOF payload)";
reading_payload_->complete_parse();
stop();
return td::Status::OK();
}
} else {
LOG(INFO) << "stopping (no req)";
stop();
return td::Status::OK();
}
}
void alarm() override {
LOG(INFO) << "closing outbound HTTP connection because of request timeout";
if (promise_) {
answer_error(HttpStatusCode::status_gateway_timeout, "", std::move(promise_));
}
stop();
}
void start_up() override {
class Cb : public HttpConnection::Callback {
public:
Cb(std::shared_ptr<HttpClient::Callback> callback) : callback_(std::move(callback)) {
}
void on_ready(td::actor::ActorId<HttpConnection> conn) {
callback_->on_ready();
}
void on_close(td::actor::ActorId<HttpConnection> conn) {
callback_->on_stop_ready();
}
private:
std::shared_ptr<HttpClient::Callback> callback_;
};
callback_ = std::make_unique<Cb>(std::move(http_callback_));
HttpConnection::start_up();
}
td::Status receive(td::ChainBufferReader &input) override;
void send_query(std::unique_ptr<HttpRequest> request, std::shared_ptr<HttpPayload> payload, td::Timestamp timeout,
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise);
void send_next_query();
void payload_read() override {
reading_payload_ = nullptr;
if (!close_after_read_) {
alarm_timestamp() = td::Timestamp::never();
send_next_query();
} else {
stop();
}
}
void payload_written() override {
writing_payload_ = nullptr;
}
private:
std::shared_ptr<HttpClient::Callback> http_callback_;
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise_;
bool force_no_payload_;
bool keep_alive_;
std::unique_ptr<HttpResponse> cur_response_;
std::string cur_line_;
std::list<Query> next_;
};
} // namespace http
} // namespace ton

308
http/http-proxy.cpp Normal file
View file

@ -0,0 +1,308 @@
/*
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/OptionsParser.h"
#include "td/utils/FileLog.h"
#include <algorithm>
#include <list>
#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::OptionsParser 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);
return td::Status::OK();
});
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);
return td::Status::OK();
});
p.add_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();
return td::Status::OK();
});
#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();
return td::Status::OK();
});
#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;
}

51
http/http-server.cpp Normal file
View file

@ -0,0 +1,51 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#include "http-server.h"
#include "http-inbound-connection.h"
namespace ton {
namespace http {
void HttpServer::start_up() {
class Callback : public td::TcpListener::Callback {
private:
td::actor::ActorId<HttpServer> id_;
public:
Callback(td::actor::ActorId<HttpServer> id) : id_(id) {
}
void accept(td::SocketFd fd) override {
td::actor::send_closure(id_, &HttpServer::accepted, std::move(fd));
}
};
listener_ = td::actor::create_actor<td::TcpInfiniteListener>(
td::actor::ActorOptions().with_name("listener").with_poll(), port_, std::make_unique<Callback>(actor_id(this)));
}
void HttpServer::accepted(td::SocketFd fd) {
td::actor::create_actor<HttpInboundConnection>(td::actor::ActorOptions().with_name("inhttpconn").with_poll(),
std::move(fd), callback_)
.release();
}
} // namespace http
} // namespace ton

61
http/http-server.h Normal file
View file

@ -0,0 +1,61 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#pragma once
#include "td/actor/actor.h"
#include "http.h"
#include "td/net/TcpListener.h"
namespace ton {
namespace http {
class HttpInboundConnection;
class HttpServer : public td::actor::Actor {
public:
class Callback {
public:
virtual ~Callback() = default;
virtual void receive_request(
std::unique_ptr<HttpRequest> request, std::shared_ptr<HttpPayload> payload,
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise) = 0;
};
HttpServer(td::uint16 port, std::shared_ptr<Callback> callback) : port_(port), callback_(std::move(callback)) {
}
void start_up() override;
void accepted(td::SocketFd fd);
static td::actor::ActorOwn<HttpServer> create(td::uint16 port, std::shared_ptr<Callback> callback) {
return td::actor::create_actor<HttpServer>("httpserver", port, std::move(callback));
}
private:
td::uint16 port_;
std::shared_ptr<Callback> callback_;
td::actor::ActorOwn<td::TcpInfiniteListener> listener_;
};
} // namespace http
} // namespace ton

901
http/http.cpp Normal file
View file

@ -0,0 +1,901 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#include "http.h"
#include <algorithm>
namespace ton {
namespace http {
namespace util {
td::Result<std::string> get_line(td::ChainBufferReader &input, std::string &cur_line, bool &read,
size_t max_line_size) {
while (true) {
if (input.size() == 0) {
read = false;
return "";
}
auto S = input.prepare_read();
auto f = S.find('\n');
if (f == td::Slice::npos) {
if (cur_line.size() + S.size() > max_line_size) {
return td::Status::Error("too big http header");
}
cur_line += S.str();
input.confirm_read(S.size());
continue;
}
if (f > 0) {
if (S[f - 1] == '\r') {
cur_line += S.truncate(f - 1).str();
} else {
cur_line += S.truncate(f).str();
}
} else {
if (cur_line.size() > 0 && cur_line[cur_line.size() - 1] == '\r') {
cur_line = cur_line.substr(0, cur_line.size() - 1);
}
}
input.confirm_read(f + 1);
auto s = std::move(cur_line);
cur_line = "";
read = true;
return s;
}
}
td::Result<HttpHeader> get_header(std::string line) {
auto p = line.find(':');
if (p == std::string::npos) {
return td::Status::Error("failed to parse header");
}
return HttpHeader{line.substr(0, p), td::trim(line.substr(p + 1))};
}
} // namespace util
void HttpHeader::store_http(td::ChainBufferWriter &output) {
output.append(name);
output.append(": ");
output.append(value);
output.append("\r\n");
}
tl_object_ptr<ton_api::http_header> HttpHeader::store_tl() {
return create_tl_object<ton_api::http_header>(name, value);
}
td::Result<std::unique_ptr<HttpRequest>> HttpRequest::parse(std::unique_ptr<HttpRequest> request, std::string &cur_line,
bool &exit_loop, td::ChainBufferReader &input) {
exit_loop = false;
CHECK(!request || !request->check_parse_header_completed());
while (true) {
bool read;
TRY_RESULT(line, util::get_line(input, cur_line, read, HttpRequest::max_one_header_size()));
if (!read) {
exit_loop = true;
break;
}
if (!request) {
auto v = td::full_split(line);
if (v.size() != 3) {
return td::Status::Error("expected http header in form ");
}
TRY_RESULT_ASSIGN(request, HttpRequest::create(v[0], v[1], v[2]));
} else {
if (line.size() == 0) {
TRY_STATUS(request->complete_parse_header());
break;
} else {
TRY_RESULT(h, util::get_header(std::move(line)));
TRY_STATUS(request->add_header(std::move(h)));
}
}
}
return std::move(request);
}
HttpRequest::HttpRequest(std::string method, std::string url, std::string proto_version)
: method_(std::move(method)), url_(std::move(url)), proto_version_(std::move(proto_version)) {
if (proto_version_ == "HTTP/1.1") {
keep_alive_ = true;
} else {
keep_alive_ = false;
}
}
td::Result<std::unique_ptr<HttpRequest>> HttpRequest::create(std::string method, std::string url,
std::string proto_version) {
if (proto_version != "HTTP/1.0" && proto_version != "HTTP/1.1") {
return td::Status::Error(PSTRING() << "unsupported http version '" << proto_version << "'");
}
static const std::vector<std::string> supported_methods{"GET", "HEAD", "POST", "PUT",
"DELETE", "CONNECT", "OPTIONS", "TRACE"};
bool found = false;
for (const auto &e : supported_methods) {
if (e == method) {
found = true;
break;
}
}
if (!found) {
return td::Status::Error(PSTRING() << "unsupported http method '" << method << "'");
}
return std::make_unique<HttpRequest>(std::move(method), std::move(url), std::move(proto_version));
}
bool HttpRequest::check_parse_header_completed() const {
return parse_header_completed_;
}
td::Status HttpRequest::complete_parse_header() {
CHECK(!parse_header_completed_);
parse_header_completed_ = true;
return td::Status::OK();
}
td::Result<std::shared_ptr<HttpPayload>> HttpRequest::create_empty_payload() {
CHECK(check_parse_header_completed());
if (!need_payload()) {
return std::make_shared<HttpPayload>(HttpPayload::PayloadType::pt_empty);
} else if (found_content_length_) {
return std::make_shared<HttpPayload>(HttpPayload::PayloadType::pt_content_length, low_watermark(), high_watermark(),
content_length_);
} else if (found_transfer_encoding_) {
return std::make_shared<HttpPayload>(HttpPayload::PayloadType::pt_chunked, low_watermark(), high_watermark());
} else {
return td::Status::Error("expected Content-Length/Transfer-Encoding header");
}
}
bool HttpRequest::need_payload() const {
return found_content_length_ || found_transfer_encoding_;
}
td::Status HttpRequest::add_header(HttpHeader header) {
auto lc_name = header.name;
auto lc_value = header.value;
std::transform(lc_name.begin(), lc_name.end(), lc_name.begin(), [](unsigned char c) { return std::tolower(c); });
std::transform(lc_value.begin(), lc_value.end(), lc_value.begin(), [](unsigned char c) { return std::tolower(c); });
auto S = td::trim(td::Slice(lc_value));
if (lc_name == "content-length") {
TRY_RESULT(len, td::to_integer_safe<td::uint32>(S));
if (found_transfer_encoding_ || found_content_length_) {
return td::Status::Error("duplicate Content-Length/Transfer-Encoding");
}
if (len > HttpRequest::max_payload_size()) {
return td::Status::Error("too big Content-Length");
}
content_length_ = len;
found_content_length_ = true;
} else if (lc_name == "transfer-encoding") {
// expect chunked, don't event check
if (found_transfer_encoding_ || found_content_length_) {
return td::Status::Error("duplicate Content-Length/Transfer-Encoding");
}
found_transfer_encoding_ = true;
} else if (lc_name == "host") {
if (host_.size() > 0) {
return td::Status::Error("duplicate Host");
}
host_ = S.str();
} else if (lc_name == "connection" && S == "keep-alive") {
keep_alive_ = true;
return td::Status::OK();
} else if (lc_name == "connection" && S == "close") {
keep_alive_ = false;
return td::Status::OK();
} else if (lc_name == "proxy-connection" && S == "keep-alive") {
keep_alive_ = true;
return td::Status::OK();
} else if (lc_name == "proxy-connection" && S == "close") {
keep_alive_ = false;
return td::Status::OK();
}
options_.emplace_back(std::move(header));
return td::Status::OK();
}
void HttpRequest::store_http(td::ChainBufferWriter &output) {
std::string line = method_ + " " + url_ + " " + proto_version_ + "\r\n";
output.append(line);
for (auto &x : options_) {
x.store_http(output);
}
if (keep_alive_) {
HttpHeader{"Connection", "Keep-Alive"}.store_http(output);
} else {
HttpHeader{"Connection", "Close"}.store_http(output);
}
output.append(td::Slice("\r\n", 2));
}
tl_object_ptr<ton_api::http_request> HttpRequest::store_tl(td::Bits256 req_id) {
std::vector<tl_object_ptr<ton_api::http_header>> headers;
headers.reserve(options_.size());
for (auto &h : options_) {
headers.push_back(h.store_tl());
}
if (keep_alive_) {
headers.push_back(HttpHeader{"Connection", "Keep-Alive"}.store_tl());
} else {
headers.push_back(HttpHeader{"Connection", "Close"}.store_tl());
}
return create_tl_object<ton_api::http_request>(req_id, method_, url_, proto_version_, std::move(headers));
}
td::Status HttpPayload::parse(td::ChainBufferReader &input) {
CHECK(!parse_completed());
while (true) {
if (high_watermark_reached()) {
return td::Status::OK();
}
switch (state_) {
case ParseState::reading_chunk_header: {
bool read;
TRY_RESULT(l, util::get_line(input, tmp_, read, HttpRequest::max_one_header_size()));
if (!read) {
return td::Status::OK();
}
if (l.size() == 0) {
return td::Status::Error("expected chunk, found empty line");
}
auto v = td::split(l);
TRY_RESULT(size, td::hex_to_integer_safe<size_t>(v.first));
if (size == 0) {
state_ = ParseState::reading_trailer;
break;
}
cur_chunk_size_ = size;
state_ = ParseState::reading_chunk_data;
} break;
case ParseState::reading_chunk_data: {
if (cur_chunk_size_ == 0) {
switch (type_) {
case PayloadType::pt_empty:
UNREACHABLE();
case PayloadType::pt_eof:
cur_chunk_size_ = 1 << 30;
break;
case PayloadType::pt_chunked:
state_ = ParseState::reading_crlf;
break;
case PayloadType::pt_content_length: {
LOG(INFO) << "payload parse success";
const std::lock_guard<std::mutex> lock{mutex_};
state_ = ParseState::completed;
run_callbacks();
return td::Status::OK();
} break;
}
break;
}
if (input.size() == 0) {
return td::Status::OK();
}
auto S = get_read_slice();
auto s = input.size();
if (S.size() > s) {
S.truncate(s);
}
CHECK(input.advance(S.size(), S) == S.size());
confirm_read(S.size());
} break;
case ParseState::reading_trailer: {
bool read;
TRY_RESULT(l, util::get_line(input, tmp_, read, HttpRequest::max_one_header_size()));
if (!read) {
return td::Status::OK();
}
if (!l.size()) {
LOG(INFO) << "payload parse success";
const std::lock_guard<std::mutex> lock{mutex_};
state_ = ParseState::completed;
run_callbacks();
return td::Status::OK();
}
TRY_RESULT(h, util::get_header(std::move(l)));
add_trailer(std::move(h));
if (trailer_size_ > HttpRequest::max_header_size()) {
return td::Status::Error("too big trailer part");
}
} break;
case ParseState::reading_crlf: {
if (input.size() < 2) {
return td::Status::OK();
}
td::uint8 buf[2];
CHECK(input.advance(2, td::MutableSlice(buf, 2)) == 2);
if (buf[0] != '\r' || buf[1] != '\n') {
return td::Status::Error(PSTRING()
<< "expected CRLF " << static_cast<int>(buf[0]) << " " << static_cast<int>(buf[1]));
}
state_ = ParseState::reading_chunk_header;
} break;
case ParseState::completed:
return td::Status::OK();
}
}
}
bool HttpPayload::parse_completed() const {
return state_.load(std::memory_order_consume) == ParseState::completed;
}
td::MutableSlice HttpPayload::get_read_slice() {
const std::lock_guard<std::mutex> lock{mutex_};
if (last_chunk_free_ == 0) {
auto B = td::BufferSlice{chunk_size_};
last_chunk_free_ = B.size();
chunks_.push_back(std::move(B));
}
auto b = chunks_.back().as_slice();
b.remove_prefix(b.size() - last_chunk_free_);
if (b.size() > cur_chunk_size_) {
b.truncate(cur_chunk_size_);
}
return b;
}
void HttpPayload::confirm_read(size_t s) {
const std::lock_guard<std::mutex> lock{mutex_};
last_chunk_free_ -= s;
cur_chunk_size_ -= s;
ready_bytes_ += s;
run_callbacks();
}
void HttpPayload::add_trailer(HttpHeader header) {
const std::lock_guard<std::mutex> lock{mutex_};
ready_bytes_ += header.size();
trailer_size_ += header.size();
run_callbacks();
trailer_.push_back(std::move(header));
}
void HttpPayload::add_chunk(td::BufferSlice data) {
//LOG(INFO) << "payload: added " << data.size() << " bytes";
while (data.size() > 0) {
if (!cur_chunk_size_) {
cur_chunk_size_ = data.size();
}
auto S = get_read_slice();
CHECK(S.size() > 0);
if (S.size() > data.size()) {
S.truncate(data.size());
}
S.copy_from(data.as_slice().truncate(S.size()));
data.confirm_read(S.size());
confirm_read(S.size());
}
}
void HttpPayload::slice_gc() {
const std::lock_guard<std::mutex> lock{mutex_};
while (chunks_.size() > 0) {
auto &x = chunks_.front();
if (state_ == ParseState::completed || state_ == ParseState::reading_trailer) {
if (chunks_.size() == 1) {
x.truncate(x.size() - last_chunk_free_);
last_chunk_free_ = 0;
}
}
if (x.size() == 0) {
CHECK(chunks_.size() > 1 || !last_chunk_free_);
chunks_.pop_front();
continue;
}
break;
}
}
td::BufferSlice HttpPayload::get_slice(size_t max_size) {
const std::lock_guard<std::mutex> lock{mutex_};
while (chunks_.size() > 0) {
auto &x = chunks_.front();
if (x.size() == 0) {
CHECK(chunks_.size() > 1 || !last_chunk_free_);
chunks_.pop_front();
continue;
}
td::BufferSlice b;
if (chunks_.size() > 1 || !last_chunk_free_) {
if (x.size() <= max_size) {
b = std::move(x);
chunks_.pop_front();
} else {
b = x.clone();
b.truncate(max_size);
x.confirm_read(max_size);
}
} else {
b = x.clone();
CHECK(b.size() >= last_chunk_free_);
if (b.size() == last_chunk_free_) {
return td::BufferSlice{};
}
b.truncate(b.size() - last_chunk_free_);
if (b.size() > max_size) {
b.truncate(max_size);
}
x.confirm_read(b.size());
}
ready_bytes_ -= b.size();
run_callbacks();
return b;
}
return td::BufferSlice{};
}
HttpHeader HttpPayload::get_header() {
const std::lock_guard<std::mutex> lock{mutex_};
if (trailer_.size() == 0) {
return HttpHeader{};
} else {
auto h = std::move(trailer_.front());
auto s = h.size();
trailer_.pop_front();
ready_bytes_ -= s;
run_callbacks();
return h;
}
}
void HttpPayload::run_callbacks() {
for (auto &x : callbacks_) {
if (state_.load(std::memory_order_relaxed) == ParseState::completed) {
x->completed();
} else {
x->run(ready_bytes_);
}
}
}
void HttpPayload::store_http(td::ChainBufferWriter &output, size_t max_size, HttpPayload::PayloadType store_type) {
if (store_type == PayloadType::pt_empty) {
return;
}
slice_gc();
while (chunks_.size() > 0 && max_size > 0) {
auto cur_state = state_.load(std::memory_order_consume);
auto s = get_slice(max_size);
if (s.size() == 0) {
if (cur_state != ParseState::reading_trailer && cur_state != ParseState::completed) {
return;
} else {
break;
}
}
CHECK(s.size() <= max_size);
max_size -= s.size();
if (store_type == PayloadType::pt_chunked) {
char buf[64];
::sprintf(buf, "%lx\r\n", s.size());
output.append(td::Slice(buf, strlen(buf)));
}
output.append(std::move(s));
if (store_type == PayloadType::pt_chunked) {
output.append(td::Slice("\r\n", 2));
}
}
if (chunks_.size() != 0) {
return;
}
if (!written_zero_chunk_) {
if (store_type == PayloadType::pt_chunked) {
output.append(td::Slice("0\r\n", 3));
}
written_zero_chunk_ = true;
}
if (store_type != PayloadType::pt_chunked) {
written_trailer_ = true;
return;
}
while (max_size > 0) {
auto cur_state = state_.load(std::memory_order_consume);
HttpHeader h = get_header();
if (h.empty()) {
if (cur_state != ParseState::completed) {
return;
} else {
break;
}
}
auto s = h.size();
h.store_http(output);
if (max_size <= s) {
return;
}
max_size -= s;
}
if (!written_trailer_) {
output.append(td::Slice("\r\n", 2));
written_trailer_ = true;
}
}
tl_object_ptr<ton_api::http_payloadPart> HttpPayload::store_tl(size_t max_size) {
auto b = ready_bytes();
if (b > max_size) {
b = max_size;
}
max_size = b;
td::BufferSlice x{b};
auto S = x.as_slice();
auto obj = create_tl_object<ton_api::http_payloadPart>(std::move(x),
std::vector<tl_object_ptr<ton_api::http_header>>(), false);
slice_gc();
while (chunks_.size() > 0 && max_size > 0) {
auto cur_state = state_.load(std::memory_order_consume);
auto s = get_slice(max_size);
if (s.size() == 0) {
if (cur_state != ParseState::reading_trailer && cur_state != ParseState::completed) {
LOG(INFO) << "state not trailer/completed";
obj->data_.truncate(obj->data_.size() - S.size());
return obj;
} else {
break;
}
}
CHECK(s.size() <= max_size);
S.copy_from(s);
S.remove_prefix(s.size());
max_size -= s.size();
}
obj->data_.truncate(obj->data_.size() - S.size());
if (chunks_.size() != 0) {
return obj;
}
if (!written_zero_chunk_) {
written_zero_chunk_ = true;
}
LOG(INFO) << "data completed";
while (max_size > 0) {
auto cur_state = state_.load(std::memory_order_consume);
HttpHeader h = get_header();
if (h.empty()) {
if (cur_state != ParseState::completed) {
LOG(INFO) << "state not completed";
return obj;
} else {
break;
}
}
auto s = h.size();
obj->trailer_.push_back(h.store_tl());
if (max_size <= s) {
return obj;
}
max_size -= s;
}
written_trailer_ = true;
obj->last_ = true;
return obj;
}
/*tl_object_ptr<ton_api::http_payloadPart> HttpPayload::store_tl(size_t max_size) {
auto obj = create_tl_object<ton_api::http_payloadPart>(std::vector<td::BufferSlice>(),
std::vector<tl_object_ptr<ton_api::http_header>>(), false);
if (type_ == PayloadType::pt_empty) {
return obj;
}
size_t sum = 0;
while (chunks_.size() > 0) {
auto &p = chunks_.front();
size_t s = p.size();
bool m = true;
if (chunks_.size() == 1) {
s -= last_chunk_free_;
m = false;
}
sum += s;
if (m) {
obj->data_.push_back(std::move(p));
} else {
auto B = p.clone();
B.truncate(s);
obj->data_.push_back(std::move(B));
p.confirm_read(s);
}
CHECK(ready_bytes_ >= s);
ready_bytes_ -= s;
if (!m) {
return obj;
}
chunks_.pop_front();
if (sum > max_size) {
return obj;
}
}
if (state_ != ParseState::reading_trailer && state_ != ParseState::completed) {
return obj;
}
if (!written_zero_chunk_) {
written_zero_chunk_ = true;
}
while (true) {
if (trailer_.size() == 0) {
break;
}
auto &p = trailer_.front();
sum += p.name.size() + p.value.size() + 2;
ready_bytes_ -= p.name.size() + p.value.size() + 2;
obj->trailer_.push_back(p.store_tl());
trailer_.pop_front();
if (sum > max_size) {
return obj;
}
}
if (state_ != ParseState::completed) {
return obj;
}
obj->last_ = true;
return obj;
}
tl_object_ptr<ton_api::http_PayloadInfo> HttpPayload::store_info(size_t max_size) {
if (type_ == PayloadType::pt_empty) {
return create_tl_object<ton_api::http_payloadInfo>(create_tl_object<ton_api::http_payloadEmpty>());
}
if (!parse_completed()) {
return create_tl_object<ton_api::http_payloadSizeUnknown>();
}
if (ready_bytes_ > max_size) {
return create_tl_object<ton_api::http_payloadBig>(ready_bytes_);
}
auto obj = store_tl(max_size);
CHECK(obj->last_);
return create_tl_object<ton_api::http_payloadInfo>(
create_tl_object<ton_api::http_payload>(std::move(obj->data_), std::move(obj->trailer_)));
}*/
void HttpPayload::add_callback(std::unique_ptr<HttpPayload::Callback> callback) {
const std::lock_guard<std::mutex> lock{mutex_};
callbacks_.push_back(std::move(callback));
}
td::Result<std::unique_ptr<HttpResponse>> HttpResponse::parse(std::unique_ptr<HttpResponse> response,
std::string &cur_line, bool force_no_payload,
bool keep_alive, bool &exit_loop,
td::ChainBufferReader &input) {
exit_loop = false;
CHECK(!response || !response->check_parse_header_completed());
while (true) {
bool read;
TRY_RESULT(line, util::get_line(input, cur_line, read, HttpRequest::max_one_header_size()));
if (!read) {
exit_loop = true;
break;
}
if (!response) {
auto v = td::full_split(line, ' ', 3);
if (v.size() != 3) {
return td::Status::Error("expected http header in form ");
}
TRY_RESULT(code, td::to_integer_safe<td::uint32>(std::move(v[1])));
TRY_RESULT_ASSIGN(response, HttpResponse::create(v[0], code, v[2], force_no_payload, keep_alive));
} else {
if (line.size() == 0) {
TRY_STATUS(response->complete_parse_header());
break;
} else {
TRY_RESULT(h, util::get_header(std::move(line)));
TRY_STATUS(response->add_header(std::move(h)));
}
}
}
return std::move(response);
}
HttpResponse::HttpResponse(std::string proto_version, td::uint32 code, std::string reason, bool force_no_payload,
bool keep_alive)
: proto_version_(std::move(proto_version))
, code_(code)
, reason_(std::move(reason))
, force_no_payload_(force_no_payload)
, force_no_keep_alive_(!keep_alive) {
}
td::Result<std::unique_ptr<HttpResponse>> HttpResponse::create(std::string proto_version, td::uint32 code,
std::string reason, bool force_no_payload,
bool keep_alive) {
if (proto_version != "HTTP/1.0" && proto_version != "HTTP/1.1") {
return td::Status::Error(PSTRING() << "unsupported http version '" << proto_version << "'");
}
if (code < 100 || code > 999) {
return td::Status::Error(PSTRING() << "bad status code '" << code << "'");
}
return std::make_unique<HttpResponse>(std::move(proto_version), code, std::move(reason), force_no_payload,
keep_alive);
}
td::Status HttpResponse::complete_parse_header() {
CHECK(!parse_header_completed_);
parse_header_completed_ = true;
return td::Status::OK();
}
bool HttpResponse::check_parse_header_completed() const {
return parse_header_completed_;
}
td::Result<std::shared_ptr<HttpPayload>> HttpResponse::create_empty_payload() {
CHECK(check_parse_header_completed());
if (!need_payload()) {
return std::make_shared<HttpPayload>(HttpPayload::PayloadType::pt_empty);
} else if (found_content_length_) {
return std::make_shared<HttpPayload>(HttpPayload::PayloadType::pt_content_length, low_watermark(), high_watermark(),
content_length_);
} else if (found_transfer_encoding_) {
return std::make_shared<HttpPayload>(HttpPayload::PayloadType::pt_chunked, low_watermark(), high_watermark());
} else {
return std::make_shared<HttpPayload>(HttpPayload::PayloadType::pt_eof, low_watermark(), high_watermark());
}
}
bool HttpResponse::need_payload() const {
return !force_no_payload_ && (code_ >= 200) && code_ != 204 && code_ != 304;
}
td::Status HttpResponse::add_header(HttpHeader header) {
auto lc_name = header.name;
auto lc_value = header.value;
std::transform(lc_name.begin(), lc_name.end(), lc_name.begin(), [](unsigned char c) { return std::tolower(c); });
std::transform(lc_value.begin(), lc_value.end(), lc_value.begin(), [](unsigned char c) { return std::tolower(c); });
auto S = td::trim(td::Slice(lc_value));
if (lc_name == "content-length") {
TRY_RESULT(len, td::to_integer_safe<td::uint32>(S));
if (found_transfer_encoding_ || found_content_length_) {
return td::Status::Error("duplicate Content-Length/Transfer-Encoding");
}
if (len > HttpRequest::max_payload_size()) {
return td::Status::Error("too big Content-Length");
}
content_length_ = len;
found_content_length_ = true;
} else if (lc_name == "transfer-encoding") {
// expect chunked, don't event check
if (found_transfer_encoding_ || found_content_length_) {
return td::Status::Error("duplicate Content-Length/Transfer-Encoding");
}
found_transfer_encoding_ = true;
} else if (lc_name == "connection" && S == "keep-alive") {
keep_alive_ = true;
return td::Status::OK();
} else if (lc_name == "connection" && S == "close") {
keep_alive_ = false;
return td::Status::OK();
} else if (lc_name == "proxy-connection" && S == "keep-alive") {
keep_alive_ = true;
return td::Status::OK();
} else if (lc_name == "proxy-connection" && S == "close") {
keep_alive_ = false;
return td::Status::OK();
}
options_.emplace_back(std::move(header));
return td::Status::OK();
}
void HttpResponse::store_http(td::ChainBufferWriter &output) {
std::string line = proto_version_ + " " + std::to_string(code_) + " " + reason_ + "\r\n";
output.append(line);
for (auto &x : options_) {
x.store_http(output);
}
if (keep_alive_) {
HttpHeader{"Connection", "Keep-Alive"}.store_http(output);
} else {
HttpHeader{"Connection", "Close"}.store_http(output);
}
output.append(td::Slice("\r\n", 2));
}
tl_object_ptr<ton_api::http_response> HttpResponse::store_tl() {
std::vector<tl_object_ptr<ton_api::http_header>> headers;
headers.reserve(options_.size());
for (auto &h : options_) {
headers.push_back(h.store_tl());
}
if (keep_alive_) {
headers.push_back(HttpHeader{"Connection", "Keep-Alive"}.store_tl());
} else {
headers.push_back(HttpHeader{"Connection", "Close"}.store_tl());
}
return create_tl_object<ton_api::http_response>(proto_version_, code_, reason_, std::move(headers));
}
td::Status HttpHeader::basic_check() {
for (auto &c : name) {
if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == ':') {
return td::Status::Error("bad character in header name");
}
}
for (auto &c : value) {
if (c == '\r' || c == '\n') {
return td::Status::Error("bad character in header name");
}
}
return td::Status::OK();
}
void answer_error(HttpStatusCode code, std::string reason,
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise) {
if (reason.empty()) {
switch (code) {
case status_ok:
reason = "OK";
break;
case status_bad_request:
reason = "Bad Request";
break;
case status_method_not_allowed:
reason = "Method Not Allowed";
break;
case status_internal_server_error:
reason = "Internal Server Error";
break;
case status_bad_gateway:
reason = "Bad Gateway";
break;
case status_gateway_timeout:
reason = "Gateway Timeout";
break;
default:
reason = "Unknown";
break;
}
}
auto response = HttpResponse::create("HTTP/1.0", code, reason, false, false).move_as_ok();
response->add_header(HttpHeader{"Content-Length", "0"});
auto payload = response->create_empty_payload().move_as_ok();
CHECK(payload->parse_completed());
promise.set_value(std::make_pair(std::move(response), std::move(payload)));
}
} // namespace http
} // namespace ton

326
http/http.h Normal file
View file

@ -0,0 +1,326 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Library 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2019-2020 Telegram Systems LLP
*/
#pragma once
#include "td/utils/buffer.h"
#include "auto/tl/ton_api.h"
#include "td/actor/PromiseFuture.h"
#include <map>
#include <list>
#include <mutex>
namespace ton {
namespace http {
enum HttpStatusCode : td::uint32 {
status_ok = 200,
status_bad_request = 400,
status_method_not_allowed = 405,
status_internal_server_error = 500,
status_bad_gateway = 502,
status_gateway_timeout = 504
};
struct HttpHeader {
std::string name;
std::string value;
void store_http(td::ChainBufferWriter &output);
tl_object_ptr<ton_api::http_header> store_tl();
size_t size() const {
return 2 + name.size() + value.size();
}
bool empty() const {
return name.size() == 0;
}
td::Status basic_check();
};
namespace util {
td::Result<std::string> get_line(td::ChainBufferReader &input, std::string &cur_line, bool &read, size_t max_line_size);
td::Result<HttpHeader> get_header(std::string line);
} // namespace util
class HttpPayload {
public:
enum class PayloadType { pt_empty, pt_eof, pt_chunked, pt_content_length };
HttpPayload(PayloadType t, size_t low_watermark, size_t high_watermark, td::uint64 size)
: type_(t), low_watermark_(low_watermark), high_watermark_(high_watermark), cur_chunk_size_(size) {
CHECK(t == PayloadType::pt_content_length);
state_ = ParseState::reading_chunk_data;
}
HttpPayload(PayloadType t, size_t low_watermark, size_t high_watermark)
: type_(t), low_watermark_(low_watermark), high_watermark_(high_watermark) {
CHECK(t != PayloadType::pt_content_length);
CHECK(t != PayloadType::pt_empty);
switch (t) {
case PayloadType::pt_empty:
UNREACHABLE();
case PayloadType::pt_eof:
state_ = ParseState::reading_chunk_data;
break;
case PayloadType::pt_chunked:
state_ = ParseState::reading_chunk_header;
break;
case PayloadType::pt_content_length:
state_ = ParseState::reading_chunk_data;
break;
}
}
HttpPayload(PayloadType t) : type_(t) {
CHECK(t == PayloadType::pt_empty);
state_ = ParseState::completed;
written_zero_chunk_ = true;
written_trailer_ = true;
}
class Callback {
public:
virtual void run(size_t ready_bytes) = 0;
virtual void completed() = 0;
virtual ~Callback() = default;
};
void add_callback(std::unique_ptr<Callback> callback);
void run_callbacks();
td::Status parse(td::ChainBufferReader &input);
bool parse_completed() const;
void complete_parse() {
state_ = ParseState::completed;
run_callbacks();
}
size_t ready_bytes() const {
return ready_bytes_;
}
bool low_watermark_reached() const {
return ready_bytes_ <= low_watermark_;
}
bool high_watermark_reached() const {
return ready_bytes_ > high_watermark_;
}
PayloadType payload_type() const {
return type_;
}
td::MutableSlice get_read_slice();
void confirm_read(size_t s);
void add_trailer(HttpHeader header);
void add_chunk(td::BufferSlice data);
td::BufferSlice get_slice(size_t max_size);
void slice_gc();
HttpHeader get_header();
void store_http(td::ChainBufferWriter &output, size_t max_size, HttpPayload::PayloadType store_type);
tl_object_ptr<ton_api::http_payloadPart> store_tl(size_t max_size);
bool written() const {
return ready_bytes_ == 0 && parse_completed() && written_zero_chunk_ && written_trailer_;
}
private:
enum class ParseState { reading_chunk_header, reading_chunk_data, reading_trailer, reading_crlf, completed };
PayloadType type_{PayloadType::pt_chunked};
size_t low_watermark_;
size_t high_watermark_;
std::string tmp_;
std::list<td::BufferSlice> chunks_;
std::list<HttpHeader> trailer_;
size_t trailer_size_ = 0;
size_t ready_bytes_ = 0;
td::uint64 cur_chunk_size_ = 0;
size_t last_chunk_free_ = 0;
size_t chunk_size_ = 1 << 14;
bool written_zero_chunk_ = false;
bool written_trailer_ = false;
std::list<std::unique_ptr<Callback>> callbacks_;
std::atomic<ParseState> state_{ParseState::reading_chunk_header};
std::mutex mutex_;
};
class HttpRequest {
public:
static constexpr size_t max_header_size() {
return 16 << 10;
}
static constexpr size_t max_one_header_size() {
return 16 << 10;
}
static constexpr size_t max_payload_size() {
return 1 << 20;
}
static constexpr size_t low_watermark() {
return 1 << 14;
}
static constexpr size_t high_watermark() {
return 1 << 17;
}
static td::Result<std::unique_ptr<HttpRequest>> create(std::string method, std::string url,
std::string proto_version);
HttpRequest(std::string method, std::string url, std::string proto_version);
bool check_parse_header_completed() const;
bool keep_alive() const {
return keep_alive_;
}
td::Status complete_parse_header();
td::Status add_header(HttpHeader header);
td::Result<std::shared_ptr<HttpPayload>> create_empty_payload();
bool need_payload() const;
const auto &method() const {
return method_;
}
const auto &url() const {
return url_;
}
const auto &proto_version() const {
return proto_version_;
}
const auto &host() const {
return host_;
}
bool no_payload_in_answer() const {
return method_ == "HEAD";
}
void set_keep_alive(bool value) {
keep_alive_ = value;
}
void store_http(td::ChainBufferWriter &output);
tl_object_ptr<ton_api::http_request> store_tl(td::Bits256 req_id);
static td::Result<std::unique_ptr<HttpRequest>> parse(std::unique_ptr<HttpRequest> request, std::string &cur_line,
bool &exit_loop, td::ChainBufferReader &input);
private:
std::string method_;
std::string url_;
std::string proto_version_;
std::string host_;
size_t content_length_ = 0;
bool found_content_length_ = false;
bool found_transfer_encoding_ = false;
bool parse_header_completed_ = false;
bool keep_alive_ = false;
std::vector<HttpHeader> options_;
};
class HttpResponse {
public:
static constexpr size_t max_header_size() {
return 16 << 10;
}
static constexpr size_t max_one_header_size() {
return 16 << 10;
}
static constexpr size_t max_payload_size() {
return 1 << 20;
}
static constexpr size_t low_watermark() {
return 1 << 14;
}
static constexpr size_t high_watermark() {
return 1 << 17;
}
static td::Result<std::unique_ptr<HttpResponse>> create(std::string proto_version, td::uint32 code,
std::string reason, bool force_no_payload, bool keep_alive);
HttpResponse(std::string proto_version, td::uint32 code, std::string reason, bool force_no_payload, bool keep_alive);
bool check_parse_header_completed() const;
bool keep_alive() const {
return !force_no_payload_ && keep_alive_;
}
td::Status complete_parse_header();
td::Status add_header(HttpHeader header);
td::Result<std::shared_ptr<HttpPayload>> create_empty_payload();
bool need_payload() const;
auto code() const {
return code_;
}
const auto &proto_version() const {
return proto_version_;
}
void set_keep_alive(bool value) {
keep_alive_ = value;
}
void store_http(td::ChainBufferWriter &output);
tl_object_ptr<ton_api::http_response> store_tl();
static td::Result<std::unique_ptr<HttpResponse>> parse(std::unique_ptr<HttpResponse> request, std::string &cur_line,
bool force_no_payload, bool keep_alive, bool &exit_loop,
td::ChainBufferReader &input);
static std::unique_ptr<HttpResponse> create_error(HttpStatusCode code, std::string reason);
bool found_transfer_encoding() const {
return found_transfer_encoding_;
}
bool found_content_length() const {
return found_content_length_;
}
private:
std::string proto_version_;
td::uint32 code_;
std::string reason_;
bool force_no_payload_ = false;
bool force_no_keep_alive_ = false;
size_t content_length_ = 0;
bool found_content_length_ = false;
bool found_transfer_encoding_ = false;
bool parse_header_completed_ = false;
bool keep_alive_ = false;
std::vector<HttpHeader> options_;
};
void answer_error(HttpStatusCode code, std::string reason,
td::Promise<std::pair<std::unique_ptr<HttpResponse>, std::shared_ptr<HttpPayload>>> promise);
} // namespace http
} // namespace ton