curl-cpp
static c++17 wrapper for curl with -fno-exceptions support
curl_multi.hpp
1 #ifndef __curl_cpp_curl_multi_HPP__
2 # define __curl_cpp_curl_multi_HPP__
3 
4 # include "curl_easy.hpp"
5 # include <curl/curl.h>
6 
7 namespace curl {
8 /**
9  * @example curl_multi_poll.cc
10  * @example curl_multi_poll2.cc
11  * @example curl_multi_socket_action_epoll.cc
12  * @example curl_multi_socket_action_event.cc
13  * @example curl_multi_socket_action_uv.cc
14  *
15  * Multi_t enables user to simultaneously do multiple request
16  * in the same thread, using any polling interface they prefer.
17  *
18  * This class contains poll() interface (wrapping curl_multi_poll, doesn't
19  * add additional space to use it), and multi_socket_action interface
20  * that enables using any interface. They are incompatible and should not be
21  * mixed.
22  *
23  * Multi_t's member function cannot be called in multiple threads simultaneously.
24  *
25  * All easy handles must be removed from this class before it can be
26  * properly destroyed.
27  * Failure to do so invokes undefined behavior.
28  */
29 class Multi_t {
30 public:
31  /**
32  * Base class for any exceptions thrown via Ret_except in
33  * this class.
34  */
35  class Exception: public curl::Exception {
36  public:
37  const long error_code;
38 
39  Exception(long err_code_arg);
40  Exception(const Exception&) = default;
41 
42  auto what() const noexcept -> const char*;
43  };
44 
45  using perform_ret_t = Ret_except<int, std::bad_alloc, Exception, Recursive_api_call_Exception, libcurl_bug>;
46 
47 protected:
48  void *curl_multi = nullptr;
49  std::size_t handles = 0;
50 
51  /**
52  * @return handles that are finished.
53  * Return nullptr to signal all finished handles are returned.
54  */
55  auto get_finished_easy() const noexcept ->
56  std::pair<Easy_ref_t, Easy_ref_t::perform_ret_t>;
57 
58  template <class perform_callback_t, class T>
59  void callback_on_finished_easy(perform_callback_t &&perform_callback, T &&arg)
60  {
61  for (std::pair<Easy_ref_t, Easy_ref_t::perform_ret_t> ret;
62  (ret = get_finished_easy()).first.curl_easy; )
63  std::forward<perform_callback_t>(perform_callback)(ret.first, std::move(ret.second), *this,
64  std::forward<T>(arg));
65  }
66 
67  auto perform_impl() noexcept -> perform_ret_t;
68  auto multi_socket_action_impl(curl_socket_t socketfd, int ev_bitmask) noexcept ->
69  perform_ret_t;
70 
71 public:
72  /**
73  * Construct an empty Multi_t that can only be
74  * move assigned with value or be destroyed.
75  */
76  Multi_t() = default;
77 
78  /**
79  * This constructor takes CURLM*
80  *
81  * @param multi pass nullptr for an empty Multi_t that can only be
82  * move assigned with value or be destroyed.
83  */
84  Multi_t(void *multi) noexcept;
85 
86  Multi_t(const Multi_t&) = delete;
87  /**
88  * @param other after mv operation, other is in unusable state and can only be destroyed
89  * or move assign another value.
90  */
91  Multi_t(Multi_t&&) noexcept;
92 
93  Multi_t& operator = (const Multi_t&) = delete;
94  /**
95  * @param other after mv operation, other is in unusable state and can only be destroyed
96  * or move assign another value.
97  */
98  Multi_t& operator = (Multi_t&&) noexcept;
99 
100  /**
101  * @return true if this object is usable, false otherwise
102  */
103  operator bool () const noexcept;
104 
105  /**
106  * @param easy must be in valid state
107  * @return true if not yet added;
108  * <br>false if already added.
109  */
110  bool add_easy(Easy_ref_t &easy) noexcept;
111  /**
112  * Undefined behavior if easy is not valid or not added to this multi.
113  */
114  void remove_easy(Easy_ref_t &easy) noexcept;
115 
116  std::size_t get_number_of_handles() const noexcept;
117 
118  /**
119  * HTTP2 multiplexing configuration.
120  *
121  * @pre curl_t::has_http2_multiplex_support()
122  * @param max_concurrent_stream max concurrent stream for a given connection.
123  * <br>Should be between [0, 2 ^ 31 - 1].
124  * <br>Set it to 0 disable multiplexing.
125  *
126  * Since curl_t::version >= 7.62.0 (version released way after http2 multiplex support)
127  * , multiplex is turned on by default.
128  *
129  * NOTE that libcurl not always accept max_concurrent_stream tuning.
130  * <br>Check curl_t::has_max_concurrent_stream_support().
131  *
132  * If libcurl does not support tuning, this option will be only used
133  * for turning on and off the http2 multiplex.
134  */
135  void set_multiplexing(long max_concurrent_stream) noexcept;
136 
137  /* Interface for poll + perform - multi_poll interface */
138 
139  /**
140  * @pre curl_t::has_multi_poll_support()
141  * @param timeout Must be >= 0, in ms. Pass 0 for infinite.
142  * @return number of fd on which interested events occured.
143  *
144  * poll can return if interesting events has happened or timeout ms
145  * has passed and nothing has heppend.
146  *
147  * It also can return due to pending internal timeout that
148  * has a shorter expiry time than timeout_ms.
149  */
150  auto poll(curl_waitfd *extra_fds = nullptr, unsigned extra_nfds = 0U, int timeout = 0) noexcept ->
151  Ret_except<int, std::bad_alloc, libcurl_bug>;
152 
153  /**
154  * @pre curl_t::has_multi_poll_support()
155  * @param timeout Must be >= 0, in ms. Pass 0 for infinite.
156  * @return -1 when get_number_of_handles() == 0;
157  * Otherwise number of fd on which interested events occured, can be 0.
158  *
159  * Behavior is same as poll.
160  */
161  auto break_or_poll(curl_waitfd *extra_fds = nullptr, unsigned extra_nfds = 0U, int timeout = 0) noexcept ->
162  Ret_except<int, std::bad_alloc, libcurl_bug>;
163 
164  /**
165  * @pre perform_callback is set.
166  *
167  * @param arg will be passed to perform_callback
168  * @param perform_callback Must be callable with (Easy_ref_t, Easy_ref_t::perform_ret_t, Multi_t&, T)
169  * <br>If you want to destroy and free easy.curl_easy, you must first
170  * multi.remove_easy(easy_ref) it.
171  * <br>If easy_ref isn't removed from Multi, then the same transfer will happen again
172  * in the next call to Multi_t::perform.
173  *
174  * @return number of running handles
175  *
176  * perform() is called only if poll is used.
177  *
178  * After perform, perform_callback will be called for each completed
179  * easy.
180  *
181  * **YOU MUST CALL perform() after to start the transfer, then poll**
182  *
183  * Using libcurl version >= 7.10.3 can provide better error message
184  * if Easy_ref_t::ProtocolInternal_error is thrown.
185  */
186  template <class perform_callback_t, class T>
187  auto perform(perform_callback_t &&perform_callback, T &&arg) noexcept -> perform_ret_t
188  {
189  auto ret = perform_impl();
190  callback_on_finished_easy(std::forward<perform_callback_t>(perform_callback), std::forward<T>(arg));
191  return ret;
192  }
193 
194  /**
195  * @return should be 0
196  *
197  * Interface for using arbitary event-based interface - multi_socket interface
198  *
199  * Precondition for using this interface:
200  * - curl_t::has_multi_socket_support()
201  */
202  using socket_callback_t = int (*)(CURL *curl_easy,
203  curl_socket_t s,
204  /**
205  * Possible value for what:
206  * - CURL_POLL_IN,
207  * - CURL_POLL_OUT,
208  * - CURL_POLL_INOUT,
209  * - CURL_POLL_REMOVE
210  */
211  int what,
212  void *userp,
213  void *per_socketp);
214 
215  /**
216  * @param timeout_ms -1 means you should delete the timer.
217  * All other values are valid expire times in number of milliseconds.
218  * @return should be 0 on success,
219  * -1 on failure.
220  *
221  * Your callback function timer_callback should install a non-repeating timer with an interval of timeout_ms.
222  * <br>When time that timer fires, call multi_socket_action().
223  *
224  * The timer_callback will only be called when the timeout expire time is changed.
225  */
226  using timer_callback_t = int (*)(CURLM *multi, long timeout_ms, void *userp);
227 
228  /**
229  * @param socket_callback, timer_callback setting them to nullptr would
230  * disable multi_socket_action interface.
231  *
232  * You must call this function with non-NULL socket_callback and timer_callback
233  * before adding any easy handles.
234  */
235  void register_callback(socket_callback_t socket_callback, void *socket_data,
236  timer_callback_t timer_callback, void *timer_data) noexcept;
237 
238  /**
239  * @pre socketfd must be valid
240  * @return std::invalild_argument if socketfd is not valid.
241  *
242  * By default, per_sockptr == nullptr.
243  *
244  * You can call this function from socket_callback.
245  */
246  auto multi_assign(curl_socket_t socketfd, void *per_sockptr) noexcept ->
247  Ret_except<void, std::invalid_argument>;
248 
249  /**
250  * @pre enable_multi_socket_interface() is called,
251  * perform_callback, socket_callback, timer_callback is set.
252  *
253  * @param perform_callback Must be callable with (Easy_ref_t, Easy_ref_t::perform_ret_t, Multi_t&, T)
254  * <br>If you want to destroy and free easy.curl_easy, you must first
255  * multi.remove_easy(easy_ref) it.
256  * <br>If easy_ref isn't removed from Multi, then the same transfer will happen again
257  * in the next call to
258  * Multi_t::multi_socket_action(socketfd of this easy handler, events, ...).
259  *
260  * @param socketfd fd to be notified;
261  * <br>CURL_SOCKET_TIMEOUT on timeout or to initiate the whole process.
262  * @param ev_bitmask Or-ed with 0 or one of the value below:
263  * - CURL_CSELECT_IN,
264  * - CURL_CSELECT_OUT,
265  * - CURL_CSELECT_ERR,
266  *
267  * **YOU MUST CALL multi_socket_action(CURL_SOCKET_TIMEOUT, 0) to start the transfer,
268  * then call waitever poll interface you use**
269  *
270  * After multi_socket_action, perform_callback will be called for each completed
271  * easy.
272  *
273  * Using libcurl version >= 7.10.3 can provide better error message
274  * if Easy_ref_t::ProtocolInternal_error is thrown.
275  */
276  template <class perform_callback_t, class T>
277  auto multi_socket_action(curl_socket_t socketfd, int ev_bitmask,
278  perform_callback_t &&perform_callback, T &&arg) noexcept -> perform_ret_t
279  {
280  auto ret = multi_socket_action_impl(socketfd, ev_bitmask);
281  callback_on_finished_easy(std::forward<perform_callback_t>(perform_callback), std::forward<T>(arg));
282  return ret;
283  }
284 
285  /**
286  * @pre get_number_of_handles() == 0
287  */
288  ~Multi_t();
289 
290 protected:
291  auto check_perform(long code, int running_handles_tmp, const char *fname) noexcept ->
292  Ret_except<int, std::bad_alloc, Exception, Recursive_api_call_Exception, libcurl_bug>;
293 };
294 } /* namespace curl */
295 
296 #endif
curl::Multi_t::register_callback
void register_callback(socket_callback_t socket_callback, void *socket_data, timer_callback_t timer_callback, void *timer_data) noexcept
Definition: curl_multi.cc:138
curl::Exception
Definition: curl.hpp:16
curl::Multi_t::Multi_t
Multi_t(Multi_t &&) noexcept
Definition: curl_multi.cc:19
curl::Multi_t::poll
auto poll(curl_waitfd *extra_fds=nullptr, unsigned extra_nfds=0U, int timeout=0) noexcept -> Ret_except< int, std::bad_alloc, libcurl_bug >
Definition: curl_multi.cc:78
curl::Multi_t::operator=
Multi_t & operator=(Multi_t &&) noexcept
Definition: curl_multi.cc:26
curl::Multi_t::multi_assign
auto multi_assign(curl_socket_t socketfd, void *per_sockptr) noexcept -> Ret_except< void, std::invalid_argument >
Definition: curl_multi.cc:148
curl::Multi_t::multi_socket_action
auto multi_socket_action(curl_socket_t socketfd, int ev_bitmask, perform_callback_t &&perform_callback, T &&arg) noexcept -> perform_ret_t
Definition: curl_multi.hpp:277
curl::Easy_ref_t
Definition: curl_easy.hpp:53
curl::Multi_t::set_multiplexing
void set_multiplexing(long max_concurrent_stream) noexcept
Definition: curl_multi.cc:68
curl::Multi_t
Definition: curl_multi.hpp:29
curl::Multi_t::~Multi_t
~Multi_t()
Definition: curl_multi.cc:43
curl::Multi_t::perform
auto perform(perform_callback_t &&perform_callback, T &&arg) noexcept -> perform_ret_t
Definition: curl_multi.hpp:187
curl::Multi_t::Exception
Definition: curl_multi.hpp:35
curl::Multi_t::add_easy
bool add_easy(Easy_ref_t &easy) noexcept
Definition: curl_multi.cc:49
curl::Recursive_api_call_Exception
Definition: curl.hpp:40
curl::Multi_t::Multi_t
Multi_t(void *multi) noexcept
Definition: curl_multi.cc:15
curl::Multi_t::operator bool
operator bool() const noexcept
Definition: curl_multi.cc:38
curl::Multi_t::break_or_poll
auto break_or_poll(curl_waitfd *extra_fds=nullptr, unsigned extra_nfds=0U, int timeout=0) noexcept -> Ret_except< int, std::bad_alloc, libcurl_bug >
Definition: curl_multi.cc:93
curl::Multi_t::get_finished_easy
auto get_finished_easy() const noexcept -> std::pair< Easy_ref_t, Easy_ref_t::perform_ret_t >
Definition: curl_multi.cc:102
curl::Multi_t::Multi_t
Multi_t()=default
curl::Multi_t::remove_easy
void remove_easy(Easy_ref_t &easy) noexcept
Definition: curl_multi.cc:57
curl::libcurl_bug
Definition: curl.hpp:32