Hyper: Server: Provide a closed connection notification

Created on 22 Dec 2015  Â·  10Comments  Â·  Source: hyperium/hyper

Similar to what golang provides with CloseNotifier, when a request takes a while to process (or is just waiting/long-polling), it is often convenient to detect when the client closed the connection.
I did not find a way to get this information with hyper (maybe I just missed it?).

All 10 comments

While hyper uses std::net, the blocking io is used. Without select, the
only way to know a Connection was closed is to try to write to it (or read,
which will block until keep alive notices it's dead).

So you can only either read or write on it to determine if it's closed.

On Tue, Dec 22, 2015, 1:42 PM Alexandre Bury [email protected]
wrote:

Similar to what golang provides with CloseNotifier
https://golang.org/pkg/net/http/#CloseNotifier, when a request takes a
while to process (or is just waiting/long-polling), it is often convenient
to detect when the client closed the connection.
I did not find a way to get this information with hyper (maybe I just
missed it?).

—
Reply to this email directly or view it on GitHub
https://github.com/hyperium/hyper/issues/707.

I see. That's actually the way golang implement it, by spawning a thread (well, a goroutine) to try to read from the body, and notify when the call completes.

I guess channels in rust are not as widespread as in golang, making an idiomatic API for this trickier (though perhaps using std::sync::CondVar?...), and it'd also probably add the need for an Arc in there... :-/

Also, I tried this simple program:

extern crate hyper;

use std::io::Read;

use hyper::Server;
use hyper::server::Request;
use hyper::server::Response;

fn main() {
    Server::http("127.0.0.1:3000").unwrap().handle(|mut req: Request, _: Response| {
        let mut b = [0];
        // Attempt to block until the client disconnects
        println!("{}", req.read(&mut b).unwrap_or(42));
        println!("{:?}", b);
    });
}

And them I simply run curl http://localhost:3000

Unfortunately, the read comand returns immediately here (it returns 0 and writes nothing to b), and does not wait for the client to cancel the connection.

Trying to write is not always an option, it would be really nice to have this working (and golang also uses a read attempt, so it should be possible?). Maybe hyper does something on the connection?

Edit: indeed, a Request.body is a SizedReader or an EmptyReader: request.rs. And those don't forward the request to the underlying connection most of the time: h1.rs.

If the request has no body, then a read will return EOF.

Yes, so in the current condition it doesn't look possible to detect cancellation of a simple GET request?
The fact that it works in golang makes me believe it would work if reading on the underlying socket, but hyper does not expose that.

That presents a different problem though, since std::net uses blocking IO. If hyper let you try to read on the socket after having read all the declared request body, the read would block until 1) the client sent more data, or 2) the client disconnects. If it's a GET request, the client does not expect to send any more data, so you would block yourself until the client timed out. No further code would execute in that thread.

You might be able to get something if you duplicated the socket, put it in another thread, and let it readblock there...

Either way, the move to async IO should help, since epoll gives notifications when the client hangs up.

Exactly, the blocking read in another thread is what golang's standard library does to detect client disconnection. It also uses blocking IO, just like here.

Async IO is another solution to this problem, but I'm not sure it's ready just yet.

The problem is you would need to be sure that no other data would be coming from the connection. Otherwise, the other thread would get it, instead of your main thread.

You could do this yourself, for now, anyways:

let mut tcp = req.downcast_ref::<hyper::net::HttpStream>().unwrap().0.try_clone().unwrap();
let (tx, rx) = std::sync::mspc::channel();
thread::spawn(move || {
    tx.send(tcp.read(&[0u8]))
});

// else where
match rx.try_recv() {
    Ok(Ok(0)) => eof(),
    Ok(Err(io_error)) => error(),
    Err(TryRecvError::Disconnected) => thread_is_dead(),
    Err(TryRecvError::Empty) => still_connected()
}

Golang implements it by actually copying from the original socket to the one visible by the user (they use a io.Pipe for that) - it also means they keep reading until EOF is found. That way, the user still has access to the data in the body , and the library can detect when the socket is closed.

It should be doable using downcast_ref indeed, thanks. Now onto exposing this from iron...

@seanmonstar Hi! Can we detect client close in server side in the current version 0.13.x?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

da2018 picture da2018  Â·  3Comments

FGRibreau picture FGRibreau  Â·  4Comments

fabioberger picture fabioberger  Â·  5Comments

mcseemk picture mcseemk  Â·  3Comments

crackcomm picture crackcomm  Â·  5Comments