From f003e066896a39b167c4a15dcc97662f12301210 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 6 May 2026 00:01:34 -0400 Subject: [PATCH 1/5] Rename websocket() and expose on proxy, sort properties. --- include/bitcoin/network/net/proxy.hpp | 38 +++++++++++++------- include/bitcoin/network/net/socket.hpp | 38 ++++++++++---------- src/net/proxy.cpp | 31 +++++++++------- src/net/proxy_actions.cpp | 2 +- src/net/socket.cpp | 50 +++++++++++++------------- src/net/socket_connect.cpp | 2 +- src/net/socket_stop.cpp | 4 +-- src/net/socket_ws.cpp | 2 +- 8 files changed, 92 insertions(+), 75 deletions(-) diff --git a/include/bitcoin/network/net/proxy.hpp b/include/bitcoin/network/net/proxy.hpp index 6cc4aeba2..359963471 100644 --- a/include/bitcoin/network/net/proxy.hpp +++ b/include/bitcoin/network/net/proxy.hpp @@ -52,14 +52,8 @@ class BCT_API proxy /// Asserts/logs stopped. virtual ~proxy() NOEXCEPT; - /// Pause reading from the socket (requires strand). - virtual void pause() NOEXCEPT; - - /// Resume reading from the socket (requires strand). - virtual void resume() NOEXCEPT; - - /// Reading from the socket is paused (requires strand). - virtual bool paused() const NOEXCEPT; + /// Stop. + /// ----------------------------------------------------------------------- /// Idempotent, may be called multiple times. /// Stop socket, no delay, called by stop notify when iocontext is closing. @@ -74,27 +68,45 @@ class BCT_API proxy void subscribe_stop(result_handler&& handler, result_handler&& complete) NOEXCEPT; - /// The channel strand. - asio::strand& strand() NOEXCEPT; + /// Pause. + /// ----------------------------------------------------------------------- + + /// Pause reading from the socket (requires strand). + virtual void pause() NOEXCEPT; + + /// Resume reading from the socket (requires strand). + virtual void resume() NOEXCEPT; + + /// Reading from the socket is paused (requires strand). + virtual bool paused() const NOEXCEPT; + + /// Properties. + /// ----------------------------------------------------------------------- /// Get the network threadpool iocontext. asio::context& service() const NOEXCEPT; + /// The channel strand. + asio::strand& strand() NOEXCEPT; + /// The strand is running in this thread. bool stranded() const NOEXCEPT; /// The proxy (socket) is stopped. bool stopped() const NOEXCEPT; + /// The socket was accepted (vs. connected). + bool inbound() const NOEXCEPT; + /// Connection is currently secured (TLS or comparable for socket type). bool secure() const NOEXCEPT; + /// The socket was upgraded to a websocket (requires strand). + bool websocket() const NOEXCEPT; + /// The total number of bytes queued/sent to the remote endpoint. uint64_t total() const NOEXCEPT; - /// The socket was accepted (vs. connected). - bool inbound() const NOEXCEPT; - /// Get the address of the outgoing endpoint passed via construct. const config::address& address() const NOEXCEPT; diff --git a/include/bitcoin/network/net/socket.hpp b/include/bitcoin/network/net/socket.hpp index 4b2cc6591..b38e70d2c 100644 --- a/include/bitcoin/network/net/socket.hpp +++ b/include/bitcoin/network/net/socket.hpp @@ -176,14 +176,17 @@ class BCT_API socket /// Properties. /// ----------------------------------------------------------------------- - /// Get the address of the outgoing endpoint passed via construct, or the - /// resolved endpoint address for incoming connections. - virtual const config::address& address() const NOEXCEPT; + /// Get the network threadpool iocontext. + virtual asio::context& service() const NOEXCEPT; - /// Get the endpoint of the remote host. Established by connection - /// resolution for incoming and non-proxied outgoing. For a proxied - /// connection (outgoing only) this is the value passed via construct. - virtual const config::endpoint& endpoint() const NOEXCEPT; + /// Get the strand of the socket. + virtual asio::strand& strand() NOEXCEPT; + + /// The strand is running in this thread. + virtual bool stranded() const NOEXCEPT; + + /// Stop has been signaled, work is stopping. + virtual bool stopped() const NOEXCEPT; /// The socket was accepted (vs. connected). virtual bool inbound() const NOEXCEPT; @@ -191,20 +194,17 @@ class BCT_API socket /// The socket upgrades to its secure configuration upon connect. virtual bool secure() const NOEXCEPT; - /// Stop has been signaled, work is stopping. - virtual bool stopped() const NOEXCEPT; - - /// The strand is running in this thread. - virtual bool stranded() const NOEXCEPT; - - /// Get the strand of the socket. - virtual asio::strand& strand() NOEXCEPT; + /// The socket was upgraded to a websocket (requires strand). + virtual bool websocket() const NOEXCEPT; - /// Get the network threadpool iocontext. - virtual asio::context& service() const NOEXCEPT; + /// Get the address of the outgoing endpoint passed via construct, or the + /// resolved endpoint address for incoming connections. + virtual const config::address& address() const NOEXCEPT; - /// The socket was upgraded to a websocket (requires strand). - virtual bool is_websocket() const NOEXCEPT; + /// Get the endpoint of the remote host. Established by connection + /// resolution for incoming and non-proxied outgoing. For a proxied + /// connection (outgoing only) this is the value passed via construct. + virtual const config::endpoint& endpoint() const NOEXCEPT; protected: using ws_t = std::variant, ref>; diff --git a/src/net/proxy.cpp b/src/net/proxy.cpp index a6714bd9b..eed015e6d 100644 --- a/src/net/proxy.cpp +++ b/src/net/proxy.cpp @@ -47,11 +47,6 @@ proxy::~proxy() NOEXCEPT // ---------------------------------------------------------------------------- // The proxy does not (must not) stop itself. -bool proxy::stopped() const NOEXCEPT -{ - return socket_->stopped(); -} - void proxy::stop(const code& ec) NOEXCEPT { if (stopped()) @@ -168,14 +163,14 @@ void proxy::do_reading() NOEXCEPT // Properties. // ---------------------------------------------------------------------------- -asio::strand& proxy::strand() NOEXCEPT +asio::context& proxy::service() const NOEXCEPT { - return socket_->strand(); + return socket_->service(); } -asio::context& proxy::service() const NOEXCEPT +asio::strand& proxy::strand() NOEXCEPT { - return socket_->service(); + return socket_->strand(); } bool proxy::stranded() const NOEXCEPT @@ -183,19 +178,29 @@ bool proxy::stranded() const NOEXCEPT return socket_->stranded(); } +bool proxy::stopped() const NOEXCEPT +{ + return socket_->stopped(); +} + +bool proxy::inbound() const NOEXCEPT +{ + return socket_->inbound(); +} + bool proxy::secure() const NOEXCEPT { return socket_->secure(); } -uint64_t proxy::total() const NOEXCEPT +bool proxy::websocket() const NOEXCEPT { - return total_.load(std::memory_order_relaxed); + return socket_->websocket(); } -bool proxy::inbound() const NOEXCEPT +uint64_t proxy::total() const NOEXCEPT { - return socket_->inbound(); + return total_.load(std::memory_order_relaxed); } const config::address& proxy::address() const NOEXCEPT diff --git a/src/net/proxy_actions.cpp b/src/net/proxy_actions.cpp index 0ba82e9e6..b17c42113 100644 --- a/src/net/proxy_actions.cpp +++ b/src/net/proxy_actions.cpp @@ -172,7 +172,7 @@ void proxy::read(http::flat_buffer& buffer, http::request& request, void proxy::write(http::response&& response, count_handler&& handler) NOEXCEPT { - if (socket_->is_websocket()) + if (socket_->websocket()) { // Pointer ships moveable message through the send queue. const auto out = move_shared(std::move(response)); diff --git a/src/net/socket.cpp b/src/net/socket.cpp index 12a85a107..27e9c939a 100644 --- a/src/net/socket.cpp +++ b/src/net/socket.cpp @@ -89,19 +89,19 @@ socket::~socket() NOEXCEPT // Properties. // ---------------------------------------------------------------------------- -const config::address& socket::address() const NOEXCEPT +asio::context& socket::service() const NOEXCEPT { - return address_; + return service_; } -const config::endpoint& socket::endpoint() const NOEXCEPT +asio::strand& socket::strand() NOEXCEPT { - return endpoint_; + return strand_; } -bool socket::inbound() const NOEXCEPT +bool socket::stranded() const NOEXCEPT { - return inbound_; + return strand_.running_in_this_thread(); } bool socket::stopped() const NOEXCEPT @@ -109,26 +109,26 @@ bool socket::stopped() const NOEXCEPT return stopped_.load(); } -bool socket::stranded() const NOEXCEPT +bool socket::inbound() const NOEXCEPT { - return strand_.running_in_this_thread(); + return inbound_; } -asio::strand& socket::strand() NOEXCEPT +bool socket::websocket() const NOEXCEPT { - return strand_; + BC_ASSERT(stranded()); + return std::holds_alternative(socket_) || + std::holds_alternative(socket_); } -asio::context& socket::service() const NOEXCEPT +const config::address& socket::address() const NOEXCEPT { - return service_; + return address_; } -bool socket::is_websocket() const NOEXCEPT +const config::endpoint& socket::endpoint() const NOEXCEPT { - BC_ASSERT(stranded()); - return std::holds_alternative(socket_) || - std::holds_alternative(socket_); + return endpoint_; } // Context. @@ -161,7 +161,7 @@ bool socket::is_base() const NOEXCEPT socket::ws_t socket::get_ws() NOEXCEPT { BC_ASSERT(stranded()); - BC_ASSERT(is_websocket()); + BC_ASSERT(websocket()); return std::visit(overload { @@ -187,7 +187,7 @@ socket::ws_t socket::get_ws() NOEXCEPT socket::tcp_t socket::get_tcp() NOEXCEPT { BC_ASSERT(stranded()); - BC_ASSERT(!is_websocket()); + BC_ASSERT(!websocket()); return std::visit(overload { @@ -273,7 +273,7 @@ void socket::async_write(const asio::const_buffer& buffer, bool binary, try { - if (is_websocket()) + if (websocket()) { VARIANT_DISPATCH_METHOD(get_ws(), binary(binary)); VARIANT_DISPATCH_METHOD(get_ws(), @@ -300,7 +300,7 @@ void socket::async_read_some(const asio::mutable_buffer& buffer, { try { - if (is_websocket()) + if (websocket()) { VARIANT_DISPATCH_METHOD(get_ws(), async_read_some(buffer, std::bind(&socket::handle_async, @@ -327,7 +327,7 @@ void socket::async_read(http::flat_buffer& buffer, { try { - if (is_websocket()) + if (websocket()) { buffer.consume(buffer.size()); VARIANT_DISPATCH_METHOD(get_ws(), @@ -355,7 +355,7 @@ void socket::async_read(const asio::mutable_buffer& buffer, { try { - if (is_websocket()) + if (websocket()) { // Websockets read frame not size, use async_read(flat_buffer). handler(error::operation_failed, {}); @@ -382,7 +382,7 @@ void socket::async_read_http(http::flat_buffer& buffer, http::request& request, try { - if (is_websocket()) + if (websocket()) { // The body reader processes a websocket read just like a // beast::http::async_read but without the headers. The expected @@ -417,7 +417,7 @@ void socket::async_write_http(http::response&& response, try { - if (is_websocket()) + if (websocket()) { // The body writer processes a websocket write just like a // beast::http::async_write but without the headers. The expected @@ -455,7 +455,7 @@ void socket::handle_async(const boost_code& ec, size_t size, return; } - const auto code = is_websocket() ? error::ws_to_error_code(ec) : + const auto code = websocket() ? error::ws_to_error_code(ec) : error::asio_to_error_code(ec); if (code == error::unknown) diff --git a/src/net/socket_connect.cpp b/src/net/socket_connect.cpp index 6167f61fb..8626f5f26 100644 --- a/src/net/socket_connect.cpp +++ b/src/net/socket_connect.cpp @@ -112,7 +112,7 @@ void socket::do_connect(const asio::endpoints& range, const result_handler& handler) NOEXCEPT { BC_ASSERT(stranded()); - BC_ASSERT_MSG(!is_websocket(), "socket is upgraded"); + BC_ASSERT_MSG(!websocket(), "socket is upgraded"); BC_ASSERT_MSG(!std::get(socket_).is_open(), "connect on open socket"); diff --git a/src/net/socket_stop.cpp b/src/net/socket_stop.cpp index 05524c106..e91c3b75f 100644 --- a/src/net/socket_stop.cpp +++ b/src/net/socket_stop.cpp @@ -54,7 +54,7 @@ void socket::do_stop() NOEXCEPT { BC_ASSERT(stranded()); - if (is_websocket()) + if (websocket()) { // Release ws control callback handler. VARIANT_DISPATCH_METHOD(get_ws(), control_callback()); @@ -87,7 +87,7 @@ void socket::lazy_stop() NOEXCEPT void socket::do_ws_stop() NOEXCEPT { BC_ASSERT(stranded()); - if (!is_websocket()) + if (!websocket()) { do_ssl_stop(); return; diff --git a/src/net/socket_ws.cpp b/src/net/socket_ws.cpp index 168fd2ea6..63e684b99 100644 --- a/src/net/socket_ws.cpp +++ b/src/net/socket_ws.cpp @@ -128,7 +128,7 @@ void socket::handle_ws_event(ws::frame_type kind, code socket::set_websocket(const http::request& request) NOEXCEPT { BC_ASSERT(stranded()); - BC_ASSERT(!is_websocket()); + BC_ASSERT(!websocket()); try { From 0e34b64285af20e0c15c8d90b4de07a759989d69 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 6 May 2026 00:14:25 -0400 Subject: [PATCH 2/5] Comments, style. --- include/bitcoin/network/channels/channel_rpc.hpp | 15 +++------------ src/channels/channel_http.cpp | 12 +++--------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/include/bitcoin/network/channels/channel_rpc.hpp b/include/bitcoin/network/channels/channel_rpc.hpp index 3aaf697c9..6fe2e2dbe 100644 --- a/include/bitcoin/network/channels/channel_rpc.hpp +++ b/include/bitcoin/network/channels/channel_rpc.hpp @@ -26,21 +26,12 @@ #include #include -// TODO: This template can be re-based on channel_http giving it the ability to -// TODO: support RPC over both tcp and http/ws. In that case the settings type -// TODO: can be templatized and reader/writer configured at compile. Without an -// TODO: http reader the socket cannot be upgraded to ws and thus operates as a -// TODO: simple TCP socket, and TLS remains possible with all protocols. Http -// TODO: methods are dispatched from channel to base protocol and re-dispatched -// TODO: to subscribers. TCP/WS are dispatched to protocol as a custom method. -// TODO: this allows the base protocol to differentiate these as non-http for -// TODO: selection of the proper response path. As http is half duplex, notify -// TODO: is only provided to a TCP or upgraded (WS) socket. - namespace libbitcoin { namespace network { -/// Read rpc-request and send rpc-response, dispatch to Interface. +/// read/write rpc::request/response over tcp/s, dispatch to Interface. +/// For rpc over http/s and/or ws/s use channel_http with forwarding +/// protocol dispatch (e.g. from http::post and synthetic ws::unknown). template class channel_rpc : public channel diff --git a/src/channels/channel_http.cpp b/src/channels/channel_http.cpp index 15a2c01cd..842cc2f9a 100644 --- a/src/channels/channel_http.cpp +++ b/src/channels/channel_http.cpp @@ -65,8 +65,8 @@ void channel_http::resume() NOEXCEPT // Read cycle. // ---------------------------------------------------------------------------- +// Failure to call receive() after successful message handling stalls channel. -// Failure to call after successful message handling causes stalled channel. void channel_http::receive() NOEXCEPT { BC_ASSERT(stranded()); @@ -75,16 +75,9 @@ void channel_http::receive() NOEXCEPT if (stopped() || paused() || reading_) return; - // TODO: Extend support to batch (array of rpc). - // TODO: See notes in channel_rpc.ipp. This is the same except there must - // TODO: be an set of socket methods for incremental http-rpc. This can - // TODO: be handled by writing the header, whole rpc responses, and close. - // TODO: Boost beast provides these independent async calls for chunking. - reading_ = true; const auto in = to_shared(); - // Post handle_read to strand upon stop, error, or buffer full. read(request_buffer(), *in, std::bind(&channel_http::handle_receive, shared_from_base(), _1, _2, in)); @@ -114,8 +107,9 @@ void channel_http::handle_receive(const code& ec, size_t bytes, return; } - reading_ = false; LOGA(log_message(*request, bytes)); + + reading_ = false; dispatch(request); } From d3017f9d24466c96a06dbb34a64a101cdc03267d Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 6 May 2026 01:36:17 -0400 Subject: [PATCH 3/5] Expose base http and rpc channel reader config. --- .../bitcoin/network/channels/channel_http.hpp | 3 +++ .../bitcoin/network/channels/channel_rpc.hpp | 3 +++ .../network/impl/channels/channel_rpc.ipp | 15 ++++++++++++-- src/channels/channel_http.cpp | 20 +++++++++++++++++-- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/include/bitcoin/network/channels/channel_http.hpp b/include/bitcoin/network/channels/channel_http.hpp index a7e89ccbb..65a93ac75 100644 --- a/include/bitcoin/network/channels/channel_http.hpp +++ b/include/bitcoin/network/channels/channel_http.hpp @@ -80,6 +80,9 @@ class BCT_API channel_http /// Read request buffer (requires strand). virtual http::flat_buffer& request_buffer() NOEXCEPT; + /// Override to change default websocket reader expectation/config. + virtual http::request_ptr create_request() const NOEXCEPT; + /// Determine if http basic authorization is satisfied if enabled. virtual bool unauthorized(const http::request& request) NOEXCEPT; diff --git a/include/bitcoin/network/channels/channel_rpc.hpp b/include/bitcoin/network/channels/channel_rpc.hpp index 6fe2e2dbe..c775c3ac6 100644 --- a/include/bitcoin/network/channels/channel_rpc.hpp +++ b/include/bitcoin/network/channels/channel_rpc.hpp @@ -90,6 +90,9 @@ class channel_rpc /// Read request buffer (requires strand). virtual inline http::flat_buffer& request_buffer() NOEXCEPT; + /// Override to change default reader config. + virtual rpc::request_ptr create_request() const NOEXCEPT; + /// Override to dispatch request to subscribers by requested method. virtual inline void dispatch(const rpc::request_cptr& request) NOEXCEPT; diff --git a/include/bitcoin/network/impl/channels/channel_rpc.ipp b/include/bitcoin/network/impl/channels/channel_rpc.ipp index b523f0f65..6b9a68f93 100644 --- a/include/bitcoin/network/impl/channels/channel_rpc.ipp +++ b/include/bitcoin/network/impl/channels/channel_rpc.ipp @@ -69,8 +69,8 @@ inline void CLASS::receive() NOEXCEPT return; reading_ = true; - const auto in = to_shared(); - + const auto in = create_request(); + read(request_buffer(), *in, std::bind(&channel_rpc::handle_receive, shared_from_base(), _1, _2, in)); @@ -128,6 +128,9 @@ inline void CLASS::dispatch(const rpc::request_cptr& request) NOEXCEPT stop(code); } +// request helpers +// ---------------------------------------------------------------------------- + TEMPLATE inline http::flat_buffer& CLASS::request_buffer() NOEXCEPT { @@ -135,6 +138,14 @@ inline http::flat_buffer& CLASS::request_buffer() NOEXCEPT return request_buffer_; } +TEMPLATE +inline rpc::request_ptr CLASS::create_request() const NOEXCEPT +{ + const auto out = system::to_shared(); + out->strict = true; + return out; +} + // Send. // ---------------------------------------------------------------------------- diff --git a/src/channels/channel_http.cpp b/src/channels/channel_http.cpp index 842cc2f9a..a47604beb 100644 --- a/src/channels/channel_http.cpp +++ b/src/channels/channel_http.cpp @@ -28,7 +28,6 @@ namespace libbitcoin { namespace network { -#define CLASS channel_http #define CASE_REQUEST_TO_MODEL(verb_, request_, model_) \ case verb::verb_: \ model_.method = #verb_; \ @@ -76,7 +75,7 @@ void channel_http::receive() NOEXCEPT return; reading_ = true; - const auto in = to_shared(); + const auto in = create_request(); read(request_buffer(), *in, std::bind(&channel_http::handle_receive, @@ -146,11 +145,28 @@ void channel_http::dispatch(const request_cptr& request) NOEXCEPT stop(code); } +// request helpers +// ---------------------------------------------------------------------------- + flat_buffer& channel_http::request_buffer() NOEXCEPT { return request_buffer_; } +request_ptr channel_http::create_request() const NOEXCEPT +{ + const auto out = to_shared(); + if (websocket()) + { + // out->method() will return verb::unknown (mapped in dispatch). + out->method_string("websocket"); + out->body() = http::json_value{}; + out->body().plain_json = true; + } + + return out; +} + // Send. // ---------------------------------------------------------------------------- From 93edcb37b475e5c275f7413829eb7f92b2a51182 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 6 May 2026 01:41:43 -0400 Subject: [PATCH 4/5] Enable assign_json_buffer() for http. --- src/channels/channel_http.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/channels/channel_http.cpp b/src/channels/channel_http.cpp index a47604beb..0790ff3ef 100644 --- a/src/channels/channel_http.cpp +++ b/src/channels/channel_http.cpp @@ -192,15 +192,17 @@ void channel_http::handle_send(const code& ec, size_t bytes, } // private -void channel_http::assign_json_buffer(response& ) NOEXCEPT +void channel_http::assign_json_buffer(response& response) NOEXCEPT { - // TODO: limit to http (not full duplex safe). - ////if (const auto& body = response.body(); - //// body.contains()) - ////{ - //// auto& value = body.get(); - //// value.buffer = response_buffer_; - ////} + // websocket is full duplex, so cannot use shared json repsonse buffer. + if (!websocket()) + { + const auto& body = response.body(); + if (body.contains()) + body.get().buffer = response_buffer_; + else if (body.contains()) + body.get().buffer = response_buffer_; + } } // unauthorized helpers From 409bf84236934f1ec4a34d5d7efd4c0272603903 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 6 May 2026 02:02:28 -0400 Subject: [PATCH 5/5] Move protocol channel continue back to channel, comments. --- include/bitcoin/network/impl/channels/channel_rpc.ipp | 2 +- src/channels/channel_http.cpp | 4 ++++ src/protocols/protocol_http.cpp | 7 ------- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/include/bitcoin/network/impl/channels/channel_rpc.ipp b/include/bitcoin/network/impl/channels/channel_rpc.ipp index 6b9a68f93..e50c9b871 100644 --- a/include/bitcoin/network/impl/channels/channel_rpc.ipp +++ b/include/bitcoin/network/impl/channels/channel_rpc.ipp @@ -194,7 +194,7 @@ inline void CLASS::handle_send(const code& ec, size_t bytes, // Typically a noop, but handshake may pause channel here. handler(ec); - // Restart the listener (following response to request only). + // Restart the listener (only in response to requests). if constexpr (is_same_type) { receive(); diff --git a/src/channels/channel_http.cpp b/src/channels/channel_http.cpp index 0790ff3ef..c1a043f65 100644 --- a/src/channels/channel_http.cpp +++ b/src/channels/channel_http.cpp @@ -189,6 +189,10 @@ void channel_http::handle_send(const code& ec, size_t bytes, if (ec) stop(ec); LOGA(boost::format(message) % bytes); handler(ec); + + // Restart the listener (only in response to requests). + // TODO: use new ::send(rresponse, handler) method to differentiate. + receive(); } // private diff --git a/src/protocols/protocol_http.cpp b/src/protocols/protocol_http.cpp index c4bd3568b..fdc12b6d5 100644 --- a/src/protocols/protocol_http.cpp +++ b/src/protocols/protocol_http.cpp @@ -324,7 +324,6 @@ void protocol_http::send_ok(const request& request) NOEXCEPT // Handle sends. // ---------------------------------------------------------------------------- -// TODO: Move channel restart logic into channel and forward from here. void protocol_http::handle_complete(const code& ec, const code& reason) NOEXCEPT { @@ -334,13 +333,7 @@ void protocol_http::handle_complete(const code& ec, return; if (reason) - { stop(reason); - return; - } - - // Continue read loop. - channel_->receive(); } // Utilities.