flask-sock icon indicating copy to clipboard operation
flask-sock copied to clipboard

connection error with werkzeug

Open gbrault opened this issue 1 year ago • 14 comments

I am using flask-sock with flask-appbuilder. From python side I have one main sock route: /clock

It provides access to the ws to a user if it has the 'ResetMyPasswordView' access within flask-appbuilder (a user which has the right to change is password)

Here is the code

@sock.route('/clock')
def clock(ws):
    """Accepts a websocket connection
        - checks if the user has access to the websocket
        - adds the user and the socket to the list of listeners
        - echo received messages back to the client (could be extended to take some actions on the server side)
        - sends a welcome message if the href is '/'
        - and sends the current time every second
        - when closed break the echo loop
        - the user will be removed by the clock from the list of listeners
        - each time a user changes the page the websocket is closed and a new one is opened (so checking '/' is enough to know if the user is on the welcome page)"""
    from urllib.parse import urlparse
    try:
        show_welcome = False
        if 'href' in request.values:
            url = urlparse(request.values['href'])
            if url.path == '/':
                show_welcome = True
        if isinstance(g.user, AnonymousUserMixin):
            ws.send(json.dumps({"type": "log", "text": "Anonymous User has no access"}))
            ws.close()
            return
        app.logger.info(g.user.username)
        user = app.appbuilder.sm.find_user(username=g.user.username)
        if user is None:
            ws.send(json.dumps({"type": "log", "text": "User not found"}))
            ws.close()
            return
        has_access = appbuilder.sm.has_access('can_this_form_post', 'ResetMyPasswordView') # if the guy can reset his password, he can access the websocket
        if has_access:
            #send a welcome message to the user if href path is '/' (show_welcome)
            if show_welcome:
                    ws.send(json.dumps({"type": "alert", "text": f"Welcome to {app.config['APP_NAME']}", "alerttype" : "alert-success"}))
            # create a unique key for the user,socket pair (the key is the current date-time)
            key = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            # add the user and the socket to the list of listeners with the above key
            listners[key] = {"user": user, "socket": ws}
        else:
            # only user with a profile benefit from the websocket service
            ws.send(json.dumps({"type": "log", "text": "User not allowed"}))
            ws.close()
            return
        while True:
            # this means the webserver thread is active till the websocket is closed (the user changes or close the page)
            data = ws.receive()
            if data == 'close':
                break
            ws.send(json.dumps({"type": "log", "text": data}))
    except Exception as e:
        app.logger.error(e)

gbrault avatar Sep 13 '23 09:09 gbrault

I use the following python functions to interact over the ws:

def send_time():
    """Send the current time to all connected clients"""
    while True:
        if app.sock_running == False:
            break
        time.sleep(1)
        keys = list(listners.keys())
        for key in keys:
            try:
                listners[key]["socket"].send(json.dumps({"type": "clock", "text": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}))
            except:
                del listners[key]

def send_modal(username,title,message,modal_type="info"):
    """Send a modal to a user
        - username: the username of the user to send the modal to
        - title: the title of the modal
        - message: the message to send
        - modal_type: the type of modal (info, warning, error, success)"""
    keys = list(listners.keys())
    for key in keys:
        if username == "*" or listners[key]["user"].username == username:
            try:
                message = message.replace("\n","<br>")
                listners[key]["socket"].send(json.dumps({"type": "modal", "title": title, "text": message, "modaltype": modal_type}))
            except:
                del listners[key]

def send_alert(username,message,alert_type="alert-info"):
    """Send an alert to a user
        - username: the username of the user to send the alert to
        - message: the message to send
        - alert_type: the type of alert (alert-primary, alert-secondary, alert-success, alert-info, alert-warning, alert-danger, alert-light, alert-dark)"""
    keys = list(listners.keys())
    for key in keys:
        if username == "*" or listners[key]["user"].username == username:
            try:
                message = message.replace("\n","<br>")
                listners[key]["socket"].send(json.dumps({"type": "alert", "alerttype": alert_type, "text": message}))
            except:
                del listners[key]

gbrault avatar Sep 13 '23 09:09 gbrault

The clock is sent over the ws thanks to a thread

t = threading.Thread(target=send_time,args=())
t.start()

gbrault avatar Sep 13 '23 09:09 gbrault

The application works pretty well, I only have some small glitches

  • Some if I am in debug mode
  • Others in non debug mode

