Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/branches/cherrypy-2.x/cherrypy/filters/wsgiappfilter.py

Revision 1582 (checked in by dowski, 2 years ago)

2.x backport of fixes in [1388] and [1530] (safer WSGI and request close). See #567.

Also buglet fixes in test.py and helper.py.

Line 
1 """a WSGI application filter for CherryPy
2
3 also see cherrypy.lib.cptools.WSGIApp"""
4
5 # by Christian Wyglendowski
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 # is this sufficient for start_response?
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     # create and populate the wsgi environment
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     # then all the http headers
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         # keep the request body intact so the wsgi app
110         # can have its way with it
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         # if the static filter is on for this path and
119         # request.execute_main is False, assume that the
120         # static filter has already taken care of this request
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         # update the environ with the dict passed to the filter's
134         # constructor
135         environ.update(self.env_update)
136
137         # run the wsgi app and have it set response.body
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         # tell CP not to handle the request further
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     # mount standard CherryPy app
175     cherrypy.tree.mount(Root(), '/')
176     # mount the WSGI app
177     cherrypy.tree.mount(HostedWSGI(), '/app')
178
179     cherrypy.server.start()
180        
181
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets