rippled
Loading...
Searching...
No Matches
libxrpl/net/HTTPClient.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpl/basics/Log.h>
21#include <xrpl/beast/core/LexicalCast.h>
22#include <xrpl/net/AutoSocket.h>
23#include <xrpl/net/HTTPClient.h>
24#include <xrpl/net/HTTPClientSSLContext.h>
25
26#include <boost/asio.hpp>
27#include <boost/asio/ip/resolver_query_base.hpp>
28#include <boost/asio/ip/tcp.hpp>
29#include <boost/asio/ssl.hpp>
30#include <boost/regex.hpp>
31
32#include <optional>
33
34namespace ripple {
35
37
38void
40 std::string const& sslVerifyDir,
41 std::string const& sslVerifyFile,
42 bool sslVerify,
44{
45 httpClientSSLContext.emplace(sslVerifyDir, sslVerifyFile, sslVerify, j);
46}
47
48//------------------------------------------------------------------------------
49//
50// Fetch a web page via http or https.
51//
52//------------------------------------------------------------------------------
53
54class HTTPClientImp : public std::enable_shared_from_this<HTTPClientImp>,
55 public HTTPClient
56{
57public:
59 boost::asio::io_context& io_context,
60 unsigned short const port,
61 std::size_t maxResponseSize,
63 : mSocket(io_context, httpClientSSLContext->context())
64 , mResolver(io_context)
66 , mPort(port)
67 , maxResponseSize_(maxResponseSize)
68 , mDeadline(io_context)
69 , j_(j)
70 {
71 }
72
73 //--------------------------------------------------------------------------
74
75 void
77 std::string const& strPath,
78 boost::asio::streambuf& sb,
79 std::string const& strHost)
80 {
81 std::ostream osRequest(&sb);
82
83 osRequest << "GET " << strPath
84 << " HTTP/1.0\r\n"
85 "Host: "
86 << strHost
87 << "\r\n"
88 "Accept: */*\r\n" // YYY Do we need this line?
89 "Connection: close\r\n\r\n";
90 }
91
92 //--------------------------------------------------------------------------
93
94 void
96 bool bSSL,
99 void(boost::asio::streambuf& sb, std::string const& strHost)> build,
100 std::chrono::seconds timeout,
101 std::function<bool(
102 boost::system::error_code const& ecResult,
103 int iStatus,
104 std::string const& strData)> complete)
105 {
106 mSSL = bSSL;
107 mDeqSites = deqSites;
108 mBuild = build;
109 mComplete = complete;
110 mTimeout = timeout;
111
112 httpsNext();
113 }
114
115 //--------------------------------------------------------------------------
116
117 void
118 get(bool bSSL,
120 std::string const& strPath,
121 std::chrono::seconds timeout,
122 std::function<bool(
123 boost::system::error_code const& ecResult,
124 int iStatus,
125 std::string const& strData)> complete)
126 {
127 mComplete = complete;
128 mTimeout = timeout;
129
130 request(
131 bSSL,
132 deqSites,
133 std::bind(
136 strPath,
137 std::placeholders::_1,
138 std::placeholders::_2),
139 timeout,
140 complete);
141 }
142
143 //--------------------------------------------------------------------------
144
145 void
147 {
148 JLOG(j_.trace()) << "Fetch: " << mDeqSites[0];
149
150 auto query = std::make_shared<Query>(
151 mDeqSites[0],
153 boost::asio::ip::resolver_query_base::numeric_service);
154 mQuery = query;
155
156 try
157 {
158 mDeadline.expires_after(mTimeout);
159 }
160 catch (boost::system::system_error const& e)
161 {
162 mShutdown = e.code();
163
164 JLOG(j_.trace()) << "expires_after: " << mShutdown.message();
165 mDeadline.async_wait(std::bind(
168 std::placeholders::_1));
169 }
170
171 if (!mShutdown)
172 {
173 JLOG(j_.trace()) << "Resolving: " << mDeqSites[0];
174
175 mResolver.async_resolve(
176 mQuery->host,
177 mQuery->port,
178 mQuery->flags,
179 std::bind(
182 std::placeholders::_1,
183 std::placeholders::_2));
184 }
185
186 if (mShutdown)
188 }
189
190 void
191 handleDeadline(boost::system::error_code const& ecResult)
192 {
193 if (ecResult == boost::asio::error::operation_aborted)
194 {
195 // Timer canceled because deadline no longer needed.
196 JLOG(j_.trace()) << "Deadline cancelled.";
197
198 // Aborter is done.
199 }
200 else if (ecResult)
201 {
202 JLOG(j_.trace()) << "Deadline error: " << mDeqSites[0] << ": "
203 << ecResult.message();
204
205 // Can't do anything sound.
206 abort();
207 }
208 else
209 {
210 JLOG(j_.trace()) << "Deadline arrived.";
211
212 // Mark us as shutting down.
213 // XXX Use our own error code.
214 mShutdown = boost::system::error_code{
215 boost::system::errc::bad_address,
216 boost::system::system_category()};
217
218 // Cancel any resolving.
219 mResolver.cancel();
220
221 // Stop the transaction.
225 std::placeholders::_1));
226 }
227 }
228
229 void
230 handleShutdown(boost::system::error_code const& ecResult)
231 {
232 if (ecResult)
233 {
234 JLOG(j_.trace()) << "Shutdown error: " << mDeqSites[0] << ": "
235 << ecResult.message();
236 }
237 }
238
239 void
241 boost::system::error_code const& ecResult,
242 boost::asio::ip::tcp::resolver::results_type result)
243 {
244 if (!mShutdown)
245 {
246 mShutdown = ecResult ? ecResult
247 : httpClientSSLContext->preConnectVerify(
249 }
250
251 if (mShutdown)
252 {
253 JLOG(j_.trace()) << "Resolve error: " << mDeqSites[0] << ": "
254 << mShutdown.message();
255
257 }
258 else
259 {
260 JLOG(j_.trace()) << "Resolve complete.";
261
262 boost::asio::async_connect(
264 result,
265 std::bind(
268 std::placeholders::_1));
269 }
270 }
271
272 void
273 handleConnect(boost::system::error_code const& ecResult)
274 {
275 if (!mShutdown)
276 mShutdown = ecResult;
277
278 if (mShutdown)
279 {
280 JLOG(j_.trace()) << "Connect error: " << mShutdown.message();
281 }
282
283 if (!mShutdown)
284 {
285 JLOG(j_.trace()) << "Connected.";
286
287 mShutdown = httpClientSSLContext->postConnectVerify(
289
290 if (mShutdown)
291 {
292 JLOG(j_.trace()) << "postConnectVerify: " << mDeqSites[0]
293 << ": " << mShutdown.message();
294 }
295 }
296
297 if (mShutdown)
298 {
300 }
301 else if (mSSL)
302 {
304 AutoSocket::ssl_socket::client,
305 std::bind(
308 std::placeholders::_1));
309 }
310 else
311 {
312 handleRequest(ecResult);
313 }
314 }
315
316 void
317 handleRequest(boost::system::error_code const& ecResult)
318 {
319 if (!mShutdown)
320 mShutdown = ecResult;
321
322 if (mShutdown)
323 {
324 JLOG(j_.trace()) << "Handshake error:" << mShutdown.message();
325
327 }
328 else
329 {
330 JLOG(j_.trace()) << "Session started.";
331
333
335 mRequest,
336 std::bind(
339 std::placeholders::_1,
340 std::placeholders::_2));
341 }
342 }
343
344 void
346 boost::system::error_code const& ecResult,
347 std::size_t bytes_transferred)
348 {
349 if (!mShutdown)
350 mShutdown = ecResult;
351
352 if (mShutdown)
353 {
354 JLOG(j_.trace()) << "Write error: " << mShutdown.message();
355
357 }
358 else
359 {
360 JLOG(j_.trace()) << "Wrote.";
361
363 mHeader,
364 "\r\n\r\n",
365 std::bind(
368 std::placeholders::_1,
369 std::placeholders::_2));
370 }
371 }
372
373 void
375 boost::system::error_code const& ecResult,
376 std::size_t bytes_transferred)
377 {
378 std::string strHeader{
381 JLOG(j_.trace()) << "Header: \"" << strHeader << "\"";
382
383 static boost::regex reStatus{
384 "\\`HTTP/1\\S+ (\\d{3}) .*\\'"}; // HTTP/1.1 200 OK
385 static boost::regex reSize{
386 "\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'", boost::regex::icase};
387 static boost::regex reBody{"\\`.*\\r\\n\\r\\n(.*)\\'"};
388
389 boost::smatch smMatch;
390 // Match status code.
391 if (!boost::regex_match(strHeader, smMatch, reStatus))
392 {
393 // XXX Use our own error code.
394 JLOG(j_.trace()) << "No status code";
395 invokeComplete(boost::system::error_code{
396 boost::system::errc::bad_address,
397 boost::system::system_category()});
398 return;
399 }
400
401 mStatus = beast::lexicalCastThrow<int>(std::string(smMatch[1]));
402
403 if (boost::regex_match(strHeader, smMatch, reBody)) // we got some body
404 mBody = smMatch[1];
405
406 std::size_t const responseSize = [&] {
407 if (boost::regex_match(strHeader, smMatch, reSize))
408 return beast::lexicalCast<std::size_t>(
409 std::string(smMatch[1]), maxResponseSize_);
410 return maxResponseSize_;
411 }();
412
413 if (responseSize > maxResponseSize_)
414 {
415 JLOG(j_.trace()) << "Response field too large";
416 invokeComplete(boost::system::error_code{
417 boost::system::errc::value_too_large,
418 boost::system::system_category()});
419 return;
420 }
421
422 if (responseSize == 0)
423 {
424 // no body wanted or available
425 invokeComplete(ecResult, mStatus);
426 }
427 else if (mBody.size() >= responseSize)
428 {
429 // we got the whole thing
430 invokeComplete(ecResult, mStatus, mBody);
431 }
432 else
433 {
435 mResponse.prepare(responseSize - mBody.size()),
436 boost::asio::transfer_all(),
437 std::bind(
440 std::placeholders::_1,
441 std::placeholders::_2));
442 }
443 }
444
445 void
447 boost::system::error_code const& ecResult,
448 std::size_t bytes_transferred)
449 {
450 if (!mShutdown)
451 mShutdown = ecResult;
452
453 if (mShutdown && mShutdown != boost::asio::error::eof)
454 {
455 JLOG(j_.trace()) << "Read error: " << mShutdown.message();
456
458 }
459 else
460 {
461 if (mShutdown)
462 {
463 JLOG(j_.trace()) << "Complete.";
464 }
465 else
466 {
467 mResponse.commit(bytes_transferred);
468 std::string strBody{
471 invokeComplete(ecResult, mStatus, mBody + strBody);
472 }
473 }
474 }
475
476 // Call cancel the deadline timer and invoke the completion routine.
477 void
479 boost::system::error_code const& ecResult,
480 int iStatus = 0,
481 std::string const& strData = "")
482 {
483 boost::system::error_code ecCancel;
484 try
485 {
486 mDeadline.cancel();
487 }
488 catch (boost::system::system_error const& e)
489 {
490 JLOG(j_.trace())
491 << "invokeComplete: Deadline cancel error: " << e.what();
492 ecCancel = e.code();
493 }
494
495 JLOG(j_.debug()) << "invokeComplete: Deadline popping: "
496 << mDeqSites.size();
497
498 if (!mDeqSites.empty())
499 {
501 }
502
503 bool bAgain = true;
504
505 if (mDeqSites.empty() || !ecResult)
506 {
507 // ecResult: !0 = had an error, last entry
508 // iStatus: result, if no error
509 // strData: data, if no error
510 bAgain = mComplete &&
511 mComplete(ecResult ? ecResult : ecCancel, iStatus, strData);
512 }
513
514 if (!mDeqSites.empty() && bAgain)
515 {
516 httpsNext();
517 }
518 }
519
520private:
522
523 bool mSSL;
525 boost::asio::ip::tcp::resolver mResolver;
526
527 struct Query
528 {
531 boost::asio::ip::resolver_query_base::flags flags;
532 };
534
535 boost::asio::streambuf mRequest;
536 boost::asio::streambuf mHeader;
537 boost::asio::streambuf mResponse;
539 unsigned short const mPort;
542 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)>
544 std::function<bool(
545 boost::system::error_code const& ecResult,
546 int iStatus,
547 std::string const& strData)>
549
550 boost::asio::basic_waitable_timer<std::chrono::steady_clock> mDeadline;
551
552 // If not success, we are shutting down.
553 boost::system::error_code mShutdown;
554
558};
559
560//------------------------------------------------------------------------------
561
562void
564 bool bSSL,
565 boost::asio::io_context& io_context,
567 unsigned short const port,
568 std::string const& strPath,
569 std::size_t responseMax,
570 std::chrono::seconds timeout,
571 std::function<bool(
572 boost::system::error_code const& ecResult,
573 int iStatus,
574 std::string const& strData)> complete,
576{
577 auto client =
578 std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
579 client->get(bSSL, deqSites, strPath, timeout, complete);
580}
581
582void
584 bool bSSL,
585 boost::asio::io_context& io_context,
586 std::string strSite,
587 unsigned short const port,
588 std::string const& strPath,
589 std::size_t responseMax,
590 std::chrono::seconds timeout,
591 std::function<bool(
592 boost::system::error_code const& ecResult,
593 int iStatus,
594 std::string const& strData)> complete,
596{
597 std::deque<std::string> deqSites(1, strSite);
598
599 auto client =
600 std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
601 client->get(bSSL, deqSites, strPath, timeout, complete);
602}
603
604void
606 bool bSSL,
607 boost::asio::io_context& io_context,
608 std::string strSite,
609 unsigned short const port,
610 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)>
611 setRequest,
612 std::size_t responseMax,
613 std::chrono::seconds timeout,
614 std::function<bool(
615 boost::system::error_code const& ecResult,
616 int iStatus,
617 std::string const& strData)> complete,
619{
620 std::deque<std::string> deqSites(1, strSite);
621
622 auto client =
623 std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
624 client->request(bSSL, deqSites, setRequest, timeout, complete);
625}
626
627} // namespace ripple
T bind(T... args)
void async_handshake(handshake_type type, callback cbFunc)
Definition AutoSocket.h:115
void async_write(Buf const &buffers, Handler handler)
Definition AutoSocket.h:221
lowest_layer_type & lowest_layer()
Definition AutoSocket.h:95
void async_read_until(Seq const &buffers, Condition condition, Handler handler)
Definition AutoSocket.h:181
void async_read(Buf const &buffers, Condition cond, Handler handler)
Definition AutoSocket.h:243
ssl_socket & SSLSocket()
Definition AutoSocket.h:72
void async_shutdown(ShutdownHandler handler)
Definition AutoSocket.h:148
A generic endpoint for log messages.
Definition Journal.h:60
Stream debug() const
Definition Journal.h:328
Stream trace() const
Severity stream access functions.
Definition Journal.h:322
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:39
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:43
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:25
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)