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

pywsgi + WebSocket - "Invalid frame header" #1712

Closed
Ksengine opened this issue Dec 1, 2020 · 3 comments · Fixed by #1723
Closed

pywsgi + WebSocket - "Invalid frame header" #1712

Ksengine opened this issue Dec 1, 2020 · 3 comments · Fixed by #1723

Comments

@Ksengine
Copy link

Ksengine commented Dec 1, 2020

  • gevent version: 20.9.0
  • Python version: "cPython 3.7.3 OS pre-installed"
  • Operating System: "Armbian (Debian Linux 10 Linux armv7l)"

Description:

I had created a Websocket middleware named WSocket.
It works with wsgiref. but sends wrong data to client when using gevent.pywsgi

chrome devtools shows

WebSocket connection to 'ws://localhost:8080/' failed: Invalid frame header

What I've run:

from bottle import request, Bottle, run
from wsocket import WSocketApp, WebSocketError, logger, run
from time import sleep

logger.setLevel(10)  # for debugging

bottle = Bottle()
app = WSocketApp(bottle)
# app = WSocketApp(bottle, "WAMP")


@bottle.route("/")
def handle_websocket():
    wsock = request.environ.get("wsgi.websocket")
    if not wsock:
        return "Hello World!"

    while True:
        try:
            message = wsock.receive()
            if message != None:
                print("participator : " + message)
                wsock.send("you : " + message)
                sleep(2)
                wsock.send("you : " + message)
        except WebSocketError:
            break


#run(app)#, server="tornado")
from gevent.pywsgi import WSGIHandler, WSGIServer


class WebSocketHandler(WSGIHandler):
    def get_environ(self):
        env = WSGIHandler.get_environ(self)
        env['wsgi.input']=self.rfile
        return env

WSGIServer.handler_class = WebSocketHandler
server = WSGIServer(('127.0.0.1', 8080), app)
server.serve_forever()
@jamadden
Copy link
Member

You've disabled support for HTTP chunking on reads, but you also need to disable support for HTTP chunking on writes. This example works:

from gevent.time import sleep
from gevent.pywsgi import WSGIServer
from gevent.pywsgi import WSGIHandler

from wsocket import WSocketApp
from wsocket import WebSocketError

def handle_websocket(environ, start_response):
    wsock = environ.get("wsgi.websocket")
    if not wsock:
        return ["Hello World!"]

    while True:
        message = wsock.receive()
        if message:
            print("participator : " + message)
            wsock.send("you : " + message)
            sleep(2)
            wsock.send("you : " + message)


class WebSocketHandler(WSGIHandler):

    def __connection_upgrade_requested(self):
        if self.headers.get('Connection', '').lower() == 'upgrade':
            return True
        if self.headers.get('Upgrade', '').lower() == 'websocket':
            return True
        return False

    def get_environ(self):
        env = WSGIHandler.get_environ(self)
        if self.__connection_upgrade_requested():
            # Disable chunked reads
            env['wsgi.input'] = self.rfile
            env['wsgi.input_terminated'] = False
        return env

    def finalize_headers(self):
        WSGIHandler.finalize_headers(self)
        if self.code == 101:
            # Switching Protocols. Disable chunked writes.
            self.response_use_chunked = False


app = WSocketApp(handle_websocket)
server = WSGIServer(('127.0.0.1', 9001), app, handler_class=WebSocketHandler)
server.serve_forever()

Note that you can also just monkey-patch and use the provided server:

from gevent import monkey; monkey.patch_all()

from wsocket import WSocketApp
from wsocket import run

def handle_websocket(…):
    …

app = WSocketApp(handle_websocket)
run(app)

jamadden added a commit that referenced this issue Dec 19, 2020
…ing upgraded.

Let the application have full control over input and output.

Fixes #1712.
@jamadden
Copy link
Member

The next release of gevent should detect this situation and automatically disable chunking.

jamadden added a commit that referenced this issue Dec 19, 2020
…ing upgraded.

Let the application have full control over input and output.

Fixes #1712.
@Ksengine
Copy link
Author

You've disabled support for HTTP chunking on reads, but you also need to disable support for HTTP chunking on writes. This example works:

from gevent.time import sleep
from gevent.pywsgi import WSGIServer
from gevent.pywsgi import WSGIHandler

from wsocket import WSocketApp
from wsocket import WebSocketError

def handle_websocket(environ, start_response):
    wsock = environ.get("wsgi.websocket")
    if not wsock:
        return ["Hello World!"]

    while True:
        message = wsock.receive()
        if message:
            print("participator : " + message)
            wsock.send("you : " + message)
            sleep(2)
            wsock.send("you : " + message)


class WebSocketHandler(WSGIHandler):

    def __connection_upgrade_requested(self):
        if self.headers.get('Connection', '').lower() == 'upgrade':
            return True
        if self.headers.get('Upgrade', '').lower() == 'websocket':
            return True
        return False

    def get_environ(self):
        env = WSGIHandler.get_environ(self)
        if self.__connection_upgrade_requested():
            # Disable chunked reads
            env['wsgi.input'] = self.rfile
            env['wsgi.input_terminated'] = False
        return env

    def finalize_headers(self):
        WSGIHandler.finalize_headers(self)
        if self.code == 101:
            # Switching Protocols. Disable chunked writes.
            self.response_use_chunked = False


app = WSocketApp(handle_websocket)
server = WSGIServer(('127.0.0.1', 9001), app, handler_class=WebSocketHandler)
server.serve_forever()

not working, all requests are timed out.

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

Successfully merging a pull request may close this issue.

2 participants