miguelgrinberg / simple-websocket

Simple WebSocket server and client for Python.
MIT License
78 stars 17 forks source link

simple-websocket attempts to dup() SSLSocket instances #26

Closed drewmnoel closed 1 year ago

drewmnoel commented 1 year ago

When using gevent, simple-websocket calls dup() on the socket. This is not implemented for SSLSocket, which throws an error, preventing WebSockets from running:

[2023-08-07 13:32:11,001] ERROR in app: Exception on /echo [GET]
Traceback (most recent call last):
  File "/tmp/wsock/lib/python3.11/site-packages/flask/app.py", line 2190, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/wsock/lib/python3.11/site-packages/flask/app.py", line 1486, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/wsock/lib/python3.11/site-packages/flask/app.py", line 1484, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/wsock/lib/python3.11/site-packages/flask/app.py", line 1469, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/websocket-test/src/server.py", line 10, in echo
    ws = simple_websocket.Server(request.environ)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/wsock/lib/python3.11/site-packages/simple_websocket/ws.py", line 316, in __init__
    sock = wsgi_input.raw._sock.dup()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/wsock/lib/python3.11/site-packages/gevent/ssl.py", line 359, in dup
    raise NotImplementedError("Can't dup() %s instances" %
NotImplementedError: Can't dup() SSLSocket instances

After reverting to 0.10.0 or removing the call to dup(), the problem no longer occurs:

https://github.com/miguelgrinberg/simple-websocket/blob/abdfcb013acd5bd18ebbebdf459ffcfebf748d4e/src/simple_websocket/ws.py#L315-L317

Versions

simple-websocket==0.10.1
Flask==2.3.2
gevent==23.7.0

PoC

from flask import Flask, request
from gevent import pywsgi
import simple_websocket

app = Flask(__name__)

@app.route("/echo", websocket=True)
def echo():
    ws = simple_websocket.Server(request.environ)
    try:
        while True:
            data = ws.receive()
            ws.send(data)
    except simple_websocket.ConnectionClosed:
        pass
    return ""

if __name__ == "__main__":
    ssl_args = {"keyfile": "ssl/key.pem", "certfile": "ssl/cert.pem"}

    server = pywsgi.WSGIServer(("127.0.0.1", 5000), app, **ssl_args)
    server.serve_forever()
miguelgrinberg commented 1 year ago

I'm not sure this is a perfect solution because not duplicating the socket caused other problems with gevent, but it really represents no risk to only calling dup if the socket instance allows it.