mirror of
https://github.com/savoirfairelinux/opendht.git
synced 2026-11-08 04:21:51 +00:00
738 lines
21 KiB
C++
738 lines
21 KiB
C++
/*
|
|
* Copyright (C) 2016-2019 Savoir-faire Linux Inc.
|
|
* Author: Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com>
|
|
*
|
|
* This program 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "http.h"
|
|
|
|
namespace http {
|
|
|
|
constexpr char HTTP_HEADER_CONTENT_LENGTH[] = "Content-Length";
|
|
constexpr char HTTP_HEADER_CONTENT_TYPE[] = "Content-Type";
|
|
constexpr char HTTP_HEADER_CONTENT_TYPE_JSON[] = "application/json";
|
|
|
|
// connection
|
|
|
|
unsigned int Connection::ids_ = 1;
|
|
|
|
Connection::Connection(asio::io_context& ctx, std::shared_ptr<dht::Logger> logger):
|
|
id_(Connection::ids_++), socket_(ctx), logger_(logger)
|
|
{}
|
|
|
|
Connection::~Connection()
|
|
{
|
|
close();
|
|
}
|
|
|
|
unsigned int
|
|
Connection::id()
|
|
{
|
|
return id_;
|
|
}
|
|
|
|
bool
|
|
Connection::is_open()
|
|
{
|
|
return socket_.is_open();
|
|
}
|
|
|
|
bool
|
|
Connection::is_v6()
|
|
{
|
|
return endpoint_.address().is_v6();
|
|
}
|
|
|
|
void
|
|
Connection::set_endpoint(const asio::ip::tcp::endpoint& endpoint)
|
|
{
|
|
endpoint_ = endpoint;
|
|
}
|
|
|
|
asio::streambuf&
|
|
Connection::input()
|
|
{
|
|
return write_buf_;
|
|
}
|
|
|
|
asio::streambuf&
|
|
Connection::data()
|
|
{
|
|
return read_buf_;
|
|
}
|
|
|
|
asio::ip::tcp::socket&
|
|
Connection::socket()
|
|
{
|
|
return socket_;
|
|
}
|
|
|
|
void
|
|
Connection::timeout(const std::chrono::seconds timeout, HandlerCb cb)
|
|
{
|
|
if (!is_open()){
|
|
if (logger_)
|
|
logger_->e("[connection:%i] closed, can't timeout", id_);
|
|
return;
|
|
}
|
|
if (!timeout_timer_)
|
|
timeout_timer_ = std::make_unique<asio::steady_timer>(socket_.get_io_context());
|
|
timeout_timer_->expires_at(std::chrono::steady_clock::now() + timeout);
|
|
timeout_timer_->async_wait([this, cb](const asio::error_code &ec){
|
|
if (ec == asio::error::operation_aborted)
|
|
return;
|
|
else if (ec){
|
|
if (logger_)
|
|
logger_->e("[connection:%i] timeout error: %s", id_, ec.message().c_str());
|
|
}
|
|
if (cb)
|
|
cb(ec);
|
|
});
|
|
}
|
|
|
|
void
|
|
Connection::close()
|
|
{
|
|
socket_.close();
|
|
}
|
|
|
|
// connection listener
|
|
|
|
ConnectionListener::ConnectionListener()
|
|
{}
|
|
|
|
ConnectionListener::ConnectionListener(std::shared_ptr<dht::DhtRunner> dht,
|
|
std::shared_ptr<std::map<restinio::connection_id_t, http::ListenerSession>> listeners,
|
|
std::shared_ptr<std::mutex> lock, std::shared_ptr<dht::Logger> logger):
|
|
dht_(dht), lock_(lock), listeners_(listeners), logger_(logger)
|
|
{}
|
|
|
|
ConnectionListener::~ConnectionListener()
|
|
{
|
|
}
|
|
|
|
void
|
|
ConnectionListener::state_changed(const restinio::connection_state::notice_t& notice) noexcept
|
|
{
|
|
std::lock_guard<std::mutex> lock(*lock_);
|
|
auto id = notice.connection_id();
|
|
auto cause = to_str(notice.cause());
|
|
|
|
if (listeners_->find(id) != listeners_->end()){
|
|
if (notice.cause() == restinio::connection_state::cause_t::closed){
|
|
if (logger_)
|
|
logger_->d("[proxy:server] [connection:%li] cancelling listener", id);
|
|
dht_->cancelListen(listeners_->at(id).hash,
|
|
std::move(listeners_->at(id).token));
|
|
listeners_->erase(id);
|
|
if (logger_)
|
|
logger_->d("[proxy:server] %li listeners are connected", listeners_->size());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string
|
|
ConnectionListener::to_str(restinio::connection_state::cause_t cause) noexcept
|
|
{
|
|
std::string result;
|
|
switch(cause)
|
|
{
|
|
case restinio::connection_state::cause_t::accepted:
|
|
result = "accepted";
|
|
break;
|
|
case restinio::connection_state::cause_t::closed:
|
|
result = "closed";
|
|
break;
|
|
case restinio::connection_state::cause_t::upgraded_to_websocket:
|
|
result = "upgraded";
|
|
break;
|
|
default:
|
|
result = "unknown";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Resolver
|
|
|
|
Resolver::Resolver(asio::io_context& ctx, const std::string& host, const std::string& service,
|
|
std::shared_ptr<dht::Logger> logger)
|
|
: resolver_(ctx), logger_(logger)
|
|
{
|
|
resolve(host, service);
|
|
}
|
|
|
|
Resolver::Resolver(asio::io_context& ctx, asio::ip::basic_resolver_results<asio::ip::tcp> endpoints,
|
|
std::shared_ptr<dht::Logger> logger)
|
|
: resolver_(ctx), logger_(logger)
|
|
{
|
|
endpoints_ = endpoints;
|
|
completed_ = true;
|
|
}
|
|
|
|
Resolver::~Resolver()
|
|
{
|
|
decltype(cbs_) cbs;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
cbs = std::move(cbs_);
|
|
}
|
|
while (not cbs.empty()){
|
|
auto cb = cbs.front();
|
|
if (cb)
|
|
cb(asio::error::operation_aborted, {});
|
|
cbs.pop();
|
|
}
|
|
}
|
|
|
|
void
|
|
Resolver::add_callback(ResolverCb cb)
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
if (!completed_)
|
|
cbs_.push(std::move(cb));
|
|
else
|
|
cb(ec_, endpoints_);
|
|
}
|
|
|
|
void
|
|
Resolver::resolve(const std::string host, const std::string service)
|
|
{
|
|
asio::ip::tcp::resolver::query query_(host, service);
|
|
|
|
resolver_.async_resolve(query_, [this, host, service]
|
|
(const asio::error_code& ec, asio::ip::tcp::resolver::results_type endpoints)
|
|
{
|
|
if (logger_){
|
|
if (ec)
|
|
logger_->e("[http:resolver] error for %s:%s: %s",
|
|
host.c_str(), service.c_str(), ec.message().c_str());
|
|
else {
|
|
for (auto it = endpoints.begin(); it != endpoints.end(); ++it){
|
|
asio::ip::tcp::endpoint endpoint = *it;
|
|
logger_->d("[http:resolver] resolved %s:%s: address=%s ipv%i",
|
|
host.c_str(), service.c_str(), endpoint.address().to_string().c_str(),
|
|
endpoint.address().is_v6() ? 6 : 4);
|
|
}
|
|
}
|
|
}
|
|
decltype(cbs_) cbs;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
ec_ = ec;
|
|
endpoints_ = endpoints;
|
|
completed_ = true;
|
|
cbs = std::move(cbs_);
|
|
}
|
|
while (not cbs.empty()){
|
|
auto cb = cbs.front();
|
|
if (cb)
|
|
cb(ec, endpoints);
|
|
cbs.pop();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Request
|
|
|
|
unsigned int Request::ids_ = 1;
|
|
|
|
Request::Request(asio::io_context& ctx, const std::string& host, const std::string& service,
|
|
std::shared_ptr<dht::Logger> logger)
|
|
: id_(Request::ids_++), ctx_(ctx), logger_(logger)
|
|
{
|
|
cbs_ = std::make_unique<Callbacks>();
|
|
resolver_ = std::make_shared<Resolver>(ctx, host, service, logger_);
|
|
}
|
|
|
|
Request::Request(asio::io_context& ctx, std::shared_ptr<Resolver> resolver, std::shared_ptr<dht::Logger> logger)
|
|
: id_(Request::ids_++), ctx_(ctx), logger_(logger)
|
|
{
|
|
cbs_ = std::make_unique<Callbacks>();
|
|
resolver_ = resolver;
|
|
}
|
|
|
|
// user defined resolved endpoints
|
|
Request::Request(asio::io_context& ctx, asio::ip::basic_resolver_results<asio::ip::tcp>&& endpoints,
|
|
std::shared_ptr<dht::Logger> logger)
|
|
: id_(Request::ids_++), ctx_(ctx), logger_(logger)
|
|
{
|
|
cbs_ = std::make_unique<Callbacks>();
|
|
resolver_ = std::make_shared<Resolver>(ctx, std::move(endpoints), logger_);
|
|
}
|
|
|
|
Request::~Request()
|
|
{
|
|
}
|
|
|
|
void
|
|
Request::end()
|
|
{
|
|
conn_.reset();
|
|
}
|
|
|
|
unsigned int
|
|
Request::id() const
|
|
{
|
|
return id_;
|
|
}
|
|
|
|
std::shared_ptr<Connection>
|
|
Request::get_connection() const
|
|
{
|
|
return conn_;
|
|
}
|
|
|
|
void
|
|
Request::set_logger(std::shared_ptr<dht::Logger> logger)
|
|
{
|
|
logger_ = logger;
|
|
}
|
|
|
|
void
|
|
Request::set_header(const restinio::http_request_header_t header)
|
|
{
|
|
header_ = header;
|
|
}
|
|
|
|
void
|
|
Request::set_header_field(const restinio::http_field_t field, const std::string& value)
|
|
{
|
|
headers_[field] = value;
|
|
}
|
|
|
|
void
|
|
Request::set_connection_type(const restinio::http_connection_header_t connection)
|
|
{
|
|
connection_type_ = connection;
|
|
}
|
|
|
|
void
|
|
Request::set_body(const std::string& body)
|
|
{
|
|
body_ = body;
|
|
}
|
|
|
|
void
|
|
Request::build()
|
|
{
|
|
std::stringstream request;
|
|
|
|
// first header
|
|
request << header_.method().c_str() << " " << header_.request_target() << " " <<
|
|
"HTTP/" << header_.http_major() << "." << header_.http_minor() << "\r\n";
|
|
|
|
// other headers
|
|
for (auto header: headers_)
|
|
request << restinio::field_to_string(header.first) << ": " << header.second << "\r\n";
|
|
|
|
// last connection header
|
|
std::string conn_str = "";
|
|
switch (connection_type_){
|
|
case restinio::http_connection_header_t::close:
|
|
conn_str = "close";
|
|
break;
|
|
case restinio::http_connection_header_t::keep_alive:
|
|
conn_str = "keep-alive";
|
|
break;
|
|
case restinio::http_connection_header_t::upgrade:
|
|
throw std::invalid_argument("upgrade");
|
|
break;
|
|
default:
|
|
conn_str = "close";
|
|
}
|
|
request << "Connection: " << conn_str << "\r\n";
|
|
|
|
// body & content-length
|
|
if (!body_.empty()){
|
|
request << "Content-Length: " << body_.size() << "\r\n\r\n";
|
|
request << body_;
|
|
}
|
|
|
|
// last delim
|
|
request << "\r\n";
|
|
request_ = request.str();
|
|
}
|
|
|
|
void
|
|
Request::add_on_state_changed_callback(OnStateChangeCb cb)
|
|
{
|
|
std::lock_guard<std::mutex> lock(cbs_mutex_);
|
|
cbs_->on_state_changed = std::move(cb);
|
|
}
|
|
|
|
void
|
|
Request::add_on_status_callback(OnStatusCb cb)
|
|
{
|
|
std::lock_guard<std::mutex> lock(cbs_mutex_);
|
|
cbs_->on_status = std::move(cb);
|
|
}
|
|
|
|
void
|
|
Request::add_on_body_callback(OnDataCb cb)
|
|
{
|
|
std::lock_guard<std::mutex> lock(cbs_mutex_);
|
|
cbs_->on_body = std::move(cb);
|
|
}
|
|
|
|
void
|
|
Request::add_on_message_complete_callback(OnMessageCompleteCb cb)
|
|
{
|
|
std::lock_guard<std::mutex> lock(cbs_mutex_);
|
|
cbs_->on_message_complete = std::move(cb);
|
|
}
|
|
|
|
void
|
|
Request::notify_state_changed(const State state)
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(cbs_mutex_);
|
|
if (cbs_->on_state_changed)
|
|
cbs_->on_state_changed(state);
|
|
}
|
|
|
|
void
|
|
Request::init_parser()
|
|
{
|
|
if (!parser_)
|
|
parser_ = std::make_unique<http_parser>();
|
|
http_parser_init(parser_.get(), HTTP_RESPONSE);
|
|
parser_->data = static_cast<void*>(cbs_.get());
|
|
|
|
if (!parser_s_)
|
|
parser_s_ = std::make_unique<http_parser_settings>();
|
|
http_parser_settings_init(parser_s_.get());
|
|
|
|
// user registered callbacks wrappers to store its data in the response
|
|
auto on_status_cb = cbs_->on_status;
|
|
cbs_->on_status = [this, on_status_cb](unsigned int status_code){
|
|
response_.status_code = status_code;
|
|
if (on_status_cb)
|
|
on_status_cb(status_code);
|
|
};
|
|
auto header_field = std::make_shared<std::string>("");
|
|
auto on_header_field_cb = cbs_->on_header_field;
|
|
cbs_->on_header_field = [this, header_field, on_header_field_cb](const char* at, size_t length){
|
|
header_field->erase();
|
|
auto field = std::string(at, length);
|
|
header_field->append(field);
|
|
if (on_header_field_cb)
|
|
on_header_field_cb(at, length);
|
|
};
|
|
auto on_header_value_cb = cbs_->on_header_value;
|
|
cbs_->on_header_value = [this, header_field, on_header_value_cb](const char* at, size_t length){
|
|
response_.headers[*header_field] = std::string(at, length);
|
|
if (on_header_value_cb)
|
|
on_header_value_cb(at, length);
|
|
};
|
|
auto on_body_cb = cbs_->on_body;
|
|
cbs_->on_body = [this, on_body_cb](const char* at, size_t length){
|
|
auto content = std::string(at, length);
|
|
response_.body.append(content);
|
|
if (on_body_cb)
|
|
on_body_cb(at, length);
|
|
};
|
|
auto on_message_complete_cb = cbs_->on_message_complete;
|
|
cbs_->on_message_complete = [this, on_message_complete_cb](const Response){
|
|
if (on_message_complete_cb)
|
|
on_message_complete_cb(response_);
|
|
};
|
|
|
|
// http_parser raw c callback (note: no context can be passed into them)
|
|
parser_s_->on_status = [](http_parser* parser, const char* /*at*/, size_t /*length*/) -> int {
|
|
auto cbs = static_cast<Callbacks*>(parser->data);
|
|
if (cbs->on_status)
|
|
cbs->on_status(parser->status_code);
|
|
return 0;
|
|
};
|
|
parser_s_->on_header_field = [](http_parser* parser, const char* at, size_t length) -> int {
|
|
auto cbs = static_cast<Callbacks*>(parser->data);
|
|
if (cbs->on_header_field)
|
|
cbs->on_header_field(at, length);
|
|
return 0;
|
|
};
|
|
parser_s_->on_header_value = [](http_parser* parser, const char* at, size_t length) -> int {
|
|
auto cbs = static_cast<Callbacks*>(parser->data);
|
|
if (cbs->on_header_value)
|
|
cbs->on_header_value(at, length);
|
|
return 0;
|
|
};
|
|
parser_s_->on_body = [](http_parser* parser, const char* at, size_t length) -> int {
|
|
auto cbs = static_cast<Callbacks*>(parser->data);
|
|
if (cbs->on_body)
|
|
cbs->on_body(at, length);
|
|
return 0;
|
|
};
|
|
parser_s_->on_message_complete = [](http_parser* parser) -> int {
|
|
auto cbs = static_cast<Callbacks*>(parser->data);
|
|
if (cbs->on_message_complete)
|
|
cbs->on_message_complete(Response{});
|
|
return 0;
|
|
};
|
|
}
|
|
|
|
void
|
|
Request::connect(asio::ip::basic_resolver_results<asio::ip::tcp>&& endpoints, HandlerCb cb)
|
|
{
|
|
if (endpoints.empty()){
|
|
if (logger_)
|
|
logger_->e("[http:request:%i] [connect] no endpoints provided", id_);
|
|
if (cb)
|
|
cb(asio::error::connection_aborted);
|
|
return;
|
|
}
|
|
if (logger_)
|
|
logger_->d("[http:request:%i] [connect] begin", id_);
|
|
|
|
conn_ = std::make_shared<Connection>(ctx_);
|
|
|
|
// try to connect to any until one works
|
|
asio::async_connect(conn_->socket(), std::move(endpoints), [this, cb]
|
|
(const asio::error_code& ec, const asio::ip::tcp::endpoint& endpoint){
|
|
if (ec){
|
|
if (logger_)
|
|
logger_->e("[http:request:%i] [connect] failed with all endpoints", id_);
|
|
}
|
|
else {
|
|
if (logger_)
|
|
logger_->d("[http:request:%i] [connect] success", id_);
|
|
// save the associated endpoint
|
|
conn_->set_endpoint(endpoint);
|
|
}
|
|
if (cb)
|
|
cb(ec);
|
|
});
|
|
}
|
|
|
|
void
|
|
Request::send()
|
|
{
|
|
notify_state_changed(State::CREATED);
|
|
|
|
resolver_->add_callback([this](const asio::error_code& ec,
|
|
asio::ip::tcp::resolver::results_type endpoints){
|
|
if (ec){
|
|
if (logger_)
|
|
logger_->e("[http:request:%i] [send] resolve error: %s", id_, ec.message().c_str());
|
|
terminate(asio::error::connection_aborted);
|
|
}
|
|
else if (!conn_ or !conn_->is_open()){
|
|
connect(std::move(endpoints), [this](const asio::error_code &ec){
|
|
if (!ec)
|
|
post();
|
|
else
|
|
terminate(asio::error::not_connected);
|
|
});
|
|
}
|
|
else {
|
|
post();
|
|
}
|
|
});
|
|
}
|
|
|
|
void
|
|
Request::post()
|
|
{
|
|
if (!conn_ or !conn_->is_open()){
|
|
if (logger_)
|
|
logger_->e("[http:request:%i] [post] closed connection", id_);
|
|
terminate(asio::error::not_connected);
|
|
return;
|
|
}
|
|
build();
|
|
init_parser();
|
|
|
|
if (logger_)
|
|
logger_->d("[http:request:%i] [post]\n%s", id_, request_.c_str());
|
|
|
|
// write the request to buffer
|
|
std::ostream request_stream(&conn_->input());
|
|
request_stream << request_;
|
|
|
|
// send the request
|
|
notify_state_changed(State::SENDING);
|
|
asio::async_write(conn_->socket(), conn_->input(),
|
|
std::bind(&Request::handle_request, this, std::placeholders::_1));
|
|
}
|
|
|
|
void
|
|
Request::terminate(const asio::error_code& ec)
|
|
{
|
|
// set response outcome, ignore enf of file and abort
|
|
if (!ec or ec == asio::error::eof or ec == asio::error::operation_aborted)
|
|
response_.status_code = 200;
|
|
else
|
|
response_.status_code = 0;
|
|
|
|
// ensure on_message_complete callback is called
|
|
if (parser_){
|
|
std::lock_guard<std::mutex> lock(cbs_mutex_);
|
|
http_parser_execute(parser_.get(), parser_s_.get(), "", 0);
|
|
}
|
|
if (logger_)
|
|
logger_->d("[http:request:%i] done", id_);
|
|
|
|
notify_state_changed(State::DONE);
|
|
}
|
|
|
|
void
|
|
Request::handle_request(const asio::error_code& ec)
|
|
{
|
|
if (!conn_->is_open()){
|
|
if (logger_)
|
|
logger_->e("[http:request:%i] [write] closed connection", id_);
|
|
terminate(asio::error::not_connected);
|
|
return;
|
|
}
|
|
if (ec and ec != asio::error::eof){
|
|
if (logger_)
|
|
logger_->e("[http:request:%i] [write] error: %s", id_, ec.message().c_str());
|
|
terminate(ec);
|
|
return;
|
|
}
|
|
if (logger_)
|
|
logger_->d("[http:request:%i] [write] success", id_);
|
|
|
|
// read response
|
|
notify_state_changed(State::RECEIVING);
|
|
asio::async_read_until(conn_->socket(), conn_->data(), "\r\n\r\n",
|
|
std::bind(&Request::handle_response_header, this,
|
|
std::placeholders::_1, std::placeholders::_2));
|
|
}
|
|
|
|
void
|
|
Request::handle_response_header(const asio::error_code& ec, const size_t bytes)
|
|
{
|
|
if (!conn_->is_open()){
|
|
if (logger_)
|
|
logger_->e("[http:request:%i] [read:header] closed connection", id_);
|
|
terminate(asio::error::not_connected);
|
|
return;
|
|
}
|
|
if (ec && ec != asio::error::eof){
|
|
if (logger_)
|
|
logger_->e("[http:request:%i] [read:header] error: %s", id_, ec.message().c_str());
|
|
terminate(ec);
|
|
return;
|
|
}
|
|
else if ((ec == asio::error::eof) || (ec == asio::error::connection_reset)){
|
|
terminate(ec);
|
|
return;
|
|
}
|
|
// read the response buffer
|
|
std::ostringstream str_s;
|
|
str_s << &conn_->data();
|
|
auto chunk = str_s.str();
|
|
auto headers = chunk.substr(0, bytes);
|
|
if (logger_)
|
|
logger_->d("[http:request:%i] [read:header]\n%s", id_, headers.c_str());
|
|
// parse the header right away
|
|
parse_request(headers);
|
|
notify_state_changed(State::HEADER_RECEIVED);
|
|
|
|
auto content_length_it = response_.headers.find(HTTP_HEADER_CONTENT_LENGTH);
|
|
auto content_type_it = response_.headers.find(HTTP_HEADER_CONTENT_TYPE);
|
|
|
|
// has body size
|
|
if (content_length_it != response_.headers.end()){
|
|
unsigned int content_length = atoi(content_length_it->second.c_str());
|
|
auto body_chunk = chunk.substr(bytes, std::string::npos);
|
|
// append current body chunk
|
|
if (content_length > body_chunk.size()){
|
|
// read the rest of body
|
|
notify_state_changed(State::RECEIVING);
|
|
asio::async_read(conn_->socket(), conn_->data(),
|
|
asio::transfer_exactly(content_length - (body_chunk.size())),
|
|
std::bind(&Request::handle_response_body, this,
|
|
std::placeholders::_1, content_length, body_chunk));
|
|
}
|
|
else
|
|
terminate(asio::error::eof);
|
|
return;
|
|
}
|
|
// has potential body but no content-length (current proxy behavior on key get/listen)
|
|
else if (content_type_it != response_.headers.end() and
|
|
content_type_it->second == HTTP_HEADER_CONTENT_TYPE_JSON)
|
|
{
|
|
auto body_chunk = chunk.substr(bytes, std::string::npos);
|
|
if (!body_chunk.empty())
|
|
parse_request(body_chunk);
|
|
// keep reading
|
|
notify_state_changed(State::RECEIVING);
|
|
asio::async_read(conn_->socket(), conn_->data(), asio::transfer_at_least(1),
|
|
std::bind(&Request::handle_response_body, this,
|
|
std::placeholders::_1, std::placeholders::_2, ""));
|
|
}
|
|
/* NOTE:
|
|
* We ignore keep-alive from server since the current dhtproxy sets it on every operation
|
|
* thus, it wouldn't make sense to continue reading from a socket even if the dhtproxy
|
|
* terminated its operation.
|
|
*/
|
|
}
|
|
|
|
void
|
|
Request::handle_response_body(const asio::error_code& ec, const size_t bytes, const std::string chunk)
|
|
{
|
|
if (!conn_->is_open()){
|
|
if (logger_)
|
|
logger_->e("[http:request] [read:body] closed connection");
|
|
terminate(asio::error::not_connected);
|
|
return;
|
|
}
|
|
if (ec && ec != asio::error::eof){
|
|
if (logger_)
|
|
logger_->e("[http:request:%i] [read:body] error: %s", id_, ec.message().c_str());
|
|
terminate(ec);
|
|
return;
|
|
}
|
|
else if ((ec == asio::error::eof) || (ec == asio::error::connection_reset)){
|
|
terminate(ec);
|
|
return;
|
|
}
|
|
std::string body;
|
|
if (!chunk.empty()){
|
|
// append previous incomplete chunk from header
|
|
body.append(chunk);
|
|
}
|
|
// read the response buffer
|
|
std::ostringstream str_s;
|
|
str_s << &conn_->data();
|
|
body.append(str_s.str().substr(0, bytes));
|
|
response_.body = body;
|
|
if (logger_)
|
|
logger_->d("[http:request:%i] [read:body] success:\n%s", id_, body.c_str());
|
|
|
|
parse_request(body);
|
|
|
|
asio::async_read(conn_->socket(), conn_->data(), asio::transfer_at_least(1),
|
|
std::bind(&Request::handle_response_body, this, std::placeholders::_1, std::placeholders::_2, ""));
|
|
}
|
|
|
|
void
|
|
Request::parse_request(const std::string request)
|
|
{
|
|
std::lock_guard<std::mutex> lock(cbs_mutex_);
|
|
http_parser_execute(parser_.get(), parser_s_.get(), request.c_str(), request.size());
|
|
|
|
// detect parsing errors
|
|
if (HPE_OK != parser_->http_errno && HPE_PAUSED != parser_->http_errno){
|
|
if (logger_){
|
|
auto err = HTTP_PARSER_ERRNO(parser_.get());
|
|
logger_->e("[http:request:%i] [parse] error: %s", id_, http_errno_name(err));
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace http
|