diff --git a/CHANGELOG.md b/CHANGELOG.md index 34311354..96bf8e0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ WebSocket: API Changes: * Refactor http::header contents +* New ostream() returns dynamic buffer output stream -------------------------------------------------------------------------------- diff --git a/doc/quickref.xml b/doc/quickref.xml index 208c3889..b72d05ba 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -176,11 +176,11 @@ bind_handler buffer_cat + ostream prepare_buffer prepare_buffers system_category to_string - write diff --git a/include/beast/core.hpp b/include/beast/core.hpp index b0dd3fc3..c60f8e88 100644 --- a/include/beast/core.hpp +++ b/include/beast/core.hpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -28,8 +30,6 @@ #include #include #include -#include #include -#include #endif diff --git a/include/beast/core/detail/ostream.hpp b/include/beast/core/detail/ostream.hpp new file mode 100644 index 00000000..4eb5cbdc --- /dev/null +++ b/include/beast/core/detail/ostream.hpp @@ -0,0 +1,210 @@ +// +// 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) +// + +#ifndef BEAST_DETAIL_OSTREAM_HPP +#define BEAST_DETAIL_OSTREAM_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace detail { + +template< + class DynamicBuffer, + class CharT, + class Traits +> +class ostream_buffer + : public std::basic_streambuf +{ + using int_type = typename + std::basic_streambuf::int_type; + + using traits_type = typename + std::basic_streambuf::traits_type; + + static std::size_t constexpr max_size = 512; + + DynamicBuffer& buf_; + +public: + ostream_buffer(ostream_buffer&&) = default; + ostream_buffer(ostream_buffer const&) = delete; + + ~ostream_buffer() noexcept; + + explicit + ostream_buffer(DynamicBuffer& buf); + + int_type + overflow(int_type ch) override; + + int + sync() override; + +private: + void + prepare(); + + void + flush(int extra = 0); +}; + +template +ostream_buffer:: +~ostream_buffer() noexcept +{ + sync(); +} + +template +ostream_buffer:: +ostream_buffer(DynamicBuffer& buf) + : buf_(buf) +{ + prepare(); +} + +template +auto +ostream_buffer:: +overflow(int_type ch) -> + int_type +{ + if(ch != traits_type::eof()) + { + Traits::assign(*this->pptr(), ch); + flush(1); + prepare(); + return ch; + } + flush(); + return traits_type::eof(); +} + +template +int +ostream_buffer:: +sync() +{ + flush(); + prepare(); + return 0; +} + +template +void +ostream_buffer:: +prepare() +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto mbs = buf_.prepare( + read_size_helper(buf_, max_size)); + auto const mb = *mbs.begin(); + auto const p = buffer_cast(mb); + this->setp(p, + p + buffer_size(mb) / sizeof(CharT) - 1); +} + +template +void +ostream_buffer:: +flush(int extra) +{ + buf_.commit( + (this->pptr() - this->pbase() + extra) * + sizeof(CharT)); +} + +//------------------------------------------------------------------------------ + +template +class ostream_helper; + +template +class ostream_helper< + DynamicBuffer, CharT, Traits, true> + : public std::basic_ostream +{ + ostream_buffer osb_; + +public: + explicit + ostream_helper(DynamicBuffer& buf); + + ostream_helper(ostream_helper&& other); +}; + +template +ostream_helper:: +ostream_helper(DynamicBuffer& buf) + : std::basic_ostream( + &this->osb_) + , osb_(buf) +{ +} + +template +ostream_helper:: +ostream_helper( + ostream_helper&& other) + : std::basic_ostream(&osb_) + , osb_(std::move(other.osb_)) +{ +} + +// This work-around is for libstdc++ versions that +// don't have a movable std::basic_streambuf + +template +struct ostream_helper< + DynamicBuffer, CharT, Traits, false> + : private boost::base_from_member< + std::unique_ptr>> + , std::basic_ostream +{ + explicit + ostream_helper(DynamicBuffer& buf); + + ostream_helper(ostream_helper&& other); +}; + +template +ostream_helper:: +ostream_helper(DynamicBuffer& buf) + : boost::base_from_member>>( + new ostream_buffer(buf)) + , std::basic_ostream( + this->member.get()) +{ +} + +template +ostream_helper:: +ostream_helper(ostream_helper&& other) + : boost::base_from_member>>( + std::move(other.member)) + , std::basic_ostream( + this->member.get()) +{ +} + +} // detail +} // beast + +#endif diff --git a/include/beast/core/detail/write_dynabuf.hpp b/include/beast/core/detail/write_dynabuf.hpp deleted file mode 100644 index dcef73af..00000000 --- a/include/beast/core/detail/write_dynabuf.hpp +++ /dev/null @@ -1,140 +0,0 @@ -// -// 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) -// - -#ifndef BEAST_DETAIL_WRITE_DYNABUF_HPP -#define BEAST_DETAIL_WRITE_DYNABUF_HPP - -#include -#include -#include -#include - -namespace beast { -namespace detail { - -// detects string literals. -template -struct is_string_literal : std::integral_constant::type>::value && - std::is_same::type>::value> -{ -}; - -// `true` if a call to boost::asio::buffer(T const&) is possible -// note: we exclude string literals because boost::asio::buffer() -// will include the null terminator, which we don't want. -template -class is_BufferConvertible -{ - template()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); -public: - static bool const value = type::value && - ! is_string_literal::value; -}; - -template -void -write_dynabuf(DynamicBuffer& dynabuf, - boost::asio::const_buffer const& buffer) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffer)), - buffer)); -} - -template -void -write_dynabuf(DynamicBuffer& dynabuf, - boost::asio::mutable_buffer const& buffer) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffer)), - buffer)); -} - -template -typename std::enable_if< - is_BufferConvertible::value && - ! std::is_convertible::value && - ! std::is_convertible::value ->::type -write_dynabuf(DynamicBuffer& dynabuf, T const& t) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - auto const buffers = boost::asio::buffer(t); - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffers)), - buffers)); -} - -template -typename std::enable_if< - is_ConstBufferSequence::value && - ! is_BufferConvertible::value && - ! std::is_convertible::value && - ! std::is_convertible::value ->::type -write_dynabuf(DynamicBuffer& dynabuf, Buffers const& buffers) -{ - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - dynabuf.commit(buffer_copy( - dynabuf.prepare(buffer_size(buffers)), - buffers)); -} - -template -void -write_dynabuf(DynamicBuffer& dynabuf, const char (&s)[N]) -{ - using boost::asio::buffer_copy; - dynabuf.commit(buffer_copy( - dynabuf.prepare(N - 1), - boost::asio::buffer(s, N - 1))); -} - -template -typename std::enable_if< - ! is_string_literal::value && - ! is_ConstBufferSequence::value && - ! is_BufferConvertible::value && - ! std::is_convertible::value && - ! std::is_convertible::value ->::type -write_dynabuf(DynamicBuffer& dynabuf, T const& t) -{ - using boost::asio::buffer; - using boost::asio::buffer_copy; - auto const s = boost::lexical_cast(t); - dynabuf.commit(buffer_copy( - dynabuf.prepare(s.size()), buffer(s))); -} - -template -void -write_dynabuf(DynamicBuffer& dynabuf, - T0 const& t0, T1 const& t1, TN const&... tn) -{ - write_dynabuf(dynabuf, t0); - write_dynabuf(dynabuf, t1, tn...); -} - -} // detail -} // beast - -#endif diff --git a/include/beast/core/impl/streambuf.ipp b/include/beast/core/impl/streambuf.ipp index ea39d763..ec51615f 100644 --- a/include/beast/core/impl/streambuf.ipp +++ b/include/beast/core/impl/streambuf.ipp @@ -9,7 +9,6 @@ #define BEAST_IMPL_STREAMBUF_IPP #include -#include #include #include #include @@ -871,14 +870,6 @@ read_size_helper(basic_streambuf< return (std::min)(max_size, low); } -template -basic_streambuf& -operator<<(basic_streambuf& streambuf, T const& t) -{ - detail::write_dynabuf(streambuf, t); - return streambuf; -} - } // beast #endif diff --git a/include/beast/core/ostream.hpp b/include/beast/core/ostream.hpp new file mode 100644 index 00000000..a3186963 --- /dev/null +++ b/include/beast/core/ostream.hpp @@ -0,0 +1,66 @@ +// +// 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) +// + +#ifndef BEAST_WRITE_OSTREAM_HPP +#define BEAST_WRITE_OSTREAM_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { + +/** Return an output stream that formats values into a @b DynamicBuffer. + + This function wraps the caller provided @b DynamicBuffer into + a `std::ostream` derived class, to allow `operator<<` stream style + formatting operations. + + @par Example + @code + ostream(buffer) << "Hello, world!" << std::endl; + @endcode + + @note Calling members of the underlying buffer before the output + stream is destroyed results in undefined behavior. + + @param buffer An object meeting the requirements of @b DynamicBuffer + into which the formatted output will be placed. + + @return An object derived from `std::ostream` which directs output + into the specified dynamic buffer. Ownership of the dynamic buffer + is not transferred. The caller is responsible for ensuring the + dynamic buffer is not destroyed for the lifetime of the output + stream. +*/ +template +#if BEAST_DOXYGEN +implementation_defined +#else +detail::ostream_helper< + DynamicBuffer, char, std::char_traits, + std::is_move_constructible< + detail::ostream_buffer>>::value> +#endif +ostream(DynamicBuffer& buffer) +{ + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + return detail::ostream_helper< + DynamicBuffer, char, std::char_traits, + std::is_move_constructible< + detail::ostream_buffer>>::value>{buffer}; +} + +} // beast + +#endif diff --git a/include/beast/core/streambuf.hpp b/include/beast/core/streambuf.hpp index ed0cae6d..26809043 100644 --- a/include/beast/core/streambuf.hpp +++ b/include/beast/core/streambuf.hpp @@ -326,18 +326,6 @@ private: */ using streambuf = basic_streambuf>; -/** Format output to a @ref basic_streambuf. - - @param streambuf The @ref basic_streambuf to write to. - - @param t The object to write. - - @return A reference to the @ref basic_streambuf. -*/ -template -basic_streambuf& -operator<<(basic_streambuf& streambuf, T const& t); - } // beast #include diff --git a/include/beast/core/write_dynabuf.hpp b/include/beast/core/write_dynabuf.hpp deleted file mode 100644 index c91b984c..00000000 --- a/include/beast/core/write_dynabuf.hpp +++ /dev/null @@ -1,64 +0,0 @@ -// -// 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) -// - -#ifndef BEAST_WRITE_DYNABUF_HPP -#define BEAST_WRITE_DYNABUF_HPP - -#include -#include -#include -#include -#include - -namespace beast { - -/** Write to a @b `DynamicBuffer`. - - This function appends the serialized representation of each provided - argument into the dynamic buffer. It is capable of converting the - following types of arguments: - - @li `boost::asio::const_buffer` - - @li `boost::asio::mutable_buffer` - - @li A type meeting the requirements of @b `ConvertibleToConstBuffer` - - @li A type meeting the requirements of @b `ConstBufferSequence` - - @li A type meeting the requirements of @b `MutableBufferSequence` - - For all types not listed above, the function will invoke - `boost::lexical_cast` on the argument in an attempt to convert to - a string, which is then appended to the dynamic buffer. - - When this function serializes numbers, it converts them to - their text representation as if by a call to `std::to_string`. - - @param dynabuf The dynamic buffer to write to. - - @param args A list of one or more arguments to write. - - @throws unspecified Any exceptions thrown by `boost::lexical_cast`. - - @note This function participates in overload resolution only if - the `dynabuf` parameter meets the requirements of @b `DynamicBuffer`. -*/ -template -#if BEAST_DOXYGEN -void -#else -typename std::enable_if::value>::type -#endif -write(DynamicBuffer& dynabuf, Args const&... args) -{ - detail::write_dynabuf(dynabuf, args...); -} - -} // beast - -#endif diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp index bb8f16e0..66db1648 100644 --- a/include/beast/http/impl/write.ipp +++ b/include/beast/http/impl/write.ipp @@ -13,11 +13,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -31,53 +31,39 @@ namespace http { namespace detail { -template +template void -write_start_line(DynamicBuffer& dynabuf, +write_start_line(std::ostream& os, header const& msg) { BOOST_ASSERT(msg.version == 10 || msg.version == 11); - write(dynabuf, msg.method()); - write(dynabuf, " "); - write(dynabuf, msg.target()); + os << msg.method() << " " << msg.target(); switch(msg.version) { - case 10: - write(dynabuf, " HTTP/1.0\r\n"); - break; - case 11: - write(dynabuf, " HTTP/1.1\r\n"); - break; + case 10: os << " HTTP/1.0\r\n"; break; + case 11: os << " HTTP/1.1\r\n"; break; } } -template +template void -write_start_line(DynamicBuffer& dynabuf, +write_start_line(std::ostream& os, header const& msg) { BOOST_ASSERT(msg.version == 10 || msg.version == 11); switch(msg.version) { - case 10: - write(dynabuf, "HTTP/1.0 "); - break; - case 11: - write(dynabuf, "HTTP/1.1 "); - break; + case 10: os << "HTTP/1.0 "; break; + case 11: os << "HTTP/1.1 "; break; } - write(dynabuf, msg.status); - write(dynabuf, " "); - write(dynabuf, msg.reason()); - write(dynabuf, "\r\n"); + os << msg.status << " " << msg.reason() << "\r\n"; } -template +template void -write_fields(DynamicBuffer& dynabuf, FieldSequence const& fields) +write_fields(std::ostream& os, + FieldSequence const& fields) { - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); //static_assert(is_FieldSequence::value, // "FieldSequence requirements not met"); for(auto const& field : fields) @@ -86,10 +72,7 @@ write_fields(DynamicBuffer& dynabuf, FieldSequence const& fields) BOOST_ASSERT(! name.empty()); if(name[0] == ':') continue; - write(dynabuf, field.name()); - write(dynabuf, ": "); - write(dynabuf, field.value()); - write(dynabuf, "\r\n"); + os << field.name() << ": " << field.value() << "\r\n"; } } @@ -218,9 +201,12 @@ write(SyncWriteStream& stream, static_assert(is_SyncWriteStream::value, "SyncWriteStream requirements not met"); streambuf sb; - detail::write_start_line(sb, msg); - detail::write_fields(sb, msg.fields); - beast::write(sb, "\r\n"); + { + auto os = ostream(sb); + detail::write_start_line(os, msg); + detail::write_fields(os, msg.fields); + os << "\r\n"; + } boost::asio::write(stream, sb.data(), ec); } @@ -238,9 +224,12 @@ async_write(AsyncWriteStream& stream, beast::async_completion completion{handler}; streambuf sb; - detail::write_start_line(sb, msg); - detail::write_fields(sb, msg.fields); - beast::write(sb, "\r\n"); + { + auto os = ostream(sb); + detail::write_start_line(os, msg); + detail::write_fields(os, msg.fields); + os << "\r\n"; + } detail::write_streambuf_op{ completion.handler, stream, std::move(sb)}; @@ -281,9 +270,10 @@ struct write_preparation if(ec) return; - write_start_line(sb, msg); - write_fields(sb, msg.fields); - beast::write(sb, "\r\n"); + auto os = ostream(sb); + write_start_line(os, msg); + write_fields(os, msg.fields); + os << "\r\n"; } }; diff --git a/test/Jamfile b/test/Jamfile index d39b7292..38759e84 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -29,6 +29,7 @@ unit-test core-tests : core/handler_alloc.cpp core/handler_concepts.cpp core/handler_ptr.cpp + core/ostream.cpp core/placeholders.cpp core/prepare_buffer.cpp core/prepare_buffers.cpp @@ -37,7 +38,6 @@ unit-test core-tests : core/stream_concepts.cpp core/streambuf.cpp core/to_string.cpp - core/write_dynabuf.cpp core/base64.cpp core/empty_base_optimization.cpp core/get_lowest_layer.cpp diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 1413262f..0c353d99 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable (core-tests handler_alloc.cpp handler_concepts.cpp handler_ptr.cpp + ostream.cpp placeholders.cpp prepare_buffer.cpp prepare_buffers.cpp @@ -30,7 +31,6 @@ add_executable (core-tests stream_concepts.cpp streambuf.cpp to_string.cpp - write_dynabuf.cpp base64.cpp empty_base_optimization.cpp get_lowest_layer.cpp diff --git a/test/core/ostream.cpp b/test/core/ostream.cpp new file mode 100644 index 00000000..055fbd15 --- /dev/null +++ b/test/core/ostream.cpp @@ -0,0 +1,32 @@ +// +// 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 + +#include +#include +#include + +#include + +namespace beast { + +class ostream_test : public beast::unit_test::suite +{ +public: + void run() override + { + streambuf sb; + ostream(sb) << "Hello, world!\n"; + BEAST_EXPECT(to_string(sb.data()) == "Hello, world!\n"); + } +}; + +BEAST_DEFINE_TESTSUITE(ostream,core,beast); + +} // beast diff --git a/test/core/streambuf.cpp b/test/core/streambuf.cpp index 2d3440de..02a7b56e 100644 --- a/test/core/streambuf.cpp +++ b/test/core/streambuf.cpp @@ -273,13 +273,6 @@ public: BEAST_EXPECT(test::buffer_count(sb.data()) == 4); } - void testOutputStream() - { - streambuf sb; - sb << "x"; - BEAST_EXPECT(to_string(sb.data()) == "x"); - } - void testCapacity() { using boost::asio::buffer_size; @@ -353,7 +346,6 @@ public: testConsume(); testMatrix(); testIterators(); - testOutputStream(); testCapacity(); } }; diff --git a/test/core/write_dynabuf.cpp b/test/core/write_dynabuf.cpp deleted file mode 100644 index 36a65a6c..00000000 --- a/test/core/write_dynabuf.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// -// 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 - -#include -#include - -namespace beast { - -class write_dynabuf_test : public beast::unit_test::suite -{ -public: - void run() override - { - streambuf sb; - std::string s; - write(sb, boost::asio::const_buffer{"", 0}); - write(sb, boost::asio::mutable_buffer{nullptr, 0}); - write(sb, boost::asio::null_buffers{}); - write(sb, boost::asio::const_buffers_1{"", 0}); - write(sb, boost::asio::mutable_buffers_1{nullptr, 0}); - write(sb, s); - write(sb, 23); - pass(); - } -}; - -BEAST_DEFINE_TESTSUITE(write_dynabuf,core,beast); - -} // beast diff --git a/test/http/message_fuzz.hpp b/test/http/message_fuzz.hpp index 991baaab..f199f4d5 100644 --- a/test/http/message_fuzz.hpp +++ b/test/http/message_fuzz.hpp @@ -8,7 +8,7 @@ #ifndef BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP #define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP -#include +#include #include #include #include @@ -319,7 +319,7 @@ public: } std::string - uri() + target() { //switch(rand(4)) switch(1) @@ -348,7 +348,7 @@ public: #if 0 std::string - uri() + target() { static char constexpr alpha[63] = "0123456789" "ABCDEFGHIJ" "KLMNOPQRST" @@ -476,13 +476,13 @@ public: void fields(DynamicBuffer& db) { + auto os = ostream(db); while(rand(6)) - { - write(db, field()); - write(db, rand(4) ? ": " : ":"); - write(db, value()); - write(db, "\r\n"); - } + os << + field() << + (rand(4) ? ": " : ":") << + value() << + "\r\n"; } template @@ -491,13 +491,15 @@ public: { if(! rand(4)) { - write(db, "Content-Length: 0\r\n\r\n"); + ostream(db) << + "Content-Length: 0\r\n\r\n"; return; } if(rand(2)) { auto const len = rand(500); - write(db, "Content-Length: ", len, "\r\n\r\n"); + ostream(db) << + "Content-Length: " << len << "\r\n\r\n"; for(auto const& b : db.prepare(len)) { auto p = boost::asio::buffer_cast(b); @@ -510,12 +512,14 @@ public: else { auto len = rand(500); - write(db, "Transfer-Encoding: chunked\r\n\r\n"); + ostream(db) << + "Transfer-Encoding: chunked\r\n\r\n"; while(len > 0) { auto n = (std::min)(1 + rand(300), len); len -= n; - write(db, to_hex(n), "\r\n"); + ostream(db) << + to_hex(n) << "\r\n"; for(auto const& b : db.prepare(n)) { auto p = boost::asio::buffer_cast(b); @@ -524,9 +528,9 @@ public: *p++ = static_cast(32 + rand(26+26+10+6)); } db.commit(n); - write(db, "\r\n"); + ostream(db) << "\r\n"; } - write(db, "0\r\n\r\n"); + ostream(db) << "0\r\n\r\n"; } } @@ -534,7 +538,8 @@ public: void request(DynamicBuffer& db) { - write(db, method(), " ", uri(), " HTTP/1.1\r\n"); + ostream(db) << + method() << " " << target() << " HTTP/1.1\r\n"; fields(db); body(db); } @@ -543,14 +548,15 @@ public: void response(DynamicBuffer& db) { - write(db, "HTTP/1."); - write(db, rand(2) ? "0" : "1"); - write(db, " ", 100 + rand(401), " "); - write(db, token()); - write(db, "\r\n"); + ostream(db) << + "HTTP/1." << + (rand(2) ? "0" : "1") << " " << + (100 + rand(401)) << " " << + token() << + "\r\n"; fields(db); body(db); - write(db, "\r\n"); + ostream(db) << "\r\n"; } };