diff --git a/CHANGELOG.md b/CHANGELOG.md index d6e6109b..5a96c9e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +Version 124: + +API Changes: + +* http write returns success on connection close + +Actions Required: + +* Add code to servers to close the connection after successfully + writing a message where `message::keep_alive()` would return `false`. + +-------------------------------------------------------------------------------- + Version 123: * Use unit-test subtree diff --git a/example/http/server/async-ssl/http_server_async_ssl.cpp b/example/http/server/async-ssl/http_server_async_ssl.cpp index cbdefb79..57e95d99 100644 --- a/example/http/server/async-ssl/http_server_async_ssl.cpp +++ b/example/http/server/async-ssl/http_server_async_ssl.cpp @@ -248,7 +248,8 @@ class session : public std::enable_shared_from_this &session::on_write, self_.shared_from_this(), std::placeholders::_1, - std::placeholders::_2))); + std::placeholders::_2, + ! sp->keep_alive()))); } }; @@ -331,20 +332,21 @@ public: void on_write( boost::system::error_code ec, - std::size_t bytes_transferred) + std::size_t bytes_transferred, + bool close) { boost::ignore_unused(bytes_transferred); - if(ec == http::error::end_of_stream) + if(ec) + return fail(ec, "write"); + + if(close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. return do_close(); } - if(ec) - return fail(ec, "write"); - // We're done with the response so delete it res_ = nullptr; diff --git a/example/http/server/async/http_server_async.cpp b/example/http/server/async/http_server_async.cpp index d7593683..a388e9ca 100644 --- a/example/http/server/async/http_server_async.cpp +++ b/example/http/server/async/http_server_async.cpp @@ -244,7 +244,8 @@ class session : public std::enable_shared_from_this &session::on_write, self_.shared_from_this(), std::placeholders::_1, - std::placeholders::_2))); + std::placeholders::_2, + ! sp->keep_alive()))); } }; @@ -309,20 +310,21 @@ public: void on_write( boost::system::error_code ec, - std::size_t bytes_transferred) + std::size_t bytes_transferred, + bool close) { boost::ignore_unused(bytes_transferred); - if(ec == http::error::end_of_stream) + if(ec) + return fail(ec, "write"); + + if(close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. return do_close(); } - if(ec) - return fail(ec, "write"); - // We're done with the response so delete it res_ = nullptr; diff --git a/example/http/server/coro-ssl/http_server_coro_ssl.cpp b/example/http/server/coro-ssl/http_server_coro_ssl.cpp index fd03be7b..5b16661e 100644 --- a/example/http/server/coro-ssl/http_server_coro_ssl.cpp +++ b/example/http/server/coro-ssl/http_server_coro_ssl.cpp @@ -216,15 +216,18 @@ template struct send_lambda { Stream& stream_; + bool& close_; boost::system::error_code& ec_; boost::asio::yield_context yield_; explicit send_lambda( Stream& stream, + bool& close, boost::system::error_code& ec, boost::asio::yield_context yield) : stream_(stream) + , close_(close) , ec_(ec) , yield_(yield) { @@ -234,6 +237,9 @@ struct send_lambda void operator()(http::message&& msg) const { + // Determine if we should close the connection after + close_ = ! msg.keep_alive(); + // We need the serializer here because the serializer requires // a non-const file_body, and the message oriented version of // http::write only works with const messages. @@ -250,6 +256,7 @@ do_session( std::string const& doc_root, boost::asio::yield_context yield) { + bool close = false; boost::system::error_code ec; // Construct the stream around the socket @@ -264,7 +271,7 @@ do_session( boost::beast::flat_buffer buffer; // This lambda is used to send messages - send_lambda> lambda{stream, ec, yield}; + send_lambda> lambda{stream, close, ec, yield}; for(;;) { @@ -278,14 +285,14 @@ do_session( // Send the response handle_request(doc_root, std::move(req), lambda); - if(ec == http::error::end_of_stream) + if(ec) + return fail(ec, "write"); + if(close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. break; } - if(ec) - return fail(ec, "write"); } // Perform the SSL shutdown diff --git a/example/http/server/coro/http_server_coro.cpp b/example/http/server/coro/http_server_coro.cpp index cf4d4225..81a8b1af 100644 --- a/example/http/server/coro/http_server_coro.cpp +++ b/example/http/server/coro/http_server_coro.cpp @@ -212,15 +212,18 @@ template struct send_lambda { Stream& stream_; + bool& close_; boost::system::error_code& ec_; boost::asio::yield_context yield_; explicit send_lambda( Stream& stream, + bool& close, boost::system::error_code& ec, boost::asio::yield_context yield) : stream_(stream) + , close_(close) , ec_(ec) , yield_(yield) { @@ -230,6 +233,9 @@ struct send_lambda void operator()(http::message&& msg) const { + // Determine if we should close the connection after + close_ = ! msg.keep_alive(); + // We need the serializer here because the serializer requires // a non-const file_body, and the message oriented version of // http::write only works with const messages. @@ -245,13 +251,14 @@ do_session( std::string const& doc_root, boost::asio::yield_context yield) { + bool close = false; boost::system::error_code ec; // This buffer is required to persist across reads boost::beast::flat_buffer buffer; // This lambda is used to send messages - send_lambda lambda{socket, ec, yield}; + send_lambda lambda{socket, close, ec, yield}; for(;;) { @@ -265,14 +272,14 @@ do_session( // Send the response handle_request(doc_root, std::move(req), lambda); - if(ec == http::error::end_of_stream) + if(ec) + return fail(ec, "write"); + if(close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. break; } - if(ec) - return fail(ec, "write"); } // Send a TCP shutdown diff --git a/example/http/server/fast/http_server_fast.cpp b/example/http/server/fast/http_server_fast.cpp index 06be184d..9f857c1e 100644 --- a/example/http/server/fast/http_server_fast.cpp +++ b/example/http/server/fast/http_server_fast.cpp @@ -209,8 +209,8 @@ private: std::make_tuple(alloc_)); string_response_->result(status); + string_response_->keep_alive(false); string_response_->set(http::field::server, "Beast"); - string_response_->set(http::field::connection, "close"); string_response_->set(http::field::content_type, "text/plain"); string_response_->body() = error; string_response_->prepare_payload(); @@ -265,8 +265,8 @@ private: std::make_tuple(alloc_)); file_response_->result(http::status::ok); + file_response_->keep_alive(false); file_response_->set(http::field::server, "Beast"); - file_response_->set(http::field::connection, "close"); file_response_->set(http::field::content_type, mime_type(target.to_string())); file_response_->body() = std::move(file); file_response_->prepare_payload(); diff --git a/example/http/server/flex/http_server_flex.cpp b/example/http/server/flex/http_server_flex.cpp index 0eb1c14c..46084fbc 100644 --- a/example/http/server/flex/http_server_flex.cpp +++ b/example/http/server/flex/http_server_flex.cpp @@ -259,7 +259,8 @@ class session &session::on_write, self_.derived().shared_from_this(), std::placeholders::_1, - std::placeholders::_2))); + std::placeholders::_2, + ! sp->keep_alive()))); } }; @@ -322,20 +323,21 @@ public: void on_write( boost::system::error_code ec, - std::size_t bytes_transferred) + std::size_t bytes_transferred, + bool close) { boost::ignore_unused(bytes_transferred); - if(ec == http::error::end_of_stream) + if(ec) + return fail(ec, "write"); + + if(close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. return derived().do_eof(); } - if(ec) - return fail(ec, "write"); - // We're done with the response so delete it res_ = nullptr; diff --git a/example/http/server/small/http_server_small.cpp b/example/http/server/small/http_server_small.cpp index e0708cb6..c719b2d0 100644 --- a/example/http/server/small/http_server_small.cpp +++ b/example/http/server/small/http_server_small.cpp @@ -100,8 +100,8 @@ private: void process_request() { - response_.version(11); - response_.set(http::field::connection, "close"); + response_.version(request_.version()); + response_.keep_alive(false); switch(request_.method()) { diff --git a/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp b/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp index c27d953e..ce037b58 100644 --- a/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp +++ b/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp @@ -251,7 +251,8 @@ class session &session::loop, self_.shared_from_this(), std::placeholders::_1, - std::placeholders::_2))); + std::placeholders::_2, + ! sp->keep_alive()))); } }; @@ -283,14 +284,15 @@ public: void run() { - loop({}, 0); + loop({}, 0, false); } #include void loop( boost::system::error_code ec, - std::size_t bytes_transferred) + std::size_t bytes_transferred, + bool close) { boost::ignore_unused(bytes_transferred); reenter(*this) @@ -302,7 +304,8 @@ public: &session::loop, shared_from_this(), std::placeholders::_1, - 0))); + 0, + false))); if(ec) return fail(ec, "handshake"); @@ -314,7 +317,8 @@ public: &session::loop, shared_from_this(), std::placeholders::_1, - std::placeholders::_2))); + std::placeholders::_2, + false))); if(ec == http::error::end_of_stream) { // The remote host closed the connection @@ -325,14 +329,14 @@ public: // Send the response yield handle_request(doc_root_, std::move(req_), lambda_); - if(ec == http::error::end_of_stream) + if(ec) + return fail(ec, "write"); + if(close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. break; } - if(ec) - return fail(ec, "write"); // We're done with the response so delete it res_ = nullptr; @@ -344,7 +348,8 @@ public: &session::loop, shared_from_this(), std::placeholders::_1, - 0))); + 0, + false))); if(ec) return fail(ec, "shutdown"); diff --git a/example/http/server/stackless/http_server_stackless.cpp b/example/http/server/stackless/http_server_stackless.cpp index 673d190b..2dc8d86c 100644 --- a/example/http/server/stackless/http_server_stackless.cpp +++ b/example/http/server/stackless/http_server_stackless.cpp @@ -248,7 +248,8 @@ class session &session::loop, self_.shared_from_this(), std::placeholders::_1, - std::placeholders::_2))); + std::placeholders::_2, + ! sp->keep_alive()))); } }; @@ -277,14 +278,15 @@ public: void run() { - loop({}, 0); + loop({}, 0, false); } #include void loop( boost::system::error_code ec, - std::size_t bytes_transferred) + std::size_t bytes_transferred, + bool close) { boost::ignore_unused(bytes_transferred); reenter(*this) @@ -297,7 +299,8 @@ public: &session::loop, shared_from_this(), std::placeholders::_1, - std::placeholders::_2))); + std::placeholders::_2, + false))); if(ec == http::error::end_of_stream) { // The remote host closed the connection @@ -308,14 +311,14 @@ public: // Send the response yield handle_request(doc_root_, std::move(req_), lambda_); - if(ec == http::error::end_of_stream) + if(ec) + return fail(ec, "write"); + if(close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. break; } - if(ec) - return fail(ec, "write"); // We're done with the response so delete it res_ = nullptr; diff --git a/example/http/server/sync-ssl/http_server_sync_ssl.cpp b/example/http/server/sync-ssl/http_server_sync_ssl.cpp index 8aa36052..98894711 100644 --- a/example/http/server/sync-ssl/http_server_sync_ssl.cpp +++ b/example/http/server/sync-ssl/http_server_sync_ssl.cpp @@ -213,13 +213,16 @@ template struct send_lambda { Stream& stream_; + bool& close_; boost::system::error_code& ec_; explicit send_lambda( Stream& stream, + bool& close, boost::system::error_code& ec) : stream_(stream) + , close_(close) , ec_(ec) { } @@ -228,6 +231,9 @@ struct send_lambda void operator()(http::message&& msg) const { + // Determine if we should close the connection after + close_ = ! msg.keep_alive(); + // We need the serializer here because the serializer requires // a non-const file_body, and the message oriented version of // http::write only works with const messages. @@ -243,6 +249,7 @@ do_session( ssl::context& ctx, std::string const& doc_root) { + bool close = false; boost::system::error_code ec; // Construct the stream around the socket @@ -257,7 +264,7 @@ do_session( boost::beast::flat_buffer buffer; // This lambda is used to send messages - send_lambda> lambda{stream, ec}; + send_lambda> lambda{stream, close, ec}; for(;;) { @@ -271,14 +278,14 @@ do_session( // Send the response handle_request(doc_root, std::move(req), lambda); - if(ec == http::error::end_of_stream) + if(ec) + return fail(ec, "write"); + if(close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. break; } - if(ec) - return fail(ec, "write"); } // Perform the SSL shutdown diff --git a/example/http/server/sync/http_server_sync.cpp b/example/http/server/sync/http_server_sync.cpp index a8e18eb5..1e76873c 100644 --- a/example/http/server/sync/http_server_sync.cpp +++ b/example/http/server/sync/http_server_sync.cpp @@ -211,13 +211,16 @@ template struct send_lambda { Stream& stream_; + bool& close_; boost::system::error_code& ec_; explicit send_lambda( Stream& stream, + bool& close, boost::system::error_code& ec) : stream_(stream) + , close_(close) , ec_(ec) { } @@ -226,6 +229,9 @@ struct send_lambda void operator()(http::message&& msg) const { + // Determine if we should close the connection after + close_ = ! msg.keep_alive(); + // We need the serializer here because the serializer requires // a non-const file_body, and the message oriented version of // http::write only works with const messages. @@ -240,13 +246,14 @@ do_session( tcp::socket& socket, std::string const& doc_root) { + bool close = false; boost::system::error_code ec; // This buffer is required to persist across reads boost::beast::flat_buffer buffer; // This lambda is used to send messages - send_lambda lambda{socket, ec}; + send_lambda lambda{socket, close, ec}; for(;;) { @@ -260,14 +267,14 @@ do_session( // Send the response handle_request(doc_root, std::move(req), lambda); - if(ec == http::error::end_of_stream) + if(ec) + return fail(ec, "write"); + if(close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. break; } - if(ec) - return fail(ec, "write"); } // Send a TCP shutdown diff --git a/include/boost/beast/http/impl/write.ipp b/include/boost/beast/http/impl/write.ipp index 1d9e8448..617f4cde 100644 --- a/include/boost/beast/http/impl/write.ipp +++ b/include/boost/beast/http/impl/write.ipp @@ -162,12 +162,7 @@ operator()( error_code ec, std::size_t bytes_transferred) { if(! ec) - { sr_.consume(bytes_transferred); - if(sr_.is_done()) - if(! sr_.keep_alive()) - ec = error::end_of_stream; - } h_(ec, bytes_transferred); } @@ -478,15 +473,9 @@ write_some( return f.bytes_transferred; if(f.invoked) sr.consume(f.bytes_transferred); - if(sr.is_done()) - if(! sr.keep_alive()) - ec = error::end_of_stream; return f.bytes_transferred; } - if(! sr.keep_alive()) - ec = error::end_of_stream; - else - ec.assign(0, ec.category()); + ec.assign(0, ec.category()); return 0; } @@ -909,8 +898,6 @@ operator<<(std::ostream& os, sr.next(ec, f); if(os.fail()) break; - if(ec == error::end_of_stream) - ec.assign(0, ec.category()); if(ec) { os.setstate(std::ios::failbit); diff --git a/include/boost/beast/http/write.hpp b/include/boost/beast/http/write.hpp index 5d79fb19..964555b6 100644 --- a/include/boost/beast/http/write.hpp +++ b/include/boost/beast/http/write.hpp @@ -440,9 +440,7 @@ async_write( This operation is implemented in terms of one or more calls to the stream's `write_some` function. The algorithm will use a temporary @ref serializer - with an empty chunk decorator to produce buffers. If the semantics of the - message indicate that the connection should be closed after the message is - sent, the error delivered by this function will be @ref error::end_of_stream + with an empty chunk decorator to produce buffers. @param stream The stream to which the data is to be written. The type must support the @b SyncWriteStream concept. @@ -474,9 +472,7 @@ write( This operation is implemented in terms of one or more calls to the stream's `write_some` function. The algorithm will use a temporary @ref serializer - with an empty chunk decorator to produce buffers. If the semantics of the - message indicate that the connection should be closed after the message is - sent, the error delivered by this function will be @ref error::end_of_stream + with an empty chunk decorator to produce buffers. @param stream The stream to which the data is to be written. The type must support the @b SyncWriteStream concept. @@ -512,10 +508,7 @@ write( `async_write_some` function, and is known as a composed operation. The program must ensure that the stream performs no other write operations until this operation completes. The algorithm will use a temporary - @ref serializer with an empty chunk decorator to produce buffers. If - the semantics of the message indicate that the connection should be - closed after the message is sent, the error delivered by this function - will be @ref error::end_of_stream + @ref serializer with an empty chunk decorator to produce buffers. @param stream The stream to which the data is to be written. The type must support the @b AsyncWriteStream concept. diff --git a/test/beast/http/write.cpp b/test/beast/http/write.cpp index 35437df2..d0e8ebaa 100644 --- a/test/beast/http/write.cpp +++ b/test/beast/http/write.cpp @@ -328,7 +328,8 @@ public: test::stream ts{ios_}, tr{ios_}; ts.connect(tr); async_write(ts, m, do_yield[ec]); - if(BEAST_EXPECTS(ec == error::end_of_stream, ec.message())) + BEAST_EXPECT(! m.keep_alive()); + if(BEAST_EXPECTS(! ec, ec.message())) BEAST_EXPECT(tr.str() == "HTTP/1.0 200 OK\r\n" "Server: test\r\n" @@ -406,8 +407,9 @@ public: m.body() = "*****"; error_code ec = test::error::fail_error; write(ts, m, ec); - if(ec == error::end_of_stream) + if(! ec) { + BEAST_EXPECT(! m.keep_alive()); BEAST_EXPECT(tr.str() == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" @@ -436,8 +438,9 @@ public: m.body() = "*****"; error_code ec = test::error::fail_error; async_write(ts, m, do_yield[ec]); - if(ec == error::end_of_stream) + if(! ec) { + BEAST_EXPECT(! m.keep_alive()); BEAST_EXPECT(tr.str() == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" @@ -543,7 +546,8 @@ public: ts.connect(tr); error_code ec; write(ts, m, ec); - BEAST_EXPECT(ec == error::end_of_stream); + BEAST_EXPECT(! m.keep_alive()); + BEAST_EXPECTS(! ec, ec.message()); BEAST_EXPECT(tr.str() == "GET / HTTP/1.0\r\n" "User-Agent: test\r\n" diff --git a/test/beast/websocket/accept.cpp b/test/beast/websocket/accept.cpp index 92fd3f3e..ee6d87df 100644 --- a/test/beast/websocket/accept.cpp +++ b/test/beast/websocket/accept.cpp @@ -484,7 +484,7 @@ public: }; // wrong version - check(http::error::end_of_stream, + check(error::handshake_failed, "GET / HTTP/1.0\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n"