rippled
HTTPDownloader.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/net/HTTPDownloader.h>
21 #include <boost/asio/ssl.hpp>
22 
23 namespace ripple {
24 
26  boost::asio::io_service& io_service,
28  Config const& config)
29  : j_(j)
30  , config_(config)
31  , strand_(io_service)
32  , cancelDownloads_(false)
33  , sessionActive_(false)
34 {
35 }
36 
37 bool
39  std::string const& host,
40  std::string const& port,
41  std::string const& target,
42  int version,
43  boost::filesystem::path const& dstPath,
44  std::function<void(boost::filesystem::path)> complete,
45  bool ssl)
46 {
47  if (!checkPath(dstPath))
48  return false;
49 
50  {
51  std::lock_guard lock(m_);
52 
53  if (cancelDownloads_)
54  return true;
55 
56  sessionActive_ = true;
57  }
58 
59  if (!strand_.running_in_this_thread())
60  strand_.post(std::bind(
62  this,
63  host,
64  port,
65  target,
66  version,
67  dstPath,
68  complete,
69  ssl));
70  else
71  boost::asio::spawn(
72  strand_,
73  std::bind(
75  this,
76  host,
77  port,
78  target,
79  version,
80  dstPath,
81  complete,
82  ssl,
83  std::placeholders::_1));
84  return true;
85 }
86 
87 void
89 {
90  std::unique_lock lock(m_);
91 
92  cancelDownloads_ = true;
93 
94  if (sessionActive_)
95  {
96  // Wait for the handler to exit.
97  c_.wait(lock, [this]() { return !sessionActive_; });
98  }
99 }
100 
101 void
103  std::string const host,
104  std::string const port,
105  std::string const target,
106  int version,
107  boost::filesystem::path dstPath,
108  std::function<void(boost::filesystem::path)> complete,
109  bool ssl,
110  boost::asio::yield_context yield)
111 {
112  using namespace boost::asio;
113  using namespace boost::beast;
114 
115  boost::system::error_code ec;
116  bool skip = false;
117 
119  // Define lambdas for encapsulating download
120  // operations:
121  auto close = [&](auto p) {
122  closeBody(p);
123 
124  // Gracefully close the stream
125  stream_->getStream().shutdown(socket_base::shutdown_both, ec);
126  if (ec == boost::asio::error::eof)
127  ec.assign(0, ec.category());
128  if (ec)
129  {
130  // Most web servers don't bother with performing
131  // the SSL shutdown handshake, for speed.
132  JLOG(j_.trace()) << "shutdown: " << ec.message();
133  }
134 
135  // The stream cannot be reused
136  stream_.reset();
137  };
138 
139  // When the downloader is being stopped
140  // because the server is shutting down,
141  // this method notifies a 'Stoppable'
142  // object that the session has ended.
143  auto exit = [this]() {
145  sessionActive_ = false;
146  c_.notify_one();
147  };
148 
149  auto failAndExit = [&exit, &dstPath, complete, &ec, this](
150  std::string const& errMsg, auto p) {
151  exit();
152  fail(dstPath, complete, ec, errMsg, p);
153  };
154  // end lambdas
156 
157  if (cancelDownloads_.load())
158  return exit();
159 
160  auto p = this->getParser(dstPath, complete, ec);
161  if (ec)
162  return failAndExit("getParser", p);
163 
165  // Prepare for download and establish the
166  // connection:
167  std::uint64_t const rangeStart = size(p);
168 
169  stream_ = ssl ? HTTPStream::makeUnique<SSLStream>(config_, strand_, j_)
170  : HTTPStream::makeUnique<RawStream>(config_, strand_, j_);
171 
172  std::string error;
173  if (!stream_->connect(error, host, port, yield))
174  return failAndExit(error, p);
175 
176  // Set up an HTTP HEAD request message to find the file size
177  http::request<http::empty_body> req{http::verb::head, target, version};
178  req.set(http::field::host, host);
179  req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
180 
181  // Requesting a portion of the file
182  if (rangeStart)
183  {
184  req.set(
185  http::field::range,
186  (boost::format("bytes=%llu-") % rangeStart).str());
187  }
188 
189  stream_->asyncWrite(req, yield, ec);
190  if (ec)
191  return failAndExit("async_write", p);
192 
193  {
194  // Read the response
195  http::response_parser<http::empty_body> connectParser;
196  connectParser.skip(true);
197  stream_->asyncRead(read_buf_, connectParser, false, yield, ec);
198  if (ec)
199  return failAndExit("async_read", p);
200 
201  // Range request was rejected
202  if (connectParser.get().result() == http::status::range_not_satisfiable)
203  {
204  req.erase(http::field::range);
205 
206  stream_->asyncWrite(req, yield, ec);
207  if (ec)
208  return failAndExit("async_write_range_verify", p);
209 
210  http::response_parser<http::empty_body> rangeParser;
211  rangeParser.skip(true);
212 
213  stream_->asyncRead(read_buf_, rangeParser, false, yield, ec);
214  if (ec)
215  return failAndExit("async_read_range_verify", p);
216 
217  // The entire file is downloaded already.
218  if (rangeParser.content_length() == rangeStart)
219  skip = true;
220  else
221  return failAndExit("range_not_satisfiable", p);
222  }
223  else if (
224  rangeStart &&
225  connectParser.get().result() != http::status::partial_content)
226  {
227  ec.assign(
228  boost::system::errc::not_supported,
229  boost::system::generic_category());
230 
231  return failAndExit("Range request ignored", p);
232  }
233  else if (auto len = connectParser.content_length())
234  {
235  try
236  {
237  // Ensure sufficient space is available
238  if (*len > space(dstPath.parent_path()).available)
239  {
240  return failAndExit(
241  "Insufficient disk space for download", p);
242  }
243  }
244  catch (std::exception const& e)
245  {
246  return failAndExit(std::string("exception: ") + e.what(), p);
247  }
248  }
249  }
250 
251  if (!skip)
252  {
253  // Set up an HTTP GET request message to download the file
254  req.method(http::verb::get);
255 
256  if (rangeStart)
257  {
258  req.set(
259  http::field::range,
260  (boost::format("bytes=%llu-") % rangeStart).str());
261  }
262  }
263 
264  stream_->asyncWrite(req, yield, ec);
265  if (ec)
266  return failAndExit("async_write", p);
267 
268  // end prepare and connect
270 
271  if (skip)
272  p->skip(true);
273 
274  // Download the file
275  while (!p->is_done())
276  {
277  if (cancelDownloads_.load())
278  {
279  close(p);
280  return exit();
281  }
282 
283  stream_->asyncRead(read_buf_, *p, true, yield, ec);
284  }
285 
286  JLOG(j_.trace()) << "download completed: " << dstPath.string();
287 
288  close(p);
289  exit();
290 
291  // Notify the completion handler
292  complete(std::move(dstPath));
293 }
294 
295 void
297  boost::filesystem::path dstPath,
298  std::function<void(boost::filesystem::path)> const& complete,
299  boost::system::error_code const& ec,
300  std::string const& errMsg,
302 {
303  if (!ec)
304  {
305  JLOG(j_.error()) << errMsg;
306  }
307  else if (ec != boost::asio::error::operation_aborted)
308  {
309  JLOG(j_.error()) << errMsg << ": " << ec.message();
310  }
311 
312  if (parser)
313  closeBody(parser);
314 
315  try
316  {
317  remove(dstPath);
318  }
319  catch (std::exception const& e)
320  {
321  JLOG(j_.error()) << "exception: " << e.what()
322  << " in function: " << __func__;
323  }
324  complete(std::move(dstPath));
325 }
326 
327 } // namespace ripple
ripple::HTTPDownloader::getParser
virtual std::shared_ptr< parser > getParser(boost::filesystem::path dstPath, std::function< void(boost::filesystem::path)> complete, boost::system::error_code &ec)=0
ripple::HTTPDownloader::checkPath
virtual bool checkPath(boost::filesystem::path const &dstPath)=0
std::bind
T bind(T... args)
ripple::HTTPDownloader::c_
std::condition_variable c_
Definition: HTTPDownloader.h:92
std::string
STL class.
std::shared_ptr
STL class.
std::exception
STL class.
ripple::HTTPDownloader::fail
void fail(boost::filesystem::path dstPath, std::function< void(boost::filesystem::path)> const &complete, boost::system::error_code const &ec, std::string const &errMsg, std::shared_ptr< parser > parser)
Definition: HTTPDownloader.cpp:296
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
ripple::HTTPDownloader::read_buf_
boost::beast::flat_buffer read_buf_
Definition: HTTPDownloader.h:86
std::lock_guard
STL class.
ripple::HTTPDownloader::closeBody
virtual void closeBody(std::shared_ptr< parser > p)=0
std::function
ripple::HTTPDownloader::m_
std::mutex m_
Definition: HTTPDownloader.h:90
ripple::HTTPDownloader::stream_
std::unique_ptr< HTTPStream > stream_
Definition: HTTPDownloader.h:85
ripple::HTTPDownloader::HTTPDownloader
HTTPDownloader(boost::asio::io_service &io_service, beast::Journal j, Config const &config)
Definition: HTTPDownloader.cpp:25
std::atomic::load
T load(T... args)
ripple::Config
Definition: Config.h:67
std::unique_lock
STL class.
ripple::HTTPDownloader::config_
Config const & config_
Definition: HTTPDownloader.h:83
boost::asio
Definition: Overlay.h:42
beast::Journal::error
Stream error() const
Definition: Journal.h:333
ripple::HTTPDownloader::cancelDownloads_
std::atomic< bool > cancelDownloads_
Definition: HTTPDownloader.h:87
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint64_t
std::condition_variable::wait
T wait(T... args)
std::condition_variable::notify_one
T notify_one(T... args)
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::HTTPDownloader::onStop
void onStop()
Definition: HTTPDownloader.cpp:88
ripple::HTTPDownloader::download
bool download(std::string const &host, std::string const &port, std::string const &target, int version, boost::filesystem::path const &dstPath, std::function< void(boost::filesystem::path)> complete, bool ssl=true)
Definition: HTTPDownloader.cpp:38
ripple::HTTPDownloader::j_
const beast::Journal j_
Definition: HTTPDownloader.h:72
ripple::HTTPDownloader::parser
boost::beast::http::basic_parser< false > parser
Definition: HTTPDownloader.h:70
ripple::HTTPDownloader::do_session
void do_session(std::string host, std::string port, std::string target, int version, boost::filesystem::path dstPath, std::function< void(boost::filesystem::path)> complete, bool ssl, boost::asio::yield_context yield)
Definition: HTTPDownloader.cpp:102
ripple::HTTPDownloader::size
virtual uint64_t size(std::shared_ptr< parser > p)=0
ripple::HTTPDownloader::sessionActive_
bool sessionActive_
Definition: HTTPDownloader.h:91
ripple::HTTPDownloader::strand_
boost::asio::io_service::strand strand_
Definition: HTTPDownloader.h:84
std::exception::what
T what(T... args)