1#pragma once
2
3/// @file
4/// @brief Include for the @ref coeurl::Client
5
6#include <atomic>
7#include <functional>
8#include <map>
9#include <memory>
10#include <mutex>
11#include <string>
12#include <thread>
13#include <vector>
14
15#include <curl/curl.h>
16#include <event2/event.h>
17#include <event2/event_struct.h>
18#include <spdlog/logger.h>
19
20#include "headers.hpp"
21
22/// @namespace coeurl
23/// @brief Namespace for all public @ref coeurl classes
24namespace coeurl {
25struct Request;
26struct SockInfo;
27
28//! Global information, common to all connections
29struct Client {
30 //! construct a new client. For http/3 support, set the alt_svc_cache_path as well.
31 Client();
32 /// @brief cleans up a client
33 /// Implicitly closes the connection and blocks until all of them exited.
34 ~Client();
35
36 //! Uncopyable
37 Client(Client const &) = delete;
38 //! Uncopyable
39 void operator=(Client const &) = delete;
40 //! Unmoveable
41 Client(Client &&) = delete;
42 //! Unmoveable
43 void operator=(Client &&) = delete;
44
45 //! Submit a manually created request
46 void submit_request(std::shared_ptr<Request> conn);
47 //! Stop all currently running requests
48 void shutdown();
49 //! Stop the event loop. If you force close it, all pending requests are
50 //! cancelled.
51 void close(bool force = false);
52
53 /// @brief Make a simple GET request.
54 /// For more complicated requests, create it manually and call submit_request.
55 /// @param url The url to request.
56 /// @param callback The callback, which will be called after the request is
57 /// completed. The request will be passed as a parameter, which you can use
58 /// to access the response.
59 /// @param headers Headers to use for this request. Defaults to none.
60 /// @param max_redirects How many redirects to follow. Defaults to none.
61 void get(std::string url, std::function<void(const Request &)> callback, const Headers &headers = {},
62 long max_redirects = 0);
63
64 /// @brief Make a simple DELETE request.
65 /// @param url The url to request.
66 /// @param callback The callback, which will be called after the request is
67 /// completed. The request will be passed as a parameter, which you can use
68 /// to access the response.
69 /// @param headers Headers to use for this request. Defaults to none.
70 /// @param max_redirects How many redirects to follow. Defaults to none.
71 void delete_(std::string url, std::function<void(const Request &)> callback, const Headers &headers = {},
72 long max_redirects = 0);
73
74 /// @brief Make a simple DELETE request with a body.
75 /// @param url The url to request.
76 /// @param request_body The body to use with this request.
77 /// @param mimetype The mimetype of the @a request_body.
78 /// @param callback The callback, which will be called after the request is
79 /// completed. The request will be passed as a parameter, which you can use
80 /// to access the response.
81 /// @param headers Headers to use for this request. Defaults to none.
82 /// @param max_redirects How many redirects to follow. Defaults to none.
83 void delete_(std::string url, std::string request_body, std::string mimetype,
84 std::function<void(const Request &)> callback, const Headers &headers = {}, long max_redirects = 0);
85
86 /// @brief Make a simple HEAD request.
87 /// For more complicated requests, create it manually and call submit_request.
88 /// @param url The url to request.
89 /// @param callback The callback, which will be called after the request is
90 /// completed. The request will be passed as a parameter, which you can use
91 /// to access the response.
92 /// @param headers Headers to use for this request. Defaults to none.
93 /// @param max_redirects How many redirects to follow. Defaults to none.
94 void head(std::string url, std::function<void(const Request &)> callback, const Headers &headers = {},
95 long max_redirects = 0);
96
97 /// @brief Make a simple OPTIONS request.
98 /// For more complicated requests, create it manually and call submit_request.
99 /// @param url The url to request.
100 /// @param callback The callback, which will be called after the request is
101 /// completed. The request will be passed as a parameter, which you can use
102 /// to access the response.
103 /// @param headers Headers to use for this request. Defaults to none.
104 /// @param max_redirects How many redirects to follow. Defaults to none.
105 void options(std::string url, std::function<void(const Request &)> callback, const Headers &headers = {},
106 long max_redirects = 0);
107
108 /// @brief Make a simple PUT request with a body.
109 /// @param url The url to request.
110 /// @param request_body The body to use with this request.
111 /// @param mimetype The mimetype of the @a request_body.
112 /// @param callback The callback, which will be called after the request is
113 /// completed. The request will be passed as a parameter, which you can use
114 /// to access the response.
115 /// @param headers Headers to use for this request. Defaults to none.
116 /// @param max_redirects How many redirects to follow. Defaults to none.
117 void put(std::string url, std::string request_body, std::string mimetype,
118 std::function<void(const Request &)> callback, const Headers &headers = {}, long max_redirects = 0);
119
120 /// @brief Make a simple POST request with a body.
121 /// @param url The url to request.
122 /// @param request_body The body to use with this request.
123 /// @param mimetype The mimetype of the @a request_body.
124 /// @param callback The callback, which will be called after the request is
125 /// completed. The request will be passed as a parameter, which you can use
126 /// to access the response.
127 /// @param headers Headers to use for this request. Defaults to none.
128 /// @param max_redirects How many redirects to follow. Defaults to none.
129 void post(std::string url, std::string request_body, std::string mimetype,
130 std::function<void(const Request &)> callback, const Headers &headers = {}, long max_redirects = 0);
131
132 /// @brief Set a global @a logger.
133 /// @param logger The spdlog logger to use for logging.
134 ///
135 /// Set the logger while no requests are in flight, for example before
136 /// starting the first request.
137 static void set_logger(std::shared_ptr<spdlog::logger> logger) { log = std::move(logger); }
138 //! Set whether to \a verify the https certificates or not.
139 void set_verify_peer(bool verify) { this->verify_peer_ = verify; }
140 /// @brief Query whether certificate verification is enabled or not
141 /// @sa set_verify_peer(bool)
142 bool does_verify_peer() const { return this->verify_peer_; }
143
144 //! Set whether or not to log curl requests verbosely.
145 /// @sa verbose() const
146 /// @sa https://curl.se/libcurl/c/CURLOPT_VERBOSE.html
147 void verbose(bool verbose) { this->verbose_logging_ = verbose; }
148 /// @brief Query whether verbose logging is enabled or not
149 /// @sa verbose(bool)
150 bool verbose() const { return this->verbose_logging_; }
151
152 //! Timeout connection after the specified amount of seconds, if the server
153 //! stops sending acks.
154 void connection_timeout(long t) { connection_timeout_ = t; }
155
156 //! Maximum connections to open in parallel for this client. Set to 0 to disable the limit. Default is 64.
157 void maximum_total_connections(long count);
158 //! Maximum connections to open in parallel for each specific host for this client. Set to 0 to disable the limit.
159 //! Default is 8.
160 void maximum_connections_per_host(long count);
161
162 //! Cache alternative service entries. This allows automatically switching to http/3.
163 //! Usually you provide a path in `~/.cache` or similar.
164 void alt_svc_cache_path(const std::string &path) { alt_svc_cache_path_ = path; }
165 //! Where alternate svc entries are cached. Usually in `~/.cache`.
166 const std::string &alt_svc_cache_path() const { return alt_svc_cache_path_; }
167
168 private:
169 // Call this to run the event loop. Will block until the client is shutdown.
170 void run();
171
172 void remove_request(Request *r);
173 void check_multi_info();
174
175 static void event_cb(evutil_socket_t fd, short kind, void *userp);
176 static void timer_cb(evutil_socket_t fd, short kind, void *userp);
177 static void add_pending_requests_cb(evutil_socket_t, short, void *userp);
178 static void stop_ev_loop_cb(evutil_socket_t, short, void *userp);
179 static void cancel_requests_cb(evutil_socket_t, short, void *userp);
180
181 static int multi_timer_cb(CURLM *multi, long timeout_ms, Client *g);
182
183 static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp);
184 void addsock(curl_socket_t s, int action);
185 void setsock(SockInfo *f, curl_socket_t s, int act);
186 void remsock(SockInfo *f);
187
188 static void mcode_or_die(const char *where, CURLMcode code);
189
190 static std::shared_ptr<spdlog::logger> log;
191
192 struct event_base *evbase{};
193 struct event timer_event {};
194 struct event add_request_timer {};
195 struct event stop_event {};
196 struct event cancel_requests_timer {};
197 CURLM *multi{};
198 int still_running = 0;
199 std::atomic<bool> stopped{false};
200 std::atomic<bool> prevent_new_requests{false};
201 bool verify_peer_ = true;
202 bool verbose_logging_ = false;
203
204 long connection_timeout_ = 0;
205
206 std::mutex pending_requests_mutex;
207 std::vector<std::shared_ptr<Request>> pending_requests;
208 std::mutex running_requests_mutex;
209 std::vector<std::shared_ptr<Request>> running_requests;
210 std::mutex stopped_mutex;
211
212 std::thread bg_thread;
213
214 std::string alt_svc_cache_path_;
215
216 friend Request;
217};
218} // namespace coeurl
219