http_download.hpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /*
  2. * Copyright (c) 2017-2023 zhllxt
  3. *
  4. * author : zhllxt
  5. * email : 37792738@qq.com
  6. *
  7. * Distributed under the Boost Software License, Version 1.0. (See accompanying
  8. * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  9. */
  10. #ifndef __ASIO2_HTTP_DOWNLOAD_HPP__
  11. #define __ASIO2_HTTP_DOWNLOAD_HPP__
  12. #if defined(_MSC_VER) && (_MSC_VER >= 1200)
  13. #pragma once
  14. #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
  15. #include <asio2/base/detail/push_options.hpp>
  16. #include <fstream>
  17. #include <asio2/base/detail/function_traits.hpp>
  18. #include <asio2/util/string.hpp>
  19. #include <asio2/http/detail/http_util.hpp>
  20. #include <asio2/http/detail/http_make.hpp>
  21. #include <asio2/http/detail/http_traits.hpp>
  22. #include <asio2/component/socks/socks5_client_cp.hpp>
  23. namespace asio2::detail
  24. {
  25. template<class derived_t, class args_t, bool Enable = is_http_execute_download_enabled<args_t>::value()>
  26. struct http_download_impl_bridge;
  27. template<class derived_t, class args_t>
  28. struct http_download_impl_bridge<derived_t, args_t, false>
  29. {
  30. };
  31. template<class derived_t, class args_t>
  32. struct http_download_impl_bridge<derived_t, args_t, true>
  33. {
  34. protected:
  35. template<typename String, typename StrOrInt, class HeaderCallback, class BodyCallback, class Proxy,
  36. class Body, class Fields, class Buffer>
  37. static void _download_with_socks5(
  38. asio::io_context& ioc, asio::ip::tcp::resolver& resolver, asio::ip::tcp::socket& socket,
  39. Buffer& buffer,
  40. String&& host, StrOrInt&& port,
  41. http::request<Body, Fields>& req, HeaderCallback&& cbh, BodyCallback&& cbb, Proxy&& proxy)
  42. {
  43. auto sk5 = detail::to_shared_ptr(std::forward<Proxy>(proxy));
  44. std::string_view h{ sk5->host() };
  45. std::string_view p{ sk5->port() };
  46. if (static_cast<int>(sk5->command()) == 0)
  47. {
  48. sk5->command(socks5::command::connect);
  49. }
  50. // Look up the domain name
  51. resolver.async_resolve(h, p, [&, s5 = std::move(sk5)]
  52. (const error_code& ec1, const asio::ip::tcp::resolver::results_type& endpoints) mutable
  53. {
  54. if (ec1) { set_last_error(ec1); return; }
  55. // Make the connection on the IP address we get from a lookup
  56. asio::async_connect(socket, endpoints,
  57. [&, s5 = std::move(s5)](const error_code& ec2, const asio::ip::tcp::endpoint&) mutable
  58. {
  59. if (ec2) { set_last_error(ec2); return; }
  60. socks5_async_handshake
  61. (
  62. detail::to_string(std::forward<String >(host)),
  63. detail::to_string(std::forward<StrOrInt>(port)),
  64. socket,
  65. std::move(s5),
  66. [&](error_code ecs5, std::string, std::string) mutable
  67. {
  68. if (ecs5) { set_last_error(ecs5); return; }
  69. http::async_write(socket, req, [&](const error_code & ec3, std::size_t) mutable
  70. {
  71. if (ec3) { set_last_error(ec3); return; }
  72. http::read_large_body<false>(socket, buffer,
  73. std::forward<HeaderCallback>(cbh), std::forward<BodyCallback>(cbb));
  74. });
  75. }
  76. );
  77. });
  78. });
  79. }
  80. template<typename String, typename StrOrInt, class HeaderCallback, class BodyCallback,
  81. class Body, class Fields, class Buffer>
  82. static void _download_trivially(
  83. asio::io_context& ioc, asio::ip::tcp::resolver& resolver, asio::ip::tcp::socket& socket,
  84. Buffer& buffer,
  85. String&& host, StrOrInt&& port,
  86. http::request<Body, Fields>& req, HeaderCallback&& cbh, BodyCallback&& cbb)
  87. {
  88. detail::ignore_unused(ioc);
  89. // Look up the domain name
  90. resolver.async_resolve(std::forward<String>(host), detail::to_string(std::forward<StrOrInt>(port)),
  91. [&](const error_code& ec1, const asio::ip::tcp::resolver::results_type& endpoints) mutable
  92. {
  93. if (ec1) { set_last_error(ec1); return; }
  94. // Make the connection on the IP address we get from a lookup
  95. asio::async_connect(socket, endpoints,
  96. [&](const error_code& ec2, const asio::ip::tcp::endpoint&) mutable
  97. {
  98. if (ec2) { set_last_error(ec2); return; }
  99. http::async_write(socket, req, [&](const error_code & ec3, std::size_t) mutable
  100. {
  101. if (ec3) { set_last_error(ec3); return; }
  102. http::read_large_body<false>(socket, buffer,
  103. std::forward<HeaderCallback>(cbh), std::forward<BodyCallback>(cbb));
  104. });
  105. });
  106. });
  107. }
  108. template<typename String, typename StrOrInt, class HeaderCallback, class BodyCallback, class Proxy,
  109. class Body, class Fields, class Buffer>
  110. static void _download_impl(
  111. asio::io_context& ioc, asio::ip::tcp::resolver& resolver, asio::ip::tcp::socket& socket,
  112. Buffer& buffer,
  113. String&& host, StrOrInt&& port,
  114. http::request<Body, Fields>& req, HeaderCallback&& cbh, BodyCallback&& cbb, Proxy&& proxy)
  115. {
  116. // if has socks5 proxy
  117. if constexpr (std::is_base_of_v<asio2::socks5::option_base,
  118. typename detail::element_type_adapter<detail::remove_cvref_t<Proxy>>::type>)
  119. {
  120. derived_t::_download_with_socks5(ioc, resolver, socket, buffer
  121. , std::forward<String>(host), std::forward<StrOrInt>(port)
  122. , req, std::forward<HeaderCallback>(cbh), std::forward<BodyCallback>(cbb)
  123. , std::forward<Proxy>(proxy)
  124. );
  125. }
  126. else
  127. {
  128. detail::ignore_unused(proxy);
  129. derived_t::_download_trivially(ioc, resolver, socket, buffer
  130. , std::forward<String>(host), std::forward<StrOrInt>(port)
  131. , req, std::forward<HeaderCallback>(cbh), std::forward<BodyCallback>(cbb)
  132. );
  133. }
  134. }
  135. public:
  136. /**
  137. * @brief blocking download the http file until it is returned on success or failure.
  138. * use asio2::get_last_error() to get the error information.
  139. * @param host - The ip of the server.
  140. * @param port - The port of the server.
  141. * @param req - The http request object to send to the http server.
  142. * @param cbh - A function that recv the http response header message. void(auto& message)
  143. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  144. * @param proxy - socks5 proxy, if you do not need a proxy, pass "nullptr" is ok.
  145. */
  146. template<typename String, typename StrOrInt, class HeaderCallback, class BodyCallback, class Proxy,
  147. class Body = http::string_body, class Fields = http::fields, class Buffer = beast::flat_buffer>
  148. typename std::enable_if_t<detail::is_character_string_v<detail::remove_cvref_t<String>>
  149. && detail::http_proxy_checker_v<Proxy>, bool>
  150. static inline download(String&& host, StrOrInt&& port,
  151. http::request<Body, Fields>& req, HeaderCallback&& cbh, BodyCallback&& cbb, Proxy&& proxy)
  152. {
  153. // First assign default value 0 to last error
  154. clear_last_error();
  155. // The io_context is required for all I/O
  156. asio::io_context ioc;
  157. // These objects perform our I/O
  158. asio::ip::tcp::resolver resolver{ ioc };
  159. asio::ip::tcp::socket socket{ ioc };
  160. // This buffer is used for reading and must be persisted
  161. Buffer buffer;
  162. // Some sites must set the http::field::host
  163. if (req.find(http::field::host) == req.end())
  164. {
  165. std::string strhost = asio2::to_string(host);
  166. std::string strport = asio2::to_string(port);
  167. if (strport != "80")
  168. {
  169. strhost += ":";
  170. strhost += strport;
  171. }
  172. req.set(http::field::host, strhost);
  173. }
  174. // Some sites must set the http::field::user_agent
  175. if (req.find(http::field::user_agent) == req.end())
  176. {
  177. req.set(http::field::user_agent,
  178. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36");
  179. }
  180. // do work
  181. derived_t::_download_impl(ioc, resolver, socket, buffer
  182. , std::forward<String>(host), std::forward<StrOrInt>(port)
  183. , req, std::forward<HeaderCallback>(cbh), std::forward<BodyCallback>(cbb)
  184. , std::forward<Proxy>(proxy)
  185. );
  186. // timedout run
  187. ioc.run();
  188. error_code ec_ignore{};
  189. // Gracefully close the socket
  190. socket.shutdown(asio::ip::tcp::socket::shutdown_both, ec_ignore);
  191. socket.cancel(ec_ignore);
  192. socket.close(ec_ignore);
  193. return bool(!get_last_error());
  194. }
  195. // ----------------------------------------------------------------------------------------
  196. /**
  197. * @brief blocking download the http file until it is returned on success or failure
  198. * @param req - The web_request which can be create by http::make_request(...)
  199. * @param filepath - The file path to saved the received file content.
  200. */
  201. template<class String2>
  202. typename std::enable_if_t<detail::can_convert_to_string_v<detail::remove_cvref_t<String2>>, bool>
  203. static inline download(http::web_request& req, String2&& filepath)
  204. {
  205. std::filesystem::path path(std::forward<String2>(filepath));
  206. std::filesystem::create_directories(path.parent_path(), get_last_error());
  207. std::fstream file(path, std::ios::out | std::ios::binary | std::ios::trunc);
  208. if (!file)
  209. {
  210. set_last_error(asio::error::access_denied); // Permission denied
  211. return false;
  212. }
  213. auto cbh = [](const auto&) {};
  214. auto cbb = [&file](std::string_view chunk) mutable
  215. {
  216. file.write(chunk.data(), chunk.size());
  217. return true;
  218. };
  219. return derived_t::download(req.host(), req.port(), req.base(), cbh, cbb, std::in_place);
  220. }
  221. /**
  222. * @brief blocking download the http file until it is returned on success or failure
  223. * @param url - The url of the file to download.
  224. * @param filepath - The file path to saved the received file content.
  225. */
  226. template<class String1, class String2>
  227. typename std::enable_if_t<
  228. detail::can_convert_to_string_v<detail::remove_cvref_t<String1>> &&
  229. detail::can_convert_to_string_v<detail::remove_cvref_t<String2>>, bool>
  230. static inline download(String1&& url, String2&& filepath)
  231. {
  232. http::web_request req = http::make_request(std::forward<String1>(url));
  233. if (get_last_error())
  234. return false;
  235. return derived_t::download(req, std::forward<String2>(filepath));
  236. }
  237. // ----------------------------------------------------------------------------------------
  238. /**
  239. * @brief blocking download the http file until it is returned on success or failure
  240. * @param url - The url of the file to download.
  241. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  242. */
  243. template<class String1, class BodyCallback>
  244. typename std::enable_if_t<
  245. detail::can_convert_to_string_v<detail::remove_cvref_t<String1>> &&
  246. detail::is_callable_v<BodyCallback>, bool>
  247. static inline download(String1&& url, BodyCallback&& cbb)
  248. {
  249. http::web_request req = http::make_request(std::forward<String1>(url));
  250. if (get_last_error())
  251. return false;
  252. auto cbh = [](const auto&) {};
  253. return derived_t::download(req.host(), req.port(), req.base(), cbh, cbb, std::in_place);
  254. }
  255. /**
  256. * @brief blocking download the http file until it is returned on success or failure
  257. * @param url - The url of the file to download.
  258. * @param cbh - A function that recv the http response header message. void(auto& message)
  259. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  260. */
  261. template<class String1, class HeaderCallback, class BodyCallback>
  262. typename std::enable_if_t<
  263. detail::can_convert_to_string_v<detail::remove_cvref_t<String1>> &&
  264. detail::is_callable_v<BodyCallback>, bool>
  265. static inline download(String1&& url, HeaderCallback&& cbh, BodyCallback&& cbb)
  266. {
  267. http::web_request req = http::make_request(std::forward<String1>(url));
  268. if (get_last_error())
  269. return false;
  270. return derived_t::download(req.host(), req.port(), req.base(), cbh, cbb, std::in_place);
  271. }
  272. /**
  273. * @brief blocking download the http file until it is returned on success or failure
  274. * @param req - The web_request which can be create by http::make_request(...)
  275. * @param cbh - A function that recv the http response header message. void(auto& message)
  276. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  277. */
  278. template<class HeaderCallback, class BodyCallback>
  279. typename std::enable_if_t<detail::is_callable_v<BodyCallback>, bool>
  280. static inline download(http::web_request& req, HeaderCallback&& cbh, BodyCallback&& cbb)
  281. {
  282. return derived_t::download(req.host(), req.port(), req.base(), cbh, cbb, std::in_place);
  283. }
  284. /**
  285. * @brief blocking download the http file until it is returned on success or failure
  286. * @param req - The web_request which can be create by http::make_request(...)
  287. * @param cbh - A function that recv the http response header message. void(auto& message)
  288. * @param cbb - A function that circularly receives the contents of the file in chunks. void(std::string_view data)
  289. */
  290. template<class BodyCallback>
  291. typename std::enable_if_t<detail::is_callable_v<BodyCallback>, bool>
  292. static inline download(http::web_request& req, BodyCallback&& cbb)
  293. {
  294. auto cbh = [](const auto&) {};
  295. return derived_t::download(req, cbh, cbb);
  296. }
  297. };
  298. template<class derived_t, class args_t = void>
  299. struct http_download_impl : public http_download_impl_bridge<derived_t, args_t> {};
  300. }
  301. #include <asio2/base/detail/pop_options.hpp>
  302. #endif // !__ASIO2_HTTP_DOWNLOAD_HPP__