If the user calls Request.body() from their dispatch function we cache the entire request body in memory and pass that to downstream middlewares, but if they call Request.stream() then all we do is send an empty body so that downstream things don't hang forever.
| 18 | |
| 19 | |
| 20 | class _CachedRequest(Request): |
| 21 | """ |
| 22 | If the user calls Request.body() from their dispatch function |
| 23 | we cache the entire request body in memory and pass that to downstream middlewares, |
| 24 | but if they call Request.stream() then all we do is send an |
| 25 | empty body so that downstream things don't hang forever. |
| 26 | """ |
| 27 | |
| 28 | def __init__(self, scope: Scope, receive: Receive): |
| 29 | super().__init__(scope, receive) |
| 30 | self._wrapped_rcv_disconnected = False |
| 31 | self._wrapped_rcv_consumed = False |
| 32 | self._wrapped_rc_stream = self.stream() |
| 33 | |
| 34 | async def wrapped_receive(self) -> Message: |
| 35 | # wrapped_rcv state 1: disconnected |
| 36 | if self._wrapped_rcv_disconnected: |
| 37 | # we've already sent a disconnect to the downstream app |
| 38 | # we don't need to wait to get another one |
| 39 | # (although most ASGI servers will just keep sending it) |
| 40 | return {"type": "http.disconnect"} |
| 41 | # wrapped_rcv state 1: consumed but not yet disconnected |
| 42 | if self._wrapped_rcv_consumed: |
| 43 | # since the downstream app has consumed us all that is left |
| 44 | # is to send it a disconnect |
| 45 | if self._is_disconnected: |
| 46 | # the middleware has already seen the disconnect |
| 47 | # since we know the client is disconnected no need to wait |
| 48 | # for the message |
| 49 | self._wrapped_rcv_disconnected = True |
| 50 | return {"type": "http.disconnect"} |
| 51 | # we don't know yet if the client is disconnected or not |
| 52 | # so we'll wait until we get that message |
| 53 | msg = await self.receive() |
| 54 | if msg["type"] != "http.disconnect": # pragma: no cover |
| 55 | # at this point a disconnect is all that we should be receiving |
| 56 | # if we get something else, things went wrong somewhere |
| 57 | raise RuntimeError(f"Unexpected message received: {msg['type']}") |
| 58 | self._wrapped_rcv_disconnected = True |
| 59 | return msg |
| 60 | |
| 61 | # wrapped_rcv state 3: not yet consumed |
| 62 | if getattr(self, "_body", None) is not None: |
| 63 | # body() was called, we return it even if the client disconnected |
| 64 | self._wrapped_rcv_consumed = True |
| 65 | return { |
| 66 | "type": "http.request", |
| 67 | "body": self._body, |
| 68 | "more_body": False, |
| 69 | } |
| 70 | elif self._stream_consumed: |
| 71 | # stream() was called to completion |
| 72 | # return an empty body so that downstream apps don't hang |
| 73 | # waiting for a disconnect |
| 74 | self._wrapped_rcv_consumed = True |
| 75 | return { |
| 76 | "type": "http.request", |
| 77 | "body": b"", |