Spaces:
Running
Running
| from __future__ import annotations | |
| import gzip | |
| import io | |
| from typing import NoReturn | |
| from starlette.datastructures import Headers, MutableHeaders | |
| from starlette.types import ASGIApp, Message, Receive, Scope, Send | |
| try: | |
| import brotli | |
| HAS_BROTLI = True | |
| except ImportError: | |
| brotli = None | |
| HAS_BROTLI = False | |
| COMPRESSIBLE_CONTENT_TYPES = ( | |
| "application/json", | |
| "application/javascript", | |
| "application/manifest+json", | |
| "application/xml", | |
| "application/xhtml+xml", | |
| "image/svg+xml", | |
| "text/", | |
| ) | |
| class CompressionMiddleware: | |
| """Negotiates Brotli, then gzip, then identity based on Accept-Encoding. | |
| Only text-like content types are compressed, so already-compressed media | |
| (images, audio, video) and zero-copy file sends pass through untouched. | |
| """ | |
| def __init__( | |
| self, | |
| app: ASGIApp, | |
| minimum_size: int = 512, | |
| gzip_level: int = 6, | |
| brotli_quality: int = 5, | |
| ) -> None: | |
| self.app = app | |
| self.minimum_size = minimum_size | |
| self.gzip_level = gzip_level | |
| self.brotli_quality = brotli_quality | |
| async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: | |
| if scope["type"] != "http": | |
| await self.app(scope, receive, send) | |
| return | |
| accept_encoding = Headers(scope=scope).get("Accept-Encoding", "") | |
| responder: _CompressionResponder | |
| if HAS_BROTLI and "br" in accept_encoding: | |
| responder = _BrotliResponder( | |
| self.app, self.minimum_size, self.brotli_quality | |
| ) | |
| elif "gzip" in accept_encoding: | |
| responder = _GZipResponder(self.app, self.minimum_size, self.gzip_level) | |
| else: | |
| responder = _CompressionResponder(self.app, self.minimum_size) | |
| await responder(scope, receive, send) | |
| class _CompressionResponder: | |
| content_encoding: str = "" | |
| def __init__(self, app: ASGIApp, minimum_size: int) -> None: | |
| self.app = app | |
| self.minimum_size = minimum_size | |
| self.send: Send = _unattached_send | |
| self.initial_message: Message = {} | |
| self.started = False | |
| self.passthrough = True | |
| async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: | |
| self.send = send | |
| await self.app(scope, receive, self.send_with_compression) | |
| async def send_with_compression(self, message: Message) -> None: | |
| message_type = message["type"] | |
| if message_type == "http.response.start": | |
| self.initial_message = message | |
| headers = Headers(raw=message["headers"]) | |
| content_type = headers.get("content-type", "") | |
| already_encoded = "content-encoding" in headers | |
| self.passthrough = already_encoded or not content_type.startswith( | |
| COMPRESSIBLE_CONTENT_TYPES | |
| ) | |
| elif message_type == "http.response.body" and self.passthrough: | |
| if not self.started: | |
| self.started = True | |
| await self.send(self.initial_message) | |
| await self.send(message) | |
| elif message_type == "http.response.body" and not self.started: | |
| self.started = True | |
| body = message.get("body", b"") | |
| more_body = message.get("more_body", False) | |
| if len(body) < self.minimum_size and not more_body: | |
| await self.send(self.initial_message) | |
| await self.send(message) | |
| else: | |
| compressed = self.apply_compression(body, more_body=more_body) | |
| headers = MutableHeaders(raw=self.initial_message["headers"]) | |
| headers.add_vary_header("Accept-Encoding") | |
| if compressed != body: | |
| headers["Content-Encoding"] = self.content_encoding | |
| if more_body: | |
| del headers["Content-Length"] | |
| else: | |
| headers["Content-Length"] = str(len(compressed)) | |
| message["body"] = compressed | |
| await self.send(self.initial_message) | |
| await self.send(message) | |
| elif message_type == "http.response.body": | |
| body = message.get("body", b"") | |
| more_body = message.get("more_body", False) | |
| message["body"] = self.apply_compression(body, more_body=more_body) | |
| await self.send(message) | |
| elif message_type == "http.response.pathsend": | |
| await self.send(self.initial_message) | |
| await self.send(message) | |
| def apply_compression(self, body: bytes, *, more_body: bool) -> bytes: | |
| return body | |
| class _GZipResponder(_CompressionResponder): | |
| content_encoding = "gzip" | |
| def __init__(self, app: ASGIApp, minimum_size: int, compresslevel: int) -> None: | |
| super().__init__(app, minimum_size) | |
| self.buffer = io.BytesIO() | |
| self.file = gzip.GzipFile( | |
| mode="wb", fileobj=self.buffer, compresslevel=compresslevel | |
| ) | |
| async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: | |
| with self.buffer, self.file: | |
| await super().__call__(scope, receive, send) | |
| def apply_compression(self, body: bytes, *, more_body: bool) -> bytes: | |
| self.file.write(body) | |
| if not more_body: | |
| self.file.close() | |
| data = self.buffer.getvalue() | |
| self.buffer.seek(0) | |
| self.buffer.truncate() | |
| return data | |
| class _BrotliResponder(_CompressionResponder): | |
| content_encoding = "br" | |
| def __init__(self, app: ASGIApp, minimum_size: int, quality: int) -> None: | |
| super().__init__(app, minimum_size) | |
| self.compressor = brotli.Compressor(quality=quality) | |
| def apply_compression(self, body: bytes, *, more_body: bool) -> bytes: | |
| data = self.compressor.process(body) | |
| if not more_body: | |
| data += self.compressor.finish() | |
| return data | |
| async def _unattached_send(message: Message) -> NoReturn: | |
| raise RuntimeError("send awaitable not set") | |