mirror of
https://github.com/boostorg/beast.git
synced 2026-01-27 09:42:18 +01:00
fix #124 The http::header data members "method", "url", and "reason" are changed from data members, to pairs of get and set functions which forward the call to the Fields type used to instantiate the template. Previously, these data members were implemented using std::string. With this change, the implementation of the Fields type used to instantiate the template is now in control of the representation of those values. This permits custom memory allocation strategies including uniform use of the Allocator type already provided to beast::http::basic_fields.
684 lines
20 KiB
C++
684 lines
20 KiB
C++
//
|
|
// Copyright (c) 2013-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)
|
|
//
|
|
|
|
// Test that header file is self-contained.
|
|
#include <beast/http/write.hpp>
|
|
|
|
#include <beast/http/fields.hpp>
|
|
#include <beast/http/message.hpp>
|
|
#include <beast/http/string_body.hpp>
|
|
#include <beast/http/write.hpp>
|
|
#include <beast/core/error.hpp>
|
|
#include <beast/core/streambuf.hpp>
|
|
#include <beast/core/to_string.hpp>
|
|
#include <beast/test/fail_stream.hpp>
|
|
#include <beast/test/string_ostream.hpp>
|
|
#include <beast/test/yield_to.hpp>
|
|
#include <beast/unit_test/suite.hpp>
|
|
#include <boost/asio/error.hpp>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
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:
|
|
template<bool isRequest, class Allocator>
|
|
explicit
|
|
writer(message<isRequest, unsized_body, Allocator> const& msg) noexcept
|
|
: body_(msg.body)
|
|
{
|
|
}
|
|
|
|
void
|
|
init(error_code& ec) noexcept
|
|
{
|
|
beast::detail::ignore_unused(ec);
|
|
}
|
|
|
|
template<class WriteFunction>
|
|
bool
|
|
write(error_code&, WriteFunction&& wf) noexcept
|
|
{
|
|
wf(boost::asio::buffer(body_));
|
|
return true;
|
|
}
|
|
};
|
|
};
|
|
|
|
struct fail_body
|
|
{
|
|
class writer;
|
|
|
|
class value_type
|
|
{
|
|
friend class writer;
|
|
|
|
std::string s_;
|
|
test::fail_counter& fc_;
|
|
boost::asio::io_service& ios_;
|
|
|
|
public:
|
|
value_type(test::fail_counter& fc,
|
|
boost::asio::io_service& ios)
|
|
: fc_(fc)
|
|
, ios_(ios)
|
|
{
|
|
}
|
|
|
|
boost::asio::io_service&
|
|
get_io_service() const
|
|
{
|
|
return ios_;
|
|
}
|
|
|
|
value_type&
|
|
operator=(std::string s)
|
|
{
|
|
s_ = std::move(s);
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
class writer
|
|
{
|
|
std::size_t n_ = 0;
|
|
value_type const& body_;
|
|
|
|
public:
|
|
template<bool isRequest, class Allocator>
|
|
explicit
|
|
writer(message<isRequest, fail_body, Allocator> const& msg) noexcept
|
|
: body_(msg.body)
|
|
{
|
|
}
|
|
|
|
void
|
|
init(error_code& ec) noexcept
|
|
{
|
|
body_.fc_.fail(ec);
|
|
}
|
|
|
|
template<class WriteFunction>
|
|
bool
|
|
write(error_code& ec, WriteFunction&& wf) noexcept
|
|
{
|
|
if(body_.fc_.fail(ec))
|
|
return false;
|
|
if(n_ >= body_.s_.size())
|
|
return true;
|
|
wf(boost::asio::buffer(body_.s_.data() + n_, 1));
|
|
++n_;
|
|
return n_ == body_.s_.size();
|
|
}
|
|
};
|
|
};
|
|
|
|
template<bool isRequest, class Body, class Fields>
|
|
std::string
|
|
str(message<isRequest, Body, Fields> const& m)
|
|
{
|
|
test::string_ostream ss(ios_);
|
|
write(ss, m);
|
|
return ss.str;
|
|
}
|
|
|
|
void
|
|
testAsyncWriteHeaders(yield_context do_yield)
|
|
{
|
|
{
|
|
header<true, fields> m;
|
|
m.version = 11;
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.fields.insert("User-Agent", "test");
|
|
error_code ec;
|
|
test::string_ostream ss{ios_};
|
|
async_write(ss, m, do_yield[ec]);
|
|
if(BEAST_EXPECTS(! ec, ec.message()))
|
|
BEAST_EXPECT(ss.str ==
|
|
"GET / HTTP/1.1\r\n"
|
|
"User-Agent: test\r\n"
|
|
"\r\n");
|
|
}
|
|
{
|
|
header<false, fields> m;
|
|
m.version = 10;
|
|
m.status = 200;
|
|
m.reason("OK");
|
|
m.fields.insert("Server", "test");
|
|
m.fields.insert("Content-Length", "5");
|
|
error_code ec;
|
|
test::string_ostream ss{ios_};
|
|
async_write(ss, m, do_yield[ec]);
|
|
if(BEAST_EXPECTS(! ec, ec.message()))
|
|
BEAST_EXPECT(ss.str ==
|
|
"HTTP/1.0 200 OK\r\n"
|
|
"Server: test\r\n"
|
|
"Content-Length: 5\r\n"
|
|
"\r\n");
|
|
}
|
|
}
|
|
|
|
void
|
|
testAsyncWrite(yield_context do_yield)
|
|
{
|
|
{
|
|
message<false, string_body, fields> m;
|
|
m.version = 10;
|
|
m.status = 200;
|
|
m.reason("OK");
|
|
m.fields.insert("Server", "test");
|
|
m.fields.insert("Content-Length", "5");
|
|
m.body = "*****";
|
|
error_code ec;
|
|
test::string_ostream ss{ios_};
|
|
async_write(ss, m, do_yield[ec]);
|
|
if(BEAST_EXPECTS(! ec, ec.message()))
|
|
BEAST_EXPECT(ss.str ==
|
|
"HTTP/1.0 200 OK\r\n"
|
|
"Server: test\r\n"
|
|
"Content-Length: 5\r\n"
|
|
"\r\n"
|
|
"*****");
|
|
}
|
|
{
|
|
message<false, string_body, fields> m;
|
|
m.version = 11;
|
|
m.status = 200;
|
|
m.reason("OK");
|
|
m.fields.insert("Server", "test");
|
|
m.fields.insert("Transfer-Encoding", "chunked");
|
|
m.body = "*****";
|
|
error_code ec;
|
|
test::string_ostream ss(ios_);
|
|
async_write(ss, m, do_yield[ec]);
|
|
if(BEAST_EXPECTS(! ec, ec.message()))
|
|
BEAST_EXPECT(ss.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)
|
|
{
|
|
test::fail_counter fc(n);
|
|
test::fail_stream<
|
|
test::string_ostream> fs(fc, ios_);
|
|
message<true, fail_body, fields> m(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(fc, ios_));
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 10;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.fields.insert("Content-Length", "5");
|
|
m.body = "*****";
|
|
try
|
|
{
|
|
write(fs, m);
|
|
BEAST_EXPECT(fs.next_layer().str ==
|
|
"GET / HTTP/1.0\r\n"
|
|
"User-Agent: test\r\n"
|
|
"Content-Length: 5\r\n"
|
|
"\r\n"
|
|
"*****"
|
|
);
|
|
pass();
|
|
break;
|
|
}
|
|
catch(std::exception const&)
|
|
{
|
|
}
|
|
}
|
|
BEAST_EXPECT(n < limit);
|
|
|
|
for(n = 0; n < limit; ++n)
|
|
{
|
|
test::fail_counter fc(n);
|
|
test::fail_stream<
|
|
test::string_ostream> fs(fc, ios_);
|
|
message<true, fail_body, fields> m(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(fc, ios_));
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 10;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.fields.insert("Transfer-Encoding", "chunked");
|
|
m.body = "*****";
|
|
error_code ec;
|
|
write(fs, m, ec);
|
|
if(ec == boost::asio::error::eof)
|
|
{
|
|
BEAST_EXPECT(fs.next_layer().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;
|
|
}
|
|
}
|
|
BEAST_EXPECT(n < limit);
|
|
|
|
for(n = 0; n < limit; ++n)
|
|
{
|
|
test::fail_counter fc(n);
|
|
test::fail_stream<
|
|
test::string_ostream> fs(fc, ios_);
|
|
message<true, fail_body, fields> m(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(fc, ios_));
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 10;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.fields.insert("Transfer-Encoding", "chunked");
|
|
m.body = "*****";
|
|
error_code ec;
|
|
async_write(fs, m, do_yield[ec]);
|
|
if(ec == boost::asio::error::eof)
|
|
{
|
|
BEAST_EXPECT(fs.next_layer().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;
|
|
}
|
|
}
|
|
BEAST_EXPECT(n < limit);
|
|
|
|
for(n = 0; n < limit; ++n)
|
|
{
|
|
test::fail_counter fc(n);
|
|
test::fail_stream<
|
|
test::string_ostream> fs(fc, ios_);
|
|
message<true, fail_body, fields> m(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(fc, ios_));
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 10;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.fields.insert("Content-Length", "5");
|
|
m.body = "*****";
|
|
error_code ec;
|
|
write(fs, m, ec);
|
|
if(! ec)
|
|
{
|
|
BEAST_EXPECT(fs.next_layer().str ==
|
|
"GET / HTTP/1.0\r\n"
|
|
"User-Agent: test\r\n"
|
|
"Content-Length: 5\r\n"
|
|
"\r\n"
|
|
"*****"
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
BEAST_EXPECT(n < limit);
|
|
|
|
for(n = 0; n < limit; ++n)
|
|
{
|
|
test::fail_counter fc(n);
|
|
test::fail_stream<
|
|
test::string_ostream> fs(fc, ios_);
|
|
message<true, fail_body, fields> m(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(fc, ios_));
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 10;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.fields.insert("Content-Length", "5");
|
|
m.body = "*****";
|
|
error_code ec;
|
|
async_write(fs, m, do_yield[ec]);
|
|
if(! ec)
|
|
{
|
|
BEAST_EXPECT(fs.next_layer().str ==
|
|
"GET / HTTP/1.0\r\n"
|
|
"User-Agent: test\r\n"
|
|
"Content-Length: 5\r\n"
|
|
"\r\n"
|
|
"*****"
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
BEAST_EXPECT(n < limit);
|
|
}
|
|
|
|
void
|
|
testOutput()
|
|
{
|
|
// auto content-length HTTP/1.0
|
|
{
|
|
message<true, string_body, fields> m;
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 10;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.body = "*";
|
|
prepare(m);
|
|
BEAST_EXPECT(str(m) ==
|
|
"GET / HTTP/1.0\r\n"
|
|
"User-Agent: test\r\n"
|
|
"Content-Length: 1\r\n"
|
|
"\r\n"
|
|
"*"
|
|
);
|
|
}
|
|
// keep-alive HTTP/1.0
|
|
{
|
|
message<true, string_body, fields> m;
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 10;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.body = "*";
|
|
prepare(m, connection::keep_alive);
|
|
BEAST_EXPECT(str(m) ==
|
|
"GET / HTTP/1.0\r\n"
|
|
"User-Agent: test\r\n"
|
|
"Content-Length: 1\r\n"
|
|
"Connection: keep-alive\r\n"
|
|
"\r\n"
|
|
"*"
|
|
);
|
|
}
|
|
// upgrade HTTP/1.0
|
|
{
|
|
message<true, string_body, fields> m;
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 10;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.body = "*";
|
|
try
|
|
{
|
|
prepare(m, connection::upgrade);
|
|
fail();
|
|
}
|
|
catch(std::exception const&)
|
|
{
|
|
pass();
|
|
}
|
|
}
|
|
// no content-length HTTP/1.0
|
|
{
|
|
message<true, unsized_body, fields> m;
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 10;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.body = "*";
|
|
prepare(m);
|
|
test::string_ostream ss(ios_);
|
|
error_code ec;
|
|
write(ss, m, ec);
|
|
BEAST_EXPECT(ec == boost::asio::error::eof);
|
|
BEAST_EXPECT(ss.str ==
|
|
"GET / HTTP/1.0\r\n"
|
|
"User-Agent: test\r\n"
|
|
"\r\n"
|
|
"*"
|
|
);
|
|
}
|
|
// auto content-length HTTP/1.1
|
|
{
|
|
message<true, string_body, fields> m;
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 11;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.body = "*";
|
|
prepare(m);
|
|
BEAST_EXPECT(str(m) ==
|
|
"GET / HTTP/1.1\r\n"
|
|
"User-Agent: test\r\n"
|
|
"Content-Length: 1\r\n"
|
|
"\r\n"
|
|
"*"
|
|
);
|
|
}
|
|
// close HTTP/1.1
|
|
{
|
|
message<true, string_body, fields> m;
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 11;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.body = "*";
|
|
prepare(m, connection::close);
|
|
test::string_ostream ss(ios_);
|
|
error_code ec;
|
|
write(ss, m, ec);
|
|
BEAST_EXPECT(ec == boost::asio::error::eof);
|
|
BEAST_EXPECT(ss.str ==
|
|
"GET / HTTP/1.1\r\n"
|
|
"User-Agent: test\r\n"
|
|
"Content-Length: 1\r\n"
|
|
"Connection: close\r\n"
|
|
"\r\n"
|
|
"*"
|
|
);
|
|
}
|
|
// upgrade HTTP/1.1
|
|
{
|
|
message<true, string_body, fields> m;
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 11;
|
|
m.fields.insert("User-Agent", "test");
|
|
prepare(m, connection::upgrade);
|
|
BEAST_EXPECT(str(m) ==
|
|
"GET / HTTP/1.1\r\n"
|
|
"User-Agent: test\r\n"
|
|
"Connection: upgrade\r\n"
|
|
"\r\n"
|
|
);
|
|
}
|
|
// no content-length HTTP/1.1
|
|
{
|
|
message<true, unsized_body, fields> m;
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 11;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.body = "*";
|
|
prepare(m);
|
|
test::string_ostream ss(ios_);
|
|
error_code ec;
|
|
write(ss, m, ec);
|
|
BEAST_EXPECT(ss.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<<
|
|
message<true, string_body, fields> m;
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 11;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.body = "*";
|
|
BEAST_EXPECT(boost::lexical_cast<std::string>(m) ==
|
|
"GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n*");
|
|
BEAST_EXPECT(boost::lexical_cast<std::string>(m.base()) ==
|
|
"GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n");
|
|
// Cause exceptions in operator<<
|
|
{
|
|
std::stringstream ss;
|
|
ss.setstate(ss.rdstate() |
|
|
std::stringstream::failbit);
|
|
try
|
|
{
|
|
// header
|
|
ss << m.base();
|
|
fail("", __FILE__, __LINE__);
|
|
}
|
|
catch(std::exception const&)
|
|
{
|
|
pass();
|
|
}
|
|
try
|
|
{
|
|
// message
|
|
ss << m;
|
|
fail("", __FILE__, __LINE__);
|
|
}
|
|
catch(std::exception const&)
|
|
{
|
|
pass();
|
|
}
|
|
}
|
|
}
|
|
|
|
void testOstream()
|
|
{
|
|
message<true, string_body, fields> m;
|
|
m.method("GET");
|
|
m.target("/");
|
|
m.version = 11;
|
|
m.fields.insert("User-Agent", "test");
|
|
m.body = "*";
|
|
prepare(m);
|
|
std::stringstream ss;
|
|
ss.setstate(ss.rdstate() |
|
|
std::stringstream::failbit);
|
|
try
|
|
{
|
|
ss << m;
|
|
fail();
|
|
}
|
|
catch(std::exception const&)
|
|
{
|
|
pass();
|
|
}
|
|
}
|
|
|
|
// 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&) const {}
|
|
};
|
|
|
|
void
|
|
testIoService()
|
|
{
|
|
{
|
|
// Make sure handlers are not destroyed
|
|
// after calling io_service::stop
|
|
boost::asio::io_service ios;
|
|
test::string_ostream os{ios};
|
|
BEAST_EXPECT(handler::count() == 0);
|
|
message<true, string_body, fields> m;
|
|
m.method("GET");
|
|
m.version = 11;
|
|
m.target("/");
|
|
m.fields.insert("Content-Length", 5);
|
|
m.body = "*****";
|
|
async_write(os, m, handler{});
|
|
BEAST_EXPECT(handler::count() > 0);
|
|
ios.stop();
|
|
BEAST_EXPECT(handler::count() > 0);
|
|
ios.reset();
|
|
BEAST_EXPECT(handler::count() > 0);
|
|
ios.run_one();
|
|
BEAST_EXPECT(handler::count() == 0);
|
|
}
|
|
{
|
|
// Make sure uninvoked handlers are
|
|
// destroyed when calling ~io_service
|
|
{
|
|
boost::asio::io_service ios;
|
|
test::string_ostream is{ios};
|
|
BEAST_EXPECT(handler::count() == 0);
|
|
message<true, string_body, fields> m;
|
|
m.method("GET");
|
|
m.version = 11;
|
|
m.target("/");
|
|
m.fields.insert("Content-Length", 5);
|
|
m.body = "*****";
|
|
async_write(is, m, handler{});
|
|
BEAST_EXPECT(handler::count() > 0);
|
|
}
|
|
BEAST_EXPECT(handler::count() == 0);
|
|
}
|
|
}
|
|
|
|
void run() override
|
|
{
|
|
yield_to(&write_test::testAsyncWriteHeaders, this);
|
|
yield_to(&write_test::testAsyncWrite, this);
|
|
yield_to(&write_test::testFailures, this);
|
|
testOutput();
|
|
test_std_ostream();
|
|
testOstream();
|
|
testIoService();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(write,http,beast);
|
|
|
|
} // http
|
|
} // beast
|