gbrault avatar Sep 13 '23 09:09 gbrault

From the client side here is the code

    $(document).ready(function() {
        // =====================
        let socket = null
        // this is the websocket connection
        if (location.protocol === 'https:') {
            socket = new WebSocket('wss://' + location.host + '/clock?href='+encodeURIComponent(location.href));
        } else {
            socket = new WebSocket('ws://' + location.host + '/clock?href='+encodeURIComponent(location.href));
        }
        // =====================
        if (socket !== null) {
            // this is the websocket event listener
            socket.addEventListener('message', ev => {  // this is the websocket event listener
                var msg = JSON.parse(ev.data);
                if (msg.type === 'clock') {
                    if (document.getElementById('clock') === null){
                        // if the clock span doesnt exist then create it 
                        $('body > header > div > div.container').prepend('<span id="clock" style="font-size: 7px; color: white;"></span>');
                    }
                    document.getElementById('clock').innerHTML = msg.text;
                    document.getElementById('clock').title = msg.text;
                } else if (msg.type === 'alert') {
                    showalert(msg.text,msg.alerttype)
                } else {
                    console.log('<<<', ev.data);
                }
            });

            socket.addEventListener('close', ev => {
                console.log('<<< closed');
            });
            // =====================
            // this is the function that will show the alert
            function showalert(message,alerttype) {
                // showalert("Invalid Login","alert-error")
                var idx = Math.floor(Math.random() * 1000);
                var alertdiv = "alertdiv_"+idx
                $("body > div.container").prepend('<div id="' + alertdiv + '"" class="alert ' +  alerttype + '"><a class="close" data-dismiss="alert">×</a><span>'+message+'</span></div>')

                    setTimeout(function() { // this will automatically close the alert and remove this if the users doesnt close it in 10 secs
                            $("#"+alertdiv).remove();
                    }, 10000);
            }
        }
    });

gbrault avatar Sep 13 '23 09:09 gbrault

connection error with werkzeug I only have some small glitches

This is not sufficient description of the problem for me to investigate. Please be specific.

miguelgrinberg avatar Sep 13 '23 09:09 miguelgrinberg

The non debug

When I launch my app: flask run -h 0.0.0.0 -p 5000

If I get connected to the server, as I am not authenticated with flask-appbuilder, the connection is nuturally closed.

  • I receive everything well in the browser and ouput the close message on the browser console
  • But I get an error in the Flask log
2023-09-13 09:35:19,751:ERROR:werkzeug:172.18.0.12 - - [13/Sep/2023 09:35:19] code 400, message Bad request syntax ('\x88\x82?"\x8f¬<Ê')
2023-09-13 09:35:19,751:INFO:werkzeug:172.18.0.12 - - [13/Sep/2023 09:35:19] "None /clock?href=https://bbfta.ilexascenseurs.fr/ HTTP/0.9" 400 -
2

gbrault avatar Sep 13 '23 09:09 gbrault

The debug

When I launch my app: flask run -h 0.0.0.0 -p 5000 --debug

If I connect with the browser I get this in the Flask log:

2023-09-13 09:42:50,311:INFO:werkzeug:172.18.0.12 - - [13/Sep/2023 09:42:50] "GET /clock?href=https://bbfta.ilexascenseurs.fr/ HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/home/appuser/venv/lib/python3.11/site-packages/flask/app.py", line 2213, in __call__
    return self.wsgi_app(environ, start_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/appuser/venv/lib/python3.11/site-packages/flask/app.py", line 2197, in wsgi_app
    return response(environ, start_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/appuser/venv/lib/python3.11/site-packages/flask_sock/__init__.py", line 86, in __call__
    raise ConnectionError()
ConnectionError

gbrault avatar Sep 13 '23 09:09 gbrault

If I authenticate, everything works great. But when I quit the browser, I get the same message again.

gbrault avatar Sep 13 '23 09:09 gbrault

@miguelgrinberg if you need some more details, don't hesitate to ask

gbrault avatar Sep 13 '23 09:09 gbrault

Yes, please provide the output of pip freeze

miguelgrinberg avatar Sep 13 '23 10:09 miguelgrinberg

This is the freeze on Linux, where I have the issue

aiohttp==3.8.5
aiosignal==1.3.1
anyio==3.7.1
apispec==5.2.2
argon2-cffi==21.3.0
argon2-cffi-bindings==21.2.0
arrow==1.2.3
asttokens==2.2.1
async-lru==2.0.3
async-timeout==4.0.2
attrs==23.1.0
Babel==2.12.1
backcall==0.2.0
beautifulsoup4==4.12.2
binaryornot==0.4.4
bleach==6.0.0
blinker==1.6.2
certifi==2023.7.22
cffi==1.15.1
chardet==5.1.0
charset-normalizer==3.2.0
click==8.1.6
colorama==0.4.6
comm==0.1.3
cookiecutter @ git+https://github.com/cookiecutter/cookiecutter.git@33a36b382776aeb713f4cc66f68cd3c20633f297
cryptography==41.0.2
dataclasses-json==0.5.13
dateparser==1.1.8
debugpy==1.6.7
decorator==5.1.1
defusedxml==0.7.1
Deprecated==1.2.14
dnspython==2.4.0
docxcompose==1.4.0
docxtpl==0.16.7
email-validator==1.3.1
ERAlchemy @ git+https://github.com/gbrault/eralchemy.git@32845d27ee08d62c582d08f576f88a02dcaf98e3
et-xmlfile==1.1.0
executing==1.2.0
fastjsonschema==2.18.0
Flask==2.3.2
Flask-AppBuilder==4.3.1
Flask-Babel==2.0.0
Flask-Cors==4.0.0
Flask-JWT-Extended==4.5.2
Flask-Limiter==3.3.1
Flask-Login==0.6.2
flask-sock==0.6.0
Flask-SQLAlchemy==2.5.1
Flask-WTF==1.1.1
fqdn==1.5.1
frozenlist==1.4.0
ftputil==5.0.4
gitdb==4.0.10
GitPython==3.1.32
greenlet==2.0.2
h11==0.14.0
htmlmin==0.1.12
httpcore==0.17.3
idna==3.4
importlib-resources==6.0.0
ipyfilechooser==0.6.0
ipykernel==6.24.0
ipython==8.14.0
ipywidgets==8.0.7
isoduration==20.11.0
itsdangerous==2.1.2
jedi==0.18.2
Jinja2==3.1.2
json-schema-for-humans @ git+https://github.com/gbrault/json-schema-for-humans.git@172d53f4ccb360a875f645733dac5bb44419dd40
json5==0.9.14
jsonpointer==2.4
jsonschema==4.18.4
jsonschema-specifications==2023.7.1
jupyter-events==0.6.3
jupyter-lsp==2.2.0
jupyter_client==8.3.0
jupyter_core==5.3.1
jupyter_server==2.7.0
jupyter_server_proxy==4.0.0
jupyter_server_terminals==0.4.4
jupyterlab==4.0.3
jupyterlab-pygments==0.2.2
jupyterlab-widgets==3.0.8
jupyterlab_server==2.23.0
limits==3.5.0
lxml==4.9.3
Markdown==3.4.3
markdown-it-py==3.0.0
markdown2==2.4.9
MarkupSafe==2.1.3
marshmallow==3.20.1
marshmallow-enum==1.5.1
marshmallow-sqlalchemy==0.26.1
matplotlib-inline==0.1.6
mdurl==0.1.2
mistune==3.0.1
multidict==6.0.4
mypy-extensions==1.0.0
mysql-connector-python==8.1.0
nbclient==0.8.0
nbconvert==7.7.2
nbformat==5.9.1
nest-asyncio==1.5.6
notebook_shim==0.2.3
numpy==1.25.1
openpyxl==3.1.2
ordered-set==4.1.0
overrides==7.3.1
packaging==23.1
pandas==2.0.3
pandocfilters==1.5.0
parso==0.8.3
pexpect==4.8.0
pickleshare==0.7.5
Pillow==10.0.0
platformdirs==3.9.1
prison==0.2.1
prometheus-client==0.17.1
prompt-toolkit==3.0.39
protobuf==4.21.12
psutil==5.9.5
ptyprocess==0.7.0
pure-eval==0.2.2
pycparser==2.21
Pygments==2.15.1
pygraphviz==1.11
PyJWT==2.8.0
PyMuPDF==1.22.5
pyOpenSSL==23.2.0
python-dateutil==2.8.2
python-docx==0.8.11
python-dotenv==1.0.0
python-json-logger==2.0.7
python-slugify==8.0.1
pytz==2021.3
PyYAML==6.0.1
pyzmq==25.1.0
referencing==0.30.0
regex==2023.6.3
requests==2.31.0
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rich==13.4.2
rpds-py==0.9.2
Send2Trash==1.8.2
simpervisor==1.0.0
simple-websocket==0.10.1
six==1.16.0
smmap==5.0.0
sniffio==1.3.0
soupsieve==2.4.1
SQLAlchemy==1.4.0
SQLAlchemy-Utils==0.41.1
stack-data==0.6.2
supervisor==4.2.5
terminado==0.17.1
text-unidecode==1.3
tinycss2==1.2.1
tornado==6.3.2
traitlets==5.9.0
typing-inspect==0.9.0
typing_extensions==4.7.1
tzdata==2023.3
tzlocal==5.0.1
uri-template==1.3.0
urllib3==2.0.4
wcwidth==0.2.6
webcolors==1.13
webencodings==0.5.1
websocket-client==1.6.1
Werkzeug==2.3.6
widgetsnbextension==4.0.8
wrapt==1.15.0
wsproto==1.2.0
WTForms==3.0.1
xlrd==2.0.1
XlsxWriter==3.1.2
yarl==1.9.2

gbrault avatar Sep 13 '23 14:09 gbrault

Something I just notice is that I don't have the error on windows on my dev computer here is the freeze for windows

(venv) PS D:\Dev\Ilex Projects\BBFTA> pip freeze
aiofiles==22.1.0
aiohttp==3.8.4
aiosignal==1.3.1
aiosqlite==0.18.0
anyio==3.6.2
apispec==5.2.2
argon2-cffi==21.3.0
argon2-cffi-bindings==21.2.0
arrow==1.2.3
aspose-words-cloud==23.4.0
asttokens==2.2.1
async-timeout==4.0.2
attrs==22.2.0
Babel==2.12.1
backcall==0.2.0
beautifulsoup4==4.12.0
binaryornot==0.4.4
bleach==6.0.0
Brotli==1.0.9
certifi==2022.12.7
cffi==1.15.1
chardet==5.1.0
charset-normalizer==3.1.0
click==8.1.3
cobble==0.1.3
colorama==0.4.6
comm==0.1.3
convertapi==1.6.0
cookiecutter @ git+https://github.com/cookiecutter/cookiecutter.git@1b8520e7075175db4a3deae85e71081730ca7ad1
cryptography==40.0.1
cssselect2==0.7.0
dataclasses-json==0.5.7
dateparser==1.1.8
debugpy==1.6.6
decorator==5.1.1
defusedxml==0.7.1
Deprecated==1.2.13
dnspython==2.3.0
docopt==0.6.2
docx2pdf==0.1.8
docxcompose==1.4.0
docxtpl==0.16.6
doxypypy==0.8.8.7
email-validator==1.3.1
entrypoints==0.4
ERAlchemy @ git+https://github.com/gbrault/eralchemy.git@32845d27ee08d62c582d08f576f88a02dcaf98e3
et-xmlfile==1.1.0
executing==1.2.0
fastjsonschema==2.16.3
Flask==2.2.3
Flask-AppBuilder==4.3.1
Flask-Babel==2.0.0
Flask-Cors==4.0.0
Flask-JWT-Extended==4.4.4
Flask-Limiter==3.3.0
Flask-Login==0.6.2
flask-sock==0.6.0
Flask-SQLAlchemy==2.5.1
Flask-WTF==1.1.1
fonttools==4.42.1
fqdn==1.5.1
frozenlist==1.3.3
ftputil==5.0.4
gitdb==4.0.10
GitPython==3.1.31
greenlet==2.0.2
h11==0.14.0
html5lib==1.1
htmlmin==0.1.12
idna==3.4
importlib-resources==5.12.0
ipyfilechooser==0.6.0
ipykernel==6.22.0
ipython==8.12.0
ipython-genutils==0.2.0
ipywidgets==8.0.6
isoduration==20.11.0
itsdangerous==2.1.2
jedi==0.18.2
Jinja2==3.1.2
jinja2-time==0.2.0
json-schema-for-humans @ git+https://github.com/gbrault/json-schema-for-humans.git@172d53f4ccb360a875f645733dac5bb44419dd40
json5==0.9.11
jsonpointer==2.3
jsonschema==4.17.3
jupyter-events==0.6.3
jupyter-server==1.23.6
jupyter-server-proxy==3.2.2
jupyter-ydoc==0.2.3
jupyter_client==7.4.1
jupyter_core==5.3.0
jupyter_server_fileid==0.8.0
jupyter_server_ydoc==0.8.0
jupyterlab==3.6.3
jupyterlab-pygments==0.2.2
jupyterlab-widgets==3.0.7
jupyterlab_server==2.22.0
limits==3.3.1
lxml==4.9.2
Mako==1.2.4
mammoth==1.5.1
Markdown==3.4.3
markdown-it-py==2.2.0
markdown2==2.4.8
MarkupSafe==2.1.2
marshmallow==3.19.0
marshmallow-enum==1.5.1
marshmallow-sqlalchemy==0.26.1
matplotlib-inline==0.1.6
md2pdf==1.0.1
mdurl==0.1.2
mistune==2.0.5
multidict==6.0.4
mypy-extensions==1.0.0
mysql-connector-python==8.1.0
nbclassic==0.5.4
nbclient==0.7.2
nbconvert==7.2.10
nbformat==5.8.0
nest-asyncio==1.5.6
notebook==6.5.3
notebook_shim==0.2.2
numpy==1.24.2
openpyxl==3.1.2
ordered-set==4.1.0
packaging==23.0
pandas==1.5.3
pandocfilters==1.5.0
parso==0.8.3
pdoc3==0.10.0
pickleshare==0.7.5
Pillow==9.5.0
platformdirs==3.2.0
prison==0.2.1
prometheus-client==0.16.0
prompt-toolkit==3.0.38
protobuf==4.21.12
psutil==5.9.4
pure-eval==0.2.2
pycparser==2.21
pycryptodome==3.17
pydyf==0.7.0
Pygments==2.14.0
pygraphviz==1.10
PyJWT==2.6.0
PyMuPDF==1.22.5
pypandoc-binary==1.11
pyphen==0.14.0
pyrsistent==0.19.3
pystache==0.6.0
python-dateutil==2.8.2
python-docx==0.8.11
python-dotenv==1.0.0
python-json-logger==2.0.7
python-slugify==8.0.1
pytz==2021.3
pywin32==306
pywinpty==2.0.10
PyYAML==6.0
pyzmq==25.0.2
regex==2023.6.3
requests==2.28.2
requests-toolbelt==1.0.0
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rich==13.3.3
Send2Trash==1.8.0
simpervisor==0.4
simple-websocket==0.9.0
six==1.16.0
smartypants==2.0.1
smmap==5.0.0
sniffio==1.3.0
soupsieve==2.4
SQLAlchemy==1.4.16
SQLAlchemy-Utils==0.40.0
stack-data==0.6.2
supervisor-win==4.7.0
terminado==0.17.1
text-unidecode==1.3
tinycss2==1.2.1
tornado==6.2
tqdm==4.65.0
traitlets==5.9.0
typing-inspect==0.8.0
typing_extensions==4.5.0
tzdata==2023.3
tzlocal==5.0.1
uri-template==1.2.0
urllib3==1.26.15
voila==0.4.0
wcwidth==0.2.6
weasyprint==59.0
webcolors==1.13
webencodings==0.5.1
websocket-client==1.5.1
websockets==11.0
Werkzeug==2.2.3
widgetsnbextension==4.0.7
wrapt==1.15.0
wsproto==1.2.0
WTForms==3.0.1
xlrd==2.0.1
XlsxWriter==3.1.2
y-py==0.5.9
yarl==1.8.2
ypy-websocket==0.8.2
zopfli==0.2.2
(venv) PS D:\Dev\Ilex Projects\BBFTA> 

gbrault avatar Sep 15 '23 09:09 gbrault

The ConnectionError problem should be addressed in the Flask-Sock 0.7.0 release. For the other problem I'm not sure. You are comparing two machines that have different versions of packages, so you can try to figure our a combination of requirements that work well. For this problem I'd say the packages that matter are Flask, Werkzeug, simple-websocket and Flask-Sock. If you see issues with the latest versions of these packages, then please report exactly the issue and I'll investigate.

miguelgrinberg avatar Oct 02 '23 22:10 miguelgrinberg

Thanks @miguelgrinberg , will do!

gbrault avatar Oct 13 '23 07:10 gbrault