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