rippled
Loading...
Searching...
No Matches
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 <xrpld/net/AutoSocket.h>
21#include <xrpld/net/HTTPClient.h>
22#include <xrpld/net/HTTPClientSSLContext.h>
23
24#include <xrpl/basics/Log.h>
25#include <xrpl/beast/core/LexicalCast.h>
26
27#include <boost/asio.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{
41 httpClientSSLContext.emplace(config, j);
42}
43
44//------------------------------------------------------------------------------
45//
46// Fetch a web page via http or https.
47//
48//------------------------------------------------------------------------------
49
50class HTTPClientImp : public std::enable_shared_from_this<HTTPClientImp>,
51 public HTTPClient
52{
53public:
55 boost::asio::io_service& io_service,
56 const unsigned short port,
57 std::size_t maxResponseSize,
59 : mSocket(io_service, httpClientSSLContext->context())
60 , mResolver(io_service)
62 , mPort(port)
63 , maxResponseSize_(maxResponseSize)
64 , mDeadline(io_service)
65 , j_(j)
66 {
67 }
68
69 //--------------------------------------------------------------------------
70
71 void
73 std::string const& strPath,
74 boost::asio::streambuf& sb,
75 std::string const& strHost)
76 {
77 std::ostream osRequest(&sb);
78
79 osRequest << "GET " << strPath
80 << " HTTP/1.0\r\n"
81 "Host: "
82 << strHost
83 << "\r\n"
84 "Accept: */*\r\n" // YYY Do we need this line?
85 "Connection: close\r\n\r\n";
86 }
87
88 //--------------------------------------------------------------------------
89
90 void
92 bool bSSL,
95 void(boost::asio::streambuf& sb, std::string const& strHost)> build,
97 std::function<bool(
98 const boost::system::error_code& ecResult,
99 int iStatus,
100 std::string const& strData)> complete)
101 {
102 mSSL = bSSL;
103 mDeqSites = deqSites;
104 mBuild = build;
105 mComplete = complete;
106 mTimeout = timeout;
107
108 httpsNext();
109 }
110
111 //--------------------------------------------------------------------------
112
113 void
114 get(bool bSSL,
116 std::string const& strPath,
117 std::chrono::seconds timeout,
118 std::function<bool(
119 const boost::system::error_code& ecResult,
120 int iStatus,
121 std::string const& strData)> complete)
122 {
123 mComplete = complete;
124 mTimeout = timeout;
125
126 request(
127 bSSL,
128 deqSites,
129 std::bind(
132 strPath,
133 std::placeholders::_1,
134 std::placeholders::_2),
135 timeout,
136 complete);
137 }
138
139 //--------------------------------------------------------------------------
140
141 void
143 {
144 JLOG(j_.trace()) << "Fetch: " << mDeqSites[0];
145
146 auto query = std::make_shared<boost::asio::ip::tcp::resolver::query>(
147 mDeqSites[0],
149 boost::asio::ip::resolver_query_base::numeric_service);
150 mQuery = query;
151
152 mDeadline.expires_from_now(mTimeout, mShutdown);
153
154 JLOG(j_.trace()) << "expires_from_now: " << mShutdown.message();
155
156 if (!mShutdown)
157 {
158 mDeadline.async_wait(std::bind(
161 std::placeholders::_1));
162 }
163
164 if (!mShutdown)
165 {
166 JLOG(j_.trace()) << "Resolving: " << mDeqSites[0];
167
168 mResolver.async_resolve(
169 *mQuery,
170 std::bind(
173 std::placeholders::_1,
174 std::placeholders::_2));
175 }
176
177 if (mShutdown)
179 }
180
181 void
182 handleDeadline(const boost::system::error_code& ecResult)
183 {
184 if (ecResult == boost::asio::error::operation_aborted)
185 {
186 // Timer canceled because deadline no longer needed.
187 JLOG(j_.trace()) << "Deadline cancelled.";
188
189 // Aborter is done.
190 }
191 else if (ecResult)
192 {
193 JLOG(j_.trace()) << "Deadline error: " << mDeqSites[0] << ": "
194 << ecResult.message();
195
196 // Can't do anything sound.
197 abort();
198 }
199 else
200 {
201 JLOG(j_.trace()) << "Deadline arrived.";
202
203 // Mark us as shutting down.
204 // XXX Use our own error code.
205 mShutdown = boost::system::error_code{
206 boost::system::errc::bad_address,
207 boost::system::system_category()};
208
209 // Cancel any resolving.
210 mResolver.cancel();
211
212 // Stop the transaction.
216 std::placeholders::_1));
217 }
218 }
219
220 void
221 handleShutdown(const boost::system::error_code& ecResult)
222 {
223 if (ecResult)
224 {
225 JLOG(j_.trace()) << "Shutdown error: " << mDeqSites[0] << ": "
226 << ecResult.message();
227 }
228 }
229
230 void
232 const boost::system::error_code& ecResult,
233 boost::asio::ip::tcp::resolver::iterator itrEndpoint)
234 {
235 if (!mShutdown)
236 {
237 mShutdown = ecResult ? ecResult
238 : httpClientSSLContext->preConnectVerify(
240 }
241
242 if (mShutdown)
243 {
244 JLOG(j_.trace()) << "Resolve error: " << mDeqSites[0] << ": "
245 << mShutdown.message();
246
248 }
249 else
250 {
251 JLOG(j_.trace()) << "Resolve complete.";
252
253 boost::asio::async_connect(
255 itrEndpoint,
256 std::bind(
259 std::placeholders::_1));
260 }
261 }
262
263 void
264 handleConnect(const boost::system::error_code& ecResult)
265 {
266 if (!mShutdown)
267 mShutdown = ecResult;
268
269 if (mShutdown)
270 {
271 JLOG(j_.trace()) << "Connect error: " << mShutdown.message();
272 }
273
274 if (!mShutdown)
275 {
276 JLOG(j_.trace()) << "Connected.";
277
278 mShutdown = httpClientSSLContext->postConnectVerify(
280
281 if (mShutdown)
282 {
283 JLOG(j_.trace()) << "postConnectVerify: " << mDeqSites[0]
284 << ": " << mShutdown.message();
285 }
286 }
287
288 if (mShutdown)
289 {
291 }
292 else if (mSSL)
293 {
295 AutoSocket::ssl_socket::client,
296 std::bind(
299 std::placeholders::_1));
300 }
301 else
302 {
303 handleRequest(ecResult);
304 }
305 }
306
307 void
308 handleRequest(const boost::system::error_code& ecResult)
309 {
310 if (!mShutdown)
311 mShutdown = ecResult;
312
313 if (mShutdown)
314 {
315 JLOG(j_.trace()) << "Handshake error:" << mShutdown.message();
316
318 }
319 else
320 {
321 JLOG(j_.trace()) << "Session started.";
322
324
326 mRequest,
327 std::bind(
330 std::placeholders::_1,
331 std::placeholders::_2));
332 }
333 }
334
335 void
337 const boost::system::error_code& ecResult,
338 std::size_t bytes_transferred)
339 {
340 if (!mShutdown)
341 mShutdown = ecResult;
342
343 if (mShutdown)
344 {
345 JLOG(j_.trace()) << "Write error: " << mShutdown.message();
346
348 }
349 else
350 {
351 JLOG(j_.trace()) << "Wrote.";
352
354 mHeader,
355 "\r\n\r\n",
356 std::bind(
359 std::placeholders::_1,
360 std::placeholders::_2));
361 }
362 }
363
364 void
366 const boost::system::error_code& ecResult,
367 std::size_t bytes_transferred)
368 {
369 std::string strHeader{
372 JLOG(j_.trace()) << "Header: \"" << strHeader << "\"";
373
374 static boost::regex reStatus{
375 "\\`HTTP/1\\S+ (\\d{3}) .*\\'"}; // HTTP/1.1 200 OK
376 static boost::regex reSize{
377 "\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'"};
378 static boost::regex reBody{"\\`.*\\r\\n\\r\\n(.*)\\'"};
379
380 boost::smatch smMatch;
381 // Match status code.
382 if (!boost::regex_match(strHeader, smMatch, reStatus))
383 {
384 // XXX Use our own error code.
385 JLOG(j_.trace()) << "No status code";
386 invokeComplete(boost::system::error_code{
387 boost::system::errc::bad_address,
388 boost::system::system_category()});
389 return;
390 }
391
392 mStatus = beast::lexicalCastThrow<int>(std::string(smMatch[1]));
393
394 if (boost::regex_match(strHeader, smMatch, reBody)) // we got some body
395 mBody = smMatch[1];
396
397 std::size_t const responseSize = [&] {
398 if (boost::regex_match(strHeader, smMatch, reSize))
399 return beast::lexicalCast<std::size_t>(
400 std::string(smMatch[1]), maxResponseSize_);
401 return maxResponseSize_;
402 }();
403
404 if (responseSize > maxResponseSize_)
405 {
406 JLOG(j_.trace()) << "Response field too large";
407 invokeComplete(boost::system::error_code{
408 boost::system::errc::value_too_large,
409 boost::system::system_category()});
410 return;
411 }
412
413 if (responseSize == 0)
414 {
415 // no body wanted or available
416 invokeComplete(ecResult, mStatus);
417 }
418 else if (mBody.size() >= responseSize)
419 {
420 // we got the whole thing
421 invokeComplete(ecResult, mStatus, mBody);
422 }
423 else
424 {
426 mResponse.prepare(responseSize - mBody.size()),
427 boost::asio::transfer_all(),
428 std::bind(
431 std::placeholders::_1,
432 std::placeholders::_2));
433 }
434 }
435
436 void
438 const boost::system::error_code& ecResult,
439 std::size_t bytes_transferred)
440 {
441 if (!mShutdown)
442 mShutdown = ecResult;
443
444 if (mShutdown && mShutdown != boost::asio::error::eof)
445 {
446 JLOG(j_.trace()) << "Read error: " << mShutdown.message();
447
449 }
450 else
451 {
452 if (mShutdown)
453 {
454 JLOG(j_.trace()) << "Complete.";
455 }
456 else
457 {
458 mResponse.commit(bytes_transferred);
459 std::string strBody{
462 invokeComplete(ecResult, mStatus, mBody + strBody);
463 }
464 }
465 }
466
467 // Call cancel the deadline timer and invoke the completion routine.
468 void
470 const boost::system::error_code& ecResult,
471 int iStatus = 0,
472 std::string const& strData = "")
473 {
474 boost::system::error_code ecCancel;
475
476 (void)mDeadline.cancel(ecCancel);
477
478 if (ecCancel)
479 {
480 JLOG(j_.trace()) << "invokeComplete: Deadline cancel error: "
481 << ecCancel.message();
482 }
483
484 JLOG(j_.debug()) << "invokeComplete: Deadline popping: "
485 << mDeqSites.size();
486
487 if (!mDeqSites.empty())
488 {
490 }
491
492 bool bAgain = true;
493
494 if (mDeqSites.empty() || !ecResult)
495 {
496 // ecResult: !0 = had an error, last entry
497 // iStatus: result, if no error
498 // strData: data, if no error
499 bAgain = mComplete &&
500 mComplete(ecResult ? ecResult : ecCancel, iStatus, strData);
501 }
502
503 if (!mDeqSites.empty() && bAgain)
504 {
505 httpsNext();
506 }
507 }
508
509private:
511
512 bool mSSL;
514 boost::asio::ip::tcp::resolver mResolver;
516 boost::asio::streambuf mRequest;
517 boost::asio::streambuf mHeader;
518 boost::asio::streambuf mResponse;
520 const unsigned short mPort;
523 std::function<void(boost::asio::streambuf& sb, std::string const& strHost)>
525 std::function<bool(
526 const boost::system::error_code& 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_service& io_service,
548 const unsigned short port,
549 std::string const& strPath,
550 std::size_t responseMax,
551 std::chrono::seconds timeout,
552 std::function<bool(
553 const boost::system::error_code& ecResult,
554 int iStatus,
555 std::string const& strData)> complete,
557{
558 auto client =
559 std::make_shared<HTTPClientImp>(io_service, port, responseMax, j);
560 client->get(bSSL, deqSites, strPath, timeout, complete);
561}
562
563void
565 bool bSSL,
566 boost::asio::io_service& io_service,
567 std::string strSite,
568 const unsigned short port,
569 std::string const& strPath,
570 std::size_t responseMax,
571 std::chrono::seconds timeout,
572 std::function<bool(
573 const boost::system::error_code& 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_service, port, responseMax, j);
582 client->get(bSSL, deqSites, strPath, timeout, complete);
583}
584
585void
587 bool bSSL,
588 boost::asio::io_service& io_service,
589 std::string strSite,
590 const unsigned short 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 const boost::system::error_code& 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_service, 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:115
void async_read_until(const Seq &buffers, Condition condition, Handler handler)
Definition: AutoSocket.h:181
lowest_layer_type & lowest_layer()
Definition: AutoSocket.h:95
void async_write(const Buf &buffers, Handler handler)
Definition: AutoSocket.h:221
void async_read(const Buf &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(const boost::system::error_code &ecResult, std::size_t bytes_transferred)
Definition: HTTPClient.cpp:336
void handleConnect(const boost::system::error_code &ecResult)
Definition: HTTPClient.cpp:264
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(const boost::system::error_code &ecResult, int iStatus, std::string const &strData)> complete)
Definition: HTTPClient.cpp:91
void makeGet(std::string const &strPath, boost::asio::streambuf &sb, std::string const &strHost)
Definition: HTTPClient.cpp:72
void handleRequest(const boost::system::error_code &ecResult)
Definition: HTTPClient.cpp:308
void get(bool bSSL, std::deque< std::string > deqSites, std::string const &strPath, std::chrono::seconds timeout, std::function< bool(const boost::system::error_code &ecResult, int iStatus, std::string const &strData)> complete)
Definition: HTTPClient.cpp:114
void invokeComplete(const boost::system::error_code &ecResult, int iStatus=0, std::string const &strData="")
Definition: HTTPClient.cpp:469
void handleDeadline(const boost::system::error_code &ecResult)
Definition: HTTPClient.cpp:182
void handleResolve(const boost::system::error_code &ecResult, boost::asio::ip::tcp::resolver::iterator itrEndpoint)
Definition: HTTPClient.cpp:231
std::chrono::seconds mTimeout
Definition: HTTPClient.cpp:537
void handleShutdown(const boost::system::error_code &ecResult)
Definition: HTTPClient.cpp:221
std::function< void(boost::asio::streambuf &sb, std::string const &strHost)> mBuild
Definition: HTTPClient.cpp:524
std::size_t const maxResponseSize_
Definition: HTTPClient.cpp:521
std::shared_ptr< boost::asio::ip::tcp::resolver::query > mQuery
Definition: HTTPClient.cpp:515
boost::asio::streambuf mHeader
Definition: HTTPClient.cpp:517
boost::asio::ip::tcp::resolver mResolver
Definition: HTTPClient.cpp:514
boost::asio::streambuf mRequest
Definition: HTTPClient.cpp:516
boost::asio::basic_waitable_timer< std::chrono::steady_clock > mDeadline
Definition: HTTPClient.cpp:531
std::deque< std::string > mDeqSites
Definition: HTTPClient.cpp:536
void handleHeader(const boost::system::error_code &ecResult, std::size_t bytes_transferred)
Definition: HTTPClient.cpp:365
void handleData(const boost::system::error_code &ecResult, std::size_t bytes_transferred)
Definition: HTTPClient.cpp:437
boost::asio::streambuf mResponse
Definition: HTTPClient.cpp:518
const unsigned short mPort
Definition: HTTPClient.cpp:520
boost::system::error_code mShutdown
Definition: HTTPClient.cpp:534
std::function< bool(const boost::system::error_code &ecResult, int iStatus, std::string const &strData)> mComplete
Definition: HTTPClient.cpp:529
HTTPClientImp(boost::asio::io_service &io_service, const unsigned short port, std::size_t maxResponseSize, beast::Journal &j)
Definition: HTTPClient.cpp:54
beast::Journal j_
Definition: HTTPClient.cpp:538
Provides an asynchronous HTTP client implementation with optional SSL.
Definition: HTTPClient.h:40
static void initializeSSLContext(Config const &config, beast::Journal j)
Definition: HTTPClient.cpp:39
static void request(bool bSSL, boost::asio::io_service &io_service, std::string strSite, const unsigned short port, std::function< void(boost::asio::streambuf &sb, std::string const &strHost)> build, std::size_t responseMax, std::chrono::seconds timeout, std::function< bool(const boost::system::error_code &ecResult, int iStatus, std::string const &strData)> complete, beast::Journal &j)
Definition: HTTPClient.cpp:586
static void get(bool bSSL, boost::asio::io_service &io_service, std::deque< std::string > deqSites, const unsigned short port, std::string const &strPath, std::size_t responseMax, std::chrono::seconds timeout, std::function< bool(const boost::system::error_code &ecResult, int iStatus, std::string const &strData)> complete, beast::Journal &j)
Definition: HTTPClient.cpp:544
static constexpr auto maxClientHeaderBytes
Definition: HTTPClient.h:44
T empty(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
static std::optional< HTTPClientSSLContext > httpClientSSLContext
Definition: HTTPClient.cpp:36
T pop_front(T... args)
T size(T... args)
T to_string(T... args)