rippled
Loading...
Searching...
No Matches
libxrpl/net/HTTPClient.cpp
1#include <xrpl/basics/Log.h>
2#include <xrpl/beast/core/LexicalCast.h>
3#include <xrpl/net/AutoSocket.h>
4#include <xrpl/net/HTTPClient.h>
5#include <xrpl/net/HTTPClientSSLContext.h>
6
7#include <boost/asio.hpp>
8#include <boost/asio/ip/resolver_query_base.hpp>
9#include <boost/asio/ip/tcp.hpp>
10#include <boost/asio/ssl.hpp>
11#include <boost/regex.hpp>
12
13#include <optional>
14
15namespace ripple {
16
18
19void
21 std::string const& sslVerifyDir,
22 std::string const& sslVerifyFile,
23 bool sslVerify,
25{
26 httpClientSSLContext.emplace(sslVerifyDir, sslVerifyFile, sslVerify, j);
27}
28
29//------------------------------------------------------------------------------
30//
31// Fetch a web page via http or https.
32//
33//------------------------------------------------------------------------------
34
35class HTTPClientImp : public std::enable_shared_from_this<HTTPClientImp>,
36 public HTTPClient
37{
38public:
40 boost::asio::io_context& io_context,
41 unsigned short const port,
42 std::size_t maxResponseSize,
44 : mSocket(io_context, httpClientSSLContext->context())
45 , mResolver(io_context)
47 , mPort(port)
48 , maxResponseSize_(maxResponseSize)
49 , mDeadline(io_context)
50 , j_(j)
51 {
52 }
53
54 //--------------------------------------------------------------------------
55
56 void
58 std::string const& strPath,
59 boost::asio::streambuf& sb,
60 std::string const& strHost)
61 {
62 std::ostream osRequest(&sb);
63
64 osRequest << "GET " << strPath
65 << " HTTP/1.0\r\n"
66 "Host: "
67 << strHost
68 << "\r\n"
69 "Accept: */*\r\n" // YYY Do we need this line?
70 "Connection: close\r\n\r\n";
71 }
72
73 //--------------------------------------------------------------------------
74
75 void
77 bool bSSL,
80 void(boost::asio::streambuf& sb, std::string const& strHost)> build,
82 std::function<bool(
83 boost::system::error_code const& ecResult,
84 int iStatus,
85 std::string const& strData)> complete)
86 {
87 mSSL = bSSL;
88 mDeqSites = deqSites;
89 mBuild = build;
90 mComplete = complete;
91 mTimeout = timeout;
92
93 httpsNext();
94 }
95
96 //--------------------------------------------------------------------------
97
98 void
99 get(bool bSSL,
101 std::string const& strPath,
102 std::chrono::seconds timeout,
103 std::function<bool(
104 boost::system::error_code const& ecResult,
105 int iStatus,
106 std::string const& strData)> complete)
107 {
108 mComplete = complete;
109 mTimeout = timeout;
110
111 request(
112 bSSL,
113 deqSites,
114 std::bind(
117 strPath,
118 std::placeholders::_1,
119 std::placeholders::_2),
120 timeout,
121 complete);
122 }
123
124 //--------------------------------------------------------------------------
125
126 void
128 {
129 JLOG(j_.trace()) << "Fetch: " << mDeqSites[0];
130
131 auto query = std::make_shared<Query>(
132 mDeqSites[0],
134 boost::asio::ip::resolver_query_base::numeric_service);
135 mQuery = query;
136
137 try
138 {
139 mDeadline.expires_after(mTimeout);
140 }
141 catch (boost::system::system_error const& e)
142 {
143 mShutdown = e.code();
144
145 JLOG(j_.trace()) << "expires_after: " << mShutdown.message();
146 mDeadline.async_wait(std::bind(
149 std::placeholders::_1));
150 }
151
152 if (!mShutdown)
153 {
154 JLOG(j_.trace()) << "Resolving: " << mDeqSites[0];
155
156 mResolver.async_resolve(
157 mQuery->host,
158 mQuery->port,
159 mQuery->flags,
160 std::bind(
163 std::placeholders::_1,
164 std::placeholders::_2));
165 }
166
167 if (mShutdown)
169 }
170
171 void
172 handleDeadline(boost::system::error_code const& ecResult)
173 {
174 if (ecResult == boost::asio::error::operation_aborted)
175 {
176 // Timer canceled because deadline no longer needed.
177 JLOG(j_.trace()) << "Deadline cancelled.";
178
179 // Aborter is done.
180 }
181 else if (ecResult)
182 {
183 JLOG(j_.trace()) << "Deadline error: " << mDeqSites[0] << ": "
184 << ecResult.message();
185
186 // Can't do anything sound.
187 abort();
188 }
189 else
190 {
191 JLOG(j_.trace()) << "Deadline arrived.";
192
193 // Mark us as shutting down.
194 // XXX Use our own error code.
195 mShutdown = boost::system::error_code{
196 boost::system::errc::bad_address,
197 boost::system::system_category()};
198
199 // Cancel any resolving.
200 mResolver.cancel();
201
202 // Stop the transaction.
206 std::placeholders::_1));
207 }
208 }
209
210 void
211 handleShutdown(boost::system::error_code const& ecResult)
212 {
213 if (ecResult)
214 {
215 JLOG(j_.trace()) << "Shutdown error: " << mDeqSites[0] << ": "
216 << ecResult.message();
217 }
218 }
219
220 void
222 boost::system::error_code const& ecResult,
223 boost::asio::ip::tcp::resolver::results_type result)
224 {
225 if (!mShutdown)
226 {
227 mShutdown = ecResult ? ecResult
228 : httpClientSSLContext->preConnectVerify(
230 }
231
232 if (mShutdown)
233 {
234 JLOG(j_.trace()) << "Resolve error: " << mDeqSites[0] << ": "
235 << mShutdown.message();
236
238 }
239 else
240 {
241 JLOG(j_.trace()) << "Resolve complete.";
242
243 boost::asio::async_connect(
245 result,
246 std::bind(
249 std::placeholders::_1));
250 }
251 }
252
253 void
254 handleConnect(boost::system::error_code const& ecResult)
255 {
256 if (!mShutdown)
257 mShutdown = ecResult;
258
259 if (mShutdown)
260 {
261 JLOG(j_.trace()) << "Connect error: " << mShutdown.message();
262 }
263
264 if (!mShutdown)
265 {
266 JLOG(j_.trace()) << "Connected.";
267
268 mShutdown = httpClientSSLContext->postConnectVerify(
270
271 if (mShutdown)
272 {
273 JLOG(j_.trace()) << "postConnectVerify: " << mDeqSites[0]
274 << ": " << mShutdown.message();
275 }
276 }
277
278 if (mShutdown)
279 {
281 }
282 else if (mSSL)
283 {
285 AutoSocket::ssl_socket::client,
286 std::bind(
289 std::placeholders::_1));
290 }
291 else
292 {
293 handleRequest(ecResult);
294 }
295 }
296
297 void
298 handleRequest(boost::system::error_code const& ecResult)
299 {
300 if (!mShutdown)
301 mShutdown = ecResult;
302
303 if (mShutdown)
304 {
305 JLOG(j_.trace()) << "Handshake error:" << mShutdown.message();
306
308 }
309 else
310 {
311 JLOG(j_.trace()) << "Session started.";
312
314
316 mRequest,
317 std::bind(
320 std::placeholders::_1,
321 std::placeholders::_2));
322 }
323 }
324
325 void
327 boost::system::error_code const& ecResult,
328 std::size_t bytes_transferred)
329 {
330 if (!mShutdown)
331 mShutdown = ecResult;
332
333 if (mShutdown)
334 {
335 JLOG(j_.trace()) << "Write error: " << mShutdown.message();
336
338 }
339 else
340 {
341 JLOG(j_.trace()) << "Wrote.";
342
344 mHeader,
345 "\r\n\r\n",
346 std::bind(
349 std::placeholders::_1,
350 std::placeholders::_2));
351 }
352 }
353
354 void
356 boost::system::error_code const& ecResult,
357 std::size_t bytes_transferred)
358 {
359 std::string strHeader{
362 JLOG(j_.trace()) << "Header: \"" << strHeader << "\"";
363
364 static boost::regex reStatus{
365 "\\`HTTP/1\\S+ (\\d{3}) .*\\'"}; // HTTP/1.1 200 OK
366 static boost::regex reSize{
367 "\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'", boost::regex::icase};
368 static boost::regex reBody{"\\`.*\\r\\n\\r\\n(.*)\\'"};
369
370 boost::smatch smMatch;
371 // Match status code.
372 if (!boost::regex_match(strHeader, smMatch, reStatus))
373 {
374 // XXX Use our own error code.
375 JLOG(j_.trace()) << "No status code";
376 invokeComplete(boost::system::error_code{
377 boost::system::errc::bad_address,
378 boost::system::system_category()});
379 return;
380 }
381
382 mStatus = beast::lexicalCastThrow<int>(std::string(smMatch[1]));
383
384 if (boost::regex_match(strHeader, smMatch, reBody)) // we got some body
385 mBody = smMatch[1];
386
387 std::size_t const responseSize = [&] {
388 if (boost::regex_match(strHeader, smMatch, reSize))
389 return beast::lexicalCast<std::size_t>(
390 std::string(smMatch[1]), maxResponseSize_);
391 return maxResponseSize_;
392 }();
393
394 if (responseSize > maxResponseSize_)
395 {
396 JLOG(j_.trace()) << "Response field too large";
397 invokeComplete(boost::system::error_code{
398 boost::system::errc::value_too_large,
399 boost::system::system_category()});
400 return;
401 }
402
403 if (responseSize == 0)
404 {
405 // no body wanted or available
406 invokeComplete(ecResult, mStatus);
407 }
408 else if (mBody.size() >= responseSize)
409 {
410 // we got the whole thing
411 invokeComplete(ecResult, mStatus, mBody);
412 }
413 else
414 {
416 mResponse.prepare(responseSize - mBody.size()),
417 boost::asio::transfer_all(),
418 std::bind(
421 std::placeholders::_1,
422 std::placeholders::_2));
423 }
424 }
425
426 void
428 boost::system::error_code const& ecResult,
429 std::size_t bytes_transferred)
430 {
431 if (!mShutdown)
432 mShutdown = ecResult;
433
434 if (mShutdown && mShutdown != boost::asio::error::eof)
435 {
436 JLOG(j_.trace()) << "Read error: " << mShutdown.message();
437
439 }
440 else
441 {
442 if (mShutdown)
443 {
444 JLOG(j_.trace()) << "Complete.";
445 }
446 else
447 {
448 mResponse.commit(bytes_transferred);
449 std::string strBody{
452 invokeComplete(ecResult, mStatus, mBody + strBody);
453 }
454 }
455 }
456
457 // Call cancel the deadline timer and invoke the completion routine.
458 void
460 boost::system::error_code const& ecResult,
461 int iStatus = 0,
462 std::string const& strData = "")
463 {
464 boost::system::error_code ecCancel;
465 try
466 {
467 mDeadline.cancel();
468 }
469 catch (boost::system::system_error const& e)
470 {
471 JLOG(j_.trace())
472 << "invokeComplete: Deadline cancel error: " << e.what();
473 ecCancel = e.code();
474 }
475
476 JLOG(j_.debug()) << "invokeComplete: Deadline popping: "
477 << mDeqSites.size();
478
479 if (!mDeqSites.empty())
480 {
482 }
483
484 bool bAgain = true;
485
486 if (mDeqSites.empty() || !ecResult)
487 {
488 // ecResult: !0 = had an error, last entry
489 // iStatus: result, if no error
490 // strData: data, if no error
491 bAgain = mComplete &&
492 mComplete(ecResult ? ecResult : ecCancel, iStatus, strData);
493 }
494
495 if (!mDeqSites.empty() && bAgain)
496 {
497 httpsNext();
498 }
499 }
500
501private:
503
504 bool mSSL;
506 boost::asio::ip::tcp::resolver mResolver;
507
508 struct Query
509 {
512 boost::asio::ip::resolver_query_base::flags flags;
513 };
515
516 boost::asio::streambuf mRequest;
517 boost::asio::streambuf mHeader;
518 boost::asio::streambuf mResponse;
520 unsigned short const mPort;
523 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)>
525 std::function<bool(
526 boost::system::error_code const& ecResult,
527 int iStatus,
528 std::string const& strData)>
530
531 boost::asio::basic_waitable_timer<std::chrono::steady_clock> mDeadline;
532
533 // If not success, we are shutting down.
534 boost::system::error_code mShutdown;
535
539};
540
541//------------------------------------------------------------------------------
542
543void
545 bool bSSL,
546 boost::asio::io_context& io_context,
548 unsigned short const port,
549 std::string const& strPath,
550 std::size_t responseMax,
551 std::chrono::seconds timeout,
552 std::function<bool(
553 boost::system::error_code const& ecResult,
554 int iStatus,
555 std::string const& strData)> complete,
557{
558 auto client =
559 std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
560 client->get(bSSL, deqSites, strPath, timeout, complete);
561}
562
563void
565 bool bSSL,
566 boost::asio::io_context& io_context,
567 std::string strSite,
568 unsigned short const port,
569 std::string const& strPath,
570 std::size_t responseMax,
571 std::chrono::seconds timeout,
572 std::function<bool(
573 boost::system::error_code const& ecResult,
574 int iStatus,
575 std::string const& strData)> complete,
577{
578 std::deque<std::string> deqSites(1, strSite);
579
580 auto client =
581 std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
582 client->get(bSSL, deqSites, strPath, timeout, complete);
583}
584
585void
587 bool bSSL,
588 boost::asio::io_context& io_context,
589 std::string strSite,
590 unsigned short const port,
591 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)>
592 setRequest,
593 std::size_t responseMax,
594 std::chrono::seconds timeout,
595 std::function<bool(
596 boost::system::error_code const& ecResult,
597 int iStatus,
598 std::string const& strData)> complete,
600{
601 std::deque<std::string> deqSites(1, strSite);
602
603 auto client =
604 std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
605 client->request(bSSL, deqSites, setRequest, timeout, complete);
606}
607
608} // namespace ripple
T bind(T... args)
void async_handshake(handshake_type type, callback cbFunc)
Definition AutoSocket.h:96
void async_write(Buf const &buffers, Handler handler)
Definition AutoSocket.h:202
lowest_layer_type & lowest_layer()
Definition AutoSocket.h:76
void async_read_until(Seq const &buffers, Condition condition, Handler handler)
Definition AutoSocket.h:162
void async_read(Buf const &buffers, Condition cond, Handler handler)
Definition AutoSocket.h:224
ssl_socket & SSLSocket()
Definition AutoSocket.h:53
void async_shutdown(ShutdownHandler handler)
Definition AutoSocket.h:129
A generic endpoint for log messages.
Definition Journal.h:41
Stream debug() const
Definition Journal.h:309
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
void handleWrite(boost::system::error_code const &ecResult, std::size_t bytes_transferred)
void makeGet(std::string const &strPath, boost::asio::streambuf &sb, std::string const &strHost)
std::shared_ptr< Query > mQuery
std::function< void(boost::asio::streambuf &sb, std::string const &strHost)> mBuild
void handleShutdown(boost::system::error_code const &ecResult)
void invokeComplete(boost::system::error_code const &ecResult, int iStatus=0, std::string const &strData="")
void get(bool bSSL, std::deque< std::string > deqSites, std::string const &strPath, std::chrono::seconds timeout, std::function< bool(boost::system::error_code const &ecResult, int iStatus, std::string const &strData)> complete)
boost::asio::streambuf mHeader
boost::asio::ip::tcp::resolver mResolver
boost::asio::streambuf mRequest
void handleHeader(boost::system::error_code const &ecResult, std::size_t bytes_transferred)
void handleConnect(boost::system::error_code const &ecResult)
std::function< bool(boost::system::error_code const &ecResult, int iStatus, std::string const &strData)> mComplete
void handleRequest(boost::system::error_code const &ecResult)
void handleResolve(boost::system::error_code const &ecResult, boost::asio::ip::tcp::resolver::results_type result)
void request(bool bSSL, std::deque< std::string > deqSites, std::function< void(boost::asio::streambuf &sb, std::string const &strHost)> build, std::chrono::seconds timeout, std::function< bool(boost::system::error_code const &ecResult, int iStatus, std::string const &strData)> complete)
boost::asio::basic_waitable_timer< std::chrono::steady_clock > mDeadline
std::deque< std::string > mDeqSites
void handleData(boost::system::error_code const &ecResult, std::size_t bytes_transferred)
void handleDeadline(boost::system::error_code const &ecResult)
boost::asio::streambuf mResponse
boost::system::error_code mShutdown
HTTPClientImp(boost::asio::io_context &io_context, unsigned short const port, std::size_t maxResponseSize, beast::Journal &j)
Provides an asynchronous HTTP client implementation with optional SSL.
Definition HTTPClient.h:20
static void request(bool bSSL, boost::asio::io_context &io_context, std::string strSite, unsigned short const port, std::function< void(boost::asio::streambuf &sb, std::string const &strHost)> build, std::size_t responseMax, std::chrono::seconds timeout, std::function< bool(boost::system::error_code const &ecResult, int iStatus, std::string const &strData)> complete, beast::Journal &j)
static void initializeSSLContext(std::string const &sslVerifyDir, std::string const &sslVerifyFile, bool sslVerify, beast::Journal j)
static void get(bool bSSL, boost::asio::io_context &io_context, std::deque< std::string > deqSites, unsigned short const port, std::string const &strPath, std::size_t responseMax, std::chrono::seconds timeout, std::function< bool(boost::system::error_code const &ecResult, int iStatus, std::string const &strData)> complete, beast::Journal &j)
static constexpr auto maxClientHeaderBytes
Definition HTTPClient.h:24
T emplace(T... args)
T empty(T... args)
T is_same_v
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
static std::optional< HTTPClientSSLContext > httpClientSSLContext
T pop_front(T... args)
T size(T... args)
boost::asio::ip::resolver_query_base::flags flags
T to_string(T... args)