async def send_with_gzip(self, message: Message) -> None: message_type = message["type"] if message_type == "http.response.start": # Don't send the initial message until we've determined how to # modify the ougoging headers correctly. self.initial_message = 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: # Don't apply GZip to small outgoing responses. await self.send(self.initial_message) await self.send(message) elif not more_body: # Standard GZip response. self.gzip_file.write(body) self.gzip_file.close() body = self.gzip_buffer.getvalue() headers = MutableHeaders(raw=self.initial_message["headers"]) headers["Content-Encoding"] = "gzip" headers["Content-Length"] = str(len(body)) headers.add_vary_header("Accept-Encoding") message["body"] = body await self.send(self.initial_message) await self.send(message) else: # Initial body in streaming GZip response. headers = MutableHeaders(raw=self.initial_message["headers"]) headers["Content-Encoding"] = "gzip" headers.add_vary_header("Accept-Encoding") del headers["Content-Length"] self.gzip_file.write(body) message["body"] = self.gzip_buffer.getvalue() self.gzip_buffer.seek(0) self.gzip_buffer.truncate() await self.send(self.initial_message) await self.send(message) elif message_type == "http.response.body": # Remaining body in streaming GZip response. body = message.get("body", b"") more_body = message.get("more_body", False) self.gzip_file.write(body) if not more_body: self.gzip_file.close() message["body"] = self.gzip_buffer.getvalue() self.gzip_buffer.seek(0) self.gzip_buffer.truncate() await self.send(message)
async def send_with_brotli(self, message: Message) -> None: """Apply compression using brotli.""" message_type = message["type"] if message_type == "http.response.start": # Don't send the initial message until we've determined how to # modify the outgoing headers correctly. self.initial_message = 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: # Don't apply Brotli to small outgoing responses. await self.send(self.initial_message) await self.send(message) elif not more_body: # Standard Brotli response. body = self.br_file.process(body) + self.br_file.finish() headers = MutableHeaders(raw=self.initial_message["headers"]) headers["Content-Encoding"] = "br" headers["Content-Length"] = str(len(body)) headers.add_vary_header("Accept-Encoding") message["body"] = body await self.send(self.initial_message) await self.send(message) else: # Initial body in streaming Brotli response. headers = MutableHeaders(raw=self.initial_message["headers"]) headers["Content-Encoding"] = "br" headers.add_vary_header("Accept-Encoding") del headers["Content-Length"] self.br_buffer.write( self.br_file.process(body) + self.br_file.flush()) message["body"] = self.br_buffer.getvalue() self.br_buffer.seek(0) self.br_buffer.truncate() await self.send(self.initial_message) await self.send(message) elif message_type == "http.response.body": # Remaining body in streaming Brotli response. body = message.get("body", b"") more_body = message.get("more_body", False) self.br_buffer.write( self.br_file.process(body) + self.br_file.flush()) if not more_body: self.br_buffer.write(self.br_file.finish()) message["body"] = self.br_buffer.getvalue() self.br_buffer.close() await self.send(message) return message["body"] = self.br_buffer.getvalue() self.br_buffer.seek(0) self.br_buffer.truncate() await self.send(message)
async def send(self, message, send, request_headers) -> None: if message["type"] != "http.response.start": await send(message) return message.setdefault("headers", []) headers = MutableHeaders(scope=message) headers.update(self.simple_headers) origin = request_headers["Origin"] has_cookie = "cookie" in request_headers if self.allow_all_origins and has_cookie: headers["Access-Control-Allow-Origin"] = origin elif not self.allow_all_origins and \ self.is_allowed_origin(origin=origin): headers["Access-Control-Allow-Origin"] = origin headers.add_vary_header("Origin") await send(message)
async def send( self, message: Message, send: Send, request_headers: Headers ) -> None: if message["type"] != "http.response.start": await send(message) return message.setdefault("headers", []) headers = MutableHeaders(scope=message) headers.update(self.simple_headers) origin = request_headers["Origin"] has_cookie = "cookie" in request_headers # If request includes any cookie headers, then we must respond # with the specific origin instead of '*'. if self.allow_all_origins and has_cookie: headers["Access-Control-Allow-Origin"] = origin # If we only allow specific origins, then we have to mirror back # the Origin header in the response. elif not self.allow_all_origins and self.is_allowed_origin(origin=origin): headers["Access-Control-Allow-Origin"] = origin headers.add_vary_header("Origin") await send(message)
def allow_explicit_origin(headers: MutableHeaders, origin: str) -> None: headers["Access-Control-Allow-Origin"] = origin headers.add_vary_header("Origin")
async def send_compressed(self, message: Message) -> None: message_type = message["type"] if message_type == "http.response.start": # Don't send the initial message until we've determined how to # modify the outgoing headers correctly. self.initial_message = message headers = MutableHeaders(raw=self.initial_message["headers"]) media_type = headers.get("Content-Type") for encoding in self.compression_registry.encodings(media_type): if encoding in self.accepted: file_factory = self.compression_registry.dispatch( media_type=media_type, encoding=encoding) self.compressed_buffer = io.BytesIO() self.compressed_file = file_factory(self.compressed_buffer) self.encoding = encoding break else: self.encoding = None elif message_type == "http.response.body" and not self.started: headers = MutableHeaders(raw=self.initial_message["headers"]) 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: # Don't apply compression to small outgoing responses. await self.send(self.initial_message) await self.send(message) elif not more_body: if self.encoding is not None: # Standard (non-streaming) response. t0 = time.perf_counter() self.compressed_file.write(body) self.compressed_file.close() compression_time = time.perf_counter() - t0 compressed_body = self.compressed_buffer.getvalue() # Check to see if the compression ratio is significant. # If it isn't just send the original; the savings isn't worth the decompression time. compression_ratio = len(body) / len( compressed_body) # higher is better THRESHOLD = 1 / 0.9 if compression_ratio > THRESHOLD: headers["Content-Encoding"] = self.encoding headers["Content-Length"] = str(len(compressed_body)) headers.add_vary_header("Accept-Encoding") message["body"] = compressed_body # The Server-Timing middleware, which runs after this # CompressionMiddleware, formats these metrics alongside # others in the Server-Timing header. self.scope["state"]["metrics"]["compress"] = { "dur": compression_time, # Units: seconds "ratio": compression_ratio, } await self.send(self.initial_message) await self.send(message) else: # Initial body in streaming response. if self.encoding is not None: headers = MutableHeaders( raw=self.initial_message["headers"]) headers["Content-Encoding"] = self.encoding headers.add_vary_header("Accept-Encoding") del headers["Content-Length"] self.compressed_file.write(body) message["body"] = self.compressed_buffer.getvalue() self.compressed_buffer.seek(0) self.compressed_buffer.truncate() await self.send(self.initial_message) await self.send(message) elif message_type == "http.response.body": # Remaining body in streaming response. if self.encoding is not None: body = message.get("body", b"") more_body = message.get("more_body", False) self.compressed_file.write(body) if not more_body: self.compressed_file.close() message["body"] = self.compressed_buffer.getvalue() self.compressed_buffer.seek(0) self.compressed_buffer.truncate() await self.send(message)