Boost logo

Boost Users :

From: Dominique Devienne (ddevienne_at_[hidden])
Date: 2020-12-11 18:35:12


Oops, this was meant for the list of course. --DD

---------- Forwarded message ---------
From: Dominique Devienne <ddevienne_at_[hidden]>
Date: Fri, Dec 11, 2020 at 6:54 PM
Subject: Re: [Boost-users] [Beast] Slow HTTP connect on Windows. Normal?
To: Vinnie Falco <vinnie.falco_at_[hidden]>

On Wed, Nov 25, 2020 at 7:43 PM Vinnie Falco <vinnie.falco_at_[hidden]> wrote:

> On Wed, Nov 25, 2020 at 2:55 AM Dominique Devienne via Boost-users
> <boost-users_at_[hidden]> wrote:
> > Given the high connect time on Windows, I thought I'd try to keep the
> connection open on the client, and issue several send-request/read-response
> pairs, using the Beast-based (sync) HTTP client, but only the first one
> works correctly, the 2nd errors out with:
> >
> > Error: An established connection was aborted by the software in your
> host machine
>
> This means that the server closed the connection. Are you setting
> HTTP/1.1 in the request? For HTTP/1.1 the default is to enable
> keep-alive. But for 1.0 the default is to close the connection.
>

Sorry for the delay. Just getting back to this.
Yes, I'm in 1.1, and I verified that the request has Keep-Alive,
and I'm (or the example, I don't recall) is setting the same value on the
response.

> it sounds like your connection is being closed after one request. Beast
> definitely supports keep-alive, so this is certainly user error.
> Please inspect the request/response messages taking place and post
> them on the list unmodified (use a wire sniffer for this if necessary)
> if you are still having problems.
>

Debugging this, I can see that the close arg to on_write is true, despite
the Keep-Alive on the response.
That response has an empty_body BTW, in case it matters. This route just
replies 200 OK or 403 Forbidden,
there's no body to return, only the headers matter to the client.

The reason why the connection is closed after the 1st request seems to be
because of need_eof().
The async server example has something like this in `send()`:

    template <typename RES>
    void send(RES&& msg) {
        // The lifetime of the message has to extend
        // for the duration of the async operation so
        // we use a shared_ptr to manage it.
        auto sp = std::make_shared<RES>(std::move(msg));

        // Store a type-erased version of the shared
        // pointer in the class to keep it alive.
        res_ = sp;

        bool need_eof = sp->need_eof();
        ctx_.out() << "send: sp->need_eof() = " << need_eof << std::endl;

        // Write the response
        http::async_write(
            stream_, *sp,
            beast::bind_front_handler(
                &session::on_write,
                shared_from_this(),
                sp->need_eof()
            )
        );
    }

And `need_eof()` is true indeed. Which thus calls `on_write()` with `bool
close = true`,
resulting in a `do_close()` instead of waiting for the next request:

      void on_write(
        bool close,
        beast::error_code ec,
        std::size_t bytes_transferred
    ) {
        boost::ignore_unused(bytes_transferred);

        if (ec) {
            return; // fail(ec, "write");
        }

        if (close) {
            // This means we should close the connection, usually because
            // the response indicated the "Connection: close" semantic.
            ctx_.out() << "on_write: close()" << std::endl;
            return do_close();
        }

        // We're done with the response so delete it
        res_.reset();

        // Read another request
        do_read();
    }

I stepped into `need_eof()`, and all conditions of the `if` are false,
resulting in the `return true` at the end.

template<bool isRequest, class Body, class Fields>
bool
message<isRequest, Body, Fields>::
need_eof(std::false_type) const
{
    // VFALCO Do we need a way to let the caller say "the body is
intentionally skipped"?
    if( this->result() == status::no_content ||
        this->result() == status::not_modified ||
        to_status_class(this->result()) ==
            status_class::informational ||
        has_content_length() ||
        chunked())
        return ! keep_alive();
    return true;
}

So is the issue having an empty_body? I'm not supposed to have one on a GET?

BTW, I tried commenting out the `do_close()` inside `on_write()`, just to
see what happens,
and it still doesn't work, with the same error, but an additional delay
(timeout?) I think.

Here's the response I'm sending:

                ctx_.out() << "Sending response: 200 OK" << std::endl;
                http::response<http::empty_body> res{http::status::ok,
req_.version()};
                res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
                res.keep_alive(req_.keep_alive());
                ctx_.out() << "on_read: res.keep_alive() = " <<
res.keep_alive() << std::endl;
                return send(std::move(res));

I'm fairly sure it's user-error, but it's not easy to figure out for a
Beast novice like myself. --DD



Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net