Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failing to upgrade to websocket connection #175

Closed
beorn opened this issue Dec 1, 2015 · 35 comments
Closed

Failing to upgrade to websocket connection #175

beorn opened this issue Dec 1, 2015 · 35 comments
Labels

Comments

@beorn
Copy link

beorn commented Dec 1, 2015

Hi,

We're using BestHTTP for Unity3d, and I'm running into two problems:

(1) flask-socketio advertises websockets as an upgrade option even if we run it in develop=True => using werkzeug

game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | INFO:geventwebsocket.handler:10.0.2.2 - - [2015-12-01 06:02:20] "GET /experiment/ HTTP/1.1" 200 4201 0.009273
game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | INFO:engineio:c8f06319ba384e9aaf61f14df85e4746: Sending packet OPEN with {'pingInterval': 25000, 'pingTimeout': 60000, 'upgrades': ['websocket'], 'sid': 'c8f06319ba384e9aaf61f14df85e4746'}
game_1 | INFO:root:connect
[...]
game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | DEBUG:geventwebsocket.handler:Can only upgrade connection if using GET method.

or is should websockets be supported in this mode?

(2) If I run in non-develop mode, the websocket succeeds against the stock chat HTML/JavaScript client, but fails against the BestHTTP socketio client that we want to use. I know the issue might be with BestHTTP, but maybe you could let me know how to troubleshoot it better? Here's the log:

game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | INFO:engineio:133a78edf5b6479c96dad4b572539fe1: Sending packet OPEN with {'pingInterval': 25000, 'pingTimeout': 60000, 'upgrades': ['websocket'], 'sid': '133a78edf5b6479c96dad4b572539fe1'}
game_1 | INFO:root:connect
game_1 | INFO:root:<Request 'http://127.0.0.1:8088/socket.io/?EIO=4&transport=polling&t=1548055179-0&b64=true' [GET]>
game_1 | INFO:geventwebsocket.handler:10.0.2.2 - - [2015-12-01 06:40:32] "GET /socket.io/?EIO=4&transport=polling&t=1548055179-0&b64=true HTTP/1.1" 200 406 0.012079
game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | DEBUG:geventwebsocket.handler:Attempting to upgrade connection
game_1 | DEBUG:geventwebsocket.handler:WebSocket request accepted, switching protocols
game_1 | INFO:engineio:133a78edf5b6479c96dad4b572539fe1: Received request to upgrade to websocket
... then hangs for a while, then...
game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | INFO:engineio:dbca22ef0dbb42818ac0952d75bae050: Sending packet OPEN with {'pingInterval': 25000, 'pingTimeout': 60000, 'upgrades': ['websocket'], 'sid': 'dbca22ef0dbb42818ac0952d75bae050'}
game_1 | INFO:root:connect
game_1 | INFO:root:<Request 'http://127.0.0.1:8088/socket.io/?EIO=4&transport=polling&t=1548075857-1&b64=true' [GET]>
game_1 | INFO:socketio:emitting event "my response" to dbca22ef0dbb42818ac0952d75bae050 [/]
game_1 | INFO:engineio:dbca22ef0dbb42818ac0952d75bae050: Sending packet MESSAGE with 2["my response",{"count":1,"data":"You connected"}]
game_1 | INFO:engineio:dbca22ef0dbb42818ac0952d75bae050: Sending packet MESSAGE with 0
game_1 | INFO:geventwebsocket.handler:10.0.2.2 - - [2015-12-01 06:40:52] "GET /socket.io/?EIO=4&transport=polling&t=1548075857-1&b64=true HTTP/1.1" 200 406 0.005518
game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | DEBUG:geventwebsocket.handler:Attempting to upgrade connection
game_1 | DEBUG:geventwebsocket.handler:WebSocket request accepted, switching protocols
game_1 | INFO:engineio:dbca22ef0dbb42818ac0952d75bae050: Received request to upgrade to websocket
... then hangs again

And on the BestHTTP side it appears to send a ping probe and wait for the response of that, which it never gets:

case TransportEventTypes.Pong:
  // Answer for a Ping Probe. 
  if (packet.Payload == "probe")
  {
    HTTPManager.Logger.Information("WebSocketTransport", "\"probe\" packet received, sending Upgrade packet");
    // We will send an Upgrade("52") packet.
    Send(new Packet(TransportEventTypes.Upgrade, SocketIOEventTypes.Event, "/", string.Empty));
  }

BestHTTP link: https://www.assetstore.unity3d.com/en/#!/content/10872
BestHTTP docs: https://docs.google.com/document/d/181l8SggPrVF1qRoPMEwobN_1Fn7NXOu-VtfjE6wvokg/edit

@miguelgrinberg
Copy link
Owner

(1) flask-socketio advertises websockets as an upgrade option even if we run it in develop=True => using werkzeug

The current version of Flask-SocketIO does not force Werkzeug when you are in debug mode. If you have eventlet or gevent installed, those will be used, and WebSocket will be enabled.

but fails against the BestHTTP socketio client that we want to use

Do you have a link for this Socket.IO client?

@beorn
Copy link
Author

beorn commented Dec 1, 2015

The current version of Flask-SocketIO does not force Werkzeug when you are in debug mode. If you have eventlet or gevent installed, those will be used, and WebSocket will be enabled.

Ok, that makes sense (though the docs are a bit inconsistent). If that's the case, though, there's an issue with the HTML/JavaScript client upgrading the connection.

Do you have a link for this Socket.IO client?

I added a link to it - it's unfortunately only commercially available on the Unity assetstore, though it does come with source.

@miguelgrinberg
Copy link
Owner

If that's the case, though, there's an issue with the HTML/JavaScript client upgrading the connection.

What version of the Javascript client are you using for this test? It's odd that gevent-websocket complains about the upgrade request being != 'GET', that suggests the problem is on the client side. Do you get the same problem if you use the example application from this repository?

Regarding the BestHTTP Socket.IO support, do you know what version is their implementation compatible with? Socket.IO went through major changes at the 1.0 release. From looking at the file listing (the only thing I can see without making a purchase) it isn't clear if they align with pre or post 1.0.

@beorn
Copy link
Author

beorn commented Dec 1, 2015

What version of the Javascript client are you using for this test? It's odd that gevent-websocket complains about the upgrade request being != 'GET', that suggests the problem is on the client side. Do you get the same problem if you use the example application from this repository?

I'll try with a cleaner/smaller setup and let you know.

Regarding the BestHTTP Socket.IO support, do you know what version is their implementation compatible with? Socket.IO went through major changes at the 1.0 release. From looking at the file listing (the only thing I can see without making a purchase) it isn't clear if they align with pre or post 1.0.

http://forum.unity3d.com/threads/best-http-released.200006/ and
https://www.assetstore.unity3d.com/en/#!/content/10872
seems to indicate that they're compatible with v1.3.+ of Socket.IO.

@Benedicht
Copy link

Hi.

BestHTTP developer is here.

My Socket.IO implementation supports the post v1 line. The WebSocket implementation is also written by me against the official RFC, and it shouldn't switch to a non-GET method.
Is there any public endpoint that i try to connect to to debug my plugin?
Or, of course I can send a version of my plugin to anyone who want's to try it out.

