gzip.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. # coding=utf-8
  2. """
  3. @project: MaxKB
  4. @Author:虎
  5. @file: gzip.py
  6. @date:2025/2/27 10:03
  7. @desc:
  8. """
  9. from django.utils.cache import patch_vary_headers
  10. from django.utils.deprecation import MiddlewareMixin
  11. from django.utils.regex_helper import _lazy_re_compile
  12. from django.utils.text import compress_sequence, compress_string
  13. re_accepts_gzip = _lazy_re_compile(r"\bgzip\b")
  14. class GZipMiddleware(MiddlewareMixin):
  15. """
  16. Compress content if the browser allows gzip compression.
  17. Set the Vary header accordingly, so that caches will base their storage
  18. on the Accept-Encoding header.
  19. """
  20. max_random_bytes = 100
  21. def process_response(self, request, response):
  22. if request.method != 'GET' or request.path.startswith('/api'):
  23. return response
  24. # It's not worth attempting to compress really short responses.
  25. if not response.streaming and len(response.content) < 200:
  26. return response
  27. # Avoid gzipping if we've already got a content-encoding.
  28. if response.has_header("Content-Encoding"):
  29. return response
  30. patch_vary_headers(response, ("Accept-Encoding",))
  31. ae = request.META.get("HTTP_ACCEPT_ENCODING", "")
  32. if not re_accepts_gzip.search(ae):
  33. return response
  34. if response.streaming:
  35. if response.is_async:
  36. # pull to lexical scope to capture fixed reference in case
  37. # streaming_content is set again later.
  38. original_iterator = response.streaming_content
  39. async def gzip_wrapper():
  40. async for chunk in original_iterator:
  41. yield compress_string(
  42. chunk,
  43. max_random_bytes=self.max_random_bytes,
  44. )
  45. response.streaming_content = gzip_wrapper()
  46. else:
  47. response.streaming_content = compress_sequence(
  48. response.streaming_content,
  49. max_random_bytes=self.max_random_bytes,
  50. )
  51. # Delete the `Content-Length` header for streaming content, because
  52. # we won't know the compressed size until we stream it.
  53. del response.headers["Content-Length"]
  54. else:
  55. # Return the compressed content only if it's actually shorter.
  56. compressed_content = compress_string(
  57. response.content,
  58. max_random_bytes=self.max_random_bytes,
  59. )
  60. if len(compressed_content) >= len(response.content):
  61. return response
  62. response.content = compressed_content
  63. response.headers["Content-Length"] = str(len(response.content))
  64. # If there is a strong ETag, make it weak to fulfill the requirements
  65. # of RFC 9110 Section 8.8.1 while also allowing conditional request
  66. # matches on ETags.
  67. etag = response.get("ETag")
  68. if etag and etag.startswith('"'):
  69. response.headers["ETag"] = "W/" + etag
  70. response.headers["Content-Encoding"] = "gzip"
  71. return response