arsd
arsd copied to clipboard
websocket server: can't get any more data from a closed source
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?
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
recvis 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.
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.
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?
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();
}
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....
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.