diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b12ca4a..93dc1e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Version 340: + +* Expect header field with the "100-continue" is handled in upgrade. + +-------------------------------------------------------------------------------- + Version 339: * BOOST_BEAST_USE_STD_STRING_VIEW is replaced by boost/core string_view. diff --git a/example/doc/http_examples.hpp b/example/doc/http_examples.hpp index d17697a5..dac3e248 100644 --- a/example/doc/http_examples.hpp +++ b/example/doc/http_examples.hpp @@ -137,7 +137,7 @@ receive_expect_100_continue( return; // Check for the Expect field value - if(parser.get()[field::expect] == "100-continue") + if(beast::iequals(parser.get()[field::expect], "100-continue")) { // send 100 response response res; diff --git a/include/boost/beast/websocket/impl/accept.hpp b/include/boost/beast/websocket/impl/accept.hpp index 3eef063a..4f8dbcc7 100644 --- a/include/boost/beast/websocket/impl/accept.hpp +++ b/include/boost/beast/websocket/impl/accept.hpp @@ -171,6 +171,8 @@ class stream::response_op boost::weak_ptr wp_; error_code result_; // must come before res_ response_type& res_; + http::response res_100_; + bool needs_res_100_{false}; public: template< @@ -192,6 +194,15 @@ public: , res_(beast::allocate_stable(*this, sp->build_response(req, decorator, result_))) { + auto itr = req.find(http::field::expect); + if (itr != req.end() && iequals(itr->value(), "100-continue")) // do + { + res_100_.version(res_.version()); + res_100_.set(http::field::server, res_[http::field::server]); + res_100_.result(http::status::continue_); + res_100_.prepare_payload(); + needs_res_100_ = true; + } (*this)({}, 0, cont); } @@ -213,6 +224,16 @@ public: impl.change_status(status::handshake); impl.update_timer(this->get_executor()); + if (needs_res_100_) + { + BOOST_ASIO_CORO_YIELD + { + BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, "websocket::async_accept")); + http::async_write( + impl.stream(), res_100_, std::move(*this)); + } + } + // Send response BOOST_ASIO_CORO_YIELD { @@ -323,6 +344,7 @@ public: // the handler. auto const req = p_.release(); auto const decorator = d_; + response_op( this->release_handler(), sp, req, decorator, true); @@ -417,6 +439,20 @@ do_accept( error_code result; auto const res = impl_->build_response(req, decorator, result); + + auto itr = req.find(http::field::expect); + if (itr != req.end() && iequals(itr->value(), "100-continue")) // do + { + http::response res_100; + res_100.version(res.version()); + res_100.set(http::field::server, res[http::field::server]); + res_100.result(http::status::continue_); + res_100.prepare_payload(); + http::write(impl_->stream(), res_100, ec); + if (ec) + return; + } + http::write(impl_->stream(), res, ec); if(ec) return; diff --git a/test/beast/websocket/accept.cpp b/test/beast/websocket/accept.cpp index ab5a0f70..ca6d4357 100644 --- a/test/beast/websocket/accept.cpp +++ b/test/beast/websocket/accept.cpp @@ -816,6 +816,87 @@ public: } } + void testIssue264Sync() + { + net::io_context ioc; + using tcp = net::ip::tcp; + + stream ws1(ioc); + tcp::socket s2(ioc); + test::connect(ws1.next_layer(), s2); + + http::request req{http::verb::get, "/api", 11}; + req.set(http::field::connection, "upgrade"); + req.set(http::field::upgrade, "websocket"); + req.set(http::field::expect, "100-continue"); + req.set(http::field::host, "test"); + req.set(http::field::sec_websocket_version, "13"); + req.set(http::field::sec_websocket_key, "1234"); + + req.prepare_payload(); + + std::thread thr{ + [&] + { + beast::error_code ec; + ws1.accept(ec); + BEAST_EXPECTS(!ec, ec.message()); + }}; + + http::async_write(s2, req, test::success_handler()); + http::response res1, res2; + + flat_buffer buf; + http::async_read(s2, buf, res1, + [&](error_code ec, std::size_t) + { + BEAST_EXPECTS(!ec, ec.message()); + BEAST_EXPECTS(res1.result() == http::status::continue_, obsolete_reason(res1.result())); + if (res1.result() == http::status::continue_) + http::async_read(s2, buf, res2, test::success_handler()); + }); + + test::run_for(ioc, std::chrono::seconds(1)); + thr.join(); + } + + + void testIssue264Async() + { + net::io_context ioc; + using tcp = net::ip::tcp; + + stream ws1(ioc); + tcp::socket s2(ioc); + test::connect(ws1.next_layer(), s2); + + http::request req{http::verb::get, "/api", 11}; + req.set(http::field::connection, "upgrade"); + req.set(http::field::upgrade, "websocket"); + req.set(http::field::expect, "100-continue"); + req.set(http::field::host, "test"); + req.set(http::field::sec_websocket_version, "13"); + req.set(http::field::sec_websocket_key, "1234"); + + req.prepare_payload(); + + http::async_write(s2, req, test::success_handler()); + http::response res1, res2; + + ws1.async_accept(test::success_handler()); + flat_buffer buf; + http::async_read(s2, buf, res1, + [&](error_code ec, std::size_t) + { + BEAST_EXPECTS(!ec, ec.message()); + BEAST_EXPECTS(res1.result() == http::status::continue_, obsolete_reason(res1.result())); + if (res1.result() == http::status::continue_) + http::async_read(s2, buf, res2, test::success_handler()); + }); + + test::run_for(ioc, std::chrono::seconds(1)); + } + #if BOOST_ASIO_HAS_CO_AWAIT void testAwaitableCompiles( stream& s, @@ -850,6 +931,8 @@ public: #if BOOST_ASIO_HAS_CO_AWAIT boost::ignore_unused(&accept_test::testAwaitableCompiles); #endif + testIssue264Sync(); + testIssue264Async(); } };