Also, using an intermediate proxy (like https://www.charlesproxy.com/) to see and share the logs what requests the plugin really sent out would be good too.

@beorn
Copy link
Author

beorn commented Dec 1, 2015

Okay, I tried with the app in Flask-SocketIO/example, and getting the same issue:

INFO:engineio:Server initialized for gevent.
INFO:werkzeug: * Restarting with stat
INFO:engineio:Server initialized for gevent.
WARNING:werkzeug: * Debugger is active!
INFO:werkzeug: * Debugger pin code: 777-019-370
DEBUG:geventwebsocket.handler:Initializing WebSocket
DEBUG:geventwebsocket.handler:Validating WebSocket request
INFO:engineio:aa23155a91cd4ce998d009205395c503: Sending packet OPEN with {'pingInterval': 25000, 'pingTimeout': 60000, 'upgrades': ['websocket'], 'sid': 'aa23155a91cd4ce998d009205395c503'}
INFO:engineio:aa23155a91cd4ce998d009205395c503: Sending packet MESSAGE with 0
INFO:geventwebsocket.handler:192.168.99.1 - - [2015-12-01 08:25:25] "GET /socket.io/?EIO=3&transport=polling&t=1448958325556-56 HTTP/1.1" 200 375 0.003376
DEBUG:geventwebsocket.handler:Initializing WebSocket
DEBUG:geventwebsocket.handler:Validating WebSocket request
DEBUG:geventwebsocket.handler:Attempting to upgrade connection
DEBUG:geventwebsocket.handler:WebSocket request accepted, switching protocols
DEBUG:geventwebsocket.handler:Closed WebSocket
DEBUG:geventwebsocket.handler:Failed to write closing frame -> closing socket
DEBUG:geventwebsocket.handler:Closed WebSocket
DEBUG:geventwebsocket.handler:Initializing WebSocket
DEBUG:geventwebsocket.handler:Validating WebSocket request
DEBUG:geventwebsocket.handler:Can only upgrade connection if using GET method.
INFO:engineio:aa23155a91cd4ce998d009205395c503: Received packet MESSAGE with 0/test
INFO:socketio:emitting event "my response" to aa23155a91cd4ce998d009205395c503 [/test]
INFO:engineio:aa23155a91cd4ce998d009205395c503: Sending packet MESSAGE with 2/test,["my response",{"count":0,"data":"Connected"}]
INFO:engineio:aa23155a91cd4ce998d009205395c503: Sending packet MESSAGE with 0/test
INFO:geventwebsocket.handler:192.168.99.1 - - [2015-12-01 08:25:25] "POST /socket.io/?EIO=3&transport=polling&t=1448958325579-57&sid=aa23155a91cd4ce998d009205395c503 HTTP/1.1" 200 219 0.009042
DEBUG:geventwebsocket.handler:Initializing WebSocket
DEBUG:geventwebsocket.handler:Validating WebSocket request
INFO:geventwebsocket.handler:192.168.99.1 - - [2015-12-01 08:25:25] "GET /socket.io/?EIO=3&transport=polling&t=1448958325582-58&sid=aa23155a91cd4ce998d009205395c503 HTTP/1.1" 200 275 0.002284
DEBUG:geventwebsocket.handler:Initializing WebSocket
DEBUG:geventwebsocket.handler:Validating WebSocket request
DEBUG:geventwebsocket.handler:Can only upgrade connection if using GET method.

You can try the exact same environment by dropping this Dockerfile into the Flask-SocketIO/ folder:

FROM jprjr/ubuntu-base:14.04
RUN apt-get update && apt-get install \
    python python-dev python-distribute python-pip python-gevent-websocket
RUN pip install Flask
ADD ./flask_socketio /app/flask_socketio
ADD ./setup.py /app/setup.py
WORKDIR /app
RUN python setup.py install
ADD . /app
EXPOSE 5000
WORKDIR /app/example
ENTRYPOINT []
CMD ["/app/example/app.py"]

And in that same directory:

% docker build -t flaskio-example .
% docker run -p 5000:5000 docker run flaskio-example

@beorn
Copy link
Author

beorn commented Dec 1, 2015

@Benedicht you can get the Flask-SocketIO server up-and-running by doing this after installing docker:

git clone https://github.com/miguelgrinberg/Flask-SocketIO/
cd Flask-SocketIO
# put the above Dockerfile into this directory
docker build -t flaskio-example .
docker run -p 5000:5000 docker run flaskio-example

The logs from the server look like this:

INFO:engineio:Server initialized for gevent.
INFO:werkzeug: * Restarting with stat
INFO:engineio:Server initialized for gevent.
WARNING:werkzeug: * Debugger is active!
INFO:werkzeug: * Debugger pin code: 777-019-370
DEBUG:geventwebsocket.handler:Initializing WebSocket
DEBUG:geventwebsocket.handler:Validating WebSocket request
INFO:engineio:fc4f54e0c7f5447082232da177a3707e: Sending packet OPEN with {'pingInterval': 25000, 'pingTimeout': 60000, 'upgrades': ['websocket'], 'sid': 'fc4f54e0c7f5447082232da177a3707e'}
INFO:engineio:fc4f54e0c7f5447082232da177a3707e: Sending packet MESSAGE with 0
INFO:geventwebsocket.handler:10.0.2.2 - - [2015-12-01 08:42:58] "GET /socket.io/?EIO=4&transport=polling&t=1555399205-0&b64=true HTTP/1.1" 200 373 0.001819
DEBUG:geventwebsocket.handler:Initializing WebSocket
DEBUG:geventwebsocket.handler:Validating WebSocket request
DEBUG:geventwebsocket.handler:Attempting to upgrade connection
DEBUG:geventwebsocket.handler:WebSocket request accepted, switching protocols
DEBUG:geventwebsocket.handler:Closed WebSocket
DEBUG:geventwebsocket.handler:Failed to write closing frame -> closing socket
DEBUG:geventwebsocket.handler:Closed WebSocket
DEBUG:geventwebsocket.handler:Initializing WebSocket
DEBUG:geventwebsocket.handler:Validating WebSocket request
INFO:engineio:bf2a431919274f119fa3d61cd83106e6: Sending packet OPEN with {'pingInterval': 25000, 'pingTimeout': 60000, 'upgrades': ['websocket'], 'sid': 'bf2a431919274f119fa3d61cd83106e6'}
INFO:engineio:bf2a431919274f119fa3d61cd83106e6: Sending packet MESSAGE with 0
INFO:geventwebsocket.handler:10.0.2.2 - - [2015-12-01 08:42:59] "GET /socket.io/?EIO=4&transport=polling&t=1555400527-1&b64=true HTTP/1.1" 200 373 0.003109
DEBUG:geventwebsocket.handler:Initializing WebSocket
DEBUG:geventwebsocket.handler:Validating WebSocket request
DEBUG:geventwebsocket.handler:Attempting to upgrade connection
DEBUG:geventwebsocket.handler:WebSocket request accepted, switching protocols
DEBUG:geventwebsocket.handler:Closed WebSocket
DEBUG:geventwebsocket.handler:Failed to write closing frame -> closing socket
DEBUG:geventwebsocket.handler:Closed WebSocket

though you have to add this to the top of the app.py file to turn on full debugging:

import logging
logging.basicConfig(level='DEBUG')

@beorn
Copy link
Author

beorn commented Dec 1, 2015

Thank you both for helping to troubleshoot this!!!

@Benedicht The POST log entry is from me running the Flask-SocketIO/example web app in Chrome.

@Benedicht
Copy link

@beorn Thanks, i will try to set it up.

@beorn
Copy link
Author

beorn commented Dec 1, 2015

Okay, here's a log from Charles capturing BestHTTP trying to connect to the Flask-SocketIO example app: CharlesLog.txt

Here's a slightly longer session: CharlesLog2.txt

@Benedicht
Copy link

It looks that the WebSocket upgrade is successfully done, no "Can only upgrade connection if using GET method." error in your previous log, but the server immediately closed the connection by sending a Close frame.

(Docker doesn't like my win10 rig, now i try to install on my mac.)

@miguelgrinberg
Copy link
Owner

@beorn Okay, I am seeing an issue that is probably what you are seeing. When using gevent+gevent-websocket and the server is in debug mode, the WebSocket connection is not established. But I do get a WebSocket connection up when I use debug=False with gevent, and I also get a working WebSocket connection when I use eventlet instead of gevent, with or without debug.

Some component in the stack must be causing some interference and prevents the upgrade request from ever reaching the Socket.IO server. Your log does not show any of these entries, which are expected during the WebSocket upgrade:

INFO:engineio:42831cd38f664e53bcafbe91bf595f2e: Received request to upgrade to websocket
INFO:engineio:42831cd38f664e53bcafbe91bf595f2e: Sending packet NOOP with None
INFO:engineio:42831cd38f664e53bcafbe91bf595f2e: Upgrade to websocket successful

Can I ask you to try using debug=False or eventlet and confirm WebSocket works with the example app? And if that works, can you let me know if there is any difference when you use the BestHTTP client?

I need to debug this problem to figure out why these upgrade requests are lost, I'll update this bug once I have figured it out.

@beorn
Copy link
Author

beorn commented Dec 2, 2015

On Dec 2, 2015, at 2:52 , Miguel Grinberg [email protected] wrote:
Can I ask you to try using debug=False and or eventlet and confirm WebSocket works with the example app? And if that works, can you let me know if there is any difference when you use the BestHTTP client?

Yes, it works with debug=False. (I haven’t checked with eventlet.)

I’ll dump the logs from Charles and the Flask server for BestHTTP and the socket.io web chat example a bit later.

I need to debug this problem to figure out why these upgrade requests are lost, I'll update this bug once I have figured it out.

Ok.

@beorn
Copy link
Author

beorn commented Dec 2, 2015

Here are the Flask server & Charles HTTP proxy logs from running the example chat app and a BestHTTP app, with debug=False on the server so websocket upgrading works for chat app.

chat http trace
chat flask log
besthttp http trace
besthttp flask log

@miguelgrinberg
Copy link
Owner

Okay, this is looking much much better. Now the upgrade request is received, but it appears the handshake isn't what the server expects. The handshake the server expects goes like this:

  1. Client sends PING packet
  2. Server sends PONG packet
  3. Client sends UPGRADE packet

According to the BestHTTP logs you just posted, step 3 does not occur. Server logs do not say what packet the client sent, but it clearly isn't the UPGRADE packet. Relevant code is here.

@Benedicht Maybe you can help with the handshake part? Either by explaining how you expect the exchange to go from your side, or by letting me take a peek at the code? Thanks!

@Benedicht
Copy link

@miguelgrinberg

Now I was able to set up an environment to test it too. The websocket upgrade/handshake process is the same as expected. WebSocket packets captured by charles:

[Client] Text - 2probe
[Server]Text - 3probe
[Client]Text - 52
[Server]Close - (1000)
[Client]Close -

The relevant code:

case TransportEventTypes.Pong:
    // Answer for a Ping Probe. 
    if (packet.Payload == "probe")
    {
        HTTPManager.Logger.Information("WebSocketTransport", "\"probe\" packet received, sending Upgrade packet");

        // We will send an Upgrade("52") packet.
        Send(new Packet(TransportEventTypes.Upgrade, SocketIOEventTypes.Event, "/", string.Empty));
    }

    goto default;

The packet is present in the charles log, and the info log is also logged into the Unity console.

@beorn
Copy link
Author

beorn commented Dec 2, 2015

Just so I'm clear, so the client does send the upgrade packet, but the server detects/handles it?

@Benedicht
Copy link

@beorn Yes. Also I modified the socket.py a little:

                self.server.logger.info(
                    '%s: Failed websocket upgrade, no UPGRADE packet: %s',
                    self.sid, pkt)

Now, it logs out the following:

Failed websocket upgrade, no UPGRADE packet: 52

So it receives it, but it's not identified as an upgrade packet.

@Benedicht
Copy link

Changing my code to send only update("5") without the event("2") event type works now:

Send(new Packet(TransportEventTypes.Upgrade, SocketIOEventTypes.Unknown, "/", string.Empty));

@Benedicht
Copy link

@beorn Sent a modified WebSocketTransport.cs in mail that you can try out.

@miguelgrinberg
Copy link
Owner

@Benedicht Thanks for the detailed analysis.

Basically, the problem is that my way of checking for the upgrade packet is too strict, I expect the packet will have no data in it. Your client is sending a payload of "2" in the packet, so my test fails.

I will go ahead and relax my checking to allow any payloads in the upgrade packet, but what is the "2"? Is there some meaning to it?

Edit: I guess the "2" is just an empty event packet at the socketio level. I'll go ahead and change my server to ignore anything that comes with the upgrade packet, I think that should work.

@Benedicht
Copy link

@miguelgrinberg Yes, it was meant for an event packet, but now I can't recall why it's there. I think when I started my implementation I saw this sending the js client...

Anyway, thank you for your help!

Changed my implementation, and if @beorn will verify that it's working, then it will be released with the next update.

@miguelgrinberg
Copy link
Owner

I have also relaxed the criteria to accept the upgrade packet in my python-engineio package, so this should now work even without the modified BestHTTP.

I'm going to close this issue and open a separate one for the problem with debug mode and gevent.

Debug mode issue: #177

@beorn
Copy link
Author

beorn commented Dec 3, 2015

@miguelgrinberg @Benedicht I've applied the WebSocket.cs patch (don't have Flask-SocketIO patch), but it still seems like it doesn't detect the packet:

game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | DEBUG:geventwebsocket.handler:Attempting to upgrade connection
game_1 | DEBUG:geventwebsocket.handler:WebSocket request accepted, switching protocols
game_1 | INFO:engineio:4f805d8104344d3f9d4feaa43d2a1353: Received request to upgrade to websocket
... hangs here for a while ...
game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request

@miguelgrinberg
Copy link
Owner

You can try my patch by running:

pip install --upgrade python-engineio

On Wed, Dec 2, 2015, 6:58 PM Bjørn Stabell [email protected] wrote:

@miguelgrinberg https://github.com/miguelgrinberg @Benedicht
https://github.com/Benedicht I've applied the WebSocket.cs patch (don't
have Flask-SocketIO patch), but it still seems like it doesn't detect the
packet:

