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