middleware.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. """
  2. Debug Toolbar middleware
  3. """
  4. import re
  5. from functools import lru_cache
  6. from django.conf import settings
  7. from django.utils.module_loading import import_string
  8. from debug_toolbar import settings as dt_settings
  9. from debug_toolbar.toolbar import DebugToolbar
  10. from debug_toolbar.utils import clear_stack_trace_caches
  11. _HTML_TYPES = ("text/html", "application/xhtml+xml")
  12. def show_toolbar(request):
  13. """
  14. Default function to determine whether to show the toolbar on a given page.
  15. """
  16. return settings.DEBUG and request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS
  17. @lru_cache(maxsize=None)
  18. def get_show_toolbar():
  19. # If SHOW_TOOLBAR_CALLBACK is a string, which is the recommended
  20. # setup, resolve it to the corresponding callable.
  21. func_or_path = dt_settings.get_config()["SHOW_TOOLBAR_CALLBACK"]
  22. if isinstance(func_or_path, str):
  23. return import_string(func_or_path)
  24. else:
  25. return func_or_path
  26. class DebugToolbarMiddleware:
  27. """
  28. Middleware to set up Debug Toolbar on incoming request and render toolbar
  29. on outgoing response.
  30. """
  31. def __init__(self, get_response):
  32. self.get_response = get_response
  33. def __call__(self, request):
  34. # Decide whether the toolbar is active for this request.
  35. show_toolbar = get_show_toolbar()
  36. if not show_toolbar(request) or DebugToolbar.is_toolbar_request(request):
  37. return self.get_response(request)
  38. toolbar = DebugToolbar(request, self.get_response)
  39. # Activate instrumentation ie. monkey-patch.
  40. for panel in toolbar.enabled_panels:
  41. panel.enable_instrumentation()
  42. try:
  43. # Run panels like Django middleware.
  44. response = toolbar.process_request(request)
  45. finally:
  46. clear_stack_trace_caches()
  47. # Deactivate instrumentation ie. monkey-unpatch. This must run
  48. # regardless of the response. Keep 'return' clauses below.
  49. for panel in reversed(toolbar.enabled_panels):
  50. panel.disable_instrumentation()
  51. # Generate the stats for all requests when the toolbar is being shown,
  52. # but not necessarily inserted.
  53. for panel in reversed(toolbar.enabled_panels):
  54. panel.generate_stats(request, response)
  55. panel.generate_server_timing(request, response)
  56. # Always render the toolbar for the history panel, even if it is not
  57. # included in the response.
  58. rendered = toolbar.render_toolbar()
  59. for header, value in self.get_headers(request, toolbar.enabled_panels).items():
  60. response.headers[header] = value
  61. # Check for responses where the toolbar can't be inserted.
  62. content_encoding = response.get("Content-Encoding", "")
  63. content_type = response.get("Content-Type", "").split(";")[0]
  64. if (
  65. getattr(response, "streaming", False)
  66. or content_encoding != ""
  67. or content_type not in _HTML_TYPES
  68. ):
  69. return response
  70. # Insert the toolbar in the response.
  71. content = response.content.decode(response.charset)
  72. insert_before = dt_settings.get_config()["INSERT_BEFORE"]
  73. pattern = re.escape(insert_before)
  74. bits = re.split(pattern, content, flags=re.IGNORECASE)
  75. if len(bits) > 1:
  76. bits[-2] += rendered
  77. response.content = insert_before.join(bits)
  78. if "Content-Length" in response:
  79. response["Content-Length"] = len(response.content)
  80. return response
  81. @staticmethod
  82. def get_headers(request, panels):
  83. headers = {}
  84. for panel in panels:
  85. for header, value in panel.get_headers(request).items():
  86. if header in headers:
  87. headers[header] += f", {value}"
  88. else:
  89. headers[header] = value
  90. return headers