Nick
Nick

Reputation: 2803

Cherrypy routing with WSGI

I'm trying to route certain URLs to a grafted WSGI app and also route sub URLs to a normal cherrypy page handler.

I need the following routes to work. All the other routes should return 404.

The WSGI app mounted at /api is a legacy SOAP based application. It needs to accept ?wsdl parameters but that is all.

I'm writing a new RESTful api at /api/some_resource.

The issue I'm having is that if the resource doesn't exist, it ends up sending the bad request to the legacy soap application. The final example "/api/badurl" ends up going to the WSGI app.

Is there a way to tell cherrypy to only send the first two routes to the WSGI app?

I wrote up a simple example of my issue:

import cherrypy

globalConf = {
    'server.socket_host': '0.0.0.0',
    'server.socket_port': 8080,
}
cherrypy.config.update(globalConf)

class HelloApiWsgi(object):
    def __call__(self, environ, start_response):
        start_response('200 OK', [('Content-Type', 'text/html')])
        return ['Hello World from WSGI']

class HelloApi(object):
    @cherrypy.expose
    def index(self):
        return "Hello from api"

cherrypy.tree.graft(HelloApiWsgi(), '/api')
cherrypy.tree.mount(HelloApi(), '/api/hello')

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

Here's some unit tests:

import unittest
import requests

server = 'localhost:8080'

class TestRestApi(unittest.TestCase):

    def testWsgi(self):
        r = requests.get('http://%s/api?wsdl'%(server))
        self.assertEqual(r.status_code, 200)
        self.assertEqual(r.text, 'Hello World from WSGI')

        r = requests.get('http://%s/api'%(server))
        self.assertEqual(r.status_code, 200)
        self.assertEqual(r.text, 'Hello World from WSGI')

    def testGoodUrl(self):
        r = requests.get('http://%s/api/hello'%(server))
        self.assertEqual(r.status_code, 200)
        self.assertEqual(r.text, 'Hello from api')

    def testBadUrl(self):
        r = requests.get('http://%s/api/badurl'%(server))
        self.assertEqual(r.status_code, 404)

Outputs:

nosetests test_rest_api.py
F..
======================================================================
FAIL: testBadUrl (webserver.test_rest_api.TestRestApi)
----------------------------------------------------------------------
Traceback (most recent call last):
  File line 25, in testBadUrl
    self.assertEqual(r.status_code, 404)
AssertionError: 200 != 404
-------------------- >> begin captured stdout << ---------------------
Hello World from WSGI

Upvotes: 2

Views: 1647

Answers (1)

saaj
saaj

Reputation: 25224

Preface: I can not avoid mentioning that I wish everyone would ask questions in such a complete form with means to validate the answer :-)

Solutions out of CherryPy's scope:

  • do URL pre-processing at front-end server, e.g. nginx
  • create own WSGI middleware, i.e. wrap you legacy WSGI app in another app that will filter URLs

The latter is probably the preferred way to do it, but here's CherryPy's way. Documentation section host a foreign WSGI application in CherryPy says:

You cannot use tools with a foreign WSGI application.

Also you cannot set a custom dispatcher. But you can subclass the application tree.

#!/usr/bin/env python


import cherrypy


class Tree(cherrypy._cptree.Tree):

  def __call__(self, environ, start_response):
    # do more complex check likewise
    if environ['PATH_INFO'].startswith('/api/badurl'):
      start_response('404 Not Found', [])
      return []

    return super(Tree, self).__call__(environ, start_response)

cherrypy.tree = Tree()


globalConf = {
  'server.socket_host': '0.0.0.0',
  'server.socket_port': 8080,
}
cherrypy.config.update(globalConf)


class HelloApiWsgi:

  def __call__(self, environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return ['Hello World from WSGI']

class HelloApi:

  @cherrypy.expose
  def index(self):
    return "Hello from api"


cherrypy.tree.graft(HelloApiWsgi(), '/api')
cherrypy.tree.mount(HelloApi(), '/api/hello')


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

Upvotes: 3

Related Questions