Skip to content

Commit

Permalink
Refactor response management and WSGI app forward
Browse files Browse the repository at this point in the history
now only TGApp is in charge of calling start_response, also the TGController.__call__ is guaranteed to always return a WebOb Response object that can then be changed by application wrappers
  • Loading branch information
amol- committed May 15, 2013
1 parent 132608e commit 29540be
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 55 deletions.
2 changes: 1 addition & 1 deletion tests/test_stack/baseutils.py
Expand Up @@ -32,11 +32,11 @@ def __call__(self, environ, start_response):
environ['routes.url'] = None
return self.app(environ, start_response)


class ControllerWrap(object):
def __init__(self, controller):
self.controller = controller

def __call__(self, environ, start_response):
app = self.controller()
app.start_response = None
return app(environ, start_response)
18 changes: 18 additions & 0 deletions tests/test_tg_controller_dispatch.py
Expand Up @@ -548,6 +548,24 @@ def test_valid_wsgi(self):
raise AssertionError(str(e))
assert 'some_url' in r

class TestWSGIAppControllerNotHTML(TestWSGIController):

def __init__(self, *args, **kargs):
TestWSGIController.__init__(self, *args, **kargs)
class TestedWSGIAppController(WSGIAppController):
def __init__(self):
def test_app(environ, start_response):
start_response('200 OK', [('Content-type','text/plain'),
('Content-Length', '5')])
return [b'HELLO']
super(TestedWSGIAppController, self).__init__(test_app)
self.app = make_app(TestedWSGIAppController)

def test_right_wsgi_headers(self):
r = self.app.get('/some_url')
assert 'HELLO' in r
assert r.content_length == 5
assert r.content_type == 'text/plain'

class TestTGController(TestWSGIController):
def setUp(self, *args, **kargs):
Expand Down
67 changes: 22 additions & 45 deletions tg/controllers/dispatcher.py
Expand Up @@ -15,15 +15,14 @@
class which provides the ordinary TurboGears mechanism.
"""
from warnings import warn
import tg, sys
import mimetypes
from webob.exc import HTTPException
from tg._compat import unicode_text
from tg.exceptions import HTTPNotFound
from tg.i18n import setup_i18n
from tg.decorators import cached_property
from crank.dispatchstate import DispatchState
from tg.request_local import WebObResponse

def dispatched_controller():
state = tg.request._controller_state
Expand Down Expand Up @@ -110,55 +109,33 @@ def routes_placeholder(self, url='/', start_response=None, **kwargs): #pragma: n
def __call__(self, environ, start_response):
thread_locals = environ['tg.locals']
py_response = thread_locals.response
py_request = thread_locals.request

#Replace start_response and track if it is called
#this is to track if the controller is passing control to a plain
#WSGI application instead of a TG controller.
start_response_called = []
def repl_start_response(status, headers, exc_info=None):
start_response_called.append(None)
headers.extend(header for header in environ['tg.locals'].response.headerlist
if header[0] == 'Set-Cookie' or
header[0].startswith('X-'))
return start_response(status, headers, exc_info)
py_request._fast_setattr('start_response', repl_start_response)

try:
response = self._perform_call(thread_locals)
except HTTPException as httpe:
response = httpe

#If we reached a plain WSGI application do not build the response
#but simply pass the response as is.
if not start_response_called:
py_request._fast_setattr('start_response', start_response)
if isinstance(response, bytes):
py_response.body = response
elif isinstance(response, unicode_text):
if not py_response.charset:
py_response.charset = 'utf-8'
py_response.text = response
elif hasattr(response, 'wsgi_response'):
for name, value in py_response.headers.items():
if name.lower() == 'set-cookie':
response.headers.add(name, value)
else:
response.headers.setdefault(name, value)
py_response = thread_locals.response = response
elif response is None:
pass
else:
py_response.app_iter = response
response = py_response

if hasattr(response, 'wsgi_response'):
if 'paste.testing_variables' in environ:
# Copy the response object into the testing vars if we're testing
environ['paste.testing_variables']['response'] = response
return response(environ, start_response)

return response
if isinstance(response, bytes):
py_response.body = response
elif isinstance(response, unicode_text):
if not py_response.charset:
py_response.charset = 'utf-8'
py_response.text = response
elif isinstance(response, WebObResponse):
py_response.content_length = response.content_length
for name, value in py_response.headers.items():
header_name = name.lower()
if header_name == 'set-cookie':
response.headers.add(name, value)
else:
response.headers.setdefault(name, value)
py_response = thread_locals.response = response
elif response is None:
pass
else:
py_response.app_iter = response

return py_response

@cached_property
def mount_point(self):
Expand Down
2 changes: 1 addition & 1 deletion tg/controllers/util.py
Expand Up @@ -196,7 +196,7 @@ def abort(status_code=None, detail="", headers=None, comment=None):
raise exc

def use_wsgi_app(wsgi_app):
return wsgi_app(tg.request.environ, tg.request.start_response)
return tg.request.get_response(wsgi_app)


NullTranslations = tg_gettext = None
Expand Down
6 changes: 3 additions & 3 deletions tg/controllers/wsgiappcontroller.py
Expand Up @@ -42,15 +42,15 @@ def _default(self, *args, **kw):
redirect(request.path_info + '/')

new_req.body_file.seek(0)
return self.delegate(new_req.environ, request.start_response)
return self.delegate(new_req)

def delegate(self, environ, start_response):
def delegate(self, request):
"""Delegate the request to the WSGI app.
Override me if you need to update the environ, mangle response, etc...
"""
return self.app(environ, start_response)
return request.get_response(self.app)


__all__ = ['WSGIAppController']
8 changes: 3 additions & 5 deletions tg/wsgiapp.py
Expand Up @@ -125,14 +125,12 @@ def __call__(self, environ, start_response):
controller = self.resolve(environ, start_response)
response = self.wrapped_dispatch(controller, environ, start_response)

#This is probably not necessary and can be removed as
#CoreDispatcher.__call__ does the same
if testmode and hasattr(response, 'wsgi_response'): #pragma: no cover
if testmode:
environ['paste.testing_variables']['response'] = response

try:
if response is not None:
return response
return response(environ, start_response)

raise Exception("No content returned by controller (Did you "
"remember to 'return' it?) in: %r" %
Expand Down Expand Up @@ -294,7 +292,7 @@ def dispatch(self, controller, environ, start_response):
"""
if not controller:
return HTTPNotFound()(environ, start_response)
return HTTPNotFound()

#Setup pylons compatibility before calling controller
if has_pylons and self.pylons_compatible: #pragma: no cover
Expand Down

0 comments on commit 29540be

Please sign in to comment.