base.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. from __future__ import unicode_literals
  2. import logging
  3. import sys
  4. import types
  5. from django import http
  6. from django.core import signals
  7. from django.utils.encoding import force_text
  8. from django.utils.importlib import import_module
  9. from django.utils import six
  10. logger = logging.getLogger('django.request')
  11. class BaseHandler(object):
  12. # Changes that are always applied to a response (in this order).
  13. response_fixes = [
  14. http.fix_location_header,
  15. http.conditional_content_removal,
  16. http.fix_IE_for_attach,
  17. http.fix_IE_for_vary,
  18. ]
  19. def __init__(self):
  20. self._request_middleware = self._view_middleware = self._template_response_middleware = self._response_middleware = self._exception_middleware = None
  21. def load_middleware(self):
  22. """
  23. Populate middleware lists from settings.MIDDLEWARE_CLASSES.
  24. Must be called after the environment is fixed (see __call__ in subclasses).
  25. """
  26. from django.conf import settings
  27. from django.core import exceptions
  28. self._view_middleware = []
  29. self._template_response_middleware = []
  30. self._response_middleware = []
  31. self._exception_middleware = []
  32. request_middleware = []
  33. for middleware_path in settings.MIDDLEWARE_CLASSES:
  34. try:
  35. mw_module, mw_classname = middleware_path.rsplit('.', 1)
  36. except ValueError:
  37. raise exceptions.ImproperlyConfigured('%s isn\'t a middleware module' % middleware_path)
  38. try:
  39. mod = import_module(mw_module)
  40. except ImportError as e:
  41. raise exceptions.ImproperlyConfigured('Error importing middleware %s: "%s"' % (mw_module, e))
  42. try:
  43. mw_class = getattr(mod, mw_classname)
  44. except AttributeError:
  45. raise exceptions.ImproperlyConfigured('Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname))
  46. try:
  47. mw_instance = mw_class()
  48. except exceptions.MiddlewareNotUsed:
  49. continue
  50. if hasattr(mw_instance, 'process_request'):
  51. request_middleware.append(mw_instance.process_request)
  52. if hasattr(mw_instance, 'process_view'):
  53. self._view_middleware.append(mw_instance.process_view)
  54. if hasattr(mw_instance, 'process_template_response'):
  55. self._template_response_middleware.insert(0, mw_instance.process_template_response)
  56. if hasattr(mw_instance, 'process_response'):
  57. self._response_middleware.insert(0, mw_instance.process_response)
  58. if hasattr(mw_instance, 'process_exception'):
  59. self._exception_middleware.insert(0, mw_instance.process_exception)
  60. # We only assign to this when initialization is complete as it is used
  61. # as a flag for initialization being complete.
  62. self._request_middleware = request_middleware
  63. def get_response(self, request):
  64. "Returns an HttpResponse object for the given HttpRequest"
  65. from django.core import exceptions, urlresolvers
  66. from django.conf import settings
  67. try:
  68. # Setup default url resolver for this thread, this code is outside
  69. # the try/except so we don't get a spurious "unbound local
  70. # variable" exception in the event an exception is raised before
  71. # resolver is set
  72. urlconf = settings.ROOT_URLCONF
  73. urlresolvers.set_urlconf(urlconf)
  74. resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
  75. try:
  76. response = None
  77. # Apply request middleware
  78. for middleware_method in self._request_middleware:
  79. response = middleware_method(request)
  80. if response:
  81. break
  82. if response is None:
  83. if hasattr(request, 'urlconf'):
  84. # Reset url resolver with a custom urlconf.
  85. urlconf = request.urlconf
  86. urlresolvers.set_urlconf(urlconf)
  87. resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
  88. resolver_match = resolver.resolve(request.path_info)
  89. callback, callback_args, callback_kwargs = resolver_match
  90. request.resolver_match = resolver_match
  91. # Apply view middleware
  92. for middleware_method in self._view_middleware:
  93. response = middleware_method(request, callback, callback_args, callback_kwargs)
  94. if response:
  95. break
  96. if response is None:
  97. try:
  98. response = callback(request, *callback_args, **callback_kwargs)
  99. except Exception as e:
  100. # If the view raised an exception, run it through exception
  101. # middleware, and if the exception middleware returns a
  102. # response, use that. Otherwise, reraise the exception.
  103. for middleware_method in self._exception_middleware:
  104. response = middleware_method(request, e)
  105. if response:
  106. break
  107. if response is None:
  108. raise
  109. # Complain if the view returned None (a common error).
  110. if response is None:
  111. if isinstance(callback, types.FunctionType): # FBV
  112. view_name = callback.__name__
  113. else: # CBV
  114. view_name = callback.__class__.__name__ + '.__call__'
  115. raise ValueError("The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name))
  116. # If the response supports deferred rendering, apply template
  117. # response middleware and then render the response
  118. if hasattr(response, 'render') and callable(response.render):
  119. for middleware_method in self._template_response_middleware:
  120. response = middleware_method(request, response)
  121. response = response.render()
  122. except http.Http404 as e:
  123. logger.warning('Not Found: %s', request.path,
  124. extra={
  125. 'status_code': 404,
  126. 'request': request
  127. })
  128. if settings.DEBUG:
  129. from django.views import debug
  130. response = debug.technical_404_response(request, e)
  131. else:
  132. try:
  133. callback, param_dict = resolver.resolve404()
  134. response = callback(request, **param_dict)
  135. except:
  136. signals.got_request_exception.send(sender=self.__class__, request=request)
  137. response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
  138. except exceptions.PermissionDenied:
  139. logger.warning(
  140. 'Forbidden (Permission denied): %s', request.path,
  141. extra={
  142. 'status_code': 403,
  143. 'request': request
  144. })
  145. try:
  146. callback, param_dict = resolver.resolve403()
  147. response = callback(request, **param_dict)
  148. except:
  149. signals.got_request_exception.send(
  150. sender=self.__class__, request=request)
  151. response = self.handle_uncaught_exception(request,
  152. resolver, sys.exc_info())
  153. except SystemExit:
  154. # Allow sys.exit() to actually exit. See tickets #1023 and #4701
  155. raise
  156. except: # Handle everything else, including SuspiciousOperation, etc.
  157. # Get the exception info now, in case another exception is thrown later.
  158. signals.got_request_exception.send(sender=self.__class__, request=request)
  159. response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
  160. finally:
  161. # Reset URLconf for this thread on the way out for complete
  162. # isolation of request.urlconf
  163. urlresolvers.set_urlconf(None)
  164. try:
  165. # Apply response middleware, regardless of the response
  166. for middleware_method in self._response_middleware:
  167. response = middleware_method(request, response)
  168. response = self.apply_response_fixes(request, response)
  169. except: # Any exception should be gathered and handled
  170. signals.got_request_exception.send(sender=self.__class__, request=request)
  171. response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
  172. return response
  173. def handle_uncaught_exception(self, request, resolver, exc_info):
  174. """
  175. Processing for any otherwise uncaught exceptions (those that will
  176. generate HTTP 500 responses). Can be overridden by subclasses who want
  177. customised 500 handling.
  178. Be *very* careful when overriding this because the error could be
  179. caused by anything, so assuming something like the database is always
  180. available would be an error.
  181. """
  182. from django.conf import settings
  183. if settings.DEBUG_PROPAGATE_EXCEPTIONS:
  184. raise
  185. logger.error('Internal Server Error: %s', request.path,
  186. exc_info=exc_info,
  187. extra={
  188. 'status_code': 500,
  189. 'request': request
  190. }
  191. )
  192. if settings.DEBUG:
  193. from django.views import debug
  194. return debug.technical_500_response(request, *exc_info)
  195. # If Http500 handler is not installed, re-raise last exception
  196. if resolver.urlconf_module is None:
  197. six.reraise(*exc_info)
  198. # Return an HttpResponse that displays a friendly error message.
  199. callback, param_dict = resolver.resolve500()
  200. return callback(request, **param_dict)
  201. def apply_response_fixes(self, request, response):
  202. """
  203. Applies each of the functions in self.response_fixes to the request and
  204. response, modifying the response in the process. Returns the new
  205. response.
  206. """
  207. for func in self.response_fixes:
  208. response = func(request, response)
  209. return response
  210. def get_script_name(environ):
  211. """
  212. Returns the equivalent of the HTTP request's SCRIPT_NAME environment
  213. variable. If Apache mod_rewrite has been used, returns what would have been
  214. the script name prior to any rewriting (so it's the script name as seen
  215. from the client's perspective), unless the FORCE_SCRIPT_NAME setting is
  216. set (to anything).
  217. """
  218. from django.conf import settings
  219. if settings.FORCE_SCRIPT_NAME is not None:
  220. return force_text(settings.FORCE_SCRIPT_NAME)
  221. # If Apache's mod_rewrite had a whack at the URL, Apache set either
  222. # SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any
  223. # rewrites. Unfortunately not every Web server (lighttpd!) passes this
  224. # information through all the time, so FORCE_SCRIPT_NAME, above, is still
  225. # needed.
  226. script_url = environ.get('SCRIPT_URL', '')
  227. if not script_url:
  228. script_url = environ.get('REDIRECT_URL', '')
  229. if script_url:
  230. return force_text(script_url[:-len(environ.get('PATH_INFO', ''))])
  231. return force_text(environ.get('SCRIPT_NAME', ''))