base.py 12 KB

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