http_client_async_ssl.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. //
  2. // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
  3. //
  4. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  5. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. //
  7. // Official repository: https://github.com/boostorg/beast
  8. //
  9. //------------------------------------------------------------------------------
  10. //
  11. // Example: HTTP SSL client, asynchronous
  12. //
  13. //------------------------------------------------------------------------------
  14. #include "example/common/root_certificates.hpp"
  15. #include <boost/beast/core.hpp>
  16. #include <boost/beast/http.hpp>
  17. #include <boost/beast/ssl.hpp>
  18. #include <boost/beast/version.hpp>
  19. #include <boost/asio/strand.hpp>
  20. #include <cstdlib>
  21. #include <functional>
  22. #include <iostream>
  23. #include <memory>
  24. #include <string>
  25. namespace beast = boost::beast; // from <boost/beast.hpp>
  26. namespace http = beast::http; // from <boost/beast/http.hpp>
  27. namespace net = boost::asio; // from <boost/asio.hpp>
  28. namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
  29. using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
  30. //------------------------------------------------------------------------------
  31. // Report a failure
  32. void
  33. fail(beast::error_code ec, char const* what)
  34. {
  35. std::cerr << what << ": " << ec.message() << "\n";
  36. }
  37. // Performs an HTTP GET and prints the response
  38. class session : public std::enable_shared_from_this<session>
  39. {
  40. tcp::resolver resolver_;
  41. beast::ssl_stream<beast::tcp_stream> stream_;
  42. beast::flat_buffer buffer_; // (Must persist between reads)
  43. http::request<http::empty_body> req_;
  44. http::response<http::string_body> res_;
  45. public:
  46. // Objects are constructed with a strand to
  47. // ensure that handlers do not execute concurrently.
  48. explicit
  49. session(net::io_context& ioc, ssl::context& ctx)
  50. : resolver_(net::make_strand(ioc))
  51. , stream_(net::make_strand(ioc), ctx)
  52. {
  53. }
  54. // Start the asynchronous operation
  55. void
  56. run(
  57. char const* host,
  58. char const* port,
  59. char const* target,
  60. int version)
  61. {
  62. // Set SNI Hostname (many hosts need this to handshake successfully)
  63. if(! SSL_set_tlsext_host_name(stream_.native_handle(), host))
  64. {
  65. beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
  66. std::cerr << ec.message() << "\n";
  67. return;
  68. }
  69. // Set up an HTTP GET request message
  70. req_.version(version);
  71. req_.method(http::verb::get);
  72. req_.target(target);
  73. req_.set(http::field::host, host);
  74. req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
  75. // Look up the domain name
  76. resolver_.async_resolve(
  77. host,
  78. port,
  79. beast::bind_front_handler(
  80. &session::on_resolve,
  81. shared_from_this()));
  82. }
  83. void
  84. on_resolve(
  85. beast::error_code ec,
  86. tcp::resolver::results_type results)
  87. {
  88. if(ec)
  89. return fail(ec, "resolve");
  90. // Set a timeout on the operation
  91. beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
  92. // Make the connection on the IP address we get from a lookup
  93. beast::get_lowest_layer(stream_).async_connect(
  94. results,
  95. beast::bind_front_handler(
  96. &session::on_connect,
  97. shared_from_this()));
  98. }
  99. void
  100. on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type)
  101. {
  102. if(ec)
  103. return fail(ec, "connect");
  104. // Perform the SSL handshake
  105. stream_.async_handshake(
  106. ssl::stream_base::client,
  107. beast::bind_front_handler(
  108. &session::on_handshake,
  109. shared_from_this()));
  110. }
  111. void
  112. on_handshake(beast::error_code ec)
  113. {
  114. if(ec)
  115. return fail(ec, "handshake");
  116. // Set a timeout on the operation
  117. beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
  118. // Send the HTTP request to the remote host
  119. http::async_write(stream_, req_,
  120. beast::bind_front_handler(
  121. &session::on_write,
  122. shared_from_this()));
  123. }
  124. void
  125. on_write(
  126. beast::error_code ec,
  127. std::size_t bytes_transferred)
  128. {
  129. boost::ignore_unused(bytes_transferred);
  130. if(ec)
  131. return fail(ec, "write");
  132. // Receive the HTTP response
  133. http::async_read(stream_, buffer_, res_,
  134. beast::bind_front_handler(
  135. &session::on_read,
  136. shared_from_this()));
  137. }
  138. void
  139. on_read(
  140. beast::error_code ec,
  141. std::size_t bytes_transferred)
  142. {
  143. boost::ignore_unused(bytes_transferred);
  144. if(ec)
  145. return fail(ec, "read");
  146. // Write the message to standard out
  147. std::cout << res_ << std::endl;
  148. // Set a timeout on the operation
  149. beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
  150. // Gracefully close the stream
  151. stream_.async_shutdown(
  152. beast::bind_front_handler(
  153. &session::on_shutdown,
  154. shared_from_this()));
  155. }
  156. void
  157. on_shutdown(beast::error_code ec)
  158. {
  159. if(ec == net::error::eof)
  160. {
  161. // Rationale:
  162. // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
  163. ec = {};
  164. }
  165. if(ec)
  166. return fail(ec, "shutdown");
  167. // If we get here then the connection is closed gracefully
  168. }
  169. };
  170. //------------------------------------------------------------------------------
  171. int main(int argc, char** argv)
  172. {
  173. // Check command line arguments.
  174. if(argc != 4 && argc != 5)
  175. {
  176. std::cerr <<
  177. "Usage: http-client-async-ssl <host> <port> <target> [<HTTP version: 1.0 or 1.1(default)>]\n" <<
  178. "Example:\n" <<
  179. " http-client-async-ssl www.example.com 443 /\n" <<
  180. " http-client-async-ssl www.example.com 443 / 1.0\n";
  181. return EXIT_FAILURE;
  182. }
  183. auto const host = argv[1];
  184. auto const port = argv[2];
  185. auto const target = argv[3];
  186. int version = argc == 5 && !std::strcmp("1.0", argv[4]) ? 10 : 11;
  187. // The io_context is required for all I/O
  188. net::io_context ioc;
  189. // The SSL context is required, and holds certificates
  190. ssl::context ctx{ssl::context::tlsv12_client};
  191. // This holds the root certificate used for verification
  192. load_root_certificates(ctx);
  193. // Verify the remote server's certificate
  194. ctx.set_verify_mode(ssl::verify_peer);
  195. // Launch the asynchronous operation
  196. std::make_shared<session>(ioc, ctx)->run(host, port, target, version);
  197. // Run the I/O service. The call will return when
  198. // the get operation is complete.
  199. ioc.run();
  200. return EXIT_SUCCESS;
  201. }