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:
parent
53ec9684bd
commit
77842f9b63
128 changed files with 10555 additions and 2285 deletions
25
http/CMakeLists.txt
Normal file
25
http/CMakeLists.txt
Normal 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
124
http/http-client.cpp
Normal 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
56
http/http-client.h
Normal 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
118
http/http-client.hpp
Normal 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
256
http/http-connection.cpp
Normal 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
141
http/http-connection.h
Normal 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
|
99
http/http-inbound-connection.cpp
Normal file
99
http/http-inbound-connection.cpp
Normal 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
|
81
http/http-inbound-connection.h
Normal file
81
http/http-inbound-connection.h
Normal 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
|
110
http/http-outbound-connection.cpp
Normal file
110
http/http-outbound-connection.cpp
Normal 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
|
125
http/http-outbound-connection.h
Normal file
125
http/http-outbound-connection.h
Normal 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
308
http/http-proxy.cpp
Normal 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
51
http/http-server.cpp
Normal 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
61
http/http-server.h
Normal 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
901
http/http.cpp
Normal 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
326
http/http.h
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue