Search code examples
pythoncherrypy

How to specify the listening server instances using cherrypy tree.mount?


Let us create an application server and an admin server. Assume that fusionListener and adminListener contain the application and admin logic we want to expose.

  from cherrypy._cpserver import Server
  fserver = Server()
  fserver.socket_port = 10000
  fserver.subscribe()

  aserver = Server()
  aserver.socket_port = 10001
  aserver.subscribe()

And then to start them:

cherrypy.engine.start()
cherrypy.engine.block()

The tree.mount parameters ask for:

  • the code/ business logic as the first parameter
  • listening url
  • config parameters

Here is how that looks for the above servers:

  cherrypy.tree.mount(fusionListener, r"/fusion.*",fusionConf)
  cherrypy.tree.mount(adminListener, r"/admin.*",adminConf)

But where is the parameter for the server itself - which includes the port being listened to?


Solution

  • This is not a well supported case for CherryPy.

    The application selection (cherrypy.tree is basically a map of /path -> App) is done before the request dispatch and... long story short, you could use cherrypy.dispatch.VirtualHost and map you sub applications under a main one (that will route depending on the hostname (which the port can be part of). For the listening on multiple ports, can be done, but again this is a very custom arrangement.

    I hope this example is illustrative of a possible way to make such feat:

    import cherrypy
    
    from cherrypy import dispatch
    from cherrypy._cpserver import Server
    
    
    class AppOne:
    
        @cherrypy.expose
        def default(self):
            return "DEFAULT from app ONE!"
    
        @cherrypy.expose
        def foo(self):
            return "FOO from app ONE"
    
    
    class AppTwo:
    
        @cherrypy.expose
        def default(self):
            return "DEFAULT from app TWO!"
    
        @cherrypy.expose
        def foo(self):
            return "FOO from app TWO"
    
    
    class Root:
    
        def __init__(self):
            self.one = AppOne()
            self.two = AppTwo()
    
    
    def bind_two_servers(app_one_port, app_two_port):
        # unsubscribe the default server
        cherrypy.server.unsubscribe()
        s1 = Server()
        s2 = Server()
        s1.socket_port = app_one_port
        s2.socket_port = app_two_port
        # subscribe the server to the `cherrypy.engine` bus events
        s1.subscribe()
        s2.subscribe()
    
    
    def start_server():
        bind_two_servers(8081, 8082)
        cherrypy.engine.signals.subscribe()
        cherrypy.engine.start()
        cherrypy.engine.block()
    
    
    config = {
        '/': {
            'request.dispatch': dispatch.VirtualHost(**{
                'localhost:8081': '/one',
                'localhost:8082': '/two',
            })
        }
    }
    
    cherrypy.tree.mount(Root(), '/', config)
    start_server()
    

    This example will serve AppOne when coming from localhost:8081 and AppTwo when coming from localhost:8082.

    The problem is that you can't do multiples cherrypy.tree.mount and expect to route into the different applications using the VirtualHost dispatcher, it assumes that the application resolution is done at that point and is only resolving the path of that application.

    Having said all of that... I do not recommend this solution, it can get complicated and it would be better to have some other server in front (like nginx) and serve each path on different processes. This could be an alternative, only if you really really want to avoid any extra server or process in your setup.