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