Files

1088 lines
30 KiB
C++
Raw Permalink Normal View History

2017-07-20 08:01:46 -07:00
//
2019-02-21 07:00:31 -08:00
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
2017-07-20 08:01:46 -07:00
//
// 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)
//
2017-07-20 13:40:34 -07:00
// Official repository: https://github.com/boostorg/beast
//
2017-07-20 08:01:46 -07:00
// Test that header file is self-contained.
2017-07-20 13:40:34 -07:00
#include <boost/beast/http/write.hpp>
#include <boost/beast/http/empty_body.hpp>
#include <boost/beast/http/fields.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/core/buffers_to_string.hpp>
2017-07-20 13:40:34 -07:00
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/multi_buffer.hpp>
#include <boost/beast/core/static_string.hpp>
2018-11-11 14:07:55 -08:00
#include <boost/beast/_experimental/test/stream.hpp>
2017-07-20 13:40:34 -07:00
#include <boost/beast/test/yield_to.hpp>
2018-11-11 14:07:55 -08:00
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/strand.hpp>
#include <sstream>
#include <string>
#if BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/use_awaitable.hpp>
#endif
2017-07-20 13:40:34 -07:00
namespace boost {
namespace beast {
namespace http {
class write_test
: public beast::unit_test::suite
, public test::enable_yield_to
{
public:
struct unsized_body
{
using value_type = std::string;
class writer
{
value_type const& body_;
public:
using const_buffers_type =
net::const_buffer;
2017-11-11 09:40:10 -08:00
template<bool isRequest, class Fields>
writer(
header<isRequest, Fields> const&,
value_type const& b)
: body_(b)
{
}
void
init(error_code& ec)
{
2019-01-20 09:50:43 -08:00
ec = {};
}
boost::optional<std::pair<const_buffers_type, bool>>
2017-06-13 07:08:52 -07:00
get(error_code& ec)
{
2019-01-20 09:50:43 -08:00
ec = {};
return {{const_buffers_type{
body_.data(), body_.size()}, false}};
}
};
};
template<
bool isSplit,
bool isFinalEmpty
>
struct test_body
{
struct value_type
{
std::string s;
bool mutable read = false;
};
class writer
{
int step_ = 0;
value_type const& body_;
public:
using const_buffers_type =
net::const_buffer;
template<bool isRequest, class Fields>
writer(
header<isRequest, Fields> const&,
value_type const& b)
: body_(b)
{
}
void
init(error_code& ec)
{
2019-01-20 09:50:43 -08:00
ec = {};
}
boost::optional<std::pair<const_buffers_type, bool>>
2017-06-13 07:08:52 -07:00
get(error_code& ec)
{
2019-01-20 09:50:43 -08:00
ec = {};
body_.read = true;
return get(
std::integral_constant<bool, isSplit>{},
std::integral_constant<bool, isFinalEmpty>{});
}
private:
boost::optional<std::pair<const_buffers_type, bool>>
get(
std::false_type, // isSplit
std::false_type) // isFinalEmpty
{
if(body_.s.empty())
return boost::none;
2019-02-02 20:44:04 -08:00
return {{net::buffer(
body_.s.data(), body_.s.size()), false}};
}
boost::optional<std::pair<const_buffers_type, bool>>
get(
std::false_type, // isSplit
std::true_type) // isFinalEmpty
{
if(body_.s.empty())
return boost::none;
switch(step_)
{
case 0:
step_ = 1;
2019-02-02 20:44:04 -08:00
return {{net::buffer(
body_.s.data(), body_.s.size()), true}};
default:
return boost::none;
}
}
boost::optional<std::pair<const_buffers_type, bool>>
get(
std::true_type, // isSplit
std::false_type) // isFinalEmpty
{
auto const n = (body_.s.size() + 1) / 2;
switch(step_)
{
case 0:
if(n == 0)
return boost::none;
step_ = 1;
2019-02-02 20:44:04 -08:00
return {{net::buffer(body_.s.data(), n),
body_.s.size() > 1}};
default:
2019-02-02 20:44:04 -08:00
return {{net::buffer(body_.s.data() + n,
body_.s.size() - n), false}};
}
}
boost::optional<std::pair<const_buffers_type, bool>>
get(
std::true_type, // isSplit
std::true_type) // isFinalEmpty
{
auto const n = (body_.s.size() + 1) / 2;
switch(step_)
{
case 0:
if(n == 0)
return boost::none;
step_ = body_.s.size() > 1 ? 1 : 2;
2019-02-02 20:44:04 -08:00
return {{net::buffer(body_.s.data(), n), true}};
case 1:
BOOST_ASSERT(body_.s.size() > 1);
step_ = 2;
2019-02-02 20:44:04 -08:00
return {{net::buffer(body_.s.data() + n,
body_.s.size() - n), true}};
default:
return boost::none;
}
}
};
};
struct fail_body
{
class writer;
class value_type
{
friend class writer;
std::string s_;
2018-05-01 14:19:35 -07:00
test::fail_count& fc_;
public:
explicit
2018-05-01 14:19:35 -07:00
value_type(test::fail_count& fc)
: fc_(fc)
{
}
value_type&
operator=(std::string s)
{
s_ = std::move(s);
return *this;
}
};
class writer
{
std::size_t n_ = 0;
value_type const& body_;
public:
using const_buffers_type =
net::const_buffer;
2017-11-11 09:40:10 -08:00
template<bool isRequest, class Fields>
explicit
writer(header<isRequest, Fields> const&, value_type const& b)
: body_(b)
{
}
void
init(error_code& ec)
{
body_.fc_.fail(ec);
}
boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec)
{
if(body_.fc_.fail(ec))
return boost::none;
if(n_ >= body_.s_.size())
return boost::none;
return {{const_buffers_type{
body_.s_.data() + n_++, 1}, true}};
}
};
};
2017-07-21 08:55:25 -07:00
template<bool isRequest, class Body, class Fields>
static
std::string
to_string(message<isRequest, Body, Fields> const& m)
{
std::stringstream ss;
ss << m;
return ss.str();
}
template<bool isRequest>
bool
equal_body(string_view sv, string_view body)
{
test::stream ts{ioc_, sv}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
message<isRequest, string_body, fields> m;
multi_buffer b;
2017-08-21 14:45:58 -07:00
ts.close_remote();
try
{
2017-08-15 22:20:13 -07:00
read(ts, b, m);
return m.body() == body;
}
catch(std::exception const& e)
{
log << "equal_body: " << e.what() << std::endl;
return false;
}
}
template<bool isRequest, class Body, class Fields>
std::string
str(message<isRequest, Body, Fields> const& m)
{
test::stream ts{ioc_}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
error_code ec;
2017-08-15 22:20:13 -07:00
write(ts, m, ec);
if(ec && ec != error::end_of_stream)
BOOST_THROW_EXCEPTION(system_error{ec});
return std::string(tr.str());
}
void
testAsyncWrite(yield_context do_yield)
{
{
2017-06-07 16:30:49 -07:00
response<string_body> m;
m.version(10);
m.result(status::ok);
m.set(field::server, "test");
m.set(field::content_length, "5");
m.body() = "*****";
error_code ec;
test::stream ts{ioc_}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
2017-08-15 22:20:13 -07:00
async_write(ts, m, do_yield[ec]);
BEAST_EXPECT(! m.keep_alive());
if(BEAST_EXPECTS(! ec, ec.message()))
2017-08-21 14:45:58 -07:00
BEAST_EXPECT(tr.str() ==
"HTTP/1.0 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****");
}
{
2017-06-07 16:30:49 -07:00
response<string_body> m;
m.version(11);
m.result(status::ok);
m.set(field::server, "test");
m.set(field::transfer_encoding, "chunked");
m.body() = "*****";
error_code ec;
test::stream ts{ioc_}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
2017-08-15 22:20:13 -07:00
async_write(ts, m, do_yield[ec]);
2017-07-25 12:35:54 -07:00
if(BEAST_EXPECTS(! ec, ec.message()))
2017-08-21 14:45:58 -07:00
BEAST_EXPECT(tr.str() ==
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5\r\n"
"*****\r\n"
"0\r\n\r\n");
}
}
void
testFailures(yield_context do_yield)
{
static std::size_t constexpr limit = 100;
std::size_t n;
for(n = 0; n < limit; ++n)
{
2018-05-01 14:19:35 -07:00
test::fail_count fc(n);
test::stream ts{ioc_, fc}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
request<fail_body> m(verb::get, "/", 10, fc);
m.set(field::user_agent, "test");
m.set(field::connection, "keep-alive");
m.set(field::content_length, "5");
m.body() = "*****";
try
{
2017-08-15 22:20:13 -07:00
write(ts, m);
2017-08-21 14:45:58 -07:00
BEAST_EXPECT(tr.str() ==
"GET / HTTP/1.0\r\n"
"User-Agent: test\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****"
);
pass();
break;
}
catch(std::exception const&)
{
}
}
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(n < limit);
for(n = 0; n < limit; ++n)
{
2018-05-01 14:19:35 -07:00
test::fail_count fc(n);
test::stream ts{ioc_, fc}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
request<fail_body> m{verb::get, "/", 10, fc};
m.set(field::user_agent, "test");
m.set(field::transfer_encoding, "chunked");
m.body() = "*****";
2018-05-01 12:39:47 -07:00
error_code ec = test::error::test_failure;
2017-08-15 22:20:13 -07:00
write(ts, m, ec);
if(! ec)
{
BEAST_EXPECT(! m.keep_alive());
2017-08-21 14:45:58 -07:00
BEAST_EXPECT(tr.str() ==
"GET / HTTP/1.0\r\n"
"User-Agent: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"1\r\n*\r\n"
"1\r\n*\r\n"
"1\r\n*\r\n"
"1\r\n*\r\n"
"1\r\n*\r\n"
"0\r\n\r\n"
);
break;
}
}
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(n < limit);
for(n = 0; n < limit; ++n)
{
2018-05-01 14:19:35 -07:00
test::fail_count fc(n);
test::stream ts{ioc_, fc}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
request<fail_body> m{verb::get, "/", 10, fc};
m.set(field::user_agent, "test");
m.set(field::transfer_encoding, "chunked");
m.body() = "*****";
2018-05-01 12:39:47 -07:00
error_code ec = test::error::test_failure;
2017-08-15 22:20:13 -07:00
async_write(ts, m, do_yield[ec]);
if(! ec)
{
BEAST_EXPECT(! m.keep_alive());
2017-08-21 14:45:58 -07:00
BEAST_EXPECT(tr.str() ==
"GET / HTTP/1.0\r\n"
"User-Agent: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"1\r\n*\r\n"
"1\r\n*\r\n"
"1\r\n*\r\n"
"1\r\n*\r\n"
"1\r\n*\r\n"
"0\r\n\r\n"
);
break;
}
}
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(n < limit);
for(n = 0; n < limit; ++n)
{
2018-05-01 14:19:35 -07:00
test::fail_count fc(n);
test::stream ts{ioc_, fc}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
request<fail_body> m{verb::get, "/", 10, fc};
m.set(field::user_agent, "test");
m.set(field::connection, "keep-alive");
m.set(field::content_length, "5");
m.body() = "*****";
2018-05-01 12:39:47 -07:00
error_code ec = test::error::test_failure;
2017-08-15 22:20:13 -07:00
write(ts, m, ec);
if(! ec)
{
2017-08-21 14:45:58 -07:00
BEAST_EXPECT(tr.str() ==
"GET / HTTP/1.0\r\n"
"User-Agent: test\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****"
);
break;
}
}
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(n < limit);
for(n = 0; n < limit; ++n)
{
2018-05-01 14:19:35 -07:00
test::fail_count fc(n);
test::stream ts{ioc_, fc}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
request<fail_body> m{verb::get, "/", 10, fc};
m.set(field::user_agent, "test");
m.set(field::connection, "keep-alive");
m.set(field::content_length, "5");
m.body() = "*****";
2018-05-01 12:39:47 -07:00
error_code ec = test::error::test_failure;
2017-08-15 22:20:13 -07:00
async_write(ts, m, do_yield[ec]);
if(! ec)
{
2017-08-21 14:45:58 -07:00
BEAST_EXPECT(tr.str() ==
"GET / HTTP/1.0\r\n"
"User-Agent: test\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****"
);
break;
}
}
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(n < limit);
}
void
testOutput()
{
// auto content-length HTTP/1.0
{
2017-06-07 16:30:49 -07:00
request<string_body> m;
m.method(verb::get);
m.target("/");
m.version(10);
m.set(field::user_agent, "test");
m.body() = "*";
m.prepare_payload();
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(str(m) ==
"GET / HTTP/1.0\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*"
);
}
// no content-length HTTP/1.0
{
2017-06-07 16:30:49 -07:00
request<unsized_body> m;
m.method(verb::get);
m.target("/");
m.version(10);
m.set(field::user_agent, "test");
m.body() = "*";
m.prepare_payload();
test::stream ts{ioc_}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
error_code ec;
2017-08-15 22:20:13 -07:00
write(ts, m, ec);
BEAST_EXPECT(! m.keep_alive());
BEAST_EXPECTS(! ec, ec.message());
2017-08-21 14:45:58 -07:00
BEAST_EXPECT(tr.str() ==
"GET / HTTP/1.0\r\n"
"User-Agent: test\r\n"
"\r\n"
"*"
);
}
// auto content-length HTTP/1.1
{
2017-06-07 16:30:49 -07:00
request<string_body> m;
m.method(verb::get);
m.target("/");
m.version(11);
m.set(field::user_agent, "test");
m.body() = "*";
m.prepare_payload();
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(str(m) ==
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*"
);
}
// no content-length HTTP/1.1
{
2017-06-07 16:30:49 -07:00
request<unsized_body> m;
m.method(verb::get);
m.target("/");
m.version(11);
m.set(field::user_agent, "test");
m.body() = "*";
m.prepare_payload();
test::stream ts{ioc_}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
error_code ec;
2017-08-15 22:20:13 -07:00
write(ts, m, ec);
2017-08-21 14:45:58 -07:00
BEAST_EXPECT(tr.str() ==
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"1\r\n"
"*\r\n"
"0\r\n\r\n"
);
}
}
void test_std_ostream()
{
// Conversion to std::string via operator<<
2017-09-21 17:28:04 +02:00
{
request<string_body> m;
m.method(verb::get);
m.target("/");
m.version(11);
m.set(field::user_agent, "test");
m.body() = "*";
BEAST_EXPECT(to_string(m) ==
"GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n*");
}
// Output to std::ostream
{
request<string_body> m{verb::get, "/", 11};
std::stringstream ss;
ss << m;
BEAST_EXPECT(ss.str() ==
"GET / HTTP/1.1\r\n"
"\r\n");
}
// Output header to std::ostream
{
request<string_body> m{verb::get, "/", 11};
std::stringstream ss;
ss << m.base();
BEAST_EXPECT(ss.str() ==
"GET / HTTP/1.1\r\n"
"\r\n");
}
}
// Ensure completion handlers are not leaked
struct handler
{
static std::atomic<std::size_t>&
count() { static std::atomic<std::size_t> n; return n; }
handler() { ++count(); }
~handler() { --count(); }
handler(handler const&) { ++count(); }
void operator()(error_code const&, std::size_t) const {}
};
void
testIoService()
{
{
// Make sure handlers are not destroyed
// after calling io_context::stop
net::io_context ioc;
test::stream ts{ioc};
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(handler::count() == 0);
2017-06-07 16:30:49 -07:00
request<string_body> m;
m.method(verb::get);
m.version(11);
m.target("/");
m.set("Content-Length", "5");
m.body() = "*****";
2017-08-15 22:20:13 -07:00
async_write(ts, m, handler{});
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(handler::count() > 0);
ioc.stop();
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(handler::count() > 0);
ioc.restart();
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(handler::count() > 0);
ioc.run_one();
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(handler::count() == 0);
}
{
// Make sure uninvoked handlers are
// destroyed when calling ~io_context
{
net::io_context ioc;
test::stream ts{ioc}, tr{ioc};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(handler::count() == 0);
2017-06-07 16:30:49 -07:00
request<string_body> m;
m.method(verb::get);
m.version(11);
m.target("/");
m.set("Content-Length", "5");
m.body() = "*****";
2017-08-15 22:20:13 -07:00
async_write(ts, m, handler{});
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(handler::count() > 0);
}
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(handler::count() == 0);
}
}
Refactor chunked-encoding serialization: New buffer sequence classes are provided to allow full control over the serialization of chunk-encoded message payloads: * chunk_header A ConstBufferSequence representing the chunk header. It includes a hexadecimal-encoded size, an optional set of chunk extensions, and the trailing CRLF required to denote the end of the chunk header. This allows the caller to manually serialize the chunk body in one or more calls to a stream output function. The caller must also output an object of type `chunk_crlf` to the stream to indicate the end of the chunk body. * chunk_crlf A small ConstBufferSequence that simply represents the two character sequence "\r\n" (CRLF). This is needed for the case where the caller wants to output a chunk body as a series of buffers (i.e. "chunking a chunk"). * chunk_body A ConstBufferSequence representing a complete chunk. This includes the size, an optional set of chunk extensions, a caller provided buffer containing the body, and the required CRLF that follows. * chunk_final A ConstBufferSequence representing a final chunk. It includes an optional set of caller-provided field trailers * chunk_extensions A container for building a set of chunk extensions to use during serialization. The use of the container is optional, callers may provide their own buffer containing a correctly formatted set of chunk extensions, or they may use their own convenience container which meets the requirements. The basic_fields container is modified to allow construction outside the context of a message. The container can be used to provide trailers to `chunk_final`. Actions Required: * Remove references to ChunkDecorators. Use the new chunk-encoding buffer sequences to manually produce a chunked payload body in the case where control over the chunk-extensions and/or trailers is required.
2017-07-09 20:09:30 -07:00
template<
class Stream,
bool isRequest, class Body, class Fields>
void
Refactor chunked-encoding serialization: New buffer sequence classes are provided to allow full control over the serialization of chunk-encoded message payloads: * chunk_header A ConstBufferSequence representing the chunk header. It includes a hexadecimal-encoded size, an optional set of chunk extensions, and the trailing CRLF required to denote the end of the chunk header. This allows the caller to manually serialize the chunk body in one or more calls to a stream output function. The caller must also output an object of type `chunk_crlf` to the stream to indicate the end of the chunk body. * chunk_crlf A small ConstBufferSequence that simply represents the two character sequence "\r\n" (CRLF). This is needed for the case where the caller wants to output a chunk body as a series of buffers (i.e. "chunking a chunk"). * chunk_body A ConstBufferSequence representing a complete chunk. This includes the size, an optional set of chunk extensions, a caller provided buffer containing the body, and the required CRLF that follows. * chunk_final A ConstBufferSequence representing a final chunk. It includes an optional set of caller-provided field trailers * chunk_extensions A container for building a set of chunk extensions to use during serialization. The use of the container is optional, callers may provide their own buffer containing a correctly formatted set of chunk extensions, or they may use their own convenience container which meets the requirements. The basic_fields container is modified to allow construction outside the context of a message. The container can be used to provide trailers to `chunk_final`. Actions Required: * Remove references to ChunkDecorators. Use the new chunk-encoding buffer sequences to manually produce a chunked payload body in the case where control over the chunk-extensions and/or trailers is required.
2017-07-09 20:09:30 -07:00
do_write(
Stream& stream,
message<isRequest, Body, Fields> const& m,
error_code& ec)
{
Refactor chunked-encoding serialization: New buffer sequence classes are provided to allow full control over the serialization of chunk-encoded message payloads: * chunk_header A ConstBufferSequence representing the chunk header. It includes a hexadecimal-encoded size, an optional set of chunk extensions, and the trailing CRLF required to denote the end of the chunk header. This allows the caller to manually serialize the chunk body in one or more calls to a stream output function. The caller must also output an object of type `chunk_crlf` to the stream to indicate the end of the chunk body. * chunk_crlf A small ConstBufferSequence that simply represents the two character sequence "\r\n" (CRLF). This is needed for the case where the caller wants to output a chunk body as a series of buffers (i.e. "chunking a chunk"). * chunk_body A ConstBufferSequence representing a complete chunk. This includes the size, an optional set of chunk extensions, a caller provided buffer containing the body, and the required CRLF that follows. * chunk_final A ConstBufferSequence representing a final chunk. It includes an optional set of caller-provided field trailers * chunk_extensions A container for building a set of chunk extensions to use during serialization. The use of the container is optional, callers may provide their own buffer containing a correctly formatted set of chunk extensions, or they may use their own convenience container which meets the requirements. The basic_fields container is modified to allow construction outside the context of a message. The container can be used to provide trailers to `chunk_final`. Actions Required: * Remove references to ChunkDecorators. Use the new chunk-encoding buffer sequences to manually produce a chunked payload body in the case where control over the chunk-extensions and/or trailers is required.
2017-07-09 20:09:30 -07:00
serializer<isRequest, Body, Fields> sr{m};
for(;;)
{
write_some(stream, sr, ec);
if(ec)
return;
if(sr.is_done())
break;
}
}
Refactor chunked-encoding serialization: New buffer sequence classes are provided to allow full control over the serialization of chunk-encoded message payloads: * chunk_header A ConstBufferSequence representing the chunk header. It includes a hexadecimal-encoded size, an optional set of chunk extensions, and the trailing CRLF required to denote the end of the chunk header. This allows the caller to manually serialize the chunk body in one or more calls to a stream output function. The caller must also output an object of type `chunk_crlf` to the stream to indicate the end of the chunk body. * chunk_crlf A small ConstBufferSequence that simply represents the two character sequence "\r\n" (CRLF). This is needed for the case where the caller wants to output a chunk body as a series of buffers (i.e. "chunking a chunk"). * chunk_body A ConstBufferSequence representing a complete chunk. This includes the size, an optional set of chunk extensions, a caller provided buffer containing the body, and the required CRLF that follows. * chunk_final A ConstBufferSequence representing a final chunk. It includes an optional set of caller-provided field trailers * chunk_extensions A container for building a set of chunk extensions to use during serialization. The use of the container is optional, callers may provide their own buffer containing a correctly formatted set of chunk extensions, or they may use their own convenience container which meets the requirements. The basic_fields container is modified to allow construction outside the context of a message. The container can be used to provide trailers to `chunk_final`. Actions Required: * Remove references to ChunkDecorators. Use the new chunk-encoding buffer sequences to manually produce a chunked payload body in the case where control over the chunk-extensions and/or trailers is required.
2017-07-09 20:09:30 -07:00
template<
class Stream,
bool isRequest, class Body, class Fields>
void
Refactor chunked-encoding serialization: New buffer sequence classes are provided to allow full control over the serialization of chunk-encoded message payloads: * chunk_header A ConstBufferSequence representing the chunk header. It includes a hexadecimal-encoded size, an optional set of chunk extensions, and the trailing CRLF required to denote the end of the chunk header. This allows the caller to manually serialize the chunk body in one or more calls to a stream output function. The caller must also output an object of type `chunk_crlf` to the stream to indicate the end of the chunk body. * chunk_crlf A small ConstBufferSequence that simply represents the two character sequence "\r\n" (CRLF). This is needed for the case where the caller wants to output a chunk body as a series of buffers (i.e. "chunking a chunk"). * chunk_body A ConstBufferSequence representing a complete chunk. This includes the size, an optional set of chunk extensions, a caller provided buffer containing the body, and the required CRLF that follows. * chunk_final A ConstBufferSequence representing a final chunk. It includes an optional set of caller-provided field trailers * chunk_extensions A container for building a set of chunk extensions to use during serialization. The use of the container is optional, callers may provide their own buffer containing a correctly formatted set of chunk extensions, or they may use their own convenience container which meets the requirements. The basic_fields container is modified to allow construction outside the context of a message. The container can be used to provide trailers to `chunk_final`. Actions Required: * Remove references to ChunkDecorators. Use the new chunk-encoding buffer sequences to manually produce a chunked payload body in the case where control over the chunk-extensions and/or trailers is required.
2017-07-09 20:09:30 -07:00
do_async_write(
Stream& stream,
message<isRequest, Body, Fields> const& m,
Refactor chunked-encoding serialization: New buffer sequence classes are provided to allow full control over the serialization of chunk-encoded message payloads: * chunk_header A ConstBufferSequence representing the chunk header. It includes a hexadecimal-encoded size, an optional set of chunk extensions, and the trailing CRLF required to denote the end of the chunk header. This allows the caller to manually serialize the chunk body in one or more calls to a stream output function. The caller must also output an object of type `chunk_crlf` to the stream to indicate the end of the chunk body. * chunk_crlf A small ConstBufferSequence that simply represents the two character sequence "\r\n" (CRLF). This is needed for the case where the caller wants to output a chunk body as a series of buffers (i.e. "chunking a chunk"). * chunk_body A ConstBufferSequence representing a complete chunk. This includes the size, an optional set of chunk extensions, a caller provided buffer containing the body, and the required CRLF that follows. * chunk_final A ConstBufferSequence representing a final chunk. It includes an optional set of caller-provided field trailers * chunk_extensions A container for building a set of chunk extensions to use during serialization. The use of the container is optional, callers may provide their own buffer containing a correctly formatted set of chunk extensions, or they may use their own convenience container which meets the requirements. The basic_fields container is modified to allow construction outside the context of a message. The container can be used to provide trailers to `chunk_final`. Actions Required: * Remove references to ChunkDecorators. Use the new chunk-encoding buffer sequences to manually produce a chunked payload body in the case where control over the chunk-extensions and/or trailers is required.
2017-07-09 20:09:30 -07:00
error_code& ec,
yield_context yield)
{
Refactor chunked-encoding serialization: New buffer sequence classes are provided to allow full control over the serialization of chunk-encoded message payloads: * chunk_header A ConstBufferSequence representing the chunk header. It includes a hexadecimal-encoded size, an optional set of chunk extensions, and the trailing CRLF required to denote the end of the chunk header. This allows the caller to manually serialize the chunk body in one or more calls to a stream output function. The caller must also output an object of type `chunk_crlf` to the stream to indicate the end of the chunk body. * chunk_crlf A small ConstBufferSequence that simply represents the two character sequence "\r\n" (CRLF). This is needed for the case where the caller wants to output a chunk body as a series of buffers (i.e. "chunking a chunk"). * chunk_body A ConstBufferSequence representing a complete chunk. This includes the size, an optional set of chunk extensions, a caller provided buffer containing the body, and the required CRLF that follows. * chunk_final A ConstBufferSequence representing a final chunk. It includes an optional set of caller-provided field trailers * chunk_extensions A container for building a set of chunk extensions to use during serialization. The use of the container is optional, callers may provide their own buffer containing a correctly formatted set of chunk extensions, or they may use their own convenience container which meets the requirements. The basic_fields container is modified to allow construction outside the context of a message. The container can be used to provide trailers to `chunk_final`. Actions Required: * Remove references to ChunkDecorators. Use the new chunk-encoding buffer sequences to manually produce a chunked payload body in the case where control over the chunk-extensions and/or trailers is required.
2017-07-09 20:09:30 -07:00
serializer<isRequest, Body, Fields> sr{m};
for(;;)
{
async_write_some(stream, sr, yield[ec]);
if(ec)
return;
if(sr.is_done())
break;
}
}
template<class Body>
void
testWriteStream(net::yield_context yield)
{
test::stream ts{ioc_}, tr{ioc_};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
2017-08-15 22:20:13 -07:00
ts.write_size(3);
2017-06-07 16:30:49 -07:00
response<Body> m0;
m0.version(11);
m0.result(status::ok);
m0.reason("OK");
m0.set(field::server, "test");
m0.body().s = "Hello, world!\n";
{
std::string const result =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"\r\n"
"Hello, world!\n";
{
auto m = m0;
error_code ec;
2017-08-15 22:20:13 -07:00
do_write(ts, m, ec);
BEAST_EXPECT(tr.str() == result);
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(equal_body<false>(
tr.str(), m.body().s));
2017-08-15 22:20:13 -07:00
tr.clear();
}
{
auto m = m0;
error_code ec;
2017-08-15 22:20:13 -07:00
do_async_write(ts, m, ec, yield);
BEAST_EXPECT(tr.str() == result);
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(equal_body<false>(
tr.str(), m.body().s));
2017-08-15 22:20:13 -07:00
tr.clear();
}
{
auto m = m0;
response_serializer<Body, fields> sr{m};
sr.split(true);
for(;;)
{
2017-08-15 22:20:13 -07:00
write_some(ts, sr);
if(sr.is_header_done())
break;
}
BEAST_EXPECT(! m.body().read);
2017-08-15 22:20:13 -07:00
tr.clear();
}
{
auto m = m0;
response_serializer<Body, fields> sr{m};
sr.split(true);
for(;;)
{
2017-08-15 22:20:13 -07:00
async_write_some(ts, sr, yield);
if(sr.is_header_done())
break;
}
BEAST_EXPECT(! m.body().read);
2017-08-15 22:20:13 -07:00
tr.clear();
}
}
{
m0.set("Transfer-Encoding", "chunked");
{
auto m = m0;
error_code ec;
2017-08-15 22:20:13 -07:00
do_write(ts, m, ec);
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(equal_body<false>(
tr.str(), m.body().s));
2017-08-15 22:20:13 -07:00
tr.clear();
}
{
auto m = m0;
error_code ec;
2017-08-15 22:20:13 -07:00
do_async_write(ts, m, ec, yield);
2017-07-25 12:35:54 -07:00
BEAST_EXPECT(equal_body<false>(
tr.str(), m.body().s));
2017-08-15 22:20:13 -07:00
tr.clear();
}
{
auto m = m0;
response_serializer<Body, fields> sr{m};
sr.split(true);
for(;;)
{
2017-08-15 22:20:13 -07:00
write_some(ts, sr);
if(sr.is_header_done())
break;
}
BEAST_EXPECT(! m.body().read);
2017-08-15 22:20:13 -07:00
tr.clear();
}
{
auto m = m0;
response_serializer<Body, fields> sr{m};
sr.split(true);
for(;;)
{
2017-08-15 22:20:13 -07:00
async_write_some(ts, sr, yield);
if(sr.is_header_done())
break;
}
BEAST_EXPECT(! m.body().read);
2017-08-15 22:20:13 -07:00
tr.clear();
}
}
}
void
testIssue655()
{
net::io_context ioc;
test::stream ts{ioc}, tr{ioc};
2017-08-21 14:45:58 -07:00
ts.connect(tr);
response<empty_body> res;
res.chunked(true);
response_serializer<empty_body> sr{res};
2017-08-15 22:20:13 -07:00
async_write_header(ts, sr,
[&](error_code const&, std::size_t)
{
});
ioc.run();
}
struct copyable_handler
{
template<class... Args>
void
operator()(Args&&...) const
{
}
};
void
testAsioHandlerInvoke()
{
using strand = net::strand<
net::io_context::executor_type>;
// make sure things compile, also can set a
// breakpoint in asio_handler_invoke to make sure
// it is instantiated.
{
net::io_context ioc;
strand s{ioc.get_executor()};
test::stream ts{ioc};
flat_buffer b;
request<empty_body> m;
request_serializer<empty_body, fields> sr{m};
async_write_some(ts, sr,
net::bind_executor(
s, copyable_handler{}));
}
{
net::io_context ioc;
strand s{ioc.get_executor()};
test::stream ts{ioc};
flat_buffer b;
request<empty_body> m;
request_serializer<empty_body, fields> sr{m};
async_write(ts, sr,
net::bind_executor(
s, copyable_handler{}));
}
{
net::io_context ioc;
strand s{ioc.get_executor()};
test::stream ts{ioc};
flat_buffer b;
request<empty_body> m;
async_write(ts, m,
net::bind_executor(
s, copyable_handler{}));
}
}
struct const_body_writer
{
struct value_type{};
2018-07-17 19:20:23 -04:00
struct writer
{
using const_buffers_type =
net::const_buffer;
template<bool isRequest, class Fields>
writer(
header<isRequest, Fields> const&,
value_type const&)
{
}
void
init(error_code& ec)
{
2019-01-20 09:50:43 -08:00
ec = {};
}
boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec)
{
2019-01-20 09:50:43 -08:00
ec = {};
return {{const_buffers_type{"", 0}, false}};
}
};
};
struct mutable_body_writer
{
struct value_type{};
2018-07-17 19:20:23 -04:00
struct writer
{
using const_buffers_type =
net::const_buffer;
template<bool isRequest, class Fields>
writer(
header<isRequest, Fields>&,
value_type&)
{
}
void
init(error_code& ec)
{
2019-01-20 09:50:43 -08:00
ec = {};
}
boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec)
{
2019-01-20 09:50:43 -08:00
ec = {};
return {{const_buffers_type{"", 0}, false}};
}
};
};
void
testBodyWriters()
{
{
test::stream s{ioc_};
message<true, const_body_writer> m;
try
{
write(s, m);
}
catch(std::exception const&)
{
}
}
{
error_code ec;
test::stream s{ioc_};
message<true, const_body_writer> m;
write(s, m, ec);
}
{
test::stream s{ioc_};
message<true, mutable_body_writer> m;
try
{
write(s, m);
}
catch(std::exception const&)
{
}
}
{
error_code ec;
test::stream s{ioc_};
message<true, mutable_body_writer> m;
write(s, m, ec);
}
}
#if BOOST_ASIO_HAS_CO_AWAIT
void testAwaitableCompiles(
test::stream& stream,
serializer<true, string_body>& request_serializer,
request<string_body>& req,
request<string_body> const& creq,
serializer<false, string_body>& response_serializer,
response<string_body>& resp,
response<string_body> const& cresp)
{
static_assert(std::is_same_v<
net::awaitable<std::size_t>, decltype(
http::async_write(stream, request_serializer, net::use_awaitable))>);
static_assert(std::is_same_v<
net::awaitable<std::size_t>, decltype(
http::async_write(stream, response_serializer, net::use_awaitable))>);
static_assert(std::is_same_v<
net::awaitable<std::size_t>, decltype(
http::async_write(stream, req, net::use_awaitable))>);
static_assert(std::is_same_v<
net::awaitable<std::size_t>, decltype(
http::async_write(stream, creq, net::use_awaitable))>);
static_assert(std::is_same_v<
net::awaitable<std::size_t>, decltype(
http::async_write(stream, resp, net::use_awaitable))>);
static_assert(std::is_same_v<
net::awaitable<std::size_t>, decltype(
http::async_write(stream, cresp, net::use_awaitable))>);
static_assert(std::is_same_v<
net::awaitable<std::size_t>, decltype(
http::async_write_some(stream, request_serializer, net::use_awaitable))>);
static_assert(std::is_same_v<
net::awaitable<std::size_t>, decltype(
http::async_write_some(stream, response_serializer, net::use_awaitable))>);
static_assert(std::is_same_v<
net::awaitable<std::size_t>, decltype(
http::async_write_header(stream, request_serializer, net::use_awaitable))>);
static_assert(std::is_same_v<
net::awaitable<std::size_t>, decltype(
http::async_write_header(stream, response_serializer, net::use_awaitable))>);
}
#endif
2017-08-21 14:45:58 -07:00
void
run() override
{
testIssue655();
yield_to(
[&](yield_context yield)
{
testAsyncWrite(yield);
testFailures(yield);
});
testOutput();
test_std_ostream();
testIoService();
yield_to(
[&](yield_context yield)
{
testWriteStream<test_body<false, false>>(yield);
testWriteStream<test_body<false, true>>(yield);
testWriteStream<test_body< true, false>>(yield);
testWriteStream<test_body< true, true>>(yield);
});
testAsioHandlerInvoke();
testBodyWriters();
#if BOOST_ASIO_HAS_CO_AWAIT
boost::ignore_unused(&write_test::testAwaitableCompiles);
#endif
}
};
2017-08-01 17:01:57 -07:00
BEAST_DEFINE_TESTSUITE(beast,http,write);
} // http
} // beast
2017-07-20 13:40:34 -07:00
} // boost