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