We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
WSGI interface: reading request body is O(n^2) wrt payload size
Hello!
This is related to https://github.com/encode/uvicorn/issues/371 and potentially a duplicate/complementary information, feel free to close if it is deemed to be the case.

I was fooling around with different WSGI and ASGI servers while hacking on a web framework that would support both in the future.

As noted in https://github.com/encode/uvicorn/issues/371, this is indeed a very inefficient scenario, however, what is worse, I found out the time required to turn around the request is at least O(n^2) or worse wrt the payload size.

`POST`ing some files and multipart forms with `curl`:
```
Payload size: 94171344
real	0m5.763s
user	0m0.033s
sys	0m0.012s
```
```
Payload size: 186054864
real	0m24.183s
user	0m0.018s
sys	0m0.061s
```
```
Payload size: 372178858
real	1m37.760s
user	0m0.044s
sys	0m0.152s
```

So it is not just inefficient loading everything into memory, but something else is going on.

Test case used:
```python
def app(environ, start_response):
    status = '200 OK'  # HTTP Status
    headers = [('Content-type', 'text/plain')]  # HTTP Headers
    start_response(status, headers)

    stream = environ['wsgi.input']
    length = 0
    while True:
        chunk = stream.read(8192)
        if not chunk:
            break
        length += len(chunk)

    resp = "Payload size: {}".format(length)
    return [resp.encode()]
```

Running `uvicorn==0.11.5` as `uvicorn --interface wsgi simple_reader:app`.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `uvicorn/middleware/wsgi.py`
Content:
```
1 import asyncio
2 import concurrent.futures
3 import io
4 import sys
5 from collections import deque
6 from typing import Deque, Iterable, Optional, Tuple
7 
8 from asgiref.typing import (
9     ASGIReceiveCallable,
10     ASGIReceiveEvent,
11     ASGISendCallable,
12     ASGISendEvent,
13     HTTPRequestEvent,
14     HTTPResponseBodyEvent,
15     HTTPResponseStartEvent,
16     HTTPScope,
17 )
18 
19 from uvicorn._types import Environ, ExcInfo, StartResponse, WSGIApp
20 
21 
22 def build_environ(scope: HTTPScope, message: ASGIReceiveEvent, body: bytes) -> Environ:
23     """
24     Builds a scope and request message into a WSGI environ object.
25     """
26     environ = {
27         "REQUEST_METHOD": scope["method"],
28         "SCRIPT_NAME": "",
29         "PATH_INFO": scope["path"].encode("utf8").decode("latin1"),
30         "QUERY_STRING": scope["query_string"].decode("ascii"),
31         "SERVER_PROTOCOL": "HTTP/%s" % scope["http_version"],
32         "wsgi.version": (1, 0),
33         "wsgi.url_scheme": scope.get("scheme", "http"),
34         "wsgi.input": io.BytesIO(body),
35         "wsgi.errors": sys.stdout,
36         "wsgi.multithread": True,
37         "wsgi.multiprocess": True,
38         "wsgi.run_once": False,
39     }
40 
41     # Get server name and port - required in WSGI, not in ASGI
42     server = scope.get("server")
43     if server is None:
44         server = ("localhost", 80)
45     environ["SERVER_NAME"] = server[0]
46     environ["SERVER_PORT"] = server[1]
47 
48     # Get client IP address
49     client = scope.get("client")
50     if client is not None:
51         environ["REMOTE_ADDR"] = client[0]
52 
53     # Go through headers and make them into environ entries
54     for name, value in scope.get("headers", []):
55         name_str: str = name.decode("latin1")
56         if name_str == "content-length":
57             corrected_name = "CONTENT_LENGTH"
58         elif name_str == "content-type":
59             corrected_name = "CONTENT_TYPE"
60         else:
61             corrected_name = "HTTP_%s" % name_str.upper().replace("-", "_")
62         # HTTPbis say only ASCII chars are allowed in headers, but we latin1
63         # just in case
64         value_str: str = value.decode("latin1")
65         if corrected_name in environ:
66             corrected_name_environ = environ[corrected_name]
67             assert isinstance(corrected_name_environ, str)
68             value_str = corrected_name_environ + "," + value_str
69         environ[corrected_name] = value_str
70     return environ
71 
72 
73 class WSGIMiddleware:
74     def __init__(self, app: WSGIApp, workers: int = 10):
75         self.app = app
76         self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers)
77 
78     async def __call__(
79         self, scope: HTTPScope, receive: ASGIReceiveCallable, send: ASGISendCallable
80     ) -> None:
81         assert scope["type"] == "http"
82         instance = WSGIResponder(self.app, self.executor, scope)
83         await instance(receive, send)
84 
85 
86 class WSGIResponder:
87     def __init__(
88         self,
89         app: WSGIApp,
90         executor: concurrent.futures.ThreadPoolExecutor,
91         scope: HTTPScope,
92     ):
93         self.app = app
94         self.executor = executor
95         self.scope = scope
96         self.status = None
97         self.response_headers = None
98         self.send_event = asyncio.Event()
99         self.send_queue: Deque[Optional[ASGISendEvent]] = deque()
100         self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
101         self.response_started = False
102         self.exc_info: Optional[ExcInfo] = None
103 
104     async def __call__(
105         self, receive: ASGIReceiveCallable, send: ASGISendCallable
106     ) -> None:
107         message: HTTPRequestEvent = await receive()  # type: ignore[assignment]
108         body = message.get("body", b"")
109         more_body = message.get("more_body", False)
110         while more_body:
111             body_message: HTTPRequestEvent = await receive()  # type: ignore[assignment]
112             body += body_message.get("body", b"")
113             more_body = body_message.get("more_body", False)
114         environ = build_environ(self.scope, message, body)
115         self.loop = asyncio.get_event_loop()
116         wsgi = self.loop.run_in_executor(
117             self.executor, self.wsgi, environ, self.start_response
118         )
119         sender = self.loop.create_task(self.sender(send))
120         try:
121             await asyncio.wait_for(wsgi, None)
122         finally:
123             self.send_queue.append(None)
124             self.send_event.set()
125             await asyncio.wait_for(sender, None)
126         if self.exc_info is not None:
127             raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2])
128 
129     async def sender(self, send: ASGISendCallable) -> None:
130         while True:
131             if self.send_queue:
132                 message = self.send_queue.popleft()
133                 if message is None:
134                     return
135                 await send(message)
136             else:
137                 await self.send_event.wait()
138                 self.send_event.clear()
139 
140     def start_response(
141         self,
142         status: str,
143         response_headers: Iterable[Tuple[str, str]],
144         exc_info: Optional[ExcInfo] = None,
145     ) -> None:
146         self.exc_info = exc_info
147         if not self.response_started:
148             self.response_started = True
149             status_code_str, _ = status.split(" ", 1)
150             status_code = int(status_code_str)
151             headers = [
152                 (name.encode("ascii"), value.encode("ascii"))
153                 for name, value in response_headers
154             ]
155             http_response_start_event: HTTPResponseStartEvent = {
156                 "type": "http.response.start",
157                 "status": status_code,
158                 "headers": headers,
159             }
160             self.send_queue.append(http_response_start_event)
161             self.loop.call_soon_threadsafe(self.send_event.set)
162 
163     def wsgi(self, environ: Environ, start_response: StartResponse) -> None:
164         for chunk in self.app(environ, start_response):  # type: ignore
165             response_body: HTTPResponseBodyEvent = {
166                 "type": "http.response.body",
167                 "body": chunk,
168                 "more_body": True,
169             }
170             self.send_queue.append(response_body)
171             self.loop.call_soon_threadsafe(self.send_event.set)
172 
173         empty_body: HTTPResponseBodyEvent = {
174             "type": "http.response.body",
175             "body": b"",
176             "more_body": False,
177         }
178         self.send_queue.append(empty_body)
179         self.loop.call_soon_threadsafe(self.send_event.set)
180
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/uvicorn/middleware/wsgi.py b/uvicorn/middleware/wsgi.py
--- a/uvicorn/middleware/wsgi.py
+++ b/uvicorn/middleware/wsgi.py
@@ -19,7 +19,9 @@
 from uvicorn._types import Environ, ExcInfo, StartResponse, WSGIApp
 
 
-def build_environ(scope: HTTPScope, message: ASGIReceiveEvent, body: bytes) -> Environ:
+def build_environ(
+    scope: HTTPScope, message: ASGIReceiveEvent, body: io.BytesIO
+) -> Environ:
     """
     Builds a scope and request message into a WSGI environ object.
     """
@@ -31,7 +33,7 @@
         "SERVER_PROTOCOL": "HTTP/%s" % scope["http_version"],
         "wsgi.version": (1, 0),
         "wsgi.url_scheme": scope.get("scheme", "http"),
-        "wsgi.input": io.BytesIO(body),
+        "wsgi.input": body,
         "wsgi.errors": sys.stdout,
         "wsgi.multithread": True,
         "wsgi.multiprocess": True,
@@ -105,12 +107,17 @@
         self, receive: ASGIReceiveCallable, send: ASGISendCallable
     ) -> None:
         message: HTTPRequestEvent = await receive()  # type: ignore[assignment]
-        body = message.get("body", b"")
+        body = io.BytesIO(message.get("body", b""))
         more_body = message.get("more_body", False)
-        while more_body:
-            body_message: HTTPRequestEvent = await receive()  # type: ignore[assignment]
-            body += body_message.get("body", b"")
-            more_body = body_message.get("more_body", False)
+        if more_body:
+            body.seek(0, io.SEEK_END)
+            while more_body:
+                body_message: HTTPRequestEvent = (
+                    await receive()  # type: ignore[assignment]
+                )
+                body.write(body_message.get("body", b""))
+                more_body = body_message.get("more_body", False)
+            body.seek(0)
         environ = build_environ(self.scope, message, body)
         self.loop = asyncio.get_event_loop()
         wsgi = self.loop.run_in_executor(

{"golden_diff": "diff --git a/uvicorn/middleware/wsgi.py b/uvicorn/middleware/wsgi.py\n--- a/uvicorn/middleware/wsgi.py\n+++ b/uvicorn/middleware/wsgi.py\n@@ -19,7 +19,9 @@\n from uvicorn._types import Environ, ExcInfo, StartResponse, WSGIApp\n \n \n-def build_environ(scope: HTTPScope, message: ASGIReceiveEvent, body: bytes) -> Environ:\n+def build_environ(\n+    scope: HTTPScope, message: ASGIReceiveEvent, body: io.BytesIO\n+) -> Environ:\n     \"\"\"\n     Builds a scope and request message into a WSGI environ object.\n     \"\"\"\n@@ -31,7 +33,7 @@\n         \"SERVER_PROTOCOL\": \"HTTP/%s\" % scope[\"http_version\"],\n         \"wsgi.version\": (1, 0),\n         \"wsgi.url_scheme\": scope.get(\"scheme\", \"http\"),\n-        \"wsgi.input\": io.BytesIO(body),\n+        \"wsgi.input\": body,\n         \"wsgi.errors\": sys.stdout,\n         \"wsgi.multithread\": True,\n         \"wsgi.multiprocess\": True,\n@@ -105,12 +107,17 @@\n         self, receive: ASGIReceiveCallable, send: ASGISendCallable\n     ) -> None:\n         message: HTTPRequestEvent = await receive()  # type: ignore[assignment]\n-        body = message.get(\"body\", b\"\")\n+        body = io.BytesIO(message.get(\"body\", b\"\"))\n         more_body = message.get(\"more_body\", False)\n-        while more_body:\n-            body_message: HTTPRequestEvent = await receive()  # type: ignore[assignment]\n-            body += body_message.get(\"body\", b\"\")\n-            more_body = body_message.get(\"more_body\", False)\n+        if more_body:\n+            body.seek(0, io.SEEK_END)\n+            while more_body:\n+                body_message: HTTPRequestEvent = (\n+                    await receive()  # type: ignore[assignment]\n+                )\n+                body.write(body_message.get(\"body\", b\"\"))\n+                more_body = body_message.get(\"more_body\", False)\n+            body.seek(0)\n         environ = build_environ(self.scope, message, body)\n         self.loop = asyncio.get_event_loop()\n         wsgi = self.loop.run_in_executor(\n", "issue": "WSGI interface: reading request body is O(n^2) wrt payload size\nHello!\r\nThis is related to https://github.com/encode/uvicorn/issues/371 and potentially a duplicate/complementary information, feel free to close if it is deemed to be the case.\r\n\r\nI was fooling around with different WSGI and ASGI servers while hacking on a web framework that would support both in the future.\r\n\r\nAs noted in https://github.com/encode/uvicorn/issues/371, this is indeed a very inefficient scenario, however, what is worse, I found out the time required to turn around the request is at least O(n^2) or worse wrt the payload size.\r\n\r\n`POST`ing some files and multipart forms with `curl`:\r\n```\r\nPayload size: 94171344\r\nreal\t0m5.763s\r\nuser\t0m0.033s\r\nsys\t0m0.012s\r\n```\r\n```\r\nPayload size: 186054864\r\nreal\t0m24.183s\r\nuser\t0m0.018s\r\nsys\t0m0.061s\r\n```\r\n```\r\nPayload size: 372178858\r\nreal\t1m37.760s\r\nuser\t0m0.044s\r\nsys\t0m0.152s\r\n```\r\n\r\nSo it is not just inefficient loading everything into memory, but something else is going on.\r\n\r\nTest case used:\r\n```python\r\ndef app(environ, start_response):\r\n    status = '200 OK'  # HTTP Status\r\n    headers = [('Content-type', 'text/plain')]  # HTTP Headers\r\n    start_response(status, headers)\r\n\r\n    stream = environ['wsgi.input']\r\n    length = 0\r\n    while True:\r\n        chunk = stream.read(8192)\r\n        if not chunk:\r\n            break\r\n        length += len(chunk)\r\n\r\n    resp = \"Payload size: {}\".format(length)\r\n    return [resp.encode()]\r\n```\r\n\r\nRunning `uvicorn==0.11.5` as `uvicorn --interface wsgi simple_reader:app`.\n", "before_files": [{"content": "import asyncio\nimport concurrent.futures\nimport io\nimport sys\nfrom collections import deque\nfrom typing import Deque, Iterable, Optional, Tuple\n\nfrom asgiref.typing import (\n    ASGIReceiveCallable,\n    ASGIReceiveEvent,\n    ASGISendCallable,\n    ASGISendEvent,\n    HTTPRequestEvent,\n    HTTPResponseBodyEvent,\n    HTTPResponseStartEvent,\n    HTTPScope,\n)\n\nfrom uvicorn._types import Environ, ExcInfo, StartResponse, WSGIApp\n\n\ndef build_environ(scope: HTTPScope, message: ASGIReceiveEvent, body: bytes) -> Environ:\n    \"\"\"\n    Builds a scope and request message into a WSGI environ object.\n    \"\"\"\n    environ = {\n        \"REQUEST_METHOD\": scope[\"method\"],\n        \"SCRIPT_NAME\": \"\",\n        \"PATH_INFO\": scope[\"path\"].encode(\"utf8\").decode(\"latin1\"),\n        \"QUERY_STRING\": scope[\"query_string\"].decode(\"ascii\"),\n        \"SERVER_PROTOCOL\": \"HTTP/%s\" % scope[\"http_version\"],\n        \"wsgi.version\": (1, 0),\n        \"wsgi.url_scheme\": scope.get(\"scheme\", \"http\"),\n        \"wsgi.input\": io.BytesIO(body),\n        \"wsgi.errors\": sys.stdout,\n        \"wsgi.multithread\": True,\n        \"wsgi.multiprocess\": True,\n        \"wsgi.run_once\": False,\n    }\n\n    # Get server name and port - required in WSGI, not in ASGI\n    server = scope.get(\"server\")\n    if server is None:\n        server = (\"localhost\", 80)\n    environ[\"SERVER_NAME\"] = server[0]\n    environ[\"SERVER_PORT\"] = server[1]\n\n    # Get client IP address\n    client = scope.get(\"client\")\n    if client is not None:\n        environ[\"REMOTE_ADDR\"] = client[0]\n\n    # Go through headers and make them into environ entries\n    for name, value in scope.get(\"headers\", []):\n        name_str: str = name.decode(\"latin1\")\n        if name_str == \"content-length\":\n            corrected_name = \"CONTENT_LENGTH\"\n        elif name_str == \"content-type\":\n            corrected_name = \"CONTENT_TYPE\"\n        else:\n            corrected_name = \"HTTP_%s\" % name_str.upper().replace(\"-\", \"_\")\n        # HTTPbis say only ASCII chars are allowed in headers, but we latin1\n        # just in case\n        value_str: str = value.decode(\"latin1\")\n        if corrected_name in environ:\n            corrected_name_environ = environ[corrected_name]\n            assert isinstance(corrected_name_environ, str)\n            value_str = corrected_name_environ + \",\" + value_str\n        environ[corrected_name] = value_str\n    return environ\n\n\nclass WSGIMiddleware:\n    def __init__(self, app: WSGIApp, workers: int = 10):\n        self.app = app\n        self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers)\n\n    async def __call__(\n        self, scope: HTTPScope, receive: ASGIReceiveCallable, send: ASGISendCallable\n    ) -> None:\n        assert scope[\"type\"] == \"http\"\n        instance = WSGIResponder(self.app, self.executor, scope)\n        await instance(receive, send)\n\n\nclass WSGIResponder:\n    def __init__(\n        self,\n        app: WSGIApp,\n        executor: concurrent.futures.ThreadPoolExecutor,\n        scope: HTTPScope,\n    ):\n        self.app = app\n        self.executor = executor\n        self.scope = scope\n        self.status = None\n        self.response_headers = None\n        self.send_event = asyncio.Event()\n        self.send_queue: Deque[Optional[ASGISendEvent]] = deque()\n        self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()\n        self.response_started = False\n        self.exc_info: Optional[ExcInfo] = None\n\n    async def __call__(\n        self, receive: ASGIReceiveCallable, send: ASGISendCallable\n    ) -> None:\n        message: HTTPRequestEvent = await receive()  # type: ignore[assignment]\n        body = message.get(\"body\", b\"\")\n        more_body = message.get(\"more_body\", False)\n        while more_body:\n            body_message: HTTPRequestEvent = await receive()  # type: ignore[assignment]\n            body += body_message.get(\"body\", b\"\")\n            more_body = body_message.get(\"more_body\", False)\n        environ = build_environ(self.scope, message, body)\n        self.loop = asyncio.get_event_loop()\n        wsgi = self.loop.run_in_executor(\n            self.executor, self.wsgi, environ, self.start_response\n        )\n        sender = self.loop.create_task(self.sender(send))\n        try:\n            await asyncio.wait_for(wsgi, None)\n        finally:\n            self.send_queue.append(None)\n            self.send_event.set()\n            await asyncio.wait_for(sender, None)\n        if self.exc_info is not None:\n            raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2])\n\n    async def sender(self, send: ASGISendCallable) -> None:\n        while True:\n            if self.send_queue:\n                message = self.send_queue.popleft()\n                if message is None:\n                    return\n                await send(message)\n            else:\n                await self.send_event.wait()\n                self.send_event.clear()\n\n    def start_response(\n        self,\n        status: str,\n        response_headers: Iterable[Tuple[str, str]],\n        exc_info: Optional[ExcInfo] = None,\n    ) -> None:\n        self.exc_info = exc_info\n        if not self.response_started:\n            self.response_started = True\n            status_code_str, _ = status.split(\" \", 1)\n            status_code = int(status_code_str)\n            headers = [\n                (name.encode(\"ascii\"), value.encode(\"ascii\"))\n                for name, value in response_headers\n            ]\n            http_response_start_event: HTTPResponseStartEvent = {\n                \"type\": \"http.response.start\",\n                \"status\": status_code,\n                \"headers\": headers,\n            }\n            self.send_queue.append(http_response_start_event)\n            self.loop.call_soon_threadsafe(self.send_event.set)\n\n    def wsgi(self, environ: Environ, start_response: StartResponse) -> None:\n        for chunk in self.app(environ, start_response):  # type: ignore\n            response_body: HTTPResponseBodyEvent = {\n                \"type\": \"http.response.body\",\n                \"body\": chunk,\n                \"more_body\": True,\n            }\n            self.send_queue.append(response_body)\n            self.loop.call_soon_threadsafe(self.send_event.set)\n\n        empty_body: HTTPResponseBodyEvent = {\n            \"type\": \"http.response.body\",\n            \"body\": b\"\",\n            \"more_body\": False,\n        }\n        self.send_queue.append(empty_body)\n        self.loop.call_soon_threadsafe(self.send_event.set)\n", "path": "uvicorn/middleware/wsgi.py"}], "after_files": [{"content": "import asyncio\nimport concurrent.futures\nimport io\nimport sys\nfrom collections import deque\nfrom typing import Deque, Iterable, Optional, Tuple\n\nfrom asgiref.typing import (\n    ASGIReceiveCallable,\n    ASGIReceiveEvent,\n    ASGISendCallable,\n    ASGISendEvent,\n    HTTPRequestEvent,\n    HTTPResponseBodyEvent,\n    HTTPResponseStartEvent,\n    HTTPScope,\n)\n\nfrom uvicorn._types import Environ, ExcInfo, StartResponse, WSGIApp\n\n\ndef build_environ(\n    scope: HTTPScope, message: ASGIReceiveEvent, body: io.BytesIO\n) -> Environ:\n    \"\"\"\n    Builds a scope and request message into a WSGI environ object.\n    \"\"\"\n    environ = {\n        \"REQUEST_METHOD\": scope[\"method\"],\n        \"SCRIPT_NAME\": \"\",\n        \"PATH_INFO\": scope[\"path\"].encode(\"utf8\").decode(\"latin1\"),\n        \"QUERY_STRING\": scope[\"query_string\"].decode(\"ascii\"),\n        \"SERVER_PROTOCOL\": \"HTTP/%s\" % scope[\"http_version\"],\n        \"wsgi.version\": (1, 0),\n        \"wsgi.url_scheme\": scope.get(\"scheme\", \"http\"),\n        \"wsgi.input\": body,\n        \"wsgi.errors\": sys.stdout,\n        \"wsgi.multithread\": True,\n        \"wsgi.multiprocess\": True,\n        \"wsgi.run_once\": False,\n    }\n\n    # Get server name and port - required in WSGI, not in ASGI\n    server = scope.get(\"server\")\n    if server is None:\n        server = (\"localhost\", 80)\n    environ[\"SERVER_NAME\"] = server[0]\n    environ[\"SERVER_PORT\"] = server[1]\n\n    # Get client IP address\n    client = scope.get(\"client\")\n    if client is not None:\n        environ[\"REMOTE_ADDR\"] = client[0]\n\n    # Go through headers and make them into environ entries\n    for name, value in scope.get(\"headers\", []):\n        name_str: str = name.decode(\"latin1\")\n        if name_str == \"content-length\":\n            corrected_name = \"CONTENT_LENGTH\"\n        elif name_str == \"content-type\":\n            corrected_name = \"CONTENT_TYPE\"\n        else:\n            corrected_name = \"HTTP_%s\" % name_str.upper().replace(\"-\", \"_\")\n        # HTTPbis say only ASCII chars are allowed in headers, but we latin1\n        # just in case\n        value_str: str = value.decode(\"latin1\")\n        if corrected_name in environ:\n            corrected_name_environ = environ[corrected_name]\n            assert isinstance(corrected_name_environ, str)\n            value_str = corrected_name_environ + \",\" + value_str\n        environ[corrected_name] = value_str\n    return environ\n\n\nclass WSGIMiddleware:\n    def __init__(self, app: WSGIApp, workers: int = 10):\n        self.app = app\n        self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers)\n\n    async def __call__(\n        self, scope: HTTPScope, receive: ASGIReceiveCallable, send: ASGISendCallable\n    ) -> None:\n        assert scope[\"type\"] == \"http\"\n        instance = WSGIResponder(self.app, self.executor, scope)\n        await instance(receive, send)\n\n\nclass WSGIResponder:\n    def __init__(\n        self,\n        app: WSGIApp,\n        executor: concurrent.futures.ThreadPoolExecutor,\n        scope: HTTPScope,\n    ):\n        self.app = app\n        self.executor = executor\n        self.scope = scope\n        self.status = None\n        self.response_headers = None\n        self.send_event = asyncio.Event()\n        self.send_queue: Deque[Optional[ASGISendEvent]] = deque()\n        self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()\n        self.response_started = False\n        self.exc_info: Optional[ExcInfo] = None\n\n    async def __call__(\n        self, receive: ASGIReceiveCallable, send: ASGISendCallable\n    ) -> None:\n        message: HTTPRequestEvent = await receive()  # type: ignore[assignment]\n        body = io.BytesIO(message.get(\"body\", b\"\"))\n        more_body = message.get(\"more_body\", False)\n        if more_body:\n            body.seek(0, io.SEEK_END)\n            while more_body:\n                body_message: HTTPRequestEvent = (\n                    await receive()  # type: ignore[assignment]\n                )\n                body.write(body_message.get(\"body\", b\"\"))\n                more_body = body_message.get(\"more_body\", False)\n            body.seek(0)\n        environ = build_environ(self.scope, message, body)\n        self.loop = asyncio.get_event_loop()\n        wsgi = self.loop.run_in_executor(\n            self.executor, self.wsgi, environ, self.start_response\n        )\n        sender = self.loop.create_task(self.sender(send))\n        try:\n            await asyncio.wait_for(wsgi, None)\n        finally:\n            self.send_queue.append(None)\n            self.send_event.set()\n            await asyncio.wait_for(sender, None)\n        if self.exc_info is not None:\n            raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2])\n\n    async def sender(self, send: ASGISendCallable) -> None:\n        while True:\n            if self.send_queue:\n                message = self.send_queue.popleft()\n                if message is None:\n                    return\n                await send(message)\n            else:\n                await self.send_event.wait()\n                self.send_event.clear()\n\n    def start_response(\n        self,\n        status: str,\n        response_headers: Iterable[Tuple[str, str]],\n        exc_info: Optional[ExcInfo] = None,\n    ) -> None:\n        self.exc_info = exc_info\n        if not self.response_started:\n            self.response_started = True\n            status_code_str, _ = status.split(\" \", 1)\n            status_code = int(status_code_str)\n            headers = [\n                (name.encode(\"ascii\"), value.encode(\"ascii\"))\n                for name, value in response_headers\n            ]\n            http_response_start_event: HTTPResponseStartEvent = {\n                \"type\": \"http.response.start\",\n                \"status\": status_code,\n                \"headers\": headers,\n            }\n            self.send_queue.append(http_response_start_event)\n            self.loop.call_soon_threadsafe(self.send_event.set)\n\n    def wsgi(self, environ: Environ, start_response: StartResponse) -> None:\n        for chunk in self.app(environ, start_response):  # type: ignore\n            response_body: HTTPResponseBodyEvent = {\n                \"type\": \"http.response.body\",\n                \"body\": chunk,\n                \"more_body\": True,\n            }\n            self.send_queue.append(response_body)\n            self.loop.call_soon_threadsafe(self.send_event.set)\n\n        empty_body: HTTPResponseBodyEvent = {\n            \"type\": \"http.response.body\",\n            \"body\": b\"\",\n            \"more_body\": False,\n        }\n        self.send_queue.append(empty_body)\n        self.loop.call_soon_threadsafe(self.send_event.set)\n", "path": "uvicorn/middleware/wsgi.py"}]}