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(); } };