// // Copyright (c) 2022 Seth Heeren (sgheeren 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 // // Test that header file is self-contained. #include #include #include #include //for operator<< #include #include #include // for is_buffers_generator and [async_]write overloads #include #include #include #include namespace boost { namespace beast { namespace http { namespace detail { // Degenerate body generator to trigger dynamic generator buffer // allocation // // Warning: magic numbers ahead // // Arbitrarily decided on 65 buffers of which two are "some" and "body", the // other bufs are chosen by the "seed fragment" struct fragmented_test_body { using value_type = net::const_buffer; static std::uint64_t size(net::const_buffer fragment) { return 8 + 63 * fragment.size(); } struct writer { using const_buffers_type = std::array; bool done_ = false; net::const_buffer seed_fragment_; template writer(Fields const&, net::const_buffer fragment) : seed_fragment_(fragment) { } void init(beast::error_code&) const { } boost::optional> get(beast::error_code&) { const_buffers_type cb; std::fill(begin(cb), end(cb), seed_fragment_); cb.at(7) = { "some", 4 }; cb.at(27) = { "body", 4 }; return std::make_pair(cb, exchange(done_, true)); } }; }; } namespace detail { template<> struct is_body_sized : std::true_type { }; } // namespace detail class message_generator_test : public beast::unit_test::suite { static_assert( is_buffers_generator::value, "buffers_generator not modeled"); BOOST_STATIC_ASSERT( std::is_constructible< message_generator, message&&>::value); BOOST_STATIC_ASSERT( std::is_constructible< message_generator, message&&>::value); // only rvalue refs BOOST_STATIC_ASSERT( not std::is_constructible< message_generator, message&>::value); BOOST_STATIC_ASSERT( not std::is_constructible< message_generator, message const&>::value); static request make_get() { return request{ verb::get, "/path/query?1", 11, "Serializable but ignored on GET" }; } static http::response make_fragmented_body_response(net::const_buffer seed_fragment) { http::response msg( http::status::ok, 11, seed_fragment); msg.prepare_payload(); BEAST_EXPECT(msg.has_content_length()); BEAST_EXPECT( msg.at(http::field::content_length) == std::to_string(8 + 63 * seed_fragment.size())); return msg; } public: void testGenerate() { message_generator gen(make_get()); error_code ec; std::string received; while(! gen.is_done()) { message_generator::const_buffers_type b = gen.prepare(ec); BEAST_EXPECT(! ec); received += buffers_to_string(b); gen.consume(buffer_bytes(b)); } BEAST_EXPECT(received == "GET /path/query?1 HTTP/1.1\r\n\r\n" "Serializable but ignored on GET"); } void testGenerateSlowConsumer() { message_generator gen(make_get()); error_code ec; std::vector received; while(! gen.is_done()) { message_generator::const_buffers_type b = gen.prepare(ec); BEAST_EXPECT(! ec); received.push_back(buffers_to_string(b).substr(0, 3)); gen.consume(3); // allowed > buffer_bytes(b) } BEAST_EXPECT( (received == std::vector{ "GET", " /p", "ath", "/qu", "ery", "?1 ", "HTT", "P/1", ".1\r", "\n\r\n", "Ser", "ial", "iza", "ble", " bu", "t i", "gno", "red", " on", " GE", "T", })); } void testAsyncWrite() { net::io_context ioc; test::stream out(ioc), in(ioc); { test::connect(out, in); message_generator gen(make_get()); beast::async_write(out, std::move(gen), [&](error_code ec, size_t total) { BEAST_EXPECT(total == 61); BEAST_EXPECT(!ec.failed()); }); ioc.run(); } BEAST_EXPECT(61 == in.nwrite_bytes()); BEAST_EXPECT(in.str() == "GET /path/query?1 HTTP/1.1\r\n\r\n" "Serializable but ignored on GET"); } void testWrite() { net::io_context ioc; test::stream out(ioc), in(ioc); test::connect(out, in); { message_generator gen(make_get()); error_code ec; std::size_t total = beast::write(out, gen, ec); BEAST_EXPECT(total == 61); BEAST_EXPECT(!ec.failed()); BEAST_EXPECT(61 == in.nwrite_bytes()); BEAST_EXPECT(in.str() == "GET /path/query?1 HTTP/1.1\r\n\r\n" "Serializable but ignored on GET"); } in.clear(); { // rvalue accepted std::size_t total = beast::write(out, message_generator{ make_get() }); BEAST_EXPECT(total == 61); BEAST_EXPECT(in.str() == "GET /path/query?1 HTTP/1.1\r\n\r\n" "Serializable but ignored on GET"); } } void testFragmentedBody() { net::io_context ioc; test::stream out(ioc), in(ioc); test::connect(out, in); { message_generator gen(make_fragmented_body_response(net::buffer("", 0))); error_code ec; std::size_t total = beast::write(out, gen, ec); BEAST_EXPECT(total == 46); BEAST_EXPECT(! ec.failed()); BEAST_EXPECT( in.str() == "HTTP/1.1 200 OK\r\n" "Content-Length: 8\r\n\r\nsomebody"); } in.clear(); { message_generator gen(make_fragmented_body_response(net::buffer("x", 1))); error_code ec; std::size_t total = beast::write(out, gen, ec); BEAST_EXPECT(total == 47 + 63); BEAST_EXPECT(! ec.failed()); BEAST_EXPECT( in.str() == "HTTP/1.1 200 OK\r\n" "Content-Length: 71\r\n\r\nxxxxxxxsomexxxxxxxxxxxxxxxxxxxbodyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); } } void testKeepAlive() { auto request = [](int version) { return http::request( http::verb::post, "/", version); }; BEAST_EXPECT( ! http::message_generator(request(10)).keep_alive()); BEAST_EXPECT( http::message_generator(request(11)).keep_alive()); } void run() override { testGenerate(); testGenerateSlowConsumer(); testAsyncWrite(); testWrite(); testFragmentedBody(); testKeepAlive(); } }; BEAST_DEFINE_TESTSUITE(beast,http,message_generator); } // http } // beast } // boost