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 xrpl {
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
29void
34
35//------------------------------------------------------------------------------
36//
37// Fetch a web page via http or https.
38//
39//------------------------------------------------------------------------------
40
41class HTTPClientImp : public std::enable_shared_from_this<HTTPClientImp>, public HTTPClient
42{
43public:
45 boost::asio::io_context& io_context,
46 unsigned short const port,
47 std::size_t maxResponseSize,
49 : mSocket(io_context, httpClientSSLContext->context())
50 , mResolver(io_context)
52 , mPort(port)
53 , maxResponseSize_(maxResponseSize)
54 , mDeadline(io_context)
55 , j_(j)
56 {
57 }
58
59 //--------------------------------------------------------------------------
60
61 void
62 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
63 makeGet(std::string const& strPath, boost::asio::streambuf& sb, std::string const& strHost)
64 {
65 std::ostream osRequest(&sb);
66
67 osRequest << "GET " << strPath
68 << " HTTP/1.0\r\n"
69 "Host: "
70 << strHost
71 << "\r\n"
72 "Accept: */*\r\n" // YYY Do we need this line?
73 "Connection: close\r\n\r\n";
74 }
75
76 //--------------------------------------------------------------------------
77
78 void
80 bool bSSL,
82 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)> build,
84 std::function<bool(
85 boost::system::error_code const& ecResult,
86 int iStatus,
87 std::string const& strData)> complete)
88 {
89 mSSL = bSSL;
90 mDeqSites = deqSites;
91 mBuild = build;
92 mComplete = complete;
93 mTimeout = timeout;
94
95 httpsNext();
96 }
97
98 //--------------------------------------------------------------------------
99
100 void
101 get(bool bSSL,
103 std::string const& strPath,
104 std::chrono::seconds timeout,
105 std::function<bool(
106 boost::system::error_code const& ecResult,
107 int iStatus,
108 std::string const& strData)> complete)
109 {
110 mComplete = complete;
111 mTimeout = timeout;
112
113 request(
114 bSSL,
115 deqSites,
116 std::bind(
119 strPath,
120 std::placeholders::_1,
121 std::placeholders::_2),
122 timeout,
123 complete);
124 }
125
126 //--------------------------------------------------------------------------
127
128 void
130 {
131 JLOG(j_.trace()) << "Fetch: " << mDeqSites[0];
132
133 auto query = std::make_shared<Query>(
134 mDeqSites[0],
136 boost::asio::ip::resolver_query_base::numeric_service);
137 mQuery = query;
138
139 try
140 {
141 mDeadline.expires_after(mTimeout);
142 }
143 catch (boost::system::system_error const& e)
144 {
145 mShutdown = e.code();
146
147 JLOG(j_.trace()) << "expires_after: " << mShutdown.message();
148 mDeadline.async_wait(
149 std::bind(
150 &HTTPClientImp::handleDeadline, shared_from_this(), std::placeholders::_1));
151 }
152
153 if (!mShutdown)
154 {
155 JLOG(j_.trace()) << "Resolving: " << mDeqSites[0];
156
157 mResolver.async_resolve(
158 mQuery->host,
159 mQuery->port,
160 mQuery->flags,
161 std::bind(
164 std::placeholders::_1,
165 std::placeholders::_2));
166 }
167
168 if (mShutdown)
170 }
171
172 void
173 handleDeadline(boost::system::error_code const& ecResult)
174 {
175 if (ecResult == boost::asio::error::operation_aborted)
176 {
177 // Timer canceled because deadline no longer needed.
178 JLOG(j_.trace()) << "Deadline cancelled.";
179
180 // Aborter is done.
181 }
182 else if (ecResult)
183 {
184 JLOG(j_.trace()) << "Deadline error: " << mDeqSites[0] << ": " << 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, boost::system::system_category()};
197
198 // Cancel any resolving.
199 mResolver.cancel();
200
201 // Stop the transaction.
203 std::bind(
204 &HTTPClientImp::handleShutdown, shared_from_this(), std::placeholders::_1));
205 }
206 }
207
208 void
209 handleShutdown(boost::system::error_code const& ecResult)
210 {
211 if (ecResult)
212 {
213 JLOG(j_.trace()) << "Shutdown error: " << mDeqSites[0] << ": " << ecResult.message();
214 }
215 }
216
217 void
219 boost::system::error_code const& ecResult,
220 boost::asio::ip::tcp::resolver::results_type result)
221 {
222 if (!mShutdown)
223 {
224 mShutdown = ecResult
225 ? ecResult
226 : httpClientSSLContext->preConnectVerify(mSocket.SSLSocket(), mDeqSites[0]);
227 }
228
229 if (mShutdown)
230 {
231 JLOG(j_.trace()) << "Resolve error: " << mDeqSites[0] << ": " << mShutdown.message();
232
234 }
235 else
236 {
237 JLOG(j_.trace()) << "Resolve complete.";
238
239 boost::asio::async_connect(
241 result,
242 std::bind(
243 &HTTPClientImp::handleConnect, shared_from_this(), std::placeholders::_1));
244 }
245 }
246
247 void
248 handleConnect(boost::system::error_code const& ecResult)
249 {
250 if (!mShutdown)
251 mShutdown = ecResult;
252
253 if (mShutdown)
254 {
255 JLOG(j_.trace()) << "Connect error: " << mShutdown.message();
256 }
257
258 if (!mShutdown)
259 {
260 JLOG(j_.trace()) << "Connected.";
261
262 mShutdown = httpClientSSLContext->postConnectVerify(mSocket.SSLSocket(), mDeqSites[0]);
263
264 if (mShutdown)
265 {
266 JLOG(j_.trace()) << "postConnectVerify: " << mDeqSites[0] << ": "
267 << mShutdown.message();
268 }
269 }
270
271 if (mShutdown)
272 {
274 }
275 else if (mSSL)
276 {
278 AutoSocket::ssl_socket::client,
279 std::bind(
280 &HTTPClientImp::handleRequest, shared_from_this(), std::placeholders::_1));
281 }
282 else
283 {
284 handleRequest(ecResult);
285 }
286 }
287
288 void
289 handleRequest(boost::system::error_code const& ecResult)
290 {
291 if (!mShutdown)
292 mShutdown = ecResult;
293
294 if (mShutdown)
295 {
296 JLOG(j_.trace()) << "Handshake error:" << mShutdown.message();
297
299 }
300 else
301 {
302 JLOG(j_.trace()) << "Session started.";
303
305
307 mRequest,
308 std::bind(
311 std::placeholders::_1,
312 std::placeholders::_2));
313 }
314 }
315
316 void
317 handleWrite(boost::system::error_code const& ecResult, std::size_t bytes_transferred)
318 {
319 if (!mShutdown)
320 mShutdown = ecResult;
321
322 if (mShutdown)
323 {
324 JLOG(j_.trace()) << "Write error: " << mShutdown.message();
325
327 }
328 else
329 {
330 JLOG(j_.trace()) << "Wrote.";
331
333 mHeader,
334 "\r\n\r\n",
335 std::bind(
338 std::placeholders::_1,
339 std::placeholders::_2));
340 }
341 }
342
343 void
344 handleHeader(boost::system::error_code const& ecResult, std::size_t bytes_transferred)
345 {
346 std::string strHeader{
348 JLOG(j_.trace()) << "Header: \"" << strHeader << "\"";
349
350 static boost::regex const reStatus{"\\`HTTP/1\\S+ (\\d{3}) .*\\'"}; // HTTP/1.1 200 OK
351 static boost::regex const reSize{
352 "\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'", boost::regex::icase};
353 static boost::regex const reBody{"\\`.*\\r\\n\\r\\n(.*)\\'"};
354
355 boost::smatch smMatch;
356 // Match status code.
357 if (!boost::regex_match(strHeader, smMatch, reStatus))
358 {
359 // XXX Use our own error code.
360 JLOG(j_.trace()) << "No status code";
362 boost::system::error_code{
363 boost::system::errc::bad_address, boost::system::system_category()});
364 return;
365 }
366
367 mStatus = beast::lexicalCastThrow<int>(std::string(smMatch[1]));
368
369 if (boost::regex_match(strHeader, smMatch, reBody)) // we got some body
370 mBody = smMatch[1];
371
372 std::size_t const responseSize = [&] {
373 if (boost::regex_match(strHeader, smMatch, reSize))
374 return beast::lexicalCast<std::size_t>(std::string(smMatch[1]), maxResponseSize_);
375 return maxResponseSize_;
376 }();
377
378 if (responseSize > maxResponseSize_)
379 {
380 JLOG(j_.trace()) << "Response field too large";
382 boost::system::error_code{
383 boost::system::errc::value_too_large, boost::system::system_category()});
384 return;
385 }
386
387 if (responseSize == 0)
388 {
389 // no body wanted or available
390 invokeComplete(ecResult, mStatus);
391 }
392 else if (mBody.size() >= responseSize)
393 {
394 // we got the whole thing
395 invokeComplete(ecResult, mStatus, mBody);
396 }
397 else
398 {
400 mResponse.prepare(responseSize - mBody.size()),
401 boost::asio::transfer_all(),
402 std::bind(
405 std::placeholders::_1,
406 std::placeholders::_2));
407 }
408 }
409
410 void
411 handleData(boost::system::error_code const& ecResult, std::size_t bytes_transferred)
412 {
413 if (!mShutdown)
414 mShutdown = ecResult;
415
416 if (mShutdown && mShutdown != boost::asio::error::eof)
417 {
418 JLOG(j_.trace()) << "Read error: " << mShutdown.message();
419
421 }
422 else
423 {
424 if (mShutdown)
425 {
426 JLOG(j_.trace()) << "Complete.";
427 }
428 else
429 {
430 mResponse.commit(bytes_transferred);
431 std::string const strBody{
433 invokeComplete(ecResult, mStatus, mBody + strBody);
434 }
435 }
436 }
437
438 // Call cancel the deadline timer and invoke the completion routine.
439 void
441 boost::system::error_code const& ecResult,
442 int iStatus = 0,
443 std::string const& strData = "")
444 {
445 boost::system::error_code ecCancel;
446 try
447 {
448 mDeadline.cancel();
449 }
450 catch (boost::system::system_error const& e)
451 {
452 JLOG(j_.trace()) << "invokeComplete: Deadline cancel error: " << e.what();
453 ecCancel = e.code();
454 }
455
456 JLOG(j_.debug()) << "invokeComplete: Deadline popping: " << mDeqSites.size();
457
458 if (!mDeqSites.empty())
459 {
461 }
462
463 bool bAgain = true;
464
465 if (mDeqSites.empty() || !ecResult)
466 {
467 // ecResult: !0 = had an error, last entry
468 // iStatus: result, if no error
469 // strData: data, if no error
470 bAgain = mComplete && mComplete(ecResult ? ecResult : ecCancel, iStatus, strData);
471 }
472
473 if (!mDeqSites.empty() && bAgain)
474 {
475 httpsNext();
476 }
477 }
478
479private:
481
482 bool mSSL{};
484 boost::asio::ip::tcp::resolver mResolver;
485
486 struct Query
487 {
490 boost::asio::ip::resolver_query_base::flags flags;
491 };
493
494 boost::asio::streambuf mRequest;
495 boost::asio::streambuf mHeader;
496 boost::asio::streambuf mResponse;
498 unsigned short const mPort;
500 int mStatus{};
501 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)> mBuild;
503 bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
505
506 boost::asio::basic_waitable_timer<std::chrono::steady_clock> mDeadline;
507
508 // If not success, we are shutting down.
509 boost::system::error_code mShutdown;
510
514};
515
516//------------------------------------------------------------------------------
517
518void
520 bool bSSL,
521 boost::asio::io_context& io_context,
523 unsigned short const port,
524 std::string const& strPath,
525 std::size_t responseMax,
526 std::chrono::seconds timeout,
528 bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
529 complete,
531{
532 auto client = std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
533 client->get(bSSL, deqSites, strPath, timeout, complete);
534}
535
536void
538 bool bSSL,
539 boost::asio::io_context& io_context,
540 std::string strSite,
541 unsigned short const port,
542 std::string const& strPath,
543 std::size_t responseMax,
544 std::chrono::seconds timeout,
546 bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
547 complete,
549{
550 std::deque<std::string> const deqSites(1, strSite);
551
552 auto client = std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
553 client->get(bSSL, deqSites, strPath, timeout, complete);
554}
555
556void
558 bool bSSL,
559 boost::asio::io_context& io_context,
560 std::string strSite,
561 unsigned short const port,
562 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)> setRequest,
563 std::size_t responseMax,
564 std::chrono::seconds timeout,
566 bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
567 complete,
569{
570 std::deque<std::string> const deqSites(1, strSite);
571
572 auto client = std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
573 client->request(bSSL, deqSites, setRequest, timeout, complete);
574}
575
576} // namespace xrpl
T bind(T... args)
void async_handshake(handshake_type type, callback cbFunc)
Definition AutoSocket.h:95
void async_write(Buf const &buffers, Handler handler)
Definition AutoSocket.h:193
lowest_layer_type & lowest_layer()
Definition AutoSocket.h:75
void async_read_until(Seq const &buffers, Condition condition, Handler handler)
Definition AutoSocket.h:157
void async_read(Buf const &buffers, Condition cond, Handler handler)
Definition AutoSocket.h:213
ssl_socket & SSLSocket()
Definition AutoSocket.h:52
void async_shutdown(ShutdownHandler handler)
Definition AutoSocket.h:126
A generic endpoint for log messages.
Definition Journal.h:40
Stream debug() const
Definition Journal.h:301
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
void handleDeadline(boost::system::error_code const &ecResult)
void handleWrite(boost::system::error_code const &ecResult, std::size_t bytes_transferred)
std::size_t const maxResponseSize_
boost::system::error_code mShutdown
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)
void handleConnect(boost::system::error_code const &ecResult)
std::deque< std::string > mDeqSites
boost::asio::basic_waitable_timer< std::chrono::steady_clock > mDeadline
boost::asio::streambuf mRequest
void makeGet(std::string const &strPath, boost::asio::streambuf &sb, std::string const &strHost)
void handleResolve(boost::system::error_code const &ecResult, boost::asio::ip::tcp::resolver::results_type result)
void handleRequest(boost::system::error_code const &ecResult)
boost::asio::streambuf mHeader
boost::asio::streambuf mResponse
void handleHeader(boost::system::error_code const &ecResult, std::size_t bytes_transferred)
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)
std::function< bool(boost::system::error_code const &ecResult, int iStatus, std::string const &strData)> mComplete
std::chrono::seconds mTimeout
std::function< void(boost::asio::streambuf &sb, std::string const &strHost)> mBuild
void handleData(boost::system::error_code const &ecResult, std::size_t bytes_transferred)
HTTPClientImp(boost::asio::io_context &io_context, unsigned short const port, std::size_t maxResponseSize, beast::Journal &j)
void invokeComplete(boost::system::error_code const &ecResult, int iStatus=0, std::string const &strData="")
void handleShutdown(boost::system::error_code const &ecResult)
boost::asio::ip::tcp::resolver mResolver
std::shared_ptr< Query > mQuery
Provides an asynchronous HTTP client implementation with optional SSL.
Definition HTTPClient.h:19
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 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 constexpr auto maxClientHeaderBytes
Definition HTTPClient.h:23
static void cleanupSSLContext()
Destroys the global SSL context created by initializeSSLContext().
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:5
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)