1010
1111from hypercorn .app_wrappers import _build_environ , InvalidPathError , WSGIWrapper
1212from hypercorn .typing import ASGIReceiveEvent , ASGISendEvent , ConnectionState , HTTPScope
13-
14-
15- def echo_body ( environ : dict , start_response : Callable ) -> list [ bytes ]:
16- status = "200 OK"
17- output = environ [ "wsgi.input" ]. read ()
18- headers = [
19- ( "Content-Type" , "text/plain; charset=utf-8" ) ,
20- ( "Content-Length" , str ( len ( output ))) ,
21- ]
22- start_response ( status , headers )
23- return [ output ]
13+ from . wsgi_applications import (
14+ wsgi_app_echo_body ,
15+ wsgi_app_generator ,
16+ wsgi_app_generator_delayed_start_response ,
17+ wsgi_app_generator_multiple_start_response_after_body ,
18+ wsgi_app_generator_no_body ,
19+ wsgi_app_multiple_start_response_no_exc_info ,
20+ wsgi_app_no_body ,
21+ wsgi_app_no_start_response ,
22+ wsgi_app_simple ,
23+ )
2424
2525
2626@pytest .mark .trio
2727async def test_wsgi_trio () -> None :
28- app = WSGIWrapper (echo_body , 2 ** 16 )
28+ app = WSGIWrapper (wsgi_app_echo_body , 2 ** 16 )
2929 scope : HTTPScope = {
3030 "http_version" : "1.1" ,
3131 "asgi" : {},
@@ -52,12 +52,12 @@ async def _send(message: ASGISendEvent) -> None:
5252
5353 await app (scope , receive_channel .receive , _send , trio .to_thread .run_sync , trio .from_thread .run )
5454 assert messages == [
55+ {"body" : bytearray (b"" ), "type" : "http.response.body" , "more_body" : True },
5556 {
5657 "headers" : [(b"content-type" , b"text/plain; charset=utf-8" ), (b"content-length" , b"0" )],
5758 "status" : 200 ,
5859 "type" : "http.response.start" ,
5960 },
60- {"body" : bytearray (b"" ), "type" : "http.response.body" , "more_body" : True },
6161 {"body" : bytearray (b"" ), "type" : "http.response.body" , "more_body" : False },
6262 ]
6363
@@ -83,7 +83,7 @@ def _call_soon(func: Callable, *args: Any) -> Any:
8383
8484@pytest .mark .asyncio
8585async def test_wsgi_asyncio () -> None :
86- app = WSGIWrapper (echo_body , 2 ** 16 )
86+ app = WSGIWrapper (wsgi_app_echo_body , 2 ** 16 )
8787 scope : HTTPScope = {
8888 "http_version" : "1.1" ,
8989 "asgi" : {},
@@ -100,21 +100,24 @@ async def test_wsgi_asyncio() -> None:
100100 "extensions" : {},
101101 "state" : ConnectionState ({}),
102102 }
103- messages = await _run_app (app , scope )
103+ messages = await _run_app (app , scope , b"Hello, world!" )
104104 assert messages == [
105105 {
106- "headers" : [(b"content-type" , b"text/plain; charset=utf-8" ), (b"content-length" , b"0" )],
106+ "headers" : [
107+ (b"content-type" , b"text/plain; charset=utf-8" ),
108+ (b"content-length" , b"13" ),
109+ ],
107110 "status" : 200 ,
108111 "type" : "http.response.start" ,
109112 },
110- {"body" : bytearray ( b"" ) , "type" : "http.response.body" , "more_body" : True },
111- {"body" : bytearray ( b"" ) , "type" : "http.response.body" , "more_body" : False },
113+ {"body" : b"Hello, world!" , "type" : "http.response.body" , "more_body" : True },
114+ {"body" : b"" , "type" : "http.response.body" , "more_body" : False },
112115 ]
113116
114117
115118@pytest .mark .asyncio
116119async def test_max_body_size () -> None :
117- app = WSGIWrapper (echo_body , 4 )
120+ app = WSGIWrapper (wsgi_app_echo_body , 4 )
118121 scope : HTTPScope = {
119122 "http_version" : "1.1" ,
120123 "asgi" : {},
@@ -138,13 +141,9 @@ async def test_max_body_size() -> None:
138141 ]
139142
140143
141- def no_start_response (environ : dict , start_response : Callable ) -> list [bytes ]:
142- return [b"result" ]
143-
144-
145144@pytest .mark .asyncio
146145async def test_no_start_response () -> None :
147- app = WSGIWrapper (no_start_response , 2 ** 16 )
146+ app = WSGIWrapper (wsgi_app_no_start_response , 2 ** 16 )
148147 scope : HTTPScope = {
149148 "http_version" : "1.1" ,
150149 "asgi" : {},
@@ -206,3 +205,151 @@ def test_build_environ_root_path() -> None:
206205 }
207206 with pytest .raises (InvalidPathError ):
208207 _build_environ (scope , b"" )
208+
209+
210+ @pytest .mark .asyncio
211+ @pytest .mark .parametrize ("wsgi_app" , [wsgi_app_simple , wsgi_app_generator ])
212+ async def test_wsgi_protocol (wsgi_app : Callable ) -> None :
213+ app = WSGIWrapper (wsgi_app , 2 ** 16 )
214+ scope : HTTPScope = {
215+ "http_version" : "1.1" ,
216+ "asgi" : {},
217+ "method" : "GET" ,
218+ "headers" : [],
219+ "path" : "/" ,
220+ "root_path" : "/" ,
221+ "query_string" : b"a=b" ,
222+ "raw_path" : b"/" ,
223+ "scheme" : "http" ,
224+ "type" : "http" ,
225+ "client" : ("localhost" , 80 ),
226+ "server" : None ,
227+ "extensions" : {},
228+ "state" : ConnectionState ({}),
229+ }
230+
231+ messages = await _run_app (app , scope )
232+ assert messages == [
233+ {
234+ "headers" : [(b"x-test-header" , b"Test-Value" )],
235+ "status" : 200 ,
236+ "type" : "http.response.start" ,
237+ },
238+ {"body" : b"Hello, " , "type" : "http.response.body" , "more_body" : True },
239+ {"body" : b"world!" , "type" : "http.response.body" , "more_body" : True },
240+ {"body" : b"" , "type" : "http.response.body" , "more_body" : False },
241+ ]
242+
243+
244+ @pytest .mark .asyncio
245+ @pytest .mark .parametrize ("wsgi_app" , [wsgi_app_no_body , wsgi_app_generator_no_body ])
246+ async def test_wsgi_protocol_no_body (wsgi_app : Callable ) -> None :
247+ app = WSGIWrapper (wsgi_app , 2 ** 16 )
248+ scope : HTTPScope = {
249+ "http_version" : "1.1" ,
250+ "asgi" : {},
251+ "method" : "GET" ,
252+ "headers" : [],
253+ "path" : "/" ,
254+ "root_path" : "/" ,
255+ "query_string" : b"a=b" ,
256+ "raw_path" : b"/" ,
257+ "scheme" : "http" ,
258+ "type" : "http" ,
259+ "client" : ("localhost" , 80 ),
260+ "server" : None ,
261+ "extensions" : {},
262+ "state" : ConnectionState ({}),
263+ }
264+
265+ messages = await _run_app (app , scope )
266+ assert messages == [
267+ {
268+ "headers" : [(b"x-test-header" , b"Test-Value" )],
269+ "status" : 200 ,
270+ "type" : "http.response.start" ,
271+ },
272+ {"body" : b"" , "type" : "http.response.body" , "more_body" : False },
273+ ]
274+
275+
276+ @pytest .mark .asyncio
277+ async def test_wsgi_protocol_overwrite_start_response () -> None :
278+ app = WSGIWrapper (wsgi_app_generator_delayed_start_response , 2 ** 16 )
279+ scope : HTTPScope = {
280+ "http_version" : "1.1" ,
281+ "asgi" : {},
282+ "method" : "GET" ,
283+ "headers" : [],
284+ "path" : "/" ,
285+ "root_path" : "/" ,
286+ "query_string" : b"a=b" ,
287+ "raw_path" : b"/" ,
288+ "scheme" : "http" ,
289+ "type" : "http" ,
290+ "client" : ("localhost" , 80 ),
291+ "server" : None ,
292+ "extensions" : {},
293+ "state" : ConnectionState ({}),
294+ }
295+
296+ messages = await _run_app (app , scope )
297+ assert messages == [
298+ {"body" : b"" , "type" : "http.response.body" , "more_body" : True },
299+ {
300+ "headers" : [(b"x-test-header" , b"New-Value" )],
301+ "status" : 500 ,
302+ "type" : "http.response.start" ,
303+ },
304+ {"body" : b"Hello, " , "type" : "http.response.body" , "more_body" : True },
305+ {"body" : b"world!" , "type" : "http.response.body" , "more_body" : True },
306+ {"body" : b"" , "type" : "http.response.body" , "more_body" : False },
307+ ]
308+
309+
310+ @pytest .mark .asyncio
311+ async def test_wsgi_protocol_multiple_start_response_no_exc_info () -> None :
312+ app = WSGIWrapper (wsgi_app_multiple_start_response_no_exc_info , 2 ** 16 )
313+ scope : HTTPScope = {
314+ "http_version" : "1.1" ,
315+ "asgi" : {},
316+ "method" : "GET" ,
317+ "headers" : [],
318+ "path" : "/" ,
319+ "root_path" : "/" ,
320+ "query_string" : b"a=b" ,
321+ "raw_path" : b"/" ,
322+ "scheme" : "http" ,
323+ "type" : "http" ,
324+ "client" : ("localhost" , 80 ),
325+ "server" : None ,
326+ "extensions" : {},
327+ "state" : ConnectionState ({}),
328+ }
329+
330+ with pytest .raises (RuntimeError ):
331+ await _run_app (app , scope )
332+
333+
334+ @pytest .mark .asyncio
335+ async def test_wsgi_protocol_multiple_start_response_after_body () -> None :
336+ app = WSGIWrapper (wsgi_app_generator_multiple_start_response_after_body , 2 ** 16 )
337+ scope : HTTPScope = {
338+ "http_version" : "1.1" ,
339+ "asgi" : {},
340+ "method" : "GET" ,
341+ "headers" : [],
342+ "path" : "/" ,
343+ "root_path" : "/" ,
344+ "query_string" : b"a=b" ,
345+ "raw_path" : b"/" ,
346+ "scheme" : "http" ,
347+ "type" : "http" ,
348+ "client" : ("localhost" , 80 ),
349+ "server" : None ,
350+ "extensions" : {},
351+ "state" : ConnectionState ({}),
352+ }
353+
354+ with pytest .raises (ValueError ):
355+ await _run_app (app , scope )
0 commit comments