My Project 3.7.1
C++ Distributed Hash Table
Loading...
Searching...
No Matches
http.h
1// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
2// SPDX-License-Identifier: MIT
3#pragma once
4
5#include "def.h"
6#include "infohash.h"
7#include "crypto.h"
8
9// some libraries may try to redefine snprintf
10// but restinio will use it in std namespace
11#ifdef _MSC_VER
12#undef snprintf
13#define snprintf snprintf
14#endif
15
16#include <asio/ip/tcp.hpp>
17#include <asio/streambuf.hpp>
18#include <asio/ssl/context.hpp>
19#include <restinio/message_builders.hpp>
20
21#include <memory>
22#include <queue>
23#include <mutex>
24#include <future>
25
26namespace Json {
27class Value;
28}
29
30namespace restinio {
31namespace impl {
32class tls_socket_t;
33}
34} // namespace restinio
35
36namespace dht {
37namespace log {
38struct Logger;
39}
40
41namespace crypto {
42struct Certificate;
43}
44
45namespace http {
46
47using HandlerCb = std::function<void(const asio::error_code& ec)>;
48using BytesHandlerCb = std::function<void(const asio::error_code& ec, size_t bytes)>;
49using ConnectHandlerCb = std::function<void(const asio::error_code& ec, const asio::ip::tcp::endpoint& endpoint)>;
50
51using ssl_socket_t = restinio::impl::tls_socket_t;
52using socket_t = asio::ip::tcp::socket;
53
54class OPENDHT_PUBLIC Url
55{
56public:
57 Url() = default;
58 Url(std::string_view url);
59 std::string url;
60 std::string protocol {"http"};
61 std::string_view user;
62 std::string_view password;
63 std::string_view host;
64 std::string_view service;
65 std::string target;
66 std::string_view query;
67 std::string_view fragment;
68
69 std::string toString() const;
70};
71
72class OPENDHT_PUBLIC Connection : public std::enable_shared_from_this<Connection>
73{
74public:
75 Connection(asio::io_context& ctx, const bool ssl = true, std::shared_ptr<log::Logger> l = {});
76 Connection(asio::io_context& ctx,
77 std::shared_ptr<dht::crypto::Certificate> server_ca,
78 const dht::crypto::Identity& identity,
79 std::shared_ptr<log::Logger> l = {});
80 ~Connection();
81
82 inline unsigned int id() const { return id_; };
83 bool is_open() const;
84 bool is_ssl() const;
85 void checkOcsp(bool check = true) { checkOcsp_ = check; }
86
87 void set_ssl_verification(const std::string& hostname, const asio::ssl::verify_mode verify_mode);
88
89 asio::streambuf& input();
90 std::istream& data() { return istream_; }
91
92 std::string read_bytes(size_t bytes = 0);
93 std::string read_until(const char delim);
94
95 void async_connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, ConnectHandlerCb);
96 void async_handshake(HandlerCb cb);
97 void async_write(BytesHandlerCb cb);
98 void async_read_until(const char* delim, BytesHandlerCb cb);
99 void async_read_until(char delim, BytesHandlerCb cb);
100 void async_read(size_t bytes, BytesHandlerCb cb);
101 void async_read_some(size_t bytes, BytesHandlerCb cb);
102
103 void set_keepalive(uint32_t seconds);
104
105 const asio::ip::address& local_address() const;
106
107 void timeout(const std::chrono::seconds& timeout, HandlerCb cb = {});
108
109 void close();
110
111private:
112 template<typename T>
113 T wrapCallback(T cb) const
114 {
115 return [t = shared_from_this(), cb = std::move(cb)](auto... params) {
116 cb(params...);
117 };
118 }
119
120 mutable std::mutex mutex_;
121
122 unsigned int id_;
123 static std::atomic_uint ids_;
124
125 asio::io_context& ctx_;
126 std::unique_ptr<socket_t> socket_;
127 std::shared_ptr<asio::ssl::context> ssl_ctx_;
128 std::unique_ptr<ssl_socket_t> ssl_socket_;
129
130 asio::ip::tcp::endpoint endpoint_;
131
132 asio::streambuf write_buf_;
133 asio::streambuf read_buf_;
134 std::istream istream_;
135
136 asio::ip::address local_address_;
137
138 std::unique_ptr<asio::steady_timer> timeout_timer_;
139 std::shared_ptr<log::Logger> logger_;
140 bool checkOcsp_ {false};
141};
142
143/* @class Resolver
144 * @brief The purpose is to only resolve once to avoid mutliple dns requests per operation.
145 */
146class OPENDHT_PUBLIC Resolver
147{
148public:
149 using ResolverCb
150 = std::function<void(const asio::error_code& ec, const std::vector<asio::ip::tcp::endpoint>& endpoints)>;
151
152 Resolver(asio::io_context& ctx, const std::string& url, std::shared_ptr<log::Logger> logger = {});
153 Resolver(asio::io_context& ctx,
154 std::string_view host,
155 std::string_view service,
156 const bool ssl = false,
157 std::shared_ptr<log::Logger> logger = {});
158
159 // use already resolved endpoints with classes using this resolver
160 Resolver(asio::io_context& ctx,
161 std::vector<asio::ip::tcp::endpoint> endpoints,
162 const bool ssl = false,
163 std::shared_ptr<log::Logger> logger = {});
164 Resolver(asio::io_context& ctx,
165 const std::string& url,
166 std::vector<asio::ip::tcp::endpoint> endpoints,
167 std::shared_ptr<log::Logger> logger = {});
168
169 ~Resolver();
170
171 inline const Url& get_url() const { return url_; }
172
173 void add_callback(ResolverCb cb, sa_family_t family = AF_UNSPEC);
174
175 std::shared_ptr<log::Logger> getLogger() const { return logger_; }
176
177 void cancel();
178
179private:
180 void resolve(std::string_view host, std::string_view service);
181
182 mutable std::mutex mutex_;
183
184 Url url_;
185 asio::error_code ec_;
186 asio::ip::tcp::resolver resolver_;
187 std::shared_ptr<bool> destroyed_;
188 std::vector<asio::ip::tcp::endpoint> endpoints_;
189
190 bool completed_ {false};
191 std::queue<ResolverCb> cbs_;
192
193 std::shared_ptr<log::Logger> logger_;
194};
195
196class Request;
197
199{
200 unsigned status_code {0};
201 std::map<std::string, std::string> headers;
202 std::string body;
203 bool aborted {false};
204 std::weak_ptr<Request> request;
205};
206
207class OPENDHT_PUBLIC Request : public std::enable_shared_from_this<Request>
208{
209public:
210 enum class State { CREATED, SENDING, HEADER_RECEIVED, RECEIVING, DONE };
211 using OnStatusCb = std::function<void(unsigned status_code)>;
212 using OnDataCb = std::function<void(const char* at, size_t length)>;
213 using OnStateChangeCb = std::function<void(State state, const Response& response)>;
214 using OnJsonCb = std::function<void(Json::Value value, const Response& response)>;
215 using OnDoneCb = std::function<void(const Response& response)>;
216
217 // resolves implicitly
218 Request(asio::io_context& ctx,
219 const std::string& url,
220 const Json::Value& json,
221 OnJsonCb jsoncb,
222 std::shared_ptr<log::Logger> logger = {});
223 Request(asio::io_context& ctx, const std::string& url, OnJsonCb jsoncb, std::shared_ptr<log::Logger> logger = {});
224
225 Request(asio::io_context& ctx, const std::string& url, std::shared_ptr<log::Logger> logger = {});
226 Request(asio::io_context& ctx,
227 std::string_view host,
228 std::string_view service,
229 const bool ssl = false,
230 std::shared_ptr<log::Logger> logger = {});
231 Request(asio::io_context& ctx, const std::string& url, OnDoneCb onDone, std::shared_ptr<log::Logger> logger = {});
232
233 // user defined resolver
234 Request(asio::io_context& ctx, std::shared_ptr<Resolver> resolver, sa_family_t family = AF_UNSPEC);
235 Request(asio::io_context& ctx,
236 std::shared_ptr<Resolver> resolver,
237 const std::string& target,
238 sa_family_t family = AF_UNSPEC);
239
240 // user defined resolved endpoints
241 Request(asio::io_context& ctx,
242 std::vector<asio::ip::tcp::endpoint>&& endpoints,
243 const bool ssl = false,
244 std::shared_ptr<log::Logger> logger = {});
245
246 ~Request();
247
248 inline unsigned int id() const { return id_; };
249 void set_connection(std::shared_ptr<Connection> connection);
250 std::shared_ptr<Connection> get_connection() const;
251 inline const Url& get_url() const { return resolver_->get_url(); };
252
253 void timeout(const std::chrono::seconds& timeout, HandlerCb cb = {})
254 {
255 timeout_ = timeout;
256 timeoutCb_ = cb;
257 }
258
260 std::shared_ptr<Request> getPrevious() const { return prev_.lock(); }
261
262 inline std::string& to_string() { return request_; }
263
264 void set_certificate_authority(std::shared_ptr<dht::crypto::Certificate> certificate);
265 void set_identity(const dht::crypto::Identity& identity);
266 void set_logger(std::shared_ptr<log::Logger> logger);
267
271 void set_header(restinio::http_request_header_t header);
272 void set_method(restinio::http_method_id_t method);
273 void set_target(std::string target);
274 void set_header_field(restinio::http_field_t field, std::string value);
275 void set_connection_type(restinio::http_connection_header_t connection);
276 void set_body(std::string body);
277 void set_body(const uint8_t* data, size_t length);
278 void set_auth(const std::string& username, const std::string& password);
279
280 void add_on_status_callback(OnStatusCb cb);
281 void add_on_body_callback(OnDataCb cb);
282 void add_on_state_change_callback(OnStateChangeCb cb);
283 void add_on_done_callback(OnDoneCb cb);
284
285 void send();
286
288 const Response& await();
289
293 void cancel();
294 void terminate(const asio::error_code& ec);
295
296private:
297 using OnCompleteCb = std::function<void()>;
298
299 struct Callbacks
300 {
301 OnStatusCb on_status;
302 OnDataCb on_header_field;
303 OnDataCb on_header_value;
304 OnDataCb on_body;
305 OnStateChangeCb on_state_change;
306 };
307
308 static std::string getRelativePath(const Url& origin, const std::string& path);
309
310 void notify_state_change(State state);
311
312 void build();
313
314 static std::string url_encode(std::string_view value);
315
316 void init_default_headers();
320 void init_parser();
321
322 void connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, HandlerCb cb = {});
323
324 void post();
325
326 void handle_request(const asio::error_code& ec);
327 void handle_response(const asio::error_code& ec, size_t bytes);
328
329 void onHeadersComplete();
330 void onBody(const char* at, size_t length);
331 void onComplete();
332
333 mutable std::mutex mutex_;
334
335 std::shared_ptr<log::Logger> logger_;
336
337 restinio::http_request_header_t header_;
338 std::map<restinio::http_field_t, std::string> headers_;
339 restinio::http_connection_header_t connection_type_ {restinio::http_connection_header_t::close};
340 std::string body_;
341
342 Callbacks cbs_;
343 State state_;
344
345 dht::crypto::Identity client_identity_;
346 std::shared_ptr<dht::crypto::Certificate> server_ca_;
347 std::string service_;
348 std::string host_;
349
350 unsigned int id_;
351 static std::atomic_uint ids_;
352 asio::io_context& ctx_;
353 sa_family_t family_ = AF_UNSPEC;
354 std::shared_ptr<Connection> conn_;
355 std::shared_ptr<Resolver> resolver_;
356
357 Response response_ {};
358 std::string request_;
359 std::atomic<bool> finishing_ {false};
360 std::unique_ptr<llhttp_t> parser_;
361 std::unique_ptr<llhttp_settings_t> parser_s_;
362
363 // Next request in case of redirect following
364 std::shared_ptr<Request> next_;
365 std::weak_ptr<Request> prev_;
366 unsigned num_redirect {0};
367 bool follow_redirect {true};
368
369 HandlerCb timeoutCb_ {};
370 std::chrono::seconds timeout_ {0};
371};
372
373} // namespace http
374} // namespace dht
std::shared_ptr< Request > getPrevious() const
Definition http.h:260
const Response & await()
void set_header(restinio::http_request_header_t header)