miguelgrinberg / flask-sock

Modern WebSocket support for Flask.
MIT License
274 stars 24 forks source link

ConnectionError is raised inconsistently #21

Closed ShunyaoLiang closed 2 years ago

ShunyaoLiang commented 2 years ago

Hello!

Thank you for your effort maintaining this library.

From what I can see in __init__.py, in decorator from Sock.route, the intended behaviour is to raise ConnectionError whenever the WebSocket connection is closed. However, I have an MWE in which ConnectionError may or may not be raised depending on the timeout parameter passed to simple_websocket.Server.receive.

from flask import Flask
from flask_sock import Sock

app = Flask(__name__)
sock = Sock(app)

@sock.route('/long_poll')
def long_poll(ws):
    while ws.connected:
        ws.receive(1)
    app.logger.info('Unsocked!')

@sock.route('/short_poll')
def short_poll(ws):
    while ws.connected:
        ws.receive(0.5)
    app.logger.info('Unsocked!') # I believe this line is necessary to flush the buffer and see the error.

When I close a WebSocket connection to /long_poll, there is no error.

However, when I close a WebSocket connection to /short_poll, I see

Traceback (most recent call last):
  File "/home/mei/.cache/venv/radish/lib/python3.10/site-packages/flask/app.py", line 2091, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/mei/.cache/venv/radish/lib/python3.10/site-packages/flask/app.py", line 2080, in wsgi_app
    return response(environ, start_response)
  File "/home/mei/.cache/venv/radish/lib/python3.10/site-packages/flask_sock/__init__.py", line 82, in __call__
    raise ConnectionError()
ConnectionError

Is this expected behaviour?

The code I used to create and close WebSocket connections is

let ws = new WebSocket('ws://localhost:5000/long_poll') ; ws.onopen = () => { ws.close() }
let ws = new WebSocket('ws://localhost:5000/short_poll') ; ws.onopen = () => { ws.close() }

The critical point seems to be timeout=1. Timeouts less raise ConnectionError, and timeouts greater do not.

The snippet works on flask-sock version 0.5.1, Python version 3.10.. The JS code was run on Mozilla Firefox version 97.

As an aside, why raise this exception at all? And why call the Flask route with the same route as the Sock route afterwards?

Thanks in advance.

miguelgrinberg commented 2 years ago

What version of Werkzeug are you running?

The ConnectionError exception is more or less a hack that works with the latest versions of Werkzeug. With these versions, it helps Werkzeug clean up and exit without errors. It is possible that this has unintended consequence on older versions. Unfortunately Werkzeug does not have support for WebSocket, it has been really difficult to get it to end a WebSocket connection without errors.

If you are seeing these errors with the latest 2.0.3, then that means that my attempt to address errors on exit has not been fully successful and I need to keep looking at this.

And why call the Flask route with the same route as the Sock route afterwards?

What do you mean here? I don't understand this.

ShunyaoLiang commented 2 years ago

These errors appear for me on Werkzeug 2.0.3.

Unfortunately Werkzeug does not have support for WebSocket, it has been really difficult to get it to end a WebSocket connection without errors.

Ah, I see. Thank you for explaining. That sounds like a lot of work. Does this mean it would be best for projects using flask-sock to use other WSGI containers as soon as possible?

What do you mean here? I don't understand this.

Sorry, I was vague and it's probably unrelated. I was referring to this line. Is this part of getting Werkzeug to end WebSocket connections?

miguelgrinberg commented 2 years ago

Does this mean it would be best for projects using flask-sock to use other WSGI containers as soon as possible?

Well, Werkzeug is a development web server, so all these little problems are an annoyance more than anything. A production deployment would use one of the other options, all of which have a well defined ending process (as far as I know).

I tested your example code above and was unable to reproduce a crash.

Sorry, I was vague and it's probably unrelated. I was referring to this line. Is this part of getting Werkzeug to end WebSocket connections?

That line runs when the @sock.route decorated function is defined, not when the route itself is invoked. The sock.route decorator depends on app.route, which is what this line you highlighted calls.

ShunyaoLiang commented 2 years ago

That line runs when the @sock.route decorated function is defined, not when the route itself is invoked. The sock.route decorator depends on app.route, which is what this line you highlighted calls.

I see. I asked because I seem to be getting GET requests to the Sock route whenever the WebSocket connection is closed (with the above code run on Firefox). They don't appear in Firefox's network log, so I wrongly concluded that it was from that line. Confirmation bias, I suppose.

Anyways, if you couldn't reproduce the error, and those GET requests aren't from flask-sock, then something strange must be afoot on my machine.

Thanks for taking a look, and answering my questions. I'll close this now.