arsd icon indicating copy to clipboard operation
arsd copied to clipboard

websocket server: can't get any more data from a closed source

Open andre2007 opened this issue 5 years ago • 6 comments

I have a websocket client written in python:

import asyncio
import websockets

async def hello():
    uri = "ws://localhost:5000"
    async with websockets.connect(uri) as websocket:
        name = input("What's your name? ")

        await websocket.send(name)
        print(f"> {name}")

        greeting = await websocket.recv()
        print(f"< {greeting}")

asyncio.get_event_loop().run_until_complete(hello())

and a websocket server written in D:

//  dmd -i -version=embedded_httpd -run server.d

import std;
import arsd.cgi;

void main() 
{
    writeln("Listening on port 5000");
    cgiMainImpl!(websocketEcho, Cgi, defaultMaxContentLength)(["--port", "5000"]);
}

void websocketEcho(Cgi cgi) {
	assert(cgi.websocketRequested(), "i want a web socket!");
	auto websocket = cgi.acceptWebsocket();
	writeln("Websocket accepted");

	auto msg = websocket.recv();
	while(msg.opcode != WebSocketOpcode.close) {
		if(msg.opcode == WebSocketOpcode.text) {
			websocket.send(msg.textData);
		} else if(msg.opcode == WebSocketOpcode.binary) {
			websocket.send(msg.data);
		}

		msg = websocket.recv();
	}

	websocket.close();
}

The python client is interactive and ask on the terminal for a string. It seems the web socket server timeouts out after 1 second if I am to slow to enter a text on the terminal. By changing the websocket client to immediately sending a string, everything works as expected.

I am unsure, whether this is the expected behavior or it is a bug. The source code in cgi.d says s.th. like recv is blocking, therefore I assumed, it should wait, without timing out?

andre2007 avatar Feb 10 '20 12:02 andre2007

On Mon, Feb 10, 2020 at 04:56:23AM -0800, andre2007 wrote:

I am unsure, whether this is the expected behavior or it is a bug. The source code in cgi.d says s.th. like recv is blocking, therefore I assumed, it should wait, without timing out?

It is blocking but with a receive timeout. We could probably tweak that, I just had some bad experience with incomplete connections throwing it off.

The websocket server is a lil buggy in general too especially when it comes to disconnects. I think I fixed most of it but part of the code tries to read another http request off the same connection which obviously doesn't work. (Doesn't break per se, it just throws one of those exceptions you saw and resets the connection but still.)

Though if the websocket thing is timing out after a second of inactivity AFTER the connection is established and does websocket handshake, that's super problematic. Does it only do this on the first message or does it time out after second messages either?

websockets are meant to have a lot of silence so i migt need to change that timeout here regardless.

adamdruppe avatar Feb 10 '20 13:02 adamdruppe

The python client application terminates with an error due to the server timeout

websockets.exceptions.ConnectionClosedError: code = 1006 (connection closed abnormally [internal]), no reason

If I restart the python client, I have exactly the same behavior on server side.

andre2007 avatar Feb 10 '20 13:02 andre2007

I just compared how vibed solves this issue, it has a function waitForData (with an optional timeout value).

class WebsocketService {
	@path("/") void getWebsocket(scope WebSocket socket){
		writeln("Websocket accepted");
		while (socket.waitForData()) {
			if (!socket.connected) break;
                        socket.send(socket.receiveText);
		}
	}
}

Maybe s.th. similiar could be added to your library?

andre2007 avatar Feb 11 '20 07:02 andre2007

I think I found a way to wait for a specific amount of time for input. This seems to work in the sample application:

void websocketEcho(Cgi cgi) {
	assert(cgi.websocketRequested(), "i want a web socket!");
	auto websocket = cgi.acceptWebsocket();
	
	while(websocket.recvAvailable(300.seconds)) 
	{
		auto msg = websocket.recv();
		if (msg.opcode == WebSocketOpcode.close) break;

		if(msg.opcode == WebSocketOpcode.text) {
			websocket.send(msg.textData);
		} else if(msg.opcode == WebSocketOpcode.binary) {
			websocket.send(msg.data);
		}
	}
	websocket.close();
}

andre2007 avatar Feb 11 '20 13:02 andre2007

yeah, the function is available, maybe it just needs to be documented or the default tweaked though.

Though I also kinda want to finish my separate websocket server. The way that'd work is you accept the websocket, then hand it off to an addon server with another function and from there it is 100% event based.

I just haven't finished all the details of how you'd provide your own handler function....

adamdruppe avatar Feb 11 '20 13:02 adamdruppe

recvAvailable timeout works by passing timeout variable to Socket.select(check, null, null, timeout);. Socket.select allows to wait an infinite amount of time, by passing null as timeout value.

Unfortunately, I cannot trigger this behavior, as passing null to recvAvailable is a syntax error.

Maybe recvAvailable could be changed like this:

// returns true if data available, false if it timed out
		bool recvAvailable(Duration timeout = dur!"msecs"(0)) {
			Socket socket = cgi.idlol.source;

			auto check = new SocketSet();
			check.add(socket);

			auto got = (timeout.isNegative) ? 
				Socket.select(check, null, null) : 
				Socket.select(check, null, null, timeout);

			if(got > 0)
				return true;
			return false;
		}

A negative duration would then cause an infinite wait time.

andre2007 avatar Feb 13 '20 13:02 andre2007