1

I have a WSGI application using CherryPy hosted using uWSGI behind a ngnix server.

I would like for the application itself to be "portable". That is, the application should not know or care what URL it is mapped to, and should even work if mapped to multiple different URLs. I want to DRY by keeping the URL mapping information in one place only. Unfortunately, the only way I have found to do this involves using uwsgi_modifier 30, which has been called an ugly hack. Can I avoid that hack?

For the present purposes, I have created a tiny application called sample that demonstrates my question.

The ngnix config looks like this:

location /sample/ {
    uwsgi_pass unix:/run/uwsgi/app/sample/socket;
    include uwsgi_params;
    uwsgi_param SCRIPT_NAME /sample;
    uwsgi_modifier1 30;
}

The uwsgi config in /etc/uwsgi/apps-enabled/sample.js:

{
    "uwsgi": {
        "uid": "nobody",
        "gid": "www-data",
        "module": "sample:app"
    }
}

...and the application itself:

#!/usr/bin/python

import cherrypy

class Root(object):
    @cherrypy.expose
    def default(self, *path):
        return "hello, world; path=%r\n" % (path,)

app = cherrypy.Application(Root(), script_name=None)

It works:

  • The URL under which the application is mapped (/sample) appears only in one place: in the ngnix config file.
  • The application does not see that prefix and does not have to worry about it, it only receives whatever appears after /sample:

    $ curl http://localhost/sample/
    hello, world; path=()
    $ curl http://localhost/sample/foo
    hello, world; path=('foo',)
    $ curl http://localhost/sample/foo/bar
    hello, world; path=('foo', 'bar')
    

To motivate the reason for my question, let's say I have a development version of the application. I can make a second uwsgi app and point it to a different copy of the source code, add an extra location /sample.test/ { ... } to ngnix pointing to the new uwsgi app, and hack on it using the alternate URL without affecting the production version.

But it makes use of uwsgi_modifier1 30 which is supposedly an ugly hack:

http://uwsgi-docs.readthedocs.org/en/latest/Nginx.html

Note: ancient uWSGI versions used to support the so called “uwsgi_modifier1 30” approach. Do not do it. it is a really ugly hack

Now, I can do this:

location /something/ {
    uwsgi_pass unix:/run/uwsgi/app/sample/socket; 
    include uwsgi_params;
}

...and this...

{
    "uwsgi": {
        "uid": "nobody",
        "gid": "www-data",
        "pythonpath": "",  # no idea why I need this, btw
        "mount": "/something=sample:app",
        "manage-script-name": true
    }
}

But it requires that I hardcode the path (/something) in 2 places instead of 1. Can I avoid that? Or should I stick with the original setup which uses uwsgi_modifier1 30?

Celada
  • 21,627
  • 4
  • 64
  • 78

1 Answers1

0

My answer is really about simplifying things, because the following and the amount of configuration you have indicates one thing -- overkill.

CherryPy ⇐ WSGI ⇒ uWSGI ⇐ uwsgi ⇒ Nginx ⇐ HTTP ⇒ Client

CherryPy has production ready server that natively speaks HTTP. No intermediary protocol, namely WSGI, is required. For low traffic you can use it on its own. For high traffic with Nginx in front, like:

CherryPy ⇐ HTTP ⇒ Nginx ⇐ HTTP ⇒ Client

CherryPy has notion of an application and you can serve several applications with one CherryPy instance. CherryPy also can serve other WSGI applications. Recently I answer a related question.

Portability

The portability your are talking about is natively supported by CherryPy. That means you can mount an app to a given path prefix and there's nothing else to configure (well, as long as you build URLs with cherrypy.url and generally keep in mind that the app can be mounted to different path prefixes).

server.py

#!/usr/bin/env python3

import cherrypy


config = {
  'global' : {
    'server.socket_host' : '127.0.0.1',
    'server.socket_port' : 8080,
    'server.thread_pool' : 8
  }
}
# proxy tool is optional
stableConf = {'/': {'tools.proxy.on': True}}
develConf  = {'/': {'tools.proxy.on': True}}

class AppStable:

  @cherrypy.expose
  def index(self):
    return 'I am stable branch'

class AppDevel:

  @cherrypy.expose
  def index(self):
    return 'I am development branch'    

cherrypy.config.update(config)
cherrypy.tree.mount(AppStable(), '/stable', stableConf)
cherrypy.tree.mount(AppDevel(), '/devel', develConf)    

if __name__ == '__main__':
  cherrypy.engine.signals.subscribe()
  cherrypy.engine.start()
  cherrypy.engine.block()

server.conf (optional)

server {
  listen  80;

  server_name localhost;

  # settings for serving static content with nginx directly, logs, ssl, etc.

  location / {
    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;
  }

}
Community
  • 1
  • 1
saaj
  • 23,253
  • 3
  • 104
  • 105
  • For the second part that suggests mounting both stable and development versions in a single CherryPy process, I definitely have concerns about that because (1) the CherryPy instance is going to need to be restarted often as development occurs, unnecessarily affecting the stable version at the same time, and (2) it's kind of tricky to do in the first place since I would kind of have to import twice the same module in the same Python process (e.g. two different clones of the same git repo, checked out to stable and devel branches respectively), maybe by playing games with `sys.path`. virtualenv? – Celada Jun 19 '15 at 06:58
  • However thanks for your answer. I took the first sentence about simplifying things to heart. I kept uWSGI because its lifecycle management, performance, and tuning and monitoring capabilities are the best fit for our application, but I ended up removing CherryPy from the mix. The application has a single URL endpoint so I really didn't need anything here beyond raw WSGI (`def application(env, start_response)`) and if I did need more then a lighter library without its own built-in HTTP server could be more suitable. – Celada Jun 19 '15 at 07:04
  • @Celada (1) If the CherryPy instance that is meant to serve both *stable* and *dev* is subject to frequent updates from the *dev*, then I share your concern. In such a case, I would suggest replacing the path prefix with a subdomain, like *dev.example.com*, *beta.example.com*, etc. It is also the simplest answer to the OP. (2) You can definitely do *sys.path* hackery to import the same module's version. Though only as a last resort. – saaj Jun 19 '15 at 10:11
  • @Celada In the sense of features like lifecycle/process management, you're making right choice. You can't have several worker processes with CherryPy out of the box. You also need to write an init script for deploying it in a traditional *nix fashion. Even though, CherryPy's HTTP server is not mandatory to use and it doesn't work in WSGI app mode, in the case of single endpoint I also tend to think your Occam's razor decision is right. The less moving part the more fault-tolerant your system is. – saaj Jun 19 '15 at 10:17