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