diff --git a/doc/qbk/02_examples/_examples.qbk b/doc/qbk/02_examples/_examples.qbk index f0abbbe1..0fefe3d9 100644 --- a/doc/qbk/02_examples/_examples.qbk +++ b/doc/qbk/02_examples/_examples.qbk @@ -44,6 +44,10 @@ used to evaluate robustness. All asynchronous clients support timeouts. [HTTP crawl (asynchronous)] [[path_link example/http/client/crawl/http_crawl.cpp http_crawl.cpp]] [] +][ + [HTTP json_body (synchronous)] + [[path_link example/http/client/body/json_body.hpp example/http/client/body/json_client.cpp]] + ]] These WebSocket clients connect to a diff --git a/example/http/client/CMakeLists.txt b/example/http/client/CMakeLists.txt index 0c9d1815..7525396b 100644 --- a/example/http/client/CMakeLists.txt +++ b/example/http/client/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory (async) add_subdirectory (coro) add_subdirectory (crawl) add_subdirectory (sync) +add_subdirectory (json) if (OPENSSL_FOUND) add_subdirectory (async-ssl) diff --git a/example/http/client/Jamfile b/example/http/client/Jamfile index 11dd77ee..e88477e8 100644 --- a/example/http/client/Jamfile +++ b/example/http/client/Jamfile @@ -16,3 +16,5 @@ build-project sync ; build-project async-ssl ; build-project coro-ssl ; build-project sync-ssl ; + +build-project body ; diff --git a/example/http/client/body/CMakeLists.txt b/example/http/client/body/CMakeLists.txt new file mode 100644 index 00000000..6709a486 --- /dev/null +++ b/example/http/client/body/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-2017 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 +# + +GroupSources(include/boost/beast beast) +GroupSources(example/http/client/coro "/") + +add_executable (http-client-json + ${BOOST_BEAST_FILES} + Jamfile + json_client.cpp + ) + +target_link_libraries(http-client-json + lib-asio + lib-beast) + +set_property(TARGET http-client-json PROPERTY FOLDER "example-json-client") diff --git a/example/http/client/body/Jamfile b/example/http/client/body/Jamfile new file mode 100644 index 00000000..3b2f249c --- /dev/null +++ b/example/http/client/body/Jamfile @@ -0,0 +1,14 @@ +# +# Copyright (c) 2016-2017 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 +# + +exe json_client : json_client.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/client/body/json_body.hpp b/example/http/client/body/json_body.hpp new file mode 100644 index 00000000..f3f98927 --- /dev/null +++ b/example/http/client/body/json_body.hpp @@ -0,0 +1,115 @@ +// +// Copyright (c) 2022 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: JSON body +// +//------------------------------------------------------------------------------ + +#ifndef BOOST_BEAST_EXAMPLE_JSON_BODY +#define BOOST_BEAST_EXAMPLE_JSON_BODY + +#include +#include +#include +#include +#include + +namespace json = boost::json; + +struct json_body +{ + using value_type = json::value; + + struct writer + { + using const_buffers_type = boost::asio::const_buffer; + template + writer(boost::beast::http::header const& h, + value_type const& body) + { + // The serializer holds a pointer to the value, so all we need to do is to reset it. + serializer.reset(&body); + } + + void + init(boost::system::error_code& ec) + { + // The serializer always works, so no error can occur here. + ec = {}; + } + + boost::optional> + get(boost::system::error_code& ec) + { + ec = {}; + // We serialize as much as we can with the buffer. Often that'll suffice + const auto len = serializer.read(buffer, sizeof(buffer)); + return std::make_pair( + boost::asio::const_buffer(len.data(), len.size()), + !serializer.done()); + } + private: + json::serializer serializer; + // half of the probable networking buffer, let's leave some space for headers + char buffer[32768]; + }; + + struct reader + { + template + reader(boost::beast::http::header& h, value_type& body) + : body(body) + { + } + void + init( + boost::optional const& content_length, + boost::system::error_code& ec) + { + + // If we know the content-length, we can allocate a monotonic resource to increase the parsing speed. + // We're using it rather then a static_resource, so a consumer can modify the resulting value. + // It is also only assumption that the parsed json will be smaller than the serialize one, + // it might not always be the case. + if (content_length) + parser.reset(json::make_shared_resource(*content_length)); + ec = {}; + } + + template + std::size_t + put(ConstBufferSequence const& buffers, boost::system::error_code& ec) + { + ec = {}; + // The parser just uses the `ec` to indicate errors, so we don't need to do anything. + return parser.write_some( + static_cast(buffers.data()), buffers.size(), ec); + } + + void + finish(boost::system::error_code& ec) + { + ec = {}; + // We check manually if the json is complete. + if (parser.done()) + body = parser.release(); + else + ec = boost::json::error::incomplete; + } + + private: + json::stream_parser parser; + value_type& body; + }; +}; + + +#endif \ No newline at end of file diff --git a/example/http/client/body/json_client.cpp b/example/http/client/body/json_client.cpp new file mode 100644 index 00000000..49cc634e --- /dev/null +++ b/example/http/client/body/json_client.cpp @@ -0,0 +1,61 @@ +#include "json_body.hpp" +#include + +#include +#include +#include +#include + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace net = boost::asio; // from +using tcp = net::ip::tcp; // from + +int main(int argc, char ** argv) +{ + // Our test endpoint for testing the json + const auto host = "postman-echo.com"; + const auto target = "/post"; + + // The io_context is required for all I/O + net::io_context ioc; + + // These objects perform our I/O + tcp::resolver resolver(ioc); + beast::tcp_stream stream(ioc); + + // Look up the domain name + auto const results = resolver.resolve(host, "80"); + + // Make the connection on the IP address we get from a lookup + stream.connect(results); + + // Set up an HTTP GET request message + http::request req{http::verb::post, target, 11}; + req.set(http::field::host, host); + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + req.set(http::field::content_type, "application/json"); + req.body() = {{"type", "test"}, {"content", "pure awesomeness"}}; + req.prepare_payload(); + // Send the HTTP request to the remote host + http::write(stream, req); + + // This buffer is used for reading and must be persisted + beast::flat_buffer buffer; + + // Get the response + http::response res; + + // Receive the HTTP response + http::read(stream, buffer, res); + + // Write the message to standard out + std::cout << res << std::endl; + + // Gracefully close the socket + beast::error_code ec; + stream.socket().shutdown(tcp::socket::shutdown_both, ec); + + + return 0; +} \ No newline at end of file diff --git a/tools/get-boost.sh b/tools/get-boost.sh index 86c86019..56ea6eb8 100755 --- a/tools/get-boost.sh +++ b/tools/get-boost.sh @@ -82,7 +82,8 @@ git submodule update --init --depth 20 --jobs 4 \ libs/tuple \ libs/type_index \ libs/typeof \ - libs/unordered + libs/unordered \ + libs/json echo Submodule update complete