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