game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | DEBUG:geventwebsocket.handler:Attempting to upgrade connection
game_1 | DEBUG:geventwebsocket.handler:WebSocket request accepted, switching protocols
game_1 | INFO:engineio:4f805d8104344d3f9d4feaa43d2a1353: Received request to upgrade to websocket
... hangs here for a while ...
game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request


Reply to this email directly or view it on GitHub
#175 (comment)
.

@beorn
Copy link
Author

beorn commented Dec 3, 2015

@miguelgrinberg Ok, I'm using the latest python-engineio (0.8.1) with the latest patched BestHTTP, but the upgrade still doesn't work.

@miguelgrinberg
Copy link
Owner

@beorn Do you have updated logs?

@beorn
Copy link
Author

beorn commented Dec 3, 2015

game_1 | INFO:socketio:emitting event "message" to 6a16c75548f04cd6bb315fe914c8ce97 [/]
game_1 | INFO:engineio:6a16c75548f04cd6bb315fe914c8ce97: Sending packet MESSAGE with 2["message",{"body":"CgAQAg==","name":"AppURLConfig"}]
game_1 | INFO:engineio:6a16c75548f04cd6bb315fe914c8ce97: Sending packet MESSAGE with 0
game_1 | INFO:geventwebsocket.handler:10.0.2.2 - - [2015-12-03 06:43:41] "GET /socket.io/?EIO=4&transport=polling&t=1721042474-0&version=v2.5.2&nutsession&build=2523&platform=OSXEditor&b64=true HTTP/1.1" 200 409 0.014416
game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | DEBUG:geventwebsocket.handler:Attempting to upgrade connection
game_1 | DEBUG:geventwebsocket.handler:WebSocket request accepted, switching protocols
game_1 | INFO:engineio:6a16c75548f04cd6bb315fe914c8ce97: Received request to upgrade to websocket
... hangs here - BestHTTP log shows "Send: 2probe" at this time ...
game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request

(There's some of our own code in there - please ignore. We're also adding some stuff to the query_strings, but that shouldn't matter.)

@miguelgrinberg
Copy link
Owner

@beorn Do you get the same log output if you use the original BaseHTTP client code? Also, I assume you are still running with debug mode disabled on the server?

@beorn
Copy link
Author

beorn commented Dec 3, 2015

Yes, looks exactly the same.

@miguelgrinberg
Copy link
Owner

I just pushed Flask-SocketIO 1.2 to pypi, with the fix for the debug mode WebSocket connection.

@beorn
Copy link
Author

beorn commented Dec 3, 2015

Okay, I added some more logging information to engineio/socket.py, and it seems like it's hanging on flushing the queue self.queue.join() - the log:

game_1 | INFO:root:connect
game_1 | INFO:geventwebsocket.handler:10.0.2.2 - - [2015-12-03 10:09:34] "GET /socket.io/?EIO=4&transport=polling&t=1733395209-1&version=v2.5.2&nutsession&build=2523&platform=OSXEditor&b64=true HTTP/1.1" 200 409 0.004272
game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | DEBUG:geventwebsocket.handler:Attempting to upgrade connection
game_1 | DEBUG:geventwebsocket.handler:WebSocket request accepted, switching protocols
game_1 | DEBUG:engineio:65bf6fea23814475a6a4845f83e78546: Received GET request {'wsgi.multiprocess': False, 'HTTP_COOKIE': 'io=65bf6fea23814475a6a4845f83e78546', 'wsgi.websocket': <geventwebsocket.websocket.WebSocket object at 0x7f1a4f0e20c0>, 'SERVER_SOFTWARE': 'gevent/1.0 Python/2.7', 'SCRIPT_NAME': '', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/socket.io/', 'SERVER_PROTOCOL': 'HTTP/1.1', 'QUERY_STRING': 'transport=websocket&sid=65bf6fea23814475a6a4845f83e78546&version=v2.5.2&nutsession&build=2523&platform=OSXEditor', 'HTTP_TE': 'identity', 'CONTENT_LENGTH': '0', 'HTTP_USER_AGENT': 'BestHTTP', 'HTTP_CONNECTION': 'keep-alive, Upgrade', 'SERVER_NAME': '6e48d40ccba9', 'REMOTE_ADDR': '10.0.2.2', 'wsgi.url_scheme': 'http', 'SERVER_PORT': '8080', 'flask.app': <Flask 'listener'>, 'HTTP_PRAGMA': 'no-cache', 'HTTP_UPGRADE': 'websocket', 'wsgi.input': <gevent.pywsgi.Input object at 0x7f1a4f0e1390>, 'HTTP_HOST': 'localhost:8088', 'wsgi.multithread': False, 'HTTP_CACHE_CONTROL': 'no-cache', 'HTTP_SEC_WEBSOCKET_VERSION': '13', 'wsgi.version': (1, 0), 'wsgi.websocket_version': '13', 'GATEWAY_INTERFACE': 'CGI/1.1', 'wsgi.run_once': False, 'wsgi.errors': <open file '<stderr>', mode 'w' at 0x7f1a575341e0>, 'REMOTE_PORT': '58011', 'HTTP_SEC_WEBSOCKET_KEY': 'QEwAjYBtm9Bgk55LwKCnjw==', 'HTTP_ACCEPT_ENCODING': 'gzip, identity'}
game_1 | INFO:engineio:65bf6fea23814475a6a4845f83e78546: Received request to upgrade to websocket
game_1 | DEBUG:engineio:65bf6fea23814475a6a4845f83e78546: Upgrading websocket {'wsgi.multiprocess': False, 'HTTP_COOKIE': 'io=65bf6fea23814475a6a4845f83e78546', 'wsgi.websocket': <geventwebsocket.websocket.WebSocket object at 0x7f1a4f0e20c0>, 'SERVER_SOFTWARE': 'gevent/1.0 Python/2.7', 'SCRIPT_NAME': '', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/socket.io/', 'SERVER_PROTOCOL': 'HTTP/1.1', 'QUERY_STRING': 'transport=websocket&sid=65bf6fea23814475a6a4845f83e78546&version=v2.5.2&nutsession&build=2523&platform=OSXEditor', 'HTTP_TE': 'identity', 'CONTENT_LENGTH': '0', 'HTTP_USER_AGENT': 'BestHTTP', 'HTTP_CONNECTION': 'keep-alive, Upgrade', 'SERVER_NAME': '6e48d40ccba9', 'REMOTE_ADDR': '10.0.2.2', 'wsgi.url_scheme': 'http', 'SERVER_PORT': '8080', 'flask.app': <Flask 'listener'>, 'HTTP_PRAGMA': 'no-cache', 'HTTP_UPGRADE': 'websocket', 'wsgi.input': <gevent.pywsgi.Input object at 0x7f1a4f0e1390>, 'HTTP_HOST': 'localhost:8088', 'wsgi.multithread': False, 'HTTP_CACHE_CONTROL': 'no-cache', 'HTTP_SEC_WEBSOCKET_VERSION': '13', 'wsgi.version': (1, 0), 'wsgi.websocket_version': '13', 'GATEWAY_INTERFACE': 'CGI/1.1', 'wsgi.run_once': False, 'wsgi.errors': <open file '<stderr>', mode 'w' at 0x7f1a575341e0>, 'REMOTE_PORT': '58011', 'HTTP_SEC_WEBSOCKET_KEY': 'QEwAjYBtm9Bgk55LwKCnjw==', 'HTTP_ACCEPT_ENCODING': 'gzip, identity'}
game_1 | DEBUG:engineio:65bf6fea23814475a6a4845f83e78546: _websocket_handler - ws: <engineio.async_gevent.WebSocketWSGI object at 0x7f1a4f0e1410>
game_1 | DEBUG:engineio:65bf6fea23814475a6a4845f83e78546: _websocket_handler - connected => upgrade
… hangs, and then ….
game_1 | DEBUG:geventwebsocket.handler:Initializing WebSocket
game_1 | DEBUG:geventwebsocket.handler:Validating WebSocket request
game_1 | INFO:engineio:c060fac61149400c889e6f691eb7e511: Sending packet OPEN with {'pingInterval': 25000, 'pingTimeout': 60000, 'upgrades': ['websocket'], 'sid': 'c060fac61149400c889e6f691eb7e511'}

The relevantly instrumented parts of engineio/socket.py:

    def handle_get_request(self, environ, start_response):
        """Handle a long-polling GET request from the client."""
        self.server.logger.debug('%s: Received GET request %s', self.sid, dict(environ))
        connections = [
            s.strip()
            for s in environ.get('HTTP_CONNECTION', '').lower().split(',')]
        transport = environ.get('HTTP_UPGRADE', '').lower()
        if 'upgrade' in connections and transport in self.upgrade_protocols:
            self.server.logger.info('%s: Received request to upgrade to %s',
                                    self.sid, transport)
            return getattr(self, '_upgrade_' + transport)(environ,
                                                          start_response)
        try:
            packets = self.poll()
        except IOError as e:
            self.close(wait=False)
            raise e
        return packets

    def handle_post_request(self, environ):
        """Handle a long-polling POST request from the client."""
        self.server.logger.debug('%s: Received POST request %s', self.sid, dict(environ))
        length = int(environ.get('CONTENT_LENGTH', '0'))
        if length > self.server.max_http_buffer_size:
            raise ValueError()
        else:
            body = environ['wsgi.input'].read(length)
            p = payload.Payload(encoded_payload=body)
            for pkt in p.packets:
                self.receive(pkt)

    def close(self, wait=True, abort=False):
        """Close the socket connection."""
        self.server._trigger_event('disconnect', self.sid)
        if not abort:
            self.send(packet.Packet(packet.CLOSE))
        self.closed = True
        if wait:
            self.queue.join()

    def _upgrade_websocket(self, environ, start_response):
        """Upgrade the connection from polling to websocket."""
        self.server.logger.debug('%s: Upgrading websocket %s', self.sid, dict(environ))
        if self.upgraded:
            raise IOError('Socket has been upgraded already')
        websocket_class = getattr(self.server.async['websocket'],
                                  self.server.async['websocket_class'])
        ws = websocket_class(self._websocket_handler)
        return ws(environ, start_response)

    def _websocket_handler(self, ws):
        """Engine.IO handler for websocket transport."""
        self.server.logger.debug('%s: _websocket_handler - ws: %s', self.sid, ws)
        if self.connected:
            self.server.logger.debug('%s: _websocket_handler - connected => upgrade', self.sid)
            # the socket was already connected, so this is an upgrade
            self.queue.join()  # flush the queue first
            self.server.logger.debug('%s: _websocket_handler - flushed queue, waiting for pkt', self.sid)

            pkt = ws.wait()
            if pkt != packet.Packet(packet.PING,
                                    data=six.text_type('probe')).encode(
                                        always_bytes=False):
                self.server.logger.info(
                    '%s: Failed websocket upgrade, no PING packet', self.sid)
                return
            self.server.logger.debug('%s: _websocket_handler - got pkt, sending PONG', self.sid)
            ws.send(packet.Packet(
                packet.PONG,
                data=six.text_type('probe')).encode(always_bytes=False))
            self.server.logger.debug('%s: _websocket_handler - got pkt, sending NOOP', self.sid)
            self.send(packet.Packet(packet.NOOP))

            self.server.logger.debug('%s: _websocket_handler - waiting for pkt', self.sid)
            pkt = ws.wait()
            decoded_pkt = packet.Packet(encoded_packet=pkt)
            self.server.logger.debug('%s: _websocket_handler - decoded_pkt: %s', self.sid, decoded_pkt)
            if decoded_pkt.packet_type != packet.UPGRADE:
                self.upgraded = False
                self.server.logger.info(
                    ('%s: Failed websocket upgrade, expected UPGRADE packet, '
                     'received %s instead.'),
                    self.sid, pkt)
                return
            self.server.logger.debug('%s: _websocket_handler - upgraded=True', self.sid)
            self.upgraded = True
        else:
            self.server.logger.debug('%s: _websocket_handler - !connected => upgraded=connected=True', self.sid)
            self.connected = True
            self.upgraded = True

@miguelgrinberg
Copy link
Owner

@beorn what I don't understand is how come this worked before. The problems that you had a couple of days ago were past the queue join. Did anything else change?

The message queue should empty eventually, assuming the client continues to use the polling transport to get messages flowing. Here is where stuff gets out of the queue, in the GET request handler.

@beorn
Copy link
Author

beorn commented Dec 3, 2015

I'm not sure, could it be that the client just keeps sending GET requests before the previous ones have finished? I don't see it continuing past the queue.join, but I do see another DEBUG:geventwebsocket.handler:Initializing WebSocket.

It seems the client sends to GET requests in rapid sequence - one with transport polling, then with websocket. I guess that's normal?

I'm attaching the entire log from unity/besthttp and flask.

besthttp-editor-log.txt
flask-log.txt

@miguelgrinberg
Copy link
Owner

The GET request that starts the WebSocket connection is never going to finish (well, it will when the client goes away, but other than that it is not going to end). Before and while the WebSocket connection is being established, the client must continue to use long-polling. Do you see new GET and POST requests coming in to the server? These would have to be regular requests, not WebSocket upgrade requests. I suspect for some reason those requests are not being sent, so any messages that got stuck in the queue are never delivered.

Do you have any issues with my example app? At this point it should work just fine in all configurations, even in debug mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants