How can I avoid uwsgi_modifier1 30 and keep WSGI my application location-independent?

533 views Asked by At

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?

1

There are 1 answers

4
saaj On BEST ANSWER

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;
  }

}