Exemplo n.º 1
0
    async def decode(self, websocket: WebSocket,
                     message: Message) -> typing.Any:

        if self.encoding == "text":
            if "text" not in message:
                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)
                raise RuntimeError(
                    "Expected text websocket messages, but got bytes")
            return message["text"]

        elif self.encoding == "bytes":
            if "bytes" not in message:
                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)
                raise RuntimeError(
                    "Expected bytes websocket messages, but got text")
            return message["bytes"]

        elif self.encoding == "json":
            if message.get("text") is not None:
                text = message["text"]
            else:
                text = message["bytes"].decode("utf-8")

            try:
                return json.loads(text)
            except json.decoder.JSONDecodeError:
                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)
                raise RuntimeError("Malformed JSON data received.")

        assert (self.encoding is
                None), f"Unsupported 'encoding' attribute {self.encoding}"
        return message["text"] if message.get("text") else message["bytes"]
Exemplo n.º 2
0
        async def send(message: Message) -> None:
            nonlocal raw_kwargs, response_started, response_complete, template, context

            if message["type"] == "http.response.start":
                assert (not response_started
                        ), 'Received multiple "http.response.start" messages.'
                raw_kwargs["version"] = 11
                raw_kwargs["status"] = message["status"]
                raw_kwargs["reason"] = _get_reason_phrase(message["status"])
                raw_kwargs["headers"] = [(key.decode(), value.decode())
                                         for key, value in message["headers"]]
                raw_kwargs["preload_content"] = False
                raw_kwargs["original_response"] = _MockOriginalResponse(
                    raw_kwargs["headers"])
                response_started = True
            elif message["type"] == "http.response.body":
                assert (
                    response_started
                ), 'Received "http.response.body" without "http.response.start".'
                assert (
                    not response_complete
                ), 'Received "http.response.body" after response completed.'
                body = message.get("body", b"")
                more_body = message.get("more_body", False)
                if request.method != "HEAD":
                    raw_kwargs["body"].write(body)
                if not more_body:
                    raw_kwargs["body"].seek(0)
                    response_complete = True
            elif message["type"] == "http.response.template":
                template = message["template"]
                context = message["context"]
Exemplo n.º 3
0
    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)
Exemplo n.º 4
0
    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)
Exemplo n.º 5
0
        async def _send(message: Message):
            try:
                message.get('headers', list()).append(
                    (b'X-Flow-ID', Hub.current.scope.transaction.to_traceparent())
                )

            except AttributeError:
                pass

            await send(message)
    async def send_with_logging(self, message: Message) -> None:

        if message["type"] == "http.response.start":
            self._response_status_code = message.get("status")
            headers = Headers(raw=message["headers"])
            self.should_log_response_body = "application/json" in headers.get(
                "content-type", "")

            await self.send(message)

        elif message["type"] == "http.response.body":
            if not self.should_log_response_body:
                await self.send(message)
                return

            body: bytes = message.get("body", b"")
            self._response_body.extend(body)

            await self.send(message)
Exemplo n.º 7
0
        async def wrapped_send(message: Message):
            if message["type"] == "http.response.start":
                headers = message.get("headers", [])
                header_value = b"default-src 'self'"
                if self.config.report_uri:
                    header_value = (header_value + b"; report-uri " +
                                    self.config.report_uri)
                headers.append([b"Content-Security-Policy", header_value])
                message["headers"] = headers

            await send(message)
Exemplo n.º 8
0
        async def send_with_tracing(message: Message) -> None:
            span = self.tracer.current_span()

            if span and message.get("type") == "http.response.start":
                if "status" in message:
                    status_code: int = message["status"]
                    span.set_tag(http_tags.STATUS_CODE, str(status_code))
                if "headers" in message:
                    response_headers = Headers(raw=message["headers"])
                    store_response_headers(response_headers, span, config.asgi)

            await send(message)
Exemplo n.º 9
0
    async def send_with_msgpack(self, message: Message) -> None:
        if not self.should_encode_from_json_to_msgpack:
            await self.send(message)
            return

        if message["type"] == "http.response.start":
            headers = Headers(raw=message["headers"])
            if headers["content-type"] != "application/json":
                # Client accepts msgpack, but the app did not send JSON data.
                # (Note that it may have sent msgpack-encoded data.)
                self.should_encode_from_json_to_msgpack = False
                await self.send(message)
                return

            # 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":
            assert self.should_encode_from_json_to_msgpack

            body = message.get("body", b"")
            more_body = message.get("more_body", False)
            if more_body:  # pragma: no cover
                raise NotImplementedError(
                    "Streaming the response body isn't supported yet"
                )

            body = msgpack.packb(json.loads(body))

            headers = MutableHeaders(raw=self.initial_message["headers"])
            headers["Content-Type"] = "application/x-msgpack"
            headers["Content-Length"] = str(len(body))
            message["body"] = body

            await self.send(self.initial_message)
            await self.send(message)
Exemplo n.º 10
0
    async def send_with_caching(self, message: Message) -> None:
        if not self.is_response_cachable:
            await self.send(message)
            return

        if message["type"] == "http.response.start":
            # Defer sending this message until we figured out
            # whether the response can be cached.
            self.initial_message = message
            return

        assert message["type"] == "http.response.body"
        if message.get("more_body", False):
            logger.trace("response_not_cachable reason=is_streaming")
            self.is_response_cachable = False
            await self.send(self.initial_message)
            await self.send(message)
            return

        assert self.request is not None
        body = message["body"]
        response = Response(content=body,
                            status_code=self.initial_message["status"])
        # NOTE: be sure not to mutate the original headers directly, as another Response
        # object might be holding a reference to the same list.
        response.raw_headers = list(self.initial_message["headers"])

        try:
            await store_in_cache(response,
                                 request=self.request,
                                 cache=self.cache)
        except ResponseNotCachable:
            self.is_response_cachable = False
        else:
            # Apply any headers added or modified by 'store_in_cache()'.
            self.initial_message["headers"] = list(response.raw_headers)

        await self.send(self.initial_message)
        await self.send(message)
Exemplo n.º 11
0
 def _raise_on_close(self, message: Message) -> None:
     if message["type"] == "websocket.close":
         raise WebSocketDisconnect(message.get("code", 1000))
Exemplo n.º 12
0
    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)