NanoHttpd / nanohttpd

Tiny, easily embeddable HTTP server in Java.
http://nanohttpd.org
BSD 3-Clause "New" or "Revised" License
6.9k stars 1.69k forks source link

How to deal with hanging sockets? #555

Open p-v-d-Veeken opened 4 years ago

p-v-d-Veeken commented 4 years ago

I have found an issue (bug?) with unresponsive open websockets being uncloseable.

To reproduce this issue simply use a browser on a laptop (Only tested with a MacBook) to connect to a NanoHttpd websocket and close the lid of the laptop, causing the laptop to instantly go into hibernate. Doing this will not disconnect the websocket, the consequence of which is that when the server tries to send a message over the socket, it will wait indefinitely for the message to be delivered.

This is in itself not that bad, because it's fairly easy to detect a hanging socket. However, this becomes significantly worse when we try to close the socket in question:

https://github.com/NanoHttpd/nanohttpd/blob/efb2ebf85a2b06f7c508aba9eaad5377e3a01e81/websocket/src/main/java/org/nanohttpd/protocols/websockets/WebSocket.java#L119-L127

Attempting to close the socket sends another message, which will also hang indefinitely.

To the best of my understanding there is no way to fix or circumvent the issue described above, but I'll be gladly proven wrong.

LordFokas commented 4 years ago

There is no such thing as "no way to fix". I could do it in a couple hours if I had the time or patience, but like everything else that is currently going on here it will have to wait.

p-v-d-Veeken commented 4 years ago

Of course it's fixable, I meant that someone using NanoHttpd has no way to avoid the problem.

What kind of solution did you have in mind? I'd be happy to take a stab at it and see if I can fix it. :)

LordFokas commented 4 years ago

For example with server side pings. In my use case in the past I had an automatic ping every 4 seconds and pongs were accounted for, if when you sent a ping more than 3 pongs were missing (aka unresponsive for >12 seconds) the socket would be forcibly closed and destroyed.

This is really something that should be built in, and if the server components were as abstract and pluggable as I'd like anyone could easily modify, fix or work around these kind of situations... I've actually started a lot of work on stuff like that, but I only have so much free time that I can sink into this.

p-v-d-Veeken commented 4 years ago

For anyone stumbling upon this issue, I've managed to kill unresponsive sockets using the following black magic incantation:

void handleUnresponsiveSocket(NanoWSD.WebSocket unresponsive) {
    try {
        Class<NanoWSD.WebSocket> wsClass = NanoWSD.WebSocket.class;
        //Get method which closes socket without sending close message
        Method doClose = wsClass.getDeclaredMethod("doClose", CloseCode.class, String.class, boolean.class);

        doClose.setAccessible(true); //Circumvent private modifier
        doClose.invoke(unresponsive, CloseCode.ProtocolError, "Unresponsive", true); //Call method
    } catch (Exception e) {
        logger.error("Error force closing socket.", e);
    }
}

It goes without saying that this shouldn't be used for sockets which are still responsive