diff --git a/starlette/middleware/gzip.py b/starlette/middleware/gzip.py index abd898b2d..b17464cc6 100644 --- a/starlette/middleware/gzip.py +++ b/starlette/middleware/gzip.py @@ -5,14 +5,28 @@ from starlette.datastructures import Headers, MutableHeaders from starlette.types import ASGIApp, Message, Receive, Scope, Send -DEFAULT_EXCLUDED_CONTENT_TYPES = ("text/event-stream",) +DEFAULT_EXCLUDED_CONTENT_TYPES = ( + "text/event-stream", + "application/zip", + "application/gzip", + "application/x-gzip", +) class GZipMiddleware: - def __init__(self, app: ASGIApp, minimum_size: int = 500, compresslevel: int = 9) -> None: + def __init__( + self, + app: ASGIApp, + minimum_size: int = 500, + compresslevel: int = 9, + *, + excluded_content_types: tuple[str, ...] = DEFAULT_EXCLUDED_CONTENT_TYPES, + additional_excluded_content_types: tuple[str, ...] = (), + ) -> None: self.app = app self.minimum_size = minimum_size self.compresslevel = compresslevel + self.excluded_content_types = excluded_content_types + additional_excluded_content_types async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if scope["type"] != "http": # pragma: no cover @@ -22,9 +36,16 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: headers = Headers(scope=scope) responder: ASGIApp if "gzip" in headers.get("Accept-Encoding", ""): - responder = GZipResponder(self.app, self.minimum_size, compresslevel=self.compresslevel) + responder = GZipResponder( + self.app, + self.minimum_size, + excluded_content_types=self.excluded_content_types, + compresslevel=self.compresslevel, + ) else: - responder = IdentityResponder(self.app, self.minimum_size) + responder = IdentityResponder( + self.app, self.minimum_size, excluded_content_types=self.excluded_content_types + ) await responder(scope, receive, send) @@ -32,9 +53,10 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: class IdentityResponder: content_encoding: str - def __init__(self, app: ASGIApp, minimum_size: int) -> None: + def __init__(self, app: ASGIApp, minimum_size: int, *, excluded_content_types: tuple[str, ...]) -> None: self.app = app self.minimum_size = minimum_size + self.excluded_content_types = excluded_content_types self.send: Send = unattached_send self.initial_message: Message = {} self.started = False @@ -53,7 +75,7 @@ async def send_with_compression(self, message: Message) -> None: self.initial_message = message headers = Headers(raw=self.initial_message["headers"]) self.content_encoding_set = "content-encoding" in headers - self.content_type_is_excluded = headers.get("content-type", "").startswith(DEFAULT_EXCLUDED_CONTENT_TYPES) + self.content_type_is_excluded = headers.get("content-type", "").startswith(self.excluded_content_types) elif message_type == "http.response.body" and (self.content_encoding_set or self.content_type_is_excluded): if not self.started: self.started = True @@ -119,8 +141,15 @@ def apply_compression(self, body: bytes, *, more_body: bool) -> bytes: class GZipResponder(IdentityResponder): content_encoding = "gzip" - def __init__(self, app: ASGIApp, minimum_size: int, compresslevel: int = 9) -> None: - super().__init__(app, minimum_size) + def __init__( + self, + app: ASGIApp, + minimum_size: int, + compresslevel: int = 9, + *, + excluded_content_types: tuple[str, ...], + ) -> None: + super().__init__(app, minimum_size, excluded_content_types=excluded_content_types) self.gzip_buffer = io.BytesIO() self.gzip_file = gzip.GzipFile(mode="wb", fileobj=self.gzip_buffer, compresslevel=compresslevel)