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
29//------------------------------------------------------------------------------
30//
31// Fetch a web page via http or https.
32//
33//------------------------------------------------------------------------------
34
35class HTTPClientImp : public std::enable_shared_from_this<HTTPClientImp>, public HTTPClient
36{
37public:
39 boost::asio::io_context& io_context,
40 unsigned short const port,
41 std::size_t maxResponseSize,
43 : mSocket(io_context, httpClientSSLContext->context())
44 , mResolver(io_context)
46 , mPort(port)
47 , maxResponseSize_(maxResponseSize)
48 , mDeadline(io_context)
49 , j_(j)
50 {
51 }
52
53 //--------------------------------------------------------------------------
54
55 void
56 makeGet(std::string const& strPath, boost::asio::streambuf& sb, std::string const& strHost)
57 {
58 std::ostream osRequest(&sb);
59
60 osRequest << "GET " << strPath
61 << " HTTP/1.0\r\n"
62 "Host: "
63 << strHost
64 << "\r\n"
65 "Accept: */*\r\n" // YYY Do we need this line?
66 "Connection: close\r\n\r\n";
67 }
68
69 //--------------------------------------------------------------------------
70
71 void
73 bool bSSL,
75 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)> build,
77 std::function<bool(
78 boost::system::error_code const& ecResult,
79 int iStatus,
80 std::string const& strData)> complete)
81 {
82 mSSL = bSSL;
83 mDeqSites = deqSites;
84 mBuild = build;
85 mComplete = complete;
86 mTimeout = timeout;
87
88 httpsNext();
89 }
90
91 //--------------------------------------------------------------------------
92
93 void
94 get(bool bSSL,
96 std::string const& strPath,
98 std::function<bool(
99 boost::system::error_code const& ecResult,
100 int iStatus,
101 std::string const& strData)> complete)
102 {
103 mComplete = complete;
104 mTimeout = timeout;
105
106 request(
107 bSSL,
108 deqSites,
109 std::bind(
112 strPath,
113 std::placeholders::_1,
114 std::placeholders::_2),
115 timeout,
116 complete);
117 }
118
119 //--------------------------------------------------------------------------
120
121 void
123 {
124 JLOG(j_.trace()) << "Fetch: " << mDeqSites[0];
125
126 auto query = std::make_shared<Query>(
127 mDeqSites[0],
129 boost::asio::ip::resolver_query_base::numeric_service);
130 mQuery = query;
131
132 try
133 {
134 mDeadline.expires_after(mTimeout);
135 }
136 catch (boost::system::system_error const& e)
137 {
138 mShutdown = e.code();
139
140 JLOG(j_.trace()) << "expires_after: " << mShutdown.message();
141 mDeadline.async_wait(
142 std::bind(
143 &HTTPClientImp::handleDeadline, shared_from_this(), std::placeholders::_1));
144 }
145
146 if (!mShutdown)
147 {
148 JLOG(j_.trace()) << "Resolving: " << mDeqSites[0];
149
150 mResolver.async_resolve(
151 mQuery->host,
152 mQuery->port,
153 mQuery->flags,
154 std::bind(
157 std::placeholders::_1,
158 std::placeholders::_2));
159 }
160
161 if (mShutdown)
163 }
164
165 void
166 handleDeadline(boost::system::error_code const& ecResult)
167 {
168 if (ecResult == boost::asio::error::operation_aborted)
169 {
170 // Timer canceled because deadline no longer needed.
171 JLOG(j_.trace()) << "Deadline cancelled.";
172
173 // Aborter is done.
174 }
175 else if (ecResult)
176 {
177 JLOG(j_.trace()) << "Deadline error: " << mDeqSites[0] << ": " << ecResult.message();
178
179 // Can't do anything sound.
180 abort();
181 }
182 else
183 {
184 JLOG(j_.trace()) << "Deadline arrived.";
185
186 // Mark us as shutting down.
187 // XXX Use our own error code.
188 mShutdown = boost::system::error_code{
189 boost::system::errc::bad_address, boost::system::system_category()};
190
191 // Cancel any resolving.
192 mResolver.cancel();
193
194 // Stop the transaction.
196 std::bind(
197 &HTTPClientImp::handleShutdown, shared_from_this(), std::placeholders::_1));
198 }
199 }
200
201 void
202 handleShutdown(boost::system::error_code const& ecResult)
203 {
204 if (ecResult)
205 {
206 JLOG(j_.trace()) << "Shutdown error: " << mDeqSites[0] << ": " << ecResult.message();
207 }
208 }
209
210 void
212 boost::system::error_code const& ecResult,
213 boost::asio::ip::tcp::resolver::results_type result)
214 {
215 if (!mShutdown)
216 {
217 mShutdown = ecResult
218 ? ecResult
219 : httpClientSSLContext->preConnectVerify(mSocket.SSLSocket(), mDeqSites[0]);
220 }
221
222 if (mShutdown)
223 {
224 JLOG(j_.trace()) << "Resolve error: " << mDeqSites[0] << ": " << mShutdown.message();
225
227 }
228 else
229 {
230 JLOG(j_.trace()) << "Resolve complete.";
231
232 boost::asio::async_connect(
234 result,
235 std::bind(
236 &HTTPClientImp::handleConnect, shared_from_this(), std::placeholders::_1));
237 }
238 }
239
240 void
241 handleConnect(boost::system::error_code const& ecResult)
242 {
243 if (!mShutdown)
244 mShutdown = ecResult;
245
246 if (mShutdown)
247 {
248 JLOG(j_.trace()) << "Connect error: " << mShutdown.message();
249 }
250
251 if (!mShutdown)
252 {
253 JLOG(j_.trace()) << "Connected.";
254
255 mShutdown = httpClientSSLContext->postConnectVerify(mSocket.SSLSocket(), mDeqSites[0]);
256
257 if (mShutdown)
258 {
259 JLOG(j_.trace()) << "postConnectVerify: " << mDeqSites[0] << ": "
260 << mShutdown.message();
261 }
262 }
263
264 if (mShutdown)
265 {
267 }
268 else if (mSSL)
269 {
271 AutoSocket::ssl_socket::client,
272 std::bind(
273 &HTTPClientImp::handleRequest, shared_from_this(), std::placeholders::_1));
274 }
275 else
276 {
277 handleRequest(ecResult);
278 }
279 }
280
281 void
282 handleRequest(boost::system::error_code const& ecResult)
283 {
284 if (!mShutdown)
285 mShutdown = ecResult;
286
287 if (mShutdown)
288 {
289 JLOG(j_.trace()) << "Handshake error:" << mShutdown.message();
290
292 }
293 else
294 {
295 JLOG(j_.trace()) << "Session started.";
296
298
300 mRequest,
301 std::bind(
304 std::placeholders::_1,
305 std::placeholders::_2));
306 }
307 }
308
309 void
310 handleWrite(boost::system::error_code const& ecResult, std::size_t bytes_transferred)
311 {
312 if (!mShutdown)
313 mShutdown = ecResult;
314
315 if (mShutdown)
316 {
317 JLOG(j_.trace()) << "Write error: " << mShutdown.message();
318
320 }
321 else
322 {
323 JLOG(j_.trace()) << "Wrote.";
324
326 mHeader,
327 "\r\n\r\n",
328 std::bind(
331 std::placeholders::_1,
332 std::placeholders::_2));
333 }
334 }
335
336 void
337 handleHeader(boost::system::error_code const& ecResult, std::size_t bytes_transferred)
338 {
339 std::string strHeader{
341 JLOG(j_.trace()) << "Header: \"" << strHeader << "\"";
342
343 static boost::regex reStatus{"\\`HTTP/1\\S+ (\\d{3}) .*\\'"}; // HTTP/1.1 200 OK
344 static boost::regex reSize{
345 "\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'", boost::regex::icase};
346 static boost::regex reBody{"\\`.*\\r\\n\\r\\n(.*)\\'"};
347
348 boost::smatch smMatch;
349 // Match status code.
350 if (!boost::regex_match(strHeader, smMatch, reStatus))
351 {
352 // XXX Use our own error code.
353 JLOG(j_.trace()) << "No status code";
355 boost::system::error_code{
356 boost::system::errc::bad_address, boost::system::system_category()});
357 return;
358 }
359
360 mStatus = beast::lexicalCastThrow<int>(std::string(smMatch[1]));
361
362 if (boost::regex_match(strHeader, smMatch, reBody)) // we got some body
363 mBody = smMatch[1];
364
365 std::size_t const responseSize = [&] {
366 if (boost::regex_match(strHeader, smMatch, reSize))
367 return beast::lexicalCast<std::size_t>(std::string(smMatch[1]), maxResponseSize_);
368 return maxResponseSize_;
369 }();
370
371 if (responseSize > maxResponseSize_)
372 {
373 JLOG(j_.trace()) << "Response field too large";
375 boost::system::error_code{
376 boost::system::errc::value_too_large, boost::system::system_category()});
377 return;
378 }
379
380 if (responseSize == 0)
381 {
382 // no body wanted or available
383 invokeComplete(ecResult, mStatus);
384 }
385 else if (mBody.size() >= responseSize)
386 {
387 // we got the whole thing
388 invokeComplete(ecResult, mStatus, mBody);
389 }
390 else
391 {
393 mResponse.prepare(responseSize - mBody.size()),
394 boost::asio::transfer_all(),
395 std::bind(
398 std::placeholders::_1,
399 std::placeholders::_2));
400 }
401 }
402
403 void
404 handleData(boost::system::error_code const& ecResult, std::size_t bytes_transferred)
405 {
406 if (!mShutdown)
407 mShutdown = ecResult;
408
409 if (mShutdown && mShutdown != boost::asio::error::eof)
410 {
411 JLOG(j_.trace()) << "Read error: " << mShutdown.message();
412
414 }
415 else
416 {
417 if (mShutdown)
418 {
419 JLOG(j_.trace()) << "Complete.";
420 }
421 else
422 {
423 mResponse.commit(bytes_transferred);
424 std::string strBody{
426 invokeComplete(ecResult, mStatus, mBody + strBody);
427 }
428 }
429 }
430
431 // Call cancel the deadline timer and invoke the completion routine.
432 void
434 boost::system::error_code const& ecResult,
435 int iStatus = 0,
436 std::string const& strData = "")
437 {
438 boost::system::error_code ecCancel;
439 try
440 {
441 mDeadline.cancel();
442 }
443 catch (boost::system::system_error const& e)
444 {
445 JLOG(j_.trace()) << "invokeComplete: Deadline cancel error: " << e.what();
446 ecCancel = e.code();
447 }
448
449 JLOG(j_.debug()) << "invokeComplete: Deadline popping: " << mDeqSites.size();
450
451 if (!mDeqSites.empty())
452 {
454 }
455
456 bool bAgain = true;
457
458 if (mDeqSites.empty() || !ecResult)
459 {
460 // ecResult: !0 = had an error, last entry
461 // iStatus: result, if no error
462 // strData: data, if no error
463 bAgain = mComplete && mComplete(ecResult ? ecResult : ecCancel, iStatus, strData);
464 }
465
466 if (!mDeqSites.empty() && bAgain)
467 {
468 httpsNext();
469 }
470 }
471
472private:
474
475 bool mSSL;
477 boost::asio::ip::tcp::resolver mResolver;
478
479 struct Query
480 {
483 boost::asio::ip::resolver_query_base::flags flags;
484 };
486
487 boost::asio::streambuf mRequest;
488 boost::asio::streambuf mHeader;
489 boost::asio::streambuf mResponse;
491 unsigned short const mPort;
494 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)> mBuild;
496 bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
498
499 boost::asio::basic_waitable_timer<std::chrono::steady_clock> mDeadline;
500
501 // If not success, we are shutting down.
502 boost::system::error_code mShutdown;
503
507};
508
509//------------------------------------------------------------------------------
510
511void
513 bool bSSL,
514 boost::asio::io_context& io_context,
516 unsigned short const port,
517 std::string const& strPath,
518 std::size_t responseMax,
519 std::chrono::seconds timeout,
521 bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
522 complete,
524{
525 auto client = std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
526 client->get(bSSL, deqSites, strPath, timeout, complete);
527}
528
529void
531 bool bSSL,
532 boost::asio::io_context& io_context,
533 std::string strSite,
534 unsigned short const port,
535 std::string const& strPath,
536 std::size_t responseMax,
537 std::chrono::seconds timeout,
539 bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
540 complete,
542{
543 std::deque<std::string> deqSites(1, strSite);
544
545 auto client = std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
546 client->get(bSSL, deqSites, strPath, timeout, complete);
547}
548
549void
551 bool bSSL,
552 boost::asio::io_context& io_context,
553 std::string strSite,
554 unsigned short const port,
555 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)> setRequest,
556 std::size_t responseMax,
557 std::chrono::seconds timeout,
559 bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
560 complete,
562{
563 std::deque<std::string> deqSites(1, strSite);
564
565 auto client = std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
566 client->request(bSSL, deqSites, setRequest, timeout, complete);
567}
568
569} // 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
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)