/
HttpClient.h
395 lines (357 loc) · 12.9 KB
/
HttpClient.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
/**
*
* @file HttpClient.h
*
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by the MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/exports.h>
#include <drogon/HttpTypes.h>
#include <drogon/drogon_callbacks.h>
#include <drogon/HttpResponse.h>
#include <drogon/HttpRequest.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoop.h>
#include <functional>
#include <memory>
#include <future>
#include "drogon/HttpBinder.h"
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#endif
namespace drogon
{
class HttpClient;
using HttpClientPtr = std::shared_ptr<HttpClient>;
#ifdef __cpp_impl_coroutine
namespace internal
{
struct HttpRespAwaiter : public CallbackAwaiter<HttpResponsePtr>
{
HttpRespAwaiter(HttpClient *client, HttpRequestPtr req, double timeout)
: client_(client), req_(std::move(req)), timeout_(timeout)
{
}
void await_suspend(std::coroutine_handle<> handle);
private:
HttpClient *client_;
HttpRequestPtr req_;
double timeout_;
};
} // namespace internal
#endif
/// Asynchronous http client
/**
* HttpClient implementation object uses the HttpAppFramework's event loop by
* default, so you should call app().run() to make the client work.
* Each HttpClient object establishes a persistent connection with the server.
* If the connection is broken, the client attempts to reconnect
* when calling the sendRequest method.
*
* Using the static method newHttpClient(...) to get shared_ptr of the object
* implementing the class, the shared_ptr is retained in the framework until all
* response callbacks are invoked without fear of accidental deconstruction.
*
*/
class DROGON_EXPORT HttpClient : public trantor::NonCopyable
{
public:
/**
* @brief Send a request asynchronously to the server
*
* @param req The request sent to the server.
* @param callback The callback is called when the response is received from
* the server.
* @param timeout In seconds. If the response is not received within the
* timeout, the callback is called with `ReqResult::Timeout` and an empty
* response. The zero value by default disables the timeout.
*
* @note
* The request object is altered(some headers are added to it) before it is
* sent, so calling this method with a same request object in different
* thread is dangerous.
* Please be careful when using timeout on an non-idempotent request.
*/
virtual void sendRequest(const HttpRequestPtr &req,
const HttpReqCallback &callback,
double timeout = 0) = 0;
/**
* @brief Send a request asynchronously to the server
*
* @param req The request sent to the server.
* @param callback The callback is called when the response is received from
* the server.
* @param timeout In seconds. If the response is not received within
* the timeout, the callback is called with `ReqResult::Timeout` and an
* empty response. The zero value by default disables the timeout.
*
* @note
* The request object is altered(some headers are added to it) before it is
* sent, so calling this method with a same request object in different
* thread is dangerous.
* Please be careful when using timeout on an non-idempotent request.
*/
virtual void sendRequest(const HttpRequestPtr &req,
HttpReqCallback &&callback,
double timeout = 0) = 0;
/**
* @brief Send a request synchronously to the server and return the
* response.
*
* @param req
* @param timeout In seconds. If the response is not received within the
* timeout, the `ReqResult::Timeout` and an empty response is returned. The
* zero value by default disables the timeout.
*
* @return std::pair<ReqResult, HttpResponsePtr>
* @note Never call this function in the event loop thread of the
* client (partially in the callback function of the asynchronous
* sendRequest method), otherwise the thread will be blocked forever.
* Please be careful when using timeout on an non-idempotent request.
*/
std::pair<ReqResult, HttpResponsePtr> sendRequest(const HttpRequestPtr &req,
double timeout = 0)
{
assert(!getLoop()->isInLoopThread() &&
"Deadlock detected! Calling a sync API from the same loop as "
"the HTTP client processes on will deadlock the event loop");
std::promise<std::pair<ReqResult, HttpResponsePtr>> prom;
auto f = prom.get_future();
sendRequest(
req,
[&prom](ReqResult r, const HttpResponsePtr &resp) {
prom.set_value({r, resp});
},
timeout);
return f.get();
}
#ifdef __cpp_impl_coroutine
/**
* @brief Send a request via coroutines to the server and return an
* awaiter what could be `co_await`-ed to retrieve the response
* (HttpResponsePtr)
*
* @param req
* @param timeout In seconds. If the response is not received within the
* timeout, A `drogon::HttpException` with `ReqResult::Timeout` is thrown.
* The zero value by default disables the timeout.
*
* @return internal::HttpRespAwaiter. Await on it to get the response
*/
internal::HttpRespAwaiter sendRequestCoro(HttpRequestPtr req,
double timeout = 0)
{
return internal::HttpRespAwaiter(this, std::move(req), timeout);
}
#endif
/// Set socket options(before connecting)
/**
* @brief Set the callback which is called before connecting to the
* server. The callback is used to set socket options on the socket fd.
*
* @code
auto client = HttpClient::newHttpClient("http://www.baidu.com");
client->setSockOptCallback([](int fd) {});
auto req = HttpRequest::newHttpRequest();
client->sendRequest(req, [](ReqResult result, const HttpResponsePtr&
response) {});
@endcode
*/
virtual void setSockOptCallback(std::function<void(int)> cb) = 0;
/// Set the pipelining depth, which is the number of requests that are not
/// responding.
/**
* If this method is not called, the default depth value is 0 which means
* the pipelining is disabled. For details about pipelining, see
* rfc2616-8.1.2.2
*/
virtual void setPipeliningDepth(size_t depth) = 0;
/// Enable cookies for the client
/**
* @param flag if the parameter is true, all requests sent by the client
* carry the cookies set by the server side. Cookies are disabled by
* default.
*/
virtual void enableCookies(bool flag = true) = 0;
/// Add a cookie to the client
/**
* @note
* These methods are independent of the enableCookies() method. Whether the
* enableCookies() is called with true or false, the cookies added by these
* methods will be sent to the server.
*/
virtual void addCookie(const std::string &key,
const std::string &value) = 0;
/// Add a cookie to the client
/**
* @note
* These methods are independent of the enableCookies() method. Whether the
* enableCookies() is called with true or false, the cookies added by these
* methods will be sent to the server.
*/
virtual void addCookie(const Cookie &cookie) = 0;
/**
* @brief Set the user_agent header, the default value is 'DrogonClient' if
* this method is not used.
*
* @param userAgent The user_agent value, if it is empty, the user_agent
* header is not sent to the server.
*/
virtual void setUserAgent(const std::string &userAgent) = 0;
/**
* @brief Create a new HTTP client which use ip and port to connect to
* server
*
* @param ip The ip address of the HTTP server
* @param port The port of the HTTP server
* @param useSSL if the parameter is set to true, the client connects to the
* server using HTTPS.
* @param loop If the loop parameter is set to nullptr, the client uses the
* HttpAppFramework's event loop, otherwise it runs in the loop identified
* by the parameter.
* @param useOldTLS If the parameter is set to true, the TLS1.0/1.1 are
* enabled for HTTPS.
* @param validateCert If the parameter is set to true, the client validates
* the server certificate when SSL handshaking.
* @return HttpClientPtr The smart pointer to the new client object.
* @note: The ip parameter support for both ipv4 and ipv6 address
*/
static HttpClientPtr newHttpClient(const std::string &ip,
uint16_t port,
bool useSSL = false,
trantor::EventLoop *loop = nullptr,
bool useOldTLS = false,
bool validateCert = true);
/// Get the event loop of the client;
virtual trantor::EventLoop *getLoop() = 0;
/// Get the number of bytes sent or received
virtual size_t bytesSent() const = 0;
virtual size_t bytesReceived() const = 0;
virtual std::string host() const = 0;
std::string getHost() const
{
return host();
}
virtual uint16_t port() const = 0;
uint16_t getPort() const
{
return port();
}
virtual bool secure() const = 0;
bool onDefaultPort() const
{
if (secure())
return port() == 443;
return port() == 80;
}
/**
* @brief Set the client certificate used by the HTTP connection
*
* @param cert Path to the certificate
* @param key Path to the certificate's private key
* @note this method has no effect if the HTTP client is communicating via
* unencrypted HTTP
*/
virtual void setCertPath(const std::string &cert,
const std::string &key) = 0;
/**
* @brief Supplies command style options for `SSL_CONF_cmd`
*
* @param sslConfCmds options for SSL_CONF_cmd
* @note this method has no effect if the HTTP client is communicating via
* unencrypted HTTP
* @code
addSSLConfigs({{"-dhparam", "/path/to/dhparam"}, {"-strict", ""}});
* @endcode
*/
virtual void addSSLConfigs(
const std::vector<std::pair<std::string, std::string>>
&sslConfCmds) = 0;
/// Create a Http client using the hostString to connect to server
/**
*
* @param hostString this parameter must be prefixed by 'http://' or
* 'https://'.
*
* Examples for hostString:
* @code
https://www.baidu.com
http://www.baidu.com
https://127.0.0.1:8080/
http://127.0.0.1
http://[::1]:8080/ //IPv6 address must be enclosed in [], rfc2732
@endcode
*
* @param loop If the loop parameter is set to nullptr, the client uses the
* HttpAppFramework's event loop, otherwise it runs in the loop identified
* by the parameter.
*
* @param useOldTLS If the parameter is set to true, the TLS1.0/1.1 are
* enabled for HTTPS.
* @note
*
* @param validateCert If the parameter is set to true, the client validates
* the server certificate when SSL handshaking.
*
* @note Don't add path and parameters in hostString, the request path and
* parameters should be set in HttpRequestPtr when calling the sendRequest()
* method.
*
*/
static HttpClientPtr newHttpClient(const std::string &hostString,
trantor::EventLoop *loop = nullptr,
bool useOldTLS = false,
bool validateCert = true);
virtual ~HttpClient()
{
}
protected:
HttpClient() = default;
};
#ifdef __cpp_impl_coroutine
class HttpException : public std::exception
{
public:
HttpException() = delete;
explicit HttpException(ReqResult res)
: resultCode_(res), message_(to_string_view(res))
{
}
const char *what() const noexcept override
{
return message_.data();
}
ReqResult code() const
{
return resultCode_;
}
private:
ReqResult resultCode_;
std::string_view message_;
};
inline void internal::HttpRespAwaiter::await_suspend(
std::coroutine_handle<> handle)
{
assert(client_ != nullptr);
assert(req_ != nullptr);
client_->sendRequest(
req_,
[handle, this](ReqResult result, const HttpResponsePtr &resp) {
if (result == ReqResult::Ok)
setValue(resp);
else
setException(std::make_exception_ptr(HttpException(result)));
handle.resume();
},
timeout_);
}
#endif
} // namespace drogon