1//
2// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
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//
12// Example: HTTP client, coroutine
13//
14//------------------------------------------------------------------------------
15
16#include <boost/beast/core.hpp>
17#include <boost/beast/http.hpp>
18#include <boost/beast/ssl.hpp>
19#include <boost/beast/version.hpp>
20#include <boost/asio/as_tuple.hpp>
21#include <boost/asio/awaitable.hpp>
22#include <boost/asio/co_spawn.hpp>
23#include <boost/asio/detached.hpp>
24#include <boost/asio/use_awaitable.hpp>
25
26#if defined(BOOST_ASIO_HAS_CO_AWAIT)
27
28#include <cstdlib>
29#include <functional>
30#include <iostream>
31#include <string>
32#include "example/common/root_certificates.hpp"
33
34namespace beast = boost::beast; // from <boost/beast.hpp>
35namespace http = beast::http; // from <boost/beast/http.hpp>
36namespace net = boost::asio; // from <boost/asio.hpp>
37namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
38using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
39
40//------------------------------------------------------------------------------
41
42// Performs an HTTP GET and prints the response
43net::awaitable<void>
44do_session(
45 std::string host,
46 std::string port,
47 std::string target,
48 int version,
49 ssl::context& ctx)
50{
51 // These objects perform our I/O
52 // They use an executor with a default completion token of use_awaitable
53 // This makes our code easy, but will use exceptions as the default error handling,
54 // i.e. if the connection drops, we might see an exception.
55 // See async_shutdown for error handling with an error_code.
56 auto resolver = net::use_awaitable.as_default_on(tcp::resolver(co_await net::this_coro::executor));
57 using executor_with_default = net::use_awaitable_t<>::executor_with_default<net::any_io_executor>;
58 using tcp_stream = typename beast::tcp_stream::rebind_executor<executor_with_default>::other;
59
60 // We construct the ssl stream from the already rebound tcp_stream.
61 beast::ssl_stream<tcp_stream> stream{
62 net::use_awaitable.as_default_on(beast::tcp_stream(co_await net::this_coro::executor)),
63 ctx};
64
65 // Set SNI Hostname (many hosts need this to handshake successfully)
66 if(! SSL_set_tlsext_host_name(stream.native_handle(), host.c_str()))
67 throw boost::system::system_error(static_cast<int>(::ERR_get_error()), net::error::get_ssl_category());
68
69 // Look up the domain name
70 auto const results = co_await resolver.async_resolve(host, port);
71
72 // Set the timeout.
73 beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(30));
74
75 // Make the connection on the IP address we get from a lookup
76 co_await beast::get_lowest_layer(stream).async_connect(results);
77
78 // Set the timeout.
79 beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(30));
80
81 // Perform the SSL handshake
82 co_await stream.async_handshake(ssl::stream_base::client);
83
84 // Set up an HTTP GET request message
85 http::request<http::string_body> req{http::verb::get, target, version};
86 req.set(http::field::host, host);
87 req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
88
89 // Set the timeout.
90 beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(30));
91
92 // Send the HTTP request to the remote host
93 co_await http::async_write(stream, req);
94
95 // This buffer is used for reading and must be persisted
96 beast::flat_buffer b;
97
98 // Declare a container to hold the response
99 http::response<http::dynamic_body> res;
100
101 // Receive the HTTP response
102 co_await http::async_read(stream, b, res);
103
104 // Write the message to standard out
105 std::cout << res << std::endl;
106
107 // Set the timeout.
108 beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(30));
109
110 // Gracefully close the stream - do not threat every error as an exception!
111 auto [ec] = co_await stream.async_shutdown(net::as_tuple(net::use_awaitable));
112 if (ec == net::error::eof)
113 {
114 // Rationale:
115 // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
116 ec = {};
117 }
118 if (ec)
119 throw boost::system::system_error(ec, "shutdown");
120
121 // If we get here then the connection is closed gracefully
122}
123
124//------------------------------------------------------------------------------
125
126int main(int argc, char** argv)
127{
128 // Check command line arguments.
129 if(argc != 4 && argc != 5)
130 {
131 std::cerr <<
132 "Usage: http-client-awaitable <host> <port> <target> [<HTTP version: 1.0 or 1.1(default)>]\n" <<
133 "Example:\n" <<
134 " http-client-awaitable www.example.com 443 /\n" <<
135 " http-client-awaitable www.example.com 443 / 1.0\n";
136 return EXIT_FAILURE;
137 }
138 auto const host = argv[1];
139 auto const port = argv[2];
140 auto const target = argv[3];
141 int version = argc == 5 && !std::strcmp("1.0", argv[4]) ? 10 : 11;
142
143 // The io_context is required for all I/O
144 net::io_context ioc;
145
146 // The SSL context is required, and holds certificates
147 ssl::context ctx{ssl::context::tlsv12_client};
148
149 // This holds the root certificate used for verification
150 load_root_certificates(ctx);
151
152 // Verify the remote server's certificate
153 ctx.set_verify_mode(ssl::verify_peer);
154
155
156 // Launch the asynchronous operation
157 net::co_spawn(
158 ioc,
159 do_session(host, port, target, version, ctx),
160 // If the awaitable exists with an exception, it gets delivered here as `e`.
161 // This can happen for regular errors, such as connection drops.
162 [](std::exception_ptr e)
163 {
164 if (!e)
165 return ;
166 try
167 {
168 std::rethrow_exception(e);
169 }
170 catch(std::exception & ex)
171 {
172 std::cerr << "Error: " << ex.what() << "\n";
173 }
174 });
175
176 // Run the I/O service. The call will return when
177 // the get operation is complete.
178 ioc.run();
179
180 return EXIT_SUCCESS;
181}
182
183#else
184
185int main(int, char * [])
186{
187 std::printf(format: "awaitables require C++20\n");
188 return 1;
189}
190
191#endif
192

source code of boost/libs/beast/example/http/client/awaitable-ssl/http_client_awaitable_ssl.cpp