简体   繁体   中英

Cherrypy + Autobahn websockets on same port

Is it possible to run (mount in cherrypy tree) autobahnn's websocket class to run on same port but different URL?

For example:

  • http://localhost:8080/web to server static content (html + javascript)
  • ws://localhost:8080/websocketA to server some WS communication through class WSA
  • ws://localhost:8080/websocketB to server some WS communication through class WSB

This is my autobahn configuration & run:

self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)

factory = WebSocketServerFactory("ws://0.0.0.0:8081", debug = False)
factory.protocol = WSA.SocketClient

coro = self.loop.create_server(factory, "0.0.0.0", 8081)
server = self.loop.run_until_complete(coro)

self.loop.run_forever()

This is my cherrypy configuration & run:

cherrypy.config.update({
    'server.socket_host' : '0.0.0.0',
    'server.socket_port' : 80,
})

cherrypy.tree.mount(WebApi.Web(), '/web', {
   '/': {
        "tools.staticdir.on": True,
        "tools.staticdir.root": os.path.dirname(os.path.abspath(__file__)),
        "tools.staticdir.dir": "Web",
        "tools.staticdir.index": "index.html"
    }
})

cherrypy.engine.start()

At this point, WebSocket server runs on port 8081, but I would like to run it on same port as web (8080). If it is possible..

Answering your question literally, is to say you can't do it with CherryPy and Autobahn. CherryPy's normal request handling is synchronous and moreover it is a threaded-server. In other words it's not feasible to dedicate a thread to a WebSocket connection. CherryPy's ability to mount separate WSGI app makes no sense here, because WSGI is inherently a synchronous protocol. And WebSockets are inherently asynchronous. But that doesn't make you can't do it in little different way.

CherryPy and ws4py

Luckily, because of smart design of CherryPy it isn't limited to WSGI and allows extension. This fact is employed in nice library by CherryPy contributor Sylvain Hellegouarch, ws4py . It has CherryPy integration.

#!/usr/bin/env python3


import cherrypy
from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
from ws4py.websocket import WebSocket


class Ws:

  @cherrypy.expose
  def a(self):
    '''WebSocket upgrade method.
    Method must exist for ``WebSocketTool`` to work, 404 returned otherwise.
    '''

  @cherrypy.expose
  def b(self):
    pass


class HandlerA(WebSocket):

  def received_message(self, message):
    self.send('"A" is my reply')


class HandlerB(WebSocket):

  def received_message(self, message):
    self.send('"B" is my reply')


class App:

  @cherrypy.expose
  def index(self):
    return '''<!DOCTYPE html>
      <html>
      <body>
        <table cellspacing='10'>
          <tr>
            <td id='a'></td>
            <td id='b'></td>
          </tr>
        </table>

        <script type='application/javascript'>
          var wsA       = new WebSocket('ws://127.0.0.1:8080/websocket/a');
          wsA.onmessage = function(event)
          {
            document.getElementById('a').innerHTML += event.data + '<br/>';
          };

          var wsB       = new WebSocket('ws://127.0.0.1:8080/websocket/b');
          wsB.onmessage = function(event)
          {
            document.getElementById('b').innerHTML += event.data + '<br/>';
          };

          setInterval(function()
          {
            wsA.send('foo');
            wsB.send('bar');
          }, 1000);
          </script>
      </body>
      </html>
    '''


if __name__ == '__main__':
  cherrypy.config.update({
    'server.socket_host' : '127.0.0.1',
    'server.socket_port' : 8080,
    'server.thread_pool' : 8
  })

  cherrypy.tools.websocket = WebSocketTool()
  WebSocketPlugin(cherrypy.engine).subscribe()

  cherrypy.tree.mount(Ws(), '/websocket', {
    '/a' : {
      'tools.websocket.on'          : True,
      'tools.websocket.handler_cls' : HandlerA
    },
    '/b' : {
      'tools.websocket.on'          : True,
      'tools.websocket.handler_cls' : HandlerB
    } 
  })

  cherrypy.tree.mount(App(), '/')

  cherrypy.engine.signals.subscribe()
  cherrypy.engine.start()
  cherrypy.engine.block()

CherryPy, nginx and Autobahn

Since 1.3 nginx supports WebSockets. So you can easily multiplex different backends.

server {
  listen  80;

  server_name localhost;

  location /web {
    proxy_pass         http://127.0.0.1:8080;
    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
  }

  location /websocket {
    proxy_pass         http://127.0.0.1:8081;
    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
  }

}

Try using cherrypy.tree.graft to mount WSA on a different endpoint (called "script_name" in the cherrypy docs).

See the example here of mounting WSGI apps on a different endpoint as the static files: http://rhodesmill.org/brandon/2011/wsgi-under-cherrypy/

More docs here: http://cherrypy.readthedocs.org/en/latest/advanced.html#host-a-foreign-wsgi-application-in-cherrypy

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM