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(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
78 complete)
79 {
80 mSSL = bSSL;
81 mDeqSites = deqSites;
82 mBuild = build;
83 mComplete = complete;
84 mTimeout = timeout;
85
86 httpsNext();
87 }
88
89 //--------------------------------------------------------------------------
90
91 void
92 get(bool bSSL,
94 std::string const& strPath,
96 std::function<bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
97 complete)
98 {
99 mComplete = complete;
100 mTimeout = timeout;
101
102 request(
103 bSSL,
104 deqSites,
105 std::bind(
106 &HTTPClientImp::makeGet, shared_from_this(), strPath, std::placeholders::_1, std::placeholders::_2),
107 timeout,
108 complete);
109 }
110
111 //--------------------------------------------------------------------------
112
113 void
115 {
116 JLOG(j_.trace()) << "Fetch: " << mDeqSites[0];
117
118 auto query = std::make_shared<Query>(
119 mDeqSites[0], std::to_string(mPort), boost::asio::ip::resolver_query_base::numeric_service);
120 mQuery = query;
121
122 try
123 {
124 mDeadline.expires_after(mTimeout);
125 }
126 catch (boost::system::system_error const& e)
127 {
128 mShutdown = e.code();
129
130 JLOG(j_.trace()) << "expires_after: " << mShutdown.message();
131 mDeadline.async_wait(std::bind(&HTTPClientImp::handleDeadline, shared_from_this(), std::placeholders::_1));
132 }
133
134 if (!mShutdown)
135 {
136 JLOG(j_.trace()) << "Resolving: " << mDeqSites[0];
137
138 mResolver.async_resolve(
139 mQuery->host,
140 mQuery->port,
141 mQuery->flags,
142 std::bind(
143 &HTTPClientImp::handleResolve, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
144 }
145
146 if (mShutdown)
148 }
149
150 void
151 handleDeadline(boost::system::error_code const& ecResult)
152 {
153 if (ecResult == boost::asio::error::operation_aborted)
154 {
155 // Timer canceled because deadline no longer needed.
156 JLOG(j_.trace()) << "Deadline cancelled.";
157
158 // Aborter is done.
159 }
160 else if (ecResult)
161 {
162 JLOG(j_.trace()) << "Deadline error: " << mDeqSites[0] << ": " << ecResult.message();
163
164 // Can't do anything sound.
165 abort();
166 }
167 else
168 {
169 JLOG(j_.trace()) << "Deadline arrived.";
170
171 // Mark us as shutting down.
172 // XXX Use our own error code.
173 mShutdown = boost::system::error_code{boost::system::errc::bad_address, boost::system::system_category()};
174
175 // Cancel any resolving.
176 mResolver.cancel();
177
178 // Stop the transaction.
180 std::bind(&HTTPClientImp::handleShutdown, shared_from_this(), std::placeholders::_1));
181 }
182 }
183
184 void
185 handleShutdown(boost::system::error_code const& ecResult)
186 {
187 if (ecResult)
188 {
189 JLOG(j_.trace()) << "Shutdown error: " << mDeqSites[0] << ": " << ecResult.message();
190 }
191 }
192
193 void
194 handleResolve(boost::system::error_code const& ecResult, boost::asio::ip::tcp::resolver::results_type result)
195 {
196 if (!mShutdown)
197 {
198 mShutdown = ecResult ? ecResult : httpClientSSLContext->preConnectVerify(mSocket.SSLSocket(), mDeqSites[0]);
199 }
200
201 if (mShutdown)
202 {
203 JLOG(j_.trace()) << "Resolve error: " << mDeqSites[0] << ": " << mShutdown.message();
204
206 }
207 else
208 {
209 JLOG(j_.trace()) << "Resolve complete.";
210
211 boost::asio::async_connect(
213 result,
214 std::bind(&HTTPClientImp::handleConnect, shared_from_this(), std::placeholders::_1));
215 }
216 }
217
218 void
219 handleConnect(boost::system::error_code const& ecResult)
220 {
221 if (!mShutdown)
222 mShutdown = ecResult;
223
224 if (mShutdown)
225 {
226 JLOG(j_.trace()) << "Connect error: " << mShutdown.message();
227 }
228
229 if (!mShutdown)
230 {
231 JLOG(j_.trace()) << "Connected.";
232
233 mShutdown = httpClientSSLContext->postConnectVerify(mSocket.SSLSocket(), mDeqSites[0]);
234
235 if (mShutdown)
236 {
237 JLOG(j_.trace()) << "postConnectVerify: " << mDeqSites[0] << ": " << mShutdown.message();
238 }
239 }
240
241 if (mShutdown)
242 {
244 }
245 else if (mSSL)
246 {
248 AutoSocket::ssl_socket::client,
249 std::bind(&HTTPClientImp::handleRequest, shared_from_this(), std::placeholders::_1));
250 }
251 else
252 {
253 handleRequest(ecResult);
254 }
255 }
256
257 void
258 handleRequest(boost::system::error_code const& ecResult)
259 {
260 if (!mShutdown)
261 mShutdown = ecResult;
262
263 if (mShutdown)
264 {
265 JLOG(j_.trace()) << "Handshake error:" << mShutdown.message();
266
268 }
269 else
270 {
271 JLOG(j_.trace()) << "Session started.";
272
274
276 mRequest,
277 std::bind(
278 &HTTPClientImp::handleWrite, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
279 }
280 }
281
282 void
283 handleWrite(boost::system::error_code const& ecResult, std::size_t bytes_transferred)
284 {
285 if (!mShutdown)
286 mShutdown = ecResult;
287
288 if (mShutdown)
289 {
290 JLOG(j_.trace()) << "Write error: " << mShutdown.message();
291
293 }
294 else
295 {
296 JLOG(j_.trace()) << "Wrote.";
297
299 mHeader,
300 "\r\n\r\n",
301 std::bind(
302 &HTTPClientImp::handleHeader, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
303 }
304 }
305
306 void
307 handleHeader(boost::system::error_code const& ecResult, std::size_t bytes_transferred)
308 {
310 JLOG(j_.trace()) << "Header: \"" << strHeader << "\"";
311
312 static boost::regex reStatus{"\\`HTTP/1\\S+ (\\d{3}) .*\\'"}; // HTTP/1.1 200 OK
313 static boost::regex reSize{"\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'", boost::regex::icase};
314 static boost::regex reBody{"\\`.*\\r\\n\\r\\n(.*)\\'"};
315
316 boost::smatch smMatch;
317 // Match status code.
318 if (!boost::regex_match(strHeader, smMatch, reStatus))
319 {
320 // XXX Use our own error code.
321 JLOG(j_.trace()) << "No status code";
323 boost::system::error_code{boost::system::errc::bad_address, boost::system::system_category()});
324 return;
325 }
326
327 mStatus = beast::lexicalCastThrow<int>(std::string(smMatch[1]));
328
329 if (boost::regex_match(strHeader, smMatch, reBody)) // we got some body
330 mBody = smMatch[1];
331
332 std::size_t const responseSize = [&] {
333 if (boost::regex_match(strHeader, smMatch, reSize))
334 return beast::lexicalCast<std::size_t>(std::string(smMatch[1]), maxResponseSize_);
335 return maxResponseSize_;
336 }();
337
338 if (responseSize > maxResponseSize_)
339 {
340 JLOG(j_.trace()) << "Response field too large";
342 boost::system::error_code{boost::system::errc::value_too_large, boost::system::system_category()});
343 return;
344 }
345
346 if (responseSize == 0)
347 {
348 // no body wanted or available
349 invokeComplete(ecResult, mStatus);
350 }
351 else if (mBody.size() >= responseSize)
352 {
353 // we got the whole thing
354 invokeComplete(ecResult, mStatus, mBody);
355 }
356 else
357 {
359 mResponse.prepare(responseSize - mBody.size()),
360 boost::asio::transfer_all(),
361 std::bind(
362 &HTTPClientImp::handleData, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
363 }
364 }
365
366 void
367 handleData(boost::system::error_code const& ecResult, std::size_t bytes_transferred)
368 {
369 if (!mShutdown)
370 mShutdown = ecResult;
371
372 if (mShutdown && mShutdown != boost::asio::error::eof)
373 {
374 JLOG(j_.trace()) << "Read error: " << mShutdown.message();
375
377 }
378 else
379 {
380 if (mShutdown)
381 {
382 JLOG(j_.trace()) << "Complete.";
383 }
384 else
385 {
386 mResponse.commit(bytes_transferred);
388 invokeComplete(ecResult, mStatus, mBody + strBody);
389 }
390 }
391 }
392
393 // Call cancel the deadline timer and invoke the completion routine.
394 void
395 invokeComplete(boost::system::error_code const& ecResult, int iStatus = 0, std::string const& strData = "")
396 {
397 boost::system::error_code ecCancel;
398 try
399 {
400 mDeadline.cancel();
401 }
402 catch (boost::system::system_error const& e)
403 {
404 JLOG(j_.trace()) << "invokeComplete: Deadline cancel error: " << e.what();
405 ecCancel = e.code();
406 }
407
408 JLOG(j_.debug()) << "invokeComplete: Deadline popping: " << mDeqSites.size();
409
410 if (!mDeqSites.empty())
411 {
413 }
414
415 bool bAgain = true;
416
417 if (mDeqSites.empty() || !ecResult)
418 {
419 // ecResult: !0 = had an error, last entry
420 // iStatus: result, if no error
421 // strData: data, if no error
422 bAgain = mComplete && mComplete(ecResult ? ecResult : ecCancel, iStatus, strData);
423 }
424
425 if (!mDeqSites.empty() && bAgain)
426 {
427 httpsNext();
428 }
429 }
430
431private:
433
434 bool mSSL;
436 boost::asio::ip::tcp::resolver mResolver;
437
438 struct Query
439 {
442 boost::asio::ip::resolver_query_base::flags flags;
443 };
445
446 boost::asio::streambuf mRequest;
447 boost::asio::streambuf mHeader;
448 boost::asio::streambuf mResponse;
450 unsigned short const mPort;
453 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)> mBuild;
454 std::function<bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)> mComplete;
455
456 boost::asio::basic_waitable_timer<std::chrono::steady_clock> mDeadline;
457
458 // If not success, we are shutting down.
459 boost::system::error_code mShutdown;
460
464};
465
466//------------------------------------------------------------------------------
467
468void
470 bool bSSL,
471 boost::asio::io_context& io_context,
473 unsigned short const port,
474 std::string const& strPath,
475 std::size_t responseMax,
476 std::chrono::seconds timeout,
477 std::function<bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)> complete,
479{
480 auto client = std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
481 client->get(bSSL, deqSites, strPath, timeout, complete);
482}
483
484void
486 bool bSSL,
487 boost::asio::io_context& io_context,
488 std::string strSite,
489 unsigned short const port,
490 std::string const& strPath,
491 std::size_t responseMax,
492 std::chrono::seconds timeout,
493 std::function<bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)> complete,
495{
496 std::deque<std::string> deqSites(1, strSite);
497
498 auto client = std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
499 client->get(bSSL, deqSites, strPath, timeout, complete);
500}
501
502void
504 bool bSSL,
505 boost::asio::io_context& io_context,
506 std::string strSite,
507 unsigned short const port,
508 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)> setRequest,
509 std::size_t responseMax,
510 std::chrono::seconds timeout,
511 std::function<bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)> complete,
513{
514 std::deque<std::string> deqSites(1, strSite);
515
516 auto client = std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
517 client->request(bSSL, deqSites, setRequest, timeout, complete);
518}
519
520} // namespace xrpl
T bind(T... args)
void async_handshake(handshake_type type, callback cbFunc)
Definition AutoSocket.h:89
void async_write(Buf const &buffers, Handler handler)
Definition AutoSocket.h:176
lowest_layer_type & lowest_layer()
Definition AutoSocket.h:69
void async_read_until(Seq const &buffers, Condition condition, Handler handler)
Definition AutoSocket.h:146
void async_read(Buf const &buffers, Condition cond, Handler handler)
Definition AutoSocket.h:196
ssl_socket & SSLSocket()
Definition AutoSocket.h:46
void async_shutdown(ShutdownHandler handler)
Definition AutoSocket.h:115
A generic endpoint for log messages.
Definition Journal.h:41
Stream debug() const
Definition Journal.h:301
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
std::function< bool(boost::system::error_code const &ecResult, int iStatus, std::string const &strData)> mComplete
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::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:20
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: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)