Python-socketio: How to enable Tornado Websocket CORS ?

Created on 24 Sep 2018  路  14Comments  路  Source: miguelgrinberg/python-socketio

I have test setup with react, that I develop separately, which later will be put inside the template dir, as it upgrade to ws, it gave me 403.
I tried following
class MyHandler(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True
and
(r"/socket.io/", MyHandler),
but it doesn't work,

Can you please help, how to do it ? Thank You

bug

Most helpful comment

Here is the snippet that override the tornado WebSocketHandler check_origin method more elegant

import os

import socketio
import tornado.ioloop
from tornado.options import define, options, parse_command_line

define("port", default=8888, help="run on the given port", type=int)
define("debug", default=False, help="run in debug mode")

sio = socketio.AsyncServer(async_mode="tornado")


@sio.on("connect")
async def on_connect(sid, environ):
    print(sid)

_Handler = socketio.get_tornado_handler(sio)


class SocketHandler(_Handler):
    def check_origin(self, origin):
        return True


def main():
    parse_command_line()
    app = tornado.web.Application(
        [
            (r"/socket.io/", SocketHandler),
        ],
        template_path=os.path.join(os.path.dirname(__file__), "templates"),
        static_path=os.path.join(os.path.dirname(__file__), "static"),
        debug=options.debug,
    )
    app.listen(options.port)
    tornado.ioloop.IOLoop.current().start()


if __name__ == "__main__":
    main()

All 14 comments

I don't understand what you are doing. Did you see the Tornado examples in this repository? Start from one of those, which already have CORS enabled.

Apparently only long pooling is cors enabled but not websocket, if I build my react.js files and then served by python.socket.io, everything is fine, socket.io connection can upgrade with 101 status to websocket connection, only that, as I develop the react.js project files served with local http server on different port, the connection cannot be upgraded to ws:// , it gave 403 denied, so my assumption is http is cors enabled, but not the websocket transport.

I noticed using http pooling has it drawback, does not consistently sending out message periodically as it programmed, but using websocket is again fine.

I hope this can clarify my point, and also help others who face the same development setup, Thanks

Okay, I think I understand now. I'll look into it. Thanks.

I had the same problem:
when starting the server the method get_tornado_handler(socketio_server) is called. This calls the same method in the engineio library (usually located in ~/-local/lib/python3.6/site-packages/engineio/async_tornado.py at least on a linux mashine)
there a Handler class is defined and returned which enharits from the tronado.websocket.WebSocketHandler). In that class the check_origin function is not overritten.
So the "dirty" fix that i would suggest is to overright the get_tornado_handler() function like this:

def get_socket_handler(engineio_server):
    class SocketHandler(tornado.websocket.WebSocketHandler):  # pragma: no cover
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.receive_queue = asyncio.Queue()

        async def get(self):
            if self.request.headers.get('Upgrade', '').lower() == 'websocket':
                super().get()
            await engineio_server.handle_request(self)

        async def post(self):
            await engineio_server.handle_request(self)

        async def options(self):
            await engineio_server.handle_request(self)

        async def check_origin(self, origin):
            return True

        async def on_message(self, message):
            await self.receive_queue.put(message)

        async def get_next_message(self):
            return await self.receive_queue.get()

        def on_close(self):
            self.receive_queue.put_nowait(None)

    return SocketHandler

either in your main app.py or in the socketio/tornado.py.
Maybe there is also a better way to deal with that problem but for now this fix works.

Thank You @MoritzD , the "dirty" fix works, so I can start working with it

I'm waiting for this to go for the production. My app is completely ready, but cannot release for the XSRF issue on socketio.
Hope developers will find a way to disable that for socketio.

Thank you very much @MoritzD your workaround works perfectly! You saved me hours of work

Here is the snippet that override the tornado WebSocketHandler check_origin method more elegant

import os

import socketio
import tornado.ioloop
from tornado.options import define, options, parse_command_line

define("port", default=8888, help="run on the given port", type=int)
define("debug", default=False, help="run in debug mode")

sio = socketio.AsyncServer(async_mode="tornado")


@sio.on("connect")
async def on_connect(sid, environ):
    print(sid)

_Handler = socketio.get_tornado_handler(sio)


class SocketHandler(_Handler):
    def check_origin(self, origin):
        return True


def main():
    parse_command_line()
    app = tornado.web.Application(
        [
            (r"/socket.io/", SocketHandler),
        ],
        template_path=os.path.join(os.path.dirname(__file__), "templates"),
        static_path=os.path.join(os.path.dirname(__file__), "static"),
        debug=options.debug,
    )
    app.listen(options.port)
    tornado.ioloop.IOLoop.current().start()


if __name__ == "__main__":
    main()

@pacoyang Thank you, I'll give it a try once at home

Sorry for the late response. It's working fine for me! Thank you

This issue is addressed in version 3.4.1 of python-engineio.

Here is the snippet that override the tornado WebSocketHandler check_origin method more elegant

import os

import socketio
import tornado.ioloop
from tornado.options import define, options, parse_command_line

define("port", default=8888, help="run on the given port", type=int)
define("debug", default=False, help="run in debug mode")

sio = socketio.AsyncServer(async_mode="tornado")


@sio.on("connect")
async def on_connect(sid, environ):
    print(sid)

_Handler = socketio.get_tornado_handler(sio)


class SocketHandler(_Handler):
    def check_origin(self, origin):
        return True


def main():
    parse_command_line()
    app = tornado.web.Application(
        [
            (r"/socket.io/", SocketHandler),
        ],
        template_path=os.path.join(os.path.dirname(__file__), "templates"),
        static_path=os.path.join(os.path.dirname(__file__), "static"),
        debug=options.debug,
    )
    app.listen(options.port)
    tornado.ioloop.IOLoop.current().start()


if __name__ == "__main__":
    main()

Working perfectly! Thanks so much!

I am using plain tornado without socket.io, and the initial solution works fine for me on tornado 4.5.2. In other words, the code below works well with plain tornado and no python-socketio in the imports.

However, what is not intuitive for me (and perhaps for others) is that self.set_header("Access-Control-Allow-Origin", "*") will NOT work as the discussion above confirms!

class MyHandler(tornado.websocket.WebSocketHandler):

def check_origin(self, origin):
    return True

(r"/socket.io/", MyHandler),

@sshivaji You have to configure CORS on this package as well. See the cors_allowed_origins option. This isn't mentioned here because there was a security related fix recently that made this necessary. Basically, if you set cors_allowed_origins='*' you will be allowing any clients to connect (yours and your hackers), and it will be your own responsibility if anything goes wrong because of that. My recommendation is that you don't use '*' and instead only enable the origin(s) that you intend to support.

Was this page helpful?
0 / 5 - 0 ratings