diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4795b56..57d6f61b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
HTTP
* Make chunk_encode public
+* Add write, async_write, operator<< for message_headers
WebSocket
diff --git a/doc/quickref.xml b/doc/quickref.xml
index 839000cd..42d683a2 100644
--- a/doc/quickref.xml
+++ b/doc/quickref.xml
@@ -69,6 +69,7 @@
swap
with_body
write
+ operator<<
Type Traits
diff --git a/doc/reference.xsl b/doc/reference.xsl
index b163e93f..ffa66952 100644
--- a/doc/reference.xsl
+++ b/doc/reference.xsl
@@ -195,6 +195,18 @@
select="concat(substring-before($name, '::'), '__', substring-after($name, '::'))"/>
+
+
+
+
+
+
+
+
+
+
```
+
+
+
+ ```
+
+ ```
+
+
diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp
index 11e7bc4b..e18011b2 100644
--- a/include/beast/http/impl/write.ipp
+++ b/include/beast/http/impl/write.ipp
@@ -32,11 +32,12 @@ namespace http {
namespace detail {
-template
+template
void
write_firstline(DynamicBuffer& dynabuf,
- message const& msg)
+ message_headers const& msg)
{
+ BOOST_ASSERT(msg.version == 10 || msg.version == 11);
write(dynabuf, msg.method);
write(dynabuf, " ");
write(dynabuf, msg.url);
@@ -48,23 +49,15 @@ write_firstline(DynamicBuffer& dynabuf,
case 11:
write(dynabuf, " HTTP/1.1\r\n");
break;
- default:
- {
- std::string s;
- s = " HTTP/" +
- std::to_string(msg.version/10) + '.' +
- std::to_string(msg.version%10) + "\r\n";
- write(dynabuf, s);
- break;
- }
}
}
-template
+template
void
write_firstline(DynamicBuffer& dynabuf,
- message const& msg)
+ message_headers const& msg)
{
+ BOOST_ASSERT(msg.version == 10 || msg.version == 11);
switch(msg.version)
{
case 10:
@@ -73,15 +66,6 @@ write_firstline(DynamicBuffer& dynabuf,
case 11:
write(dynabuf, "HTTP/1.1 ");
break;
- default:
- {
- std::string s;
- s = " HTTP/" +
- std::to_string(msg.version/10) + '.' +
- std::to_string(msg.version%10) + ' ';
- write(dynabuf, s);
- break;
- }
}
write(dynabuf, msg.status);
write(dynabuf, " ");
@@ -106,6 +90,177 @@ write_fields(DynamicBuffer& dynabuf, FieldSequence const& fields)
}
}
+} // detail
+
+//------------------------------------------------------------------------------
+
+namespace detail {
+
+template
+class write_streambuf_op
+{
+ using alloc_type =
+ handler_alloc;
+
+ struct data
+ {
+ Stream& s;
+ streambuf sb;
+ Handler h;
+ bool cont;
+ int state = 0;
+
+ template
+ data(DeducedHandler&& h_, Stream& s_,
+ streambuf&& sb_)
+ : s(s_)
+ , sb(std::move(sb_))
+ , h(std::forward(h_))
+ , cont(boost_asio_handler_cont_helpers::
+ is_continuation(h))
+ {
+ }
+ };
+
+ std::shared_ptr d_;
+
+public:
+ write_streambuf_op(write_streambuf_op&&) = default;
+ write_streambuf_op(write_streambuf_op const&) = default;
+
+ template
+ write_streambuf_op(DeducedHandler&& h, Stream& s,
+ Args&&... args)
+ : d_(std::allocate_shared(alloc_type{h},
+ std::forward(h), s,
+ std::forward(args)...))
+ {
+ (*this)(error_code{}, 0, false);
+ }
+
+ explicit
+ write_streambuf_op(std::shared_ptr d)
+ : d_(std::move(d))
+ {
+ }
+
+ void
+ operator()(error_code ec,
+ std::size_t bytes_transferred, bool again = true);
+
+ friend
+ void* asio_handler_allocate(
+ std::size_t size, write_streambuf_op* op)
+ {
+ return boost_asio_handler_alloc_helpers::
+ allocate(size, op->d_->h);
+ }
+
+ friend
+ void asio_handler_deallocate(
+ void* p, std::size_t size, write_streambuf_op* op)
+ {
+ return boost_asio_handler_alloc_helpers::
+ deallocate(p, size, op->d_->h);
+ }
+
+ friend
+ bool asio_handler_is_continuation(write_streambuf_op* op)
+ {
+ return op->d_->cont;
+ }
+
+ template
+ friend
+ void asio_handler_invoke(Function&& f, write_streambuf_op* op)
+ {
+ return boost_asio_handler_invoke_helpers::
+ invoke(f, op->d_->h);
+ }
+};
+
+template
+void
+write_streambuf_op::
+operator()(error_code ec, std::size_t, bool again)
+{
+ auto& d = *d_;
+ d.cont = d.cont || again;
+ while(! ec && d.state != 99)
+ {
+ switch(d.state)
+ {
+ case 0:
+ {
+ d.state = 99;
+ boost::asio::async_write(d.s,
+ d.sb.data(), std::move(*this));
+ return;
+ }
+ }
+ }
+ d.h(ec);
+}
+
+} // detail
+
+template
+void
+write(SyncWriteStream& stream,
+ message_headers const& msg)
+{
+ static_assert(is_SyncWriteStream::value,
+ "SyncWriteStream requirements not met");
+ error_code ec;
+ write(stream, msg, ec);
+ if(ec)
+ throw system_error{ec};
+}
+
+template
+void
+write(SyncWriteStream& stream,
+ message_headers const& msg,
+ error_code& ec)
+{
+ static_assert(is_SyncWriteStream::value,
+ "SyncWriteStream requirements not met");
+ streambuf sb;
+ detail::write_firstline(sb, msg);
+ detail::write_fields(sb, msg.headers);
+ beast::write(sb, "\r\n");
+ boost::asio::write(stream, sb.data(), ec);
+}
+
+template
+typename async_completion<
+ WriteHandler, void(error_code)>::result_type
+async_write(AsyncWriteStream& stream,
+ message_headers const& msg,
+ WriteHandler&& handler)
+{
+ static_assert(is_AsyncWriteStream::value,
+ "AsyncWriteStream requirements not met");
+ beast::async_completion completion(handler);
+ streambuf sb;
+ detail::write_firstline(sb, msg);
+ detail::write_fields(sb, msg.headers);
+ beast::write(sb, "\r\n");
+ detail::write_streambuf_op{
+ completion.handler, stream, std::move(sb)};
+ return completion.result.get();
+}
+
+//------------------------------------------------------------------------------
+
+namespace detail {
+
template
struct write_preparation
{
@@ -135,6 +290,7 @@ struct write_preparation
w.init(ec);
if(ec)
return;
+
write_firstline(sb, msg);
write_fields(sb, msg.headers);
beast::write(sb, "\r\n");
@@ -462,8 +618,6 @@ public:
} // detail
-//------------------------------------------------------------------------------
-
template
void
@@ -599,6 +753,21 @@ async_write(AsyncWriteStream& stream,
return completion.result.get();
}
+//------------------------------------------------------------------------------
+
+template
+std::ostream&
+operator<<(std::ostream& os,
+ message_headers const& msg)
+{
+ beast::detail::sync_ostream oss{os};
+ error_code ec;
+ write(oss, msg, ec);
+ if(ec)
+ throw system_error{ec};
+ return os;
+}
+
template
std::ostream&
operator<<(std::ostream& os,
diff --git a/test/http/write.cpp b/test/http/write.cpp
index dcb5f3c4..0a70ef42 100644
--- a/test/http/write.cpp
+++ b/test/http/write.cpp
@@ -230,6 +230,43 @@ public:
return ss.str;
}
+ void
+ testAsyncWriteHeaders(yield_context do_yield)
+ {
+ {
+ message_headers m;
+ m.version = 11;
+ m.method = "GET";
+ m.url = "/";
+ m.headers.insert("User-Agent", "test");
+ error_code ec;
+ string_write_stream 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");
+ }
+ {
+ message_headers m;
+ m.version = 10;
+ m.status = 200;
+ m.reason = "OK";
+ m.headers.insert("Server", "test");
+ m.headers.insert("Content-Length", "5");
+ error_code ec;
+ string_write_stream 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)
{
@@ -242,7 +279,7 @@ public:
m.headers.insert("Content-Length", "5");
m.body = "*****";
error_code ec;
- string_write_stream ss(ios_);
+ string_write_stream ss{ios_};
async_write(ss, m, do_yield[ec]);
if(BEAST_EXPECTS(! ec, ec.message()))
BEAST_EXPECT(ss.str ==
@@ -599,17 +636,45 @@ public:
}
}
- void testConvert()
+ void test_std_ostream()
{
+ // Conversion to std::string via operator<<
message m;
m.method = "GET";
m.url = "/";
m.version = 11;
m.headers.insert("User-Agent", "test");
m.body = "*";
- prepare(m);
BEAST_EXPECT(boost::lexical_cast(m) ==
- "GET / HTTP/1.1\r\nUser-Agent: test\r\nContent-Length: 1\r\n\r\n*");
+ "GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n*");
+ BEAST_EXPECT(boost::lexical_cast(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
+ {
+ // message_headers
+ 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()
@@ -637,12 +702,14 @@ public:
void run() override
{
+ yield_to(std::bind(&write_test::testAsyncWriteHeaders,
+ this, std::placeholders::_1));
yield_to(std::bind(&write_test::testAsyncWrite,
this, std::placeholders::_1));
yield_to(std::bind(&write_test::testFailures,
this, std::placeholders::_1));
testOutput();
- testConvert();
+ test_std_ostream();
testOstream();
}
};