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 caller of `onStop`
127  // (`RPC::ShardArchiveHandler` to be specific)
128  // that the session has ended.
129  auto exit = [this, &dstPath, complete] {
130  if (!stop_)
131  complete(std::move(dstPath));
132 
133  std::lock_guard lock(m_);
134  sessionActive_ = false;
135  c_.notify_one();
136  };
137 
138  auto failAndExit = [&exit, &dstPath, complete, &ec, this](
139  std::string const& errMsg, auto p) {
140  fail(dstPath, ec, errMsg, p);
141  exit();
142  };
143  // end lambdas
145 
146  if (stop_.load())
147  return exit();
148 
149  auto p = this->getParser(dstPath, complete, ec);
150  if (ec)
151  return failAndExit("getParser", p);
152 
154  // Prepare for download and establish the
155  // connection:
156  if (ssl)
157  stream_ = std::make_unique<SSLStream>(config_, strand_, j_);
158  else
159  stream_ = std::make_unique<RawStream>(strand_);
160 
161  std::string error;
162  if (!stream_->connect(error, host, port, yield))
163  return failAndExit(error, p);
164 
165  // Set up an HTTP HEAD request message to find the file size
166  http::request<http::empty_body> req{http::verb::head, target, version};
167  req.set(http::field::host, host);
168  req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
169 
170  std::uint64_t const rangeStart = size(p);
171 
172  // Requesting a portion of the file
173  if (rangeStart)
174  {
175  req.set(
176  http::field::range,
177  (boost::format("bytes=%llu-") % rangeStart).str());
178  }
179 
180  stream_->asyncWrite(req, yield, ec);
181  if (ec)
182  return failAndExit("async_write", p);
183 
184  {
185  // Read the response
186  http::response_parser<http::empty_body> connectParser;
187  connectParser.skip(true);
188  stream_->asyncRead(read_buf_, connectParser, yield, ec);
189  if (ec)
190  return failAndExit("async_read", p);
191 
192  // Range request was rejected
193  if (connectParser.get().result() == http::status::range_not_satisfiable)
194  {
195  req.erase(http::field::range);
196 
197  stream_->asyncWrite(req, yield, ec);
198  if (ec)
199  return failAndExit("async_write_range_verify", p);
200 
201  http::response_parser<http::empty_body> rangeParser;
202  rangeParser.skip(true);
203 
204  stream_->asyncRead(read_buf_, rangeParser, yield, ec);
205  if (ec)
206  return failAndExit("async_read_range_verify", p);
207 
208  // The entire file is downloaded already.
209  if (rangeParser.content_length() == rangeStart)
210  skip = true;
211  else
212  return failAndExit("range_not_satisfiable", p);
213  }
214  else if (
215  rangeStart &&
216  connectParser.get().result() != http::status::partial_content)
217  {
218  ec.assign(
219  boost::system::errc::not_supported,
220  boost::system::generic_category());
221 
222  return failAndExit("Range request ignored", p);
223  }
224  else if (auto len = connectParser.content_length())
225  {
226  try
227  {
228  // Ensure sufficient space is available
229  if (*len > space(dstPath.parent_path()).available)
230  {
231  return failAndExit(
232  "Insufficient disk space for download", p);
233  }
234  }
235  catch (std::exception const& e)
236  {
237  return failAndExit(std::string("exception: ") + e.what(), p);
238  }
239  }
240  }
241 
242  if (!skip)
243  {
244  // Set up an HTTP GET request message to download the file
245  req.method(http::verb::get);
246 
247  if (rangeStart)
248  {
249  req.set(
250  http::field::range,
251  (boost::format("bytes=%llu-") % rangeStart).str());
252  }
253  }
254 
255  stream_->asyncWrite(req, yield, ec);
256  if (ec)
257  return failAndExit("async_write", p);
258 
259  // end prepare and connect
261 
262  if (skip)
263  p->skip(true);
264 
265  // Download the file
266  while (!p->is_done())
267  {
268  if (stop_.load())
269  {
270  close(p);
271  return exit();
272  }
273 
274  stream_->asyncReadSome(read_buf_, *p, yield, ec);
275  }
276 
277  JLOG(j_.trace()) << "download completed: " << dstPath.string();
278 
279  close(p);
280  exit();
281 }
282 
283 void
285 {
286  stop_ = true;
287 
288  std::unique_lock lock(m_);
289  if (sessionActive_)
290  {
291  // Wait for the handler to exit.
292  c_.wait(lock, [this]() { return !sessionActive_; });
293  }
294 }
295 
296 bool
298 {
299  std::lock_guard lock(m_);
300  return sessionActive_;
301 }
302 
303 bool
305 {
306  std::lock_guard lock(m_);
307  return stop_;
308 }
309 
310 void
312  boost::filesystem::path dstPath,
313  boost::system::error_code const& ec,
314  std::string const& errMsg,
316 {
317  if (!ec)
318  {
319  JLOG(j_.error()) << errMsg;
320  }
321  else if (ec != boost::asio::error::operation_aborted)
322  {
323  JLOG(j_.error()) << errMsg << ": " << ec.message();
324  }
325 
326  if (parser)
327  closeBody(parser);
328 
329  try
330  {
331  remove(dstPath);
332  }
333  catch (std::exception const& e)
334  {
335  JLOG(j_.error()) << "exception: " << e.what()
336  << " in function: " << __func__;
337  }
338 }
339 
340 } // 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::ShardState::complete
@ complete
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:99
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:93
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:94
ripple::HTTPDownloader::stop
void stop()
Definition: HTTPDownloader.cpp:284
ripple::HTTPDownloader::m_
std::mutex m_
Definition: HTTPDownloader.h:97
ripple::HTTPDownloader::stream_
std::unique_ptr< HTTPStream > stream_
Definition: HTTPDownloader.h:92
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:89
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:311
std::unique_lock
STL class.
ripple::HTTPDownloader::config_
Config const & config_
Definition: HTTPDownloader.h:90
boost::asio
Definition: Overlay.h:41
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)
ripple::HTTPDownloader::sessionIsActive
bool sessionIsActive() const
Definition: HTTPDownloader.cpp:297
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::isStopping
bool isStopping() const
Definition: HTTPDownloader.cpp:304
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:80
ripple::HTTPDownloader::parser
boost::beast::http::basic_parser< false > parser
Definition: HTTPDownloader.h:78
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:98
ripple::HTTPDownloader::strand_
boost::asio::io_service::strand strand_
Definition: HTTPDownloader.h:91
std::exception::what
T what(T... args)