http write returns success on connection close (API Change):

fix #767

The write family of HTTP stream algorithms no longer returns
error::end_of_stream when the message indicates that the connection
should be closed.

Actions Required:

* Add code to servers to close the connection after successfully
  writing a message where `message::keep_alive()` would return `false`.
This commit is contained in:
Vinnie Falco
2017-10-20 10:19:58 -07:00
parent 885b9dfe0b
commit d0d4e0a740
16 changed files with 122 additions and 83 deletions

View File

@ -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

View File

@ -248,7 +248,8 @@ class session : public std::enable_shared_from_this<session>
&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;

View File

@ -244,7 +244,8 @@ class session : public std::enable_shared_from_this<session>
&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;

View File

@ -216,15 +216,18 @@ template<class Stream>
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<isRequest, Body, Fields>&& 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<ssl::stream<tcp::socket&>> lambda{stream, ec, yield};
send_lambda<ssl::stream<tcp::socket&>> 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

View File

@ -212,15 +212,18 @@ template<class Stream>
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<isRequest, Body, Fields>&& 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<tcp::socket> lambda{socket, ec, yield};
send_lambda<tcp::socket> 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

View File

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

View File

@ -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;

View File

@ -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())
{

View File

@ -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 <boost/asio/yield.hpp>
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");

View File

@ -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 <boost/asio/yield.hpp>
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;

View File

@ -213,13 +213,16 @@ template<class Stream>
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<isRequest, Body, Fields>&& 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<ssl::stream<tcp::socket&>> lambda{stream, ec};
send_lambda<ssl::stream<tcp::socket&>> 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

View File

@ -211,13 +211,16 @@ template<class Stream>
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<isRequest, Body, Fields>&& 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<tcp::socket> lambda{socket, ec};
send_lambda<tcp::socket> 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

View File

@ -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);

View File

@ -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 <em>composed operation</em>.
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.

View File

@ -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"

View File

@ -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"