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
24 namespace coeurl {
25 struct Request;
26 struct SockInfo;
27 
28 //! Global information, common to all connections
29 struct Client {
30     //! construct a new client
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.
set_loggercoeurl::Client137     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.
set_verify_peercoeurl::Client139     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
does_verify_peercoeurl::Client142     bool does_verify_peer() { return this->verify_peer_; }
143 
144     //! Timeout connection after the specified amount of seconds, if the server
145     //! stops sending acks.
connection_timeoutcoeurl::Client146     void connection_timeout(long t) { connection_timeout_ = t; }
147 
148   private:
149     // Call this to run the event loop. Will block until the client is shutdown.
150     void run();
151 
152     void remove_request(Request *r);
153     void check_multi_info();
154 
155     static void event_cb(evutil_socket_t fd, short kind, void *userp);
156     static void timer_cb(evutil_socket_t fd, short kind, void *userp);
157     static void add_pending_requests_cb(evutil_socket_t, short, void *userp);
158     static void stop_ev_loop_cb(evutil_socket_t, short, void *userp);
159     static void cancel_requests_cb(evutil_socket_t, short, void *userp);
160 
161     static int multi_timer_cb(CURLM *multi, long timeout_ms, Client *g);
162 
163     static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp);
164     void addsock(curl_socket_t s, int action);
165     void setsock(SockInfo *f, curl_socket_t s, int act);
166     void remsock(SockInfo *f);
167 
168     static void mcode_or_die(const char *where, CURLMcode code);
169 
170     static std::shared_ptr<spdlog::logger> log;
171 
172     struct event_base *evbase{};
173     struct event timer_event {};
174     struct event add_request_timer {};
175     struct event stop_event {};
176     struct event cancel_requests_timer {};
177     CURLM *multi{};
178     int still_running = 0;
179     std::atomic<bool> stopped{false};
180     bool verify_peer_ = true;
181 
182     long connection_timeout_ = 0;
183 
184     std::mutex pending_requests_mutex;
185     std::vector<std::shared_ptr<Request>> pending_requests;
186     std::mutex running_requests_mutex;
187     std::vector<std::shared_ptr<Request>> running_requests;
188     std::mutex stopped_mutex;
189 
190     std::thread bg_thread;
191 
192     friend Request;
193 };
194 } // namespace coeurl
195