mirror of
https://github.com/savoirfairelinux/opendht.git
synced 2026-11-08 04:21:51 +00:00
385 lines
12 KiB
C++
385 lines
12 KiB
C++
/*
|
|
* Copyright (C) 2014-2025 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/>.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "def.h"
|
|
#include "infohash.h"
|
|
#include "crypto.h"
|
|
|
|
// some libraries may try to redefine snprintf
|
|
// but restinio will use it in std namespace
|
|
#ifdef _MSC_VER
|
|
# undef snprintf
|
|
# define snprintf snprintf
|
|
#endif
|
|
|
|
#include <asio/ip/tcp.hpp>
|
|
#include <asio/streambuf.hpp>
|
|
#include <asio/ssl/context.hpp>
|
|
#include <restinio/message_builders.hpp>
|
|
|
|
#include <memory>
|
|
#include <queue>
|
|
#include <mutex>
|
|
#include <future>
|
|
|
|
namespace Json {
|
|
class Value;
|
|
}
|
|
|
|
namespace restinio {
|
|
namespace impl {
|
|
class tls_socket_t;
|
|
}
|
|
}
|
|
|
|
namespace dht {
|
|
namespace log {
|
|
struct Logger;
|
|
}
|
|
|
|
namespace crypto {
|
|
struct Certificate;
|
|
}
|
|
|
|
namespace http {
|
|
|
|
using HandlerCb = std::function<void(const asio::error_code& ec)>;
|
|
using BytesHandlerCb = std::function<void(const asio::error_code& ec, size_t bytes)>;
|
|
using ConnectHandlerCb = std::function<void(const asio::error_code& ec,
|
|
const asio::ip::tcp::endpoint& endpoint)>;
|
|
|
|
using ssl_socket_t = restinio::impl::tls_socket_t;
|
|
using socket_t = asio::ip::tcp::socket;
|
|
|
|
class OPENDHT_PUBLIC Url
|
|
{
|
|
public:
|
|
Url() = default;
|
|
Url(std::string_view url);
|
|
std::string url;
|
|
std::string protocol {"http"};
|
|
std::string host;
|
|
std::string service;
|
|
std::string target;
|
|
std::string query;
|
|
std::string fragment;
|
|
|
|
std::string toString() const;
|
|
};
|
|
|
|
class OPENDHT_PUBLIC Connection : public std::enable_shared_from_this<Connection>
|
|
{
|
|
public:
|
|
Connection(asio::io_context& ctx, const bool ssl = true, std::shared_ptr<log::Logger> l = {});
|
|
Connection(asio::io_context& ctx, std::shared_ptr<dht::crypto::Certificate> server_ca,
|
|
const dht::crypto::Identity& identity, std::shared_ptr<log::Logger> l = {});
|
|
~Connection();
|
|
|
|
inline unsigned int id() const { return id_; };
|
|
bool is_open() const;
|
|
bool is_ssl() const;
|
|
void checkOcsp(bool check = true) { checkOcsp_ = check; }
|
|
|
|
void set_ssl_verification(const std::string& hostname, const asio::ssl::verify_mode verify_mode);
|
|
|
|
asio::streambuf& input();
|
|
std::istream& data() { return istream_; }
|
|
|
|
std::string read_bytes(size_t bytes = 0);
|
|
std::string read_until(const char delim);
|
|
|
|
void async_connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, ConnectHandlerCb);
|
|
void async_handshake(HandlerCb cb);
|
|
void async_write(BytesHandlerCb cb);
|
|
void async_read_until(const char* delim, BytesHandlerCb cb);
|
|
void async_read_until(char delim, BytesHandlerCb cb);
|
|
void async_read(size_t bytes, BytesHandlerCb cb);
|
|
void async_read_some(size_t bytes, BytesHandlerCb cb);
|
|
|
|
void set_keepalive(uint32_t seconds);
|
|
|
|
const asio::ip::address& local_address() const;
|
|
|
|
void timeout(const std::chrono::seconds& timeout, HandlerCb cb = {});
|
|
|
|
void close();
|
|
|
|
private:
|
|
|
|
template<typename T>
|
|
T wrapCallback(T cb) const {
|
|
return [t=shared_from_this(),cb=std::move(cb)](auto ...params) {
|
|
cb(params...);
|
|
};
|
|
}
|
|
|
|
mutable std::mutex mutex_;
|
|
|
|
unsigned int id_;
|
|
static std::atomic_uint ids_;
|
|
|
|
asio::io_context& ctx_;
|
|
std::unique_ptr<socket_t> socket_;
|
|
std::shared_ptr<asio::ssl::context> ssl_ctx_;
|
|
std::unique_ptr<ssl_socket_t> ssl_socket_;
|
|
|
|
asio::ip::tcp::endpoint endpoint_;
|
|
|
|
asio::streambuf write_buf_;
|
|
asio::streambuf read_buf_;
|
|
std::istream istream_;
|
|
|
|
asio::ip::address local_address_;
|
|
|
|
std::unique_ptr<asio::steady_timer> timeout_timer_;
|
|
std::shared_ptr<log::Logger> logger_;
|
|
bool checkOcsp_ {false};
|
|
};
|
|
|
|
/* @class Resolver
|
|
* @brief The purpose is to only resolve once to avoid mutliple dns requests per operation.
|
|
*/
|
|
class OPENDHT_PUBLIC Resolver
|
|
{
|
|
public:
|
|
using ResolverCb = std::function<void(const asio::error_code& ec,
|
|
const std::vector<asio::ip::tcp::endpoint>& endpoints)>;
|
|
|
|
Resolver(asio::io_context& ctx, const std::string& url, std::shared_ptr<log::Logger> logger = {});
|
|
Resolver(asio::io_context& ctx, const std::string& host, const std::string& service,
|
|
const bool ssl = false, std::shared_ptr<log::Logger> logger = {});
|
|
|
|
// use already resolved endpoints with classes using this resolver
|
|
Resolver(asio::io_context& ctx, std::vector<asio::ip::tcp::endpoint> endpoints,
|
|
const bool ssl = false, std::shared_ptr<log::Logger> logger = {});
|
|
Resolver(asio::io_context& ctx, const std::string& url, std::vector<asio::ip::tcp::endpoint> endpoints,
|
|
std::shared_ptr<log::Logger> logger = {});
|
|
|
|
~Resolver();
|
|
|
|
inline const Url& get_url() const {
|
|
return url_;
|
|
}
|
|
|
|
void add_callback(ResolverCb cb, sa_family_t family = AF_UNSPEC);
|
|
|
|
std::shared_ptr<log::Logger> getLogger() const {
|
|
return logger_;
|
|
}
|
|
|
|
void cancel();
|
|
|
|
private:
|
|
void resolve(const std::string& host, const std::string& service);
|
|
|
|
mutable std::mutex mutex_;
|
|
|
|
Url url_;
|
|
asio::error_code ec_;
|
|
asio::ip::tcp::resolver resolver_;
|
|
std::shared_ptr<bool> destroyed_;
|
|
std::vector<asio::ip::tcp::endpoint> endpoints_;
|
|
|
|
bool completed_ {false};
|
|
std::queue<ResolverCb> cbs_;
|
|
|
|
std::shared_ptr<log::Logger> logger_;
|
|
};
|
|
|
|
class Request;
|
|
|
|
struct Response
|
|
{
|
|
unsigned status_code {0};
|
|
std::map<std::string, std::string> headers;
|
|
std::string body;
|
|
bool aborted {false};
|
|
std::weak_ptr<Request> request;
|
|
};
|
|
|
|
class OPENDHT_PUBLIC Request : public std::enable_shared_from_this<Request>
|
|
{
|
|
public:
|
|
enum class State {
|
|
CREATED,
|
|
SENDING,
|
|
HEADER_RECEIVED,
|
|
RECEIVING,
|
|
DONE
|
|
};
|
|
using OnStatusCb = std::function<void(unsigned status_code)>;
|
|
using OnDataCb = std::function<void(const char* at, size_t length)>;
|
|
using OnStateChangeCb = std::function<void(State state, const Response& response)>;
|
|
using OnJsonCb = std::function<void(Json::Value value, const Response& response)>;
|
|
using OnDoneCb = std::function<void(const Response& response)>;
|
|
|
|
// resolves implicitly
|
|
Request(asio::io_context& ctx, const std::string& url, const Json::Value& json, OnJsonCb jsoncb,
|
|
std::shared_ptr<log::Logger> logger = {});
|
|
Request(asio::io_context& ctx, const std::string& url, OnJsonCb jsoncb,
|
|
std::shared_ptr<log::Logger> logger = {});
|
|
|
|
Request(asio::io_context& ctx, const std::string& url, std::shared_ptr<log::Logger> logger = {});
|
|
Request(asio::io_context& ctx, const std::string& host, const std::string& service,
|
|
const bool ssl = false, std::shared_ptr<log::Logger> logger = {});
|
|
Request(asio::io_context& ctx, const std::string& url, OnDoneCb onDone, std::shared_ptr<log::Logger> logger = {});
|
|
|
|
// user defined resolver
|
|
Request(asio::io_context& ctx, std::shared_ptr<Resolver> resolver, sa_family_t family = AF_UNSPEC);
|
|
Request(asio::io_context& ctx, std::shared_ptr<Resolver> resolver, const std::string& target, sa_family_t family = AF_UNSPEC);
|
|
|
|
// user defined resolved endpoints
|
|
Request(asio::io_context& ctx, std::vector<asio::ip::tcp::endpoint>&& endpoints,
|
|
const bool ssl = false, std::shared_ptr<log::Logger> logger = {});
|
|
|
|
~Request();
|
|
|
|
inline unsigned int id() const { return id_; };
|
|
void set_connection(std::shared_ptr<Connection> connection);
|
|
std::shared_ptr<Connection> get_connection() const;
|
|
inline const Url& get_url() const {
|
|
return resolver_->get_url();
|
|
};
|
|
|
|
void timeout(const std::chrono::seconds& timeout, HandlerCb cb = {}) {
|
|
timeout_ = timeout;
|
|
timeoutCb_ = cb;
|
|
}
|
|
|
|
/** The previous request in case of redirect following */
|
|
std::shared_ptr<Request> getPrevious() const {
|
|
return prev_.lock();
|
|
}
|
|
|
|
inline std::string& to_string() {
|
|
return request_;
|
|
}
|
|
|
|
void set_certificate_authority(std::shared_ptr<dht::crypto::Certificate> certificate);
|
|
void set_identity(const dht::crypto::Identity& identity);
|
|
void set_logger(std::shared_ptr<log::Logger> logger);
|
|
|
|
/**
|
|
* Define the HTTP header/body as per https://tools.ietf.org/html/rfc7230.
|
|
*/
|
|
void set_header(restinio::http_request_header_t header);
|
|
void set_method(restinio::http_method_id_t method);
|
|
void set_target(std::string target);
|
|
void set_header_field(restinio::http_field_t field, std::string value);
|
|
void set_connection_type(restinio::http_connection_header_t connection);
|
|
void set_body(std::string body);
|
|
void set_auth(const std::string& username, const std::string& password);
|
|
|
|
void add_on_status_callback(OnStatusCb cb);
|
|
void add_on_body_callback(OnDataCb cb);
|
|
void add_on_state_change_callback(OnStateChangeCb cb);
|
|
void add_on_done_callback(OnDoneCb cb);
|
|
|
|
void send();
|
|
|
|
/** Send and block for response */
|
|
const Response& await();
|
|
|
|
/**
|
|
* User action to cancel the Request and call the completion callbacks.
|
|
*/
|
|
void cancel();
|
|
void terminate(const asio::error_code& ec);
|
|
|
|
private:
|
|
using OnCompleteCb = std::function<void()>;
|
|
|
|
struct Callbacks {
|
|
OnStatusCb on_status;
|
|
OnDataCb on_header_field;
|
|
OnDataCb on_header_value;
|
|
OnDataCb on_body;
|
|
OnStateChangeCb on_state_change;
|
|
};
|
|
|
|
static std::string getRelativePath(const Url& origin, const std::string& path);
|
|
|
|
void notify_state_change(State state);
|
|
|
|
void build();
|
|
|
|
static std::string url_encode(std::string_view value);
|
|
|
|
void init_default_headers();
|
|
/**
|
|
* Initialized and wraps the llhttp callbacks with our user defined callbacks.
|
|
*/
|
|
void init_parser();
|
|
|
|
void connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, HandlerCb cb = {});
|
|
|
|
void post();
|
|
|
|
void handle_request(const asio::error_code& ec);
|
|
void handle_response(const asio::error_code& ec, size_t bytes);
|
|
|
|
void onHeadersComplete();
|
|
void onBody(const char* at, size_t length);
|
|
void onComplete();
|
|
|
|
mutable std::mutex mutex_;
|
|
|
|
std::shared_ptr<log::Logger> logger_;
|
|
|
|
restinio::http_request_header_t header_;
|
|
std::map<restinio::http_field_t, std::string> headers_;
|
|
restinio::http_connection_header_t connection_type_ {restinio::http_connection_header_t::close};
|
|
std::string body_;
|
|
|
|
Callbacks cbs_;
|
|
State state_;
|
|
|
|
dht::crypto::Identity client_identity_;
|
|
std::shared_ptr<dht::crypto::Certificate> server_ca_;
|
|
std::string service_;
|
|
std::string host_;
|
|
|
|
unsigned int id_;
|
|
static std::atomic_uint ids_;
|
|
asio::io_context& ctx_;
|
|
sa_family_t family_ = AF_UNSPEC;
|
|
std::shared_ptr<Connection> conn_;
|
|
std::shared_ptr<Resolver> resolver_;
|
|
|
|
Response response_ {};
|
|
std::string request_;
|
|
std::atomic<bool> finishing_ {false};
|
|
std::unique_ptr<llhttp_t> parser_;
|
|
std::unique_ptr<llhttp_settings_t> parser_s_;
|
|
|
|
// Next request in case of redirect following
|
|
std::shared_ptr<Request> next_;
|
|
std::weak_ptr<Request> prev_;
|
|
unsigned num_redirect {0};
|
|
bool follow_redirect {true};
|
|
|
|
HandlerCb timeoutCb_ {};
|
|
std::chrono::seconds timeout_ {0};
|
|
};
|
|
|
|
} // namespace http
|
|
} // namespace dht
|
|
|