Created
March 28, 2025 05:51
-
-
Save ashtum/55736ea8557cc2a6a6628cb6f18d9336 to your computer and use it in GitHub Desktop.
How to separate the reading of an HTTP message header and body into two operations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) | |
| // | |
| // Distributed under the Boost Software License, Version 1.0. (See accompanying | |
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
| // | |
| // Official repository: https://github.com/boostorg/beast | |
| // | |
| //------------------------------------------------------------------------------ | |
| // | |
| // Example: HTTP client, asynchronous | |
| // | |
| //------------------------------------------------------------------------------ | |
| #include <boost/beast/core.hpp> | |
| #include <boost/beast/http.hpp> | |
| #include <boost/beast/version.hpp> | |
| #include <boost/asio/strand.hpp> | |
| #include <cstdlib> | |
| #include <functional> | |
| #include <iostream> | |
| #include <memory> | |
| #include <string> | |
| namespace beast = boost::beast; // from <boost/beast.hpp> | |
| namespace http = beast::http; // from <boost/beast/http.hpp> | |
| namespace net = boost::asio; // from <boost/asio.hpp> | |
| using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> | |
| //------------------------------------------------------------------------------ | |
| // Report a failure | |
| void | |
| fail(beast::error_code ec, char const* what) | |
| { | |
| std::cerr << what << ": " << ec.message() << "\n"; | |
| } | |
| // Performs an HTTP GET and prints the response | |
| class session : public std::enable_shared_from_this<session> | |
| { | |
| tcp::resolver resolver_; | |
| beast::tcp_stream stream_; | |
| beast::flat_buffer buffer_; // (Must persist between reads) | |
| http::response_parser<http::string_body> parser_; // (Must persist between reads) | |
| http::request<http::empty_body> req_; | |
| public: | |
| // Objects are constructed with a strand to | |
| // ensure that handlers do not execute concurrently. | |
| explicit | |
| session(net::io_context& ioc) | |
| : resolver_(net::make_strand(ioc)) | |
| , stream_(net::make_strand(ioc)) | |
| { | |
| } | |
| // Start the asynchronous operation | |
| void | |
| run( | |
| char const* host, | |
| char const* port, | |
| char const* target, | |
| int version) | |
| { | |
| // Set up an HTTP GET request message | |
| req_.version(version); | |
| req_.method(http::verb::get); | |
| req_.target(target); | |
| req_.set(http::field::host, host); | |
| req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); | |
| // Look up the domain name | |
| resolver_.async_resolve( | |
| host, | |
| port, | |
| beast::bind_front_handler( | |
| &session::on_resolve, | |
| shared_from_this())); | |
| } | |
| void | |
| on_resolve( | |
| beast::error_code ec, | |
| tcp::resolver::results_type results) | |
| { | |
| if(ec) | |
| return fail(ec, "resolve"); | |
| // Set a timeout on the operation | |
| stream_.expires_after(std::chrono::seconds(30)); | |
| // Make the connection on the IP address we get from a lookup | |
| stream_.async_connect( | |
| results, | |
| beast::bind_front_handler( | |
| &session::on_connect, | |
| shared_from_this())); | |
| } | |
| void | |
| on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type) | |
| { | |
| if(ec) | |
| return fail(ec, "connect"); | |
| // Set a timeout on the operation | |
| stream_.expires_after(std::chrono::seconds(30)); | |
| // Send the HTTP request to the remote host | |
| http::async_write(stream_, req_, | |
| beast::bind_front_handler( | |
| &session::on_write, | |
| shared_from_this())); | |
| } | |
| void | |
| on_write( | |
| beast::error_code ec, | |
| std::size_t bytes_transferred) | |
| { | |
| boost::ignore_unused(bytes_transferred); | |
| if(ec) | |
| return fail(ec, "write"); | |
| // Receive the HTTP response header | |
| http::async_read_header(stream_, buffer_, parser_, | |
| beast::bind_front_handler( | |
| &session::on_read_header, | |
| shared_from_this())); | |
| } | |
| void | |
| on_read_header( | |
| beast::error_code ec, | |
| std::size_t bytes_transferred) | |
| { | |
| boost::ignore_unused(bytes_transferred); | |
| if(ec) | |
| return fail(ec, "read_header"); | |
| // Write the headers to standard out | |
| std::cout << parser_.get() << std::endl; | |
| // Receive the HTTP response body | |
| http::async_read(stream_, buffer_, parser_, | |
| beast::bind_front_handler( | |
| &session::on_read, | |
| shared_from_this())); | |
| } | |
| void | |
| on_read( | |
| beast::error_code ec, | |
| std::size_t bytes_transferred) | |
| { | |
| boost::ignore_unused(bytes_transferred); | |
| if(ec) | |
| return fail(ec, "read"); | |
| // Write the body to standard out | |
| std::cout << parser_.get().body() << std::endl; | |
| // Gracefully close the socket | |
| stream_.socket().shutdown(tcp::socket::shutdown_both, ec); | |
| // not_connected happens sometimes so don't bother reporting it. | |
| if(ec && ec != beast::errc::not_connected) | |
| return fail(ec, "shutdown"); | |
| // If we get here then the connection is closed gracefully | |
| } | |
| }; | |
| //------------------------------------------------------------------------------ | |
| int main(int argc, char** argv) | |
| { | |
| // Check command line arguments. | |
| if(argc != 4 && argc != 5) | |
| { | |
| std::cerr << | |
| "Usage: http-client-async <host> <port> <target> [<HTTP version: 1.0 or 1.1(default)>]\n" << | |
| "Example:\n" << | |
| " http-client-async www.example.com 80 /\n" << | |
| " http-client-async www.example.com 80 / 1.0\n"; | |
| return EXIT_FAILURE; | |
| } | |
| auto const host = argv[1]; | |
| auto const port = argv[2]; | |
| auto const target = argv[3]; | |
| int version = argc == 5 && !std::strcmp("1.0", argv[4]) ? 10 : 11; | |
| // The io_context is required for all I/O | |
| net::io_context ioc; | |
| // Launch the asynchronous operation | |
| std::make_shared<session>(ioc)->run(host, port, target, version); | |
| // Run the I/O service. The call will return when | |
| // the get operation is complete. | |
| ioc.run(); | |
| return EXIT_SUCCESS; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment