miguelgrinberg / flask-sock

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

Server side error with `ws.close()` #4

Closed imrankhan17 closed 3 years ago

imrankhan17 commented 3 years ago

I've been using this package for some time and it works very well. I'm still trying to determine the best way to close a websocket connection using ws.close(). After a connection is made by a client and the data has finished being sent, I get this error on the server side (full stack trace below):

wsproto.utilities.LocalProtocolError: Connection cannot be closed in state ConnectionState.CLOSED

Here is the code to reproduce this error. I'm running Python 3.8.5 on Ubuntu.

$ mkdir flask-sock-example
$ cd flask-sock-example
$ python3 -m venv env
$ source env/bin/activate
$ pip install Flask==1.1.2 flask-sock==0.3.0 "werkzeug>=2.0.0rc3"
$ pip freeze
click==8.0.0
Flask==1.1.2
flask-sock==0.3.0
h11==0.12.0
itsdangerous==2.0.0
Jinja2==3.0.0
MarkupSafe==2.0.0
simple-websocket==0.1.0
Werkzeug==2.0.0
wsproto==1.0.0

In a file called app.py:

from flask import Flask
from flask_sock import Sock
import time

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

@sock.route('/stream')
def stream(ws):
    for i in range(3):
        ws.send(i)
        time.sleep(1)

    ws.close()

I use flask run to start the application. In another tab, I'm using wscat to connect to the websocket on localhost:

$ wscat -c ws://127.0.0.1:5000/stream
Connected (press CTRL+C to quit)
< 0
< 1
< 2
Disconnected (code: 1000, reason: "")
$ 

This works fine, however on the server side I get the aforementioned error:

 $ flask run
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [14/May/2021 12:57:09] "GET /stream HTTP/1.1" 200 -
Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/home/imrankhan/flask-sock-example/env/lib/python3.8/site-packages/simple_websocket/ws.py", line 89, in _thread
    self.connected = self._handle_events()
  File "/home/imrankhan/flask-sock-example/env/lib/python3.8/site-packages/simple_websocket/ws.py", line 99, in _handle_events
    out_data += self.ws.send(event.response())
  File "/home/imrankhan/flask-sock-example/env/lib/python3.8/site-packages/wsproto/__init__.py", line 62, in send
    data += self.connection.send(event)
  File "/home/imrankhan/flask-sock-example/env/lib/python3.8/site-packages/wsproto/connection.py", line 99, in send
    raise LocalProtocolError(
wsproto.utilities.LocalProtocolError: Connection cannot be closed in state ConnectionState.CLOSED

Alternatively, if I remove the ws.close() line, I don't get an error on the server, instead I see this on the client:

$ wscat -c ws://127.0.0.1:5000/stream
Connected (press CTRL+C to quit)
< 0
< 1
< 2
error: Invalid WebSocket frame: RSV1 must be clear
> $ 
miguelgrinberg commented 3 years ago

Hmm. I don't really get any errors on the server when the connection is closed. What OS are you using? I'm testing on Mac.

imrankhan17 commented 3 years ago

Ubuntu 20.04.2 LTS. I should add that the server continues to accept new connections and send data without needing to restart, but the same error appears every time.

imrankhan17 commented 3 years ago

I just tried it on Mac and I get the same error.

miguelgrinberg commented 3 years ago

Version of Python? There's got to be a difference, I don't see any errors here. I'm on 3.8.6.

imrankhan17 commented 3 years ago

Same here.

Python 3.8.6 (default, Nov 20 2020, 18:02:11)
[Clang 11.0.0 (clang-1100.0.33.17)] on darwin
miguelgrinberg commented 3 years ago

@imrankhan17 I basically coded a fix for your error without actually being able to reproduce it here. Please upgrade flask-sock and simple-websocket and let me know if that works better for you.

imrankhan17 commented 3 years ago

I installed the latest commit for flask-sock and upgraded simple-websocket and it works fine for me now - thanks a lot for that fix. Interestingly, it also worked if I only upgraded simple-websocket to 0.2.0 but kept flask-sock at the 0.3.0 release.

miguelgrinberg commented 3 years ago

@imrankhan17 yeah, that's expected. I added a handler for the LocalProtocolError you were getting in simple-websocket. The change I've made in this package is intended to attempt to close the websocket connection after the application's handler returns.