2
2
import concurrent .futures
3
3
import io
4
4
import sys
5
+ from typing import Iterable , List , Optional , Tuple
5
6
7
+ from asgiref .typing import (
8
+ ASGIReceiveCallable ,
9
+ ASGIReceiveEvent ,
10
+ ASGISendCallable ,
11
+ ASGISendEvent ,
12
+ HTTPRequestEvent ,
13
+ HTTPResponseBodyEvent ,
14
+ HTTPResponseStartEvent ,
15
+ HTTPScope ,
16
+ )
6
17
7
- def build_environ (scope , message , body ):
18
+ from uvicorn ._types import Environ , ExcInfo , StartResponse , WSGIApp
19
+
20
+
21
+ def build_environ (scope : HTTPScope , message : ASGIReceiveEvent , body : bytes ) -> Environ :
8
22
"""
9
23
Builds a scope and request message into a WSGI environ object.
10
24
"""
@@ -37,52 +51,63 @@ def build_environ(scope, message, body):
37
51
38
52
# Go through headers and make them into environ entries
39
53
for name , value in scope .get ("headers" , []):
40
- name = name .decode ("latin1" )
41
- if name == "content-length" :
54
+ name_str : str = name .decode ("latin1" )
55
+ if name_str == "content-length" :
42
56
corrected_name = "CONTENT_LENGTH"
43
- elif name == "content-type" :
57
+ elif name_str == "content-type" :
44
58
corrected_name = "CONTENT_TYPE"
45
59
else :
46
- corrected_name = "HTTP_%s" % name .upper ().replace ("-" , "_" )
60
+ corrected_name = "HTTP_%s" % name_str .upper ().replace ("-" , "_" )
47
61
# HTTPbis say only ASCII chars are allowed in headers, but we latin1
48
62
# just in case
49
- value = value .decode ("latin1" )
63
+ value_str : str = value .decode ("latin1" )
50
64
if corrected_name in environ :
51
- value = environ [corrected_name ] + "," + value
52
- environ [corrected_name ] = value
65
+ corrected_name_environ = environ [corrected_name ]
66
+ assert isinstance (corrected_name_environ , str )
67
+ value_str = corrected_name_environ + "," + value_str
68
+ environ [corrected_name ] = value_str
53
69
return environ
54
70
55
71
56
72
class WSGIMiddleware :
57
- def __init__ (self , app , workers = 10 ):
73
+ def __init__ (self , app : WSGIApp , workers : int = 10 ):
58
74
self .app = app
59
75
self .executor = concurrent .futures .ThreadPoolExecutor (max_workers = workers )
60
76
61
- async def __call__ (self , scope , receive , send ):
77
+ async def __call__ (
78
+ self , scope : HTTPScope , receive : ASGIReceiveCallable , send : ASGISendCallable
79
+ ) -> None :
62
80
assert scope ["type" ] == "http"
63
81
instance = WSGIResponder (self .app , self .executor , scope )
64
82
await instance (receive , send )
65
83
66
84
67
85
class WSGIResponder :
68
- def __init__ (self , app , executor , scope ):
86
+ def __init__ (
87
+ self ,
88
+ app : WSGIApp ,
89
+ executor : concurrent .futures .ThreadPoolExecutor ,
90
+ scope : HTTPScope ,
91
+ ):
69
92
self .app = app
70
93
self .executor = executor
71
94
self .scope = scope
72
95
self .status = None
73
96
self .response_headers = None
74
97
self .send_event = asyncio .Event ()
75
- self .send_queue = []
76
- self .loop = None
98
+ self .send_queue : List [ Optional [ ASGISendEvent ]] = []
99
+ self .loop : asyncio . AbstractEventLoop = asyncio . get_event_loop ()
77
100
self .response_started = False
78
- self .exc_info = None
101
+ self .exc_info : Optional [ ExcInfo ] = None
79
102
80
- async def __call__ (self , receive , send ):
81
- message = await receive ()
103
+ async def __call__ (
104
+ self , receive : ASGIReceiveCallable , send : ASGISendCallable
105
+ ) -> None :
106
+ message : HTTPRequestEvent = await receive () # type: ignore[assignment]
82
107
body = message .get ("body" , b"" )
83
108
more_body = message .get ("more_body" , False )
84
109
while more_body :
85
- body_message = await receive ()
110
+ body_message : HTTPRequestEvent = await receive () # type: ignore[assignment]
86
111
body += body_message .get ("body" , b"" )
87
112
more_body = body_message .get ("more_body" , False )
88
113
environ = build_environ (self .scope , message , body )
@@ -100,7 +125,7 @@ async def __call__(self, receive, send):
100
125
if self .exc_info is not None :
101
126
raise self .exc_info [0 ].with_traceback (self .exc_info [1 ], self .exc_info [2 ])
102
127
103
- async def sender (self , send ) :
128
+ async def sender (self , send : ASGISendCallable ) -> None :
104
129
while True :
105
130
if self .send_queue :
106
131
message = self .send_queue .pop (0 )
@@ -111,31 +136,43 @@ async def sender(self, send):
111
136
await self .send_event .wait ()
112
137
self .send_event .clear ()
113
138
114
- def start_response (self , status , response_headers , exc_info = None ):
139
+ def start_response (
140
+ self ,
141
+ status : str ,
142
+ response_headers : Iterable [Tuple [str , str ]],
143
+ exc_info : Optional [ExcInfo ] = None ,
144
+ ) -> None :
115
145
self .exc_info = exc_info
116
146
if not self .response_started :
117
147
self .response_started = True
118
- status_code , _ = status .split (" " , 1 )
119
- status_code = int (status_code )
148
+ status_code_str , _ = status .split (" " , 1 )
149
+ status_code = int (status_code_str )
120
150
headers = [
121
151
(name .encode ("ascii" ), value .encode ("ascii" ))
122
152
for name , value in response_headers
123
153
]
124
- self .send_queue .append (
125
- {
126
- "type" : "http.response.start" ,
127
- "status" : status_code ,
128
- "headers" : headers ,
129
- }
130
- )
154
+ http_response_start_event : HTTPResponseStartEvent = {
155
+ "type" : "http.response.start" ,
156
+ "status" : status_code ,
157
+ "headers" : headers ,
158
+ }
159
+ self .send_queue .append (http_response_start_event )
131
160
self .loop .call_soon_threadsafe (self .send_event .set )
132
161
133
- def wsgi (self , environ , start_response ):
134
- for chunk in self .app (environ , start_response ):
135
- self .send_queue .append (
136
- {"type" : "http.response.body" , "body" : chunk , "more_body" : True }
137
- )
162
+ def wsgi (self , environ : Environ , start_response : StartResponse ) -> None :
163
+ for chunk in self .app (environ , start_response ): # type: ignore
164
+ response_body : HTTPResponseBodyEvent = {
165
+ "type" : "http.response.body" ,
166
+ "body" : chunk ,
167
+ "more_body" : True ,
168
+ }
169
+ self .send_queue .append (response_body )
138
170
self .loop .call_soon_threadsafe (self .send_event .set )
139
171
140
- self .send_queue .append ({"type" : "http.response.body" , "body" : b"" })
172
+ empty_body : HTTPResponseBodyEvent = {
173
+ "type" : "http.response.body" ,
174
+ "body" : b"" ,
175
+ "more_body" : False ,
176
+ }
177
+ self .send_queue .append (empty_body )
141
178
self .loop .call_soon_threadsafe (self .send_event .set )
0 commit comments