| 1 |
"""a WSGI application filter for CherryPy |
|---|
| 2 |
|
|---|
| 3 |
also see cherrypy.lib.cptools.WSGIApp""" |
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 |
import sys |
|---|
| 8 |
|
|---|
| 9 |
import cherrypy |
|---|
| 10 |
from cherrypy.filters.basefilter import BaseFilter |
|---|
| 11 |
from cherrypy._cputil import get_object_trail |
|---|
| 12 |
|
|---|
| 13 |
|
|---|
| 14 |
|
|---|
| 15 |
def start_response(status, response_headers, exc_info=None): |
|---|
| 16 |
cherrypy.response.status = status |
|---|
| 17 |
headers_dict = dict(response_headers) |
|---|
| 18 |
cherrypy.response.headers.update(headers_dict) |
|---|
| 19 |
|
|---|
| 20 |
def get_path_components(path): |
|---|
| 21 |
"""returns (script_name, path_info) |
|---|
| 22 |
|
|---|
| 23 |
determines what part of the path belongs to cp (script_name) |
|---|
| 24 |
and what part belongs to the wsgi application (path_info) |
|---|
| 25 |
""" |
|---|
| 26 |
no_parts = [''] |
|---|
| 27 |
object_trail = get_object_trail(path) |
|---|
| 28 |
root = object_trail.pop(0) |
|---|
| 29 |
if not path.endswith('/index'): |
|---|
| 30 |
object_trail.pop() |
|---|
| 31 |
script_name_parts = [""] |
|---|
| 32 |
path_info_parts = [""] |
|---|
| 33 |
for (pc,obj) in object_trail: |
|---|
| 34 |
if obj: |
|---|
| 35 |
script_name_parts.append(pc) |
|---|
| 36 |
else: |
|---|
| 37 |
path_info_parts.append(pc) |
|---|
| 38 |
script_name = "/".join(script_name_parts) |
|---|
| 39 |
path_info = "/".join(path_info_parts) |
|---|
| 40 |
if len(script_name) > 1 and path.endswith('/'): |
|---|
| 41 |
path_info = path_info + '/' |
|---|
| 42 |
|
|---|
| 43 |
if script_name and not script_name.startswith('/'): |
|---|
| 44 |
script_name = '/' + script_name |
|---|
| 45 |
if path_info and not path_info.startswith('/'): |
|---|
| 46 |
path_info = '/' + path_info |
|---|
| 47 |
|
|---|
| 48 |
return script_name, path_info |
|---|
| 49 |
|
|---|
| 50 |
def make_environ(): |
|---|
| 51 |
"""grabbed some of below from _cpwsgiserver.py |
|---|
| 52 |
|
|---|
| 53 |
for hosting WSGI apps in non-WSGI environments (yikes!) |
|---|
| 54 |
""" |
|---|
| 55 |
|
|---|
| 56 |
script_name, path_info = get_path_components(cherrypy.request.path) |
|---|
| 57 |
|
|---|
| 58 |
|
|---|
| 59 |
environ = dict() |
|---|
| 60 |
environ["wsgi.version"] = (1,0) |
|---|
| 61 |
environ["wsgi.url_scheme"] = cherrypy.request.scheme |
|---|
| 62 |
environ["wsgi.input"] = cherrypy.request.rfile |
|---|
| 63 |
environ["wsgi.errors"] = sys.stderr |
|---|
| 64 |
environ["wsgi.multithread"] = True |
|---|
| 65 |
environ["wsgi.multiprocess"] = False |
|---|
| 66 |
environ["wsgi.run_once"] = False |
|---|
| 67 |
environ["REQUEST_METHOD"] = cherrypy.request.method |
|---|
| 68 |
environ["SCRIPT_NAME"] = script_name |
|---|
| 69 |
environ["PATH_INFO"] = path_info |
|---|
| 70 |
environ["QUERY_STRING"] = cherrypy.request.queryString |
|---|
| 71 |
environ["SERVER_PROTOCOL"] = cherrypy.request.version |
|---|
| 72 |
server_name = getattr(cherrypy.server.httpserver, 'server_name', "None") |
|---|
| 73 |
environ["SERVER_NAME"] = server_name |
|---|
| 74 |
environ["SERVER_PORT"] = cherrypy.config.get('server.socketPort') |
|---|
| 75 |
environ["REMOTE_HOST"] = cherrypy.request.remoteHost |
|---|
| 76 |
environ["REMOTE_ADDR"] = cherrypy.request.remoteAddr |
|---|
| 77 |
environ["REMOTE_PORT"] = cherrypy.request.remotePort |
|---|
| 78 |
|
|---|
| 79 |
headers = cherrypy.request.headers |
|---|
| 80 |
environ["CONTENT_TYPE"] = headers.get("Content-type", "") |
|---|
| 81 |
environ["CONTENT_LENGTH"] = headers.get("Content-length", "") |
|---|
| 82 |
for (k, v) in headers.iteritems(): |
|---|
| 83 |
envname = "HTTP_" + k.upper().replace("-","_") |
|---|
| 84 |
environ[envname] = v |
|---|
| 85 |
return environ |
|---|
| 86 |
|
|---|
| 87 |
|
|---|
| 88 |
class WSGIAppFilter(BaseFilter): |
|---|
| 89 |
"""A filter for running any WSGI middleware/application within CP. |
|---|
| 90 |
|
|---|
| 91 |
Here are the parameters: |
|---|
| 92 |
|
|---|
| 93 |
wsgi_app - any wsgi application callable |
|---|
| 94 |
env_update - a dictionary with arbitrary keys and values to be |
|---|
| 95 |
merged with the WSGI environment dictionary. |
|---|
| 96 |
|
|---|
| 97 |
Example: |
|---|
| 98 |
|
|---|
| 99 |
class Whatever: |
|---|
| 100 |
_cp_filters = [WSGIAppFilter(some_app)] |
|---|
| 101 |
""" |
|---|
| 102 |
|
|---|
| 103 |
def __init__(self, wsgi_app, env_update=None): |
|---|
| 104 |
self.app = wsgi_app |
|---|
| 105 |
self.env_update = env_update or {} |
|---|
| 106 |
|
|---|
| 107 |
def before_request_body(self): |
|---|
| 108 |
|
|---|
| 109 |
|
|---|
| 110 |
|
|---|
| 111 |
cherrypy.request.processRequestBody = False |
|---|
| 112 |
|
|---|
| 113 |
def before_main(self): |
|---|
| 114 |
"""run the wsgi_app and set response.body to its output |
|---|
| 115 |
""" |
|---|
| 116 |
|
|---|
| 117 |
request = cherrypy.request |
|---|
| 118 |
|
|---|
| 119 |
|
|---|
| 120 |
|
|---|
| 121 |
staticfilter_on = cherrypy.config.get('static_filter.on', False) |
|---|
| 122 |
if staticfilter_on and not request.execute_main: |
|---|
| 123 |
return |
|---|
| 124 |
|
|---|
| 125 |
try: |
|---|
| 126 |
environ = request.wsgi_environ |
|---|
| 127 |
sn, pi = get_path_components(request.path) |
|---|
| 128 |
environ['SCRIPT_NAME'] = sn |
|---|
| 129 |
environ['PATH_INFO'] = pi |
|---|
| 130 |
except AttributeError: |
|---|
| 131 |
environ = make_environ() |
|---|
| 132 |
|
|---|
| 133 |
|
|---|
| 134 |
|
|---|
| 135 |
environ.update(self.env_update) |
|---|
| 136 |
|
|---|
| 137 |
|
|---|
| 138 |
response = self.app(environ, start_response) |
|---|
| 139 |
try: |
|---|
| 140 |
cherrypy.response.body = response |
|---|
| 141 |
finally: |
|---|
| 142 |
if hasattr(response, "close"): |
|---|
| 143 |
response.close() |
|---|
| 144 |
|
|---|
| 145 |
|
|---|
| 146 |
request.execute_main = False |
|---|
| 147 |
|
|---|
| 148 |
|
|---|
| 149 |
if __name__ == '__main__': |
|---|
| 150 |
|
|---|
| 151 |
def my_app(environ, start_response): |
|---|
| 152 |
status = '200 OK' |
|---|
| 153 |
response_headers = [('Content-type', 'text/plain')] |
|---|
| 154 |
start_response(status, response_headers) |
|---|
| 155 |
yield 'Hello, world!\n' |
|---|
| 156 |
yield 'This is a wsgi app running within CherryPy!\n\n' |
|---|
| 157 |
keys = environ.keys() |
|---|
| 158 |
keys.sort() |
|---|
| 159 |
for k in keys: |
|---|
| 160 |
yield '%s: %s\n' % (k,environ[k]) |
|---|
| 161 |
|
|---|
| 162 |
class Root(object): |
|---|
| 163 |
def index(self): |
|---|
| 164 |
yield "<h1>Hi, from CherryPy!</h1>" |
|---|
| 165 |
yield "<a href='app'>A non-CP WSGI app</a><br>" |
|---|
| 166 |
yield "<br>" |
|---|
| 167 |
yield "SCRIPT_NAME and PATH_INFO get set " |
|---|
| 168 |
yield "<a href='app/this/n/that'>properly</a>" |
|---|
| 169 |
index.exposed = True |
|---|
| 170 |
|
|---|
| 171 |
class HostedWSGI(object): |
|---|
| 172 |
_cp_filters = [WSGIAppFilter(my_app, {'cherrypy.wsgi':True,}),] |
|---|
| 173 |
|
|---|
| 174 |
|
|---|
| 175 |
cherrypy.tree.mount(Root(), '/') |
|---|
| 176 |
|
|---|
| 177 |
cherrypy.tree.mount(HostedWSGI(), '/app') |
|---|
| 178 |
|
|---|
| 179 |
cherrypy.server.start() |
|---|
| 180 |
|
|---|
| 181 |
|
|---|