1010from collections .abc import AsyncIterator , Iterator
1111from contextlib import ExitStack , closing
1212from tempfile import NamedTemporaryFile
13- from typing import TYPE_CHECKING , Literal , TypeVar
13+ from typing import TYPE_CHECKING , Literal , TypeVar , get_args
1414
1515from _util import create_standard_streams
1616from gen .connectrpc .conformance .v1 .config_pb2 import Code as ConformanceCode
@@ -415,22 +415,64 @@ def _server_env(request: ServerCompatRequest) -> dict[str, str]:
415415
416416
417417async def _tee_to_stderr (stream : asyncio .StreamReader ) -> AsyncIterator [bytes ]:
418- try :
419- while True :
420- line = await stream .readline ()
421- if not line :
422- break
423- print (line .decode ("utf-8" ), end = "" , file = sys .stderr ) # noqa: T201
424- yield line
425- except asyncio .CancelledError :
426- pass
418+ while True :
419+ line = await stream .readline ()
420+ if not line :
421+ break
422+ print (line .decode ("utf-8" ), end = "" , file = sys .stderr ) # noqa: T201
423+ yield line
427424
428425
429426async def _consume_log (stream : AsyncIterator [bytes ]) -> None :
430427 async for _ in stream :
431428 pass
432429
433430
431+ async def serve_daphne (
432+ request : ServerCompatRequest ,
433+ certfile : str | None ,
434+ keyfile : str | None ,
435+ cafile : str | None ,
436+ port_future : asyncio .Future [int ],
437+ ):
438+ args = []
439+ ssl_endpoint_parts = []
440+ if certfile :
441+ ssl_endpoint_parts .append (f"certKey={ certfile } " )
442+ if keyfile :
443+ ssl_endpoint_parts .append (f"privateKey={ keyfile } " )
444+ if cafile :
445+ ssl_endpoint_parts .append (f"extraCertChain={ cafile } " )
446+ if ssl_endpoint_parts :
447+ args .append ("-e" )
448+ args .append (f"ssl:port=0:{ ':' .join (ssl_endpoint_parts )} " )
449+ else :
450+ args .append ("-p=0" )
451+
452+ args .append ("server:asgi_app" )
453+
454+ proc = await asyncio .create_subprocess_exec (
455+ "daphne" ,
456+ * args ,
457+ stderr = asyncio .subprocess .STDOUT ,
458+ stdout = asyncio .subprocess .PIPE ,
459+ env = _server_env (request ),
460+ )
461+ stdout = proc .stdout
462+ assert stdout is not None
463+ stdout = _tee_to_stderr (stdout )
464+ try :
465+ async for line in stdout :
466+ if b"Listening on TCP address" in line :
467+ port = line .decode ("utf-8" ).strip ().rsplit (":" , 1 )[1 ]
468+ port_future .set_result (int (port ))
469+ break
470+ await _consume_log (stdout )
471+ except asyncio .CancelledError :
472+ proc .terminate ()
473+ await proc .wait ()
474+
475+
434476async def serve_granian (
435477 request : ServerCompatRequest ,
436478 mode : Literal ["sync" , "async" ],
@@ -465,7 +507,6 @@ async def serve_granian(
465507 * args ,
466508 stderr = asyncio .subprocess .STDOUT ,
467509 stdout = asyncio .subprocess .PIPE ,
468- limit = 1024 ,
469510 env = _server_env (request ),
470511 )
471512 stdout = proc .stdout
@@ -506,7 +547,6 @@ async def serve_gunicorn(
506547 * args ,
507548 stderr = asyncio .subprocess .STDOUT ,
508549 stdout = asyncio .subprocess .PIPE ,
509- limit = 1024 ,
510550 env = _server_env (request ),
511551 )
512552 stdout = proc .stdout
@@ -551,7 +591,6 @@ async def serve_hypercorn(
551591 * args ,
552592 stderr = asyncio .subprocess .STDOUT ,
553593 stdout = asyncio .subprocess .PIPE ,
554- limit = 1024 ,
555594 env = _server_env (request ),
556595 )
557596 stdout = proc .stdout
@@ -592,7 +631,6 @@ async def serve_uvicorn(
592631 * args ,
593632 stderr = asyncio .subprocess .STDOUT ,
594633 stdout = asyncio .subprocess .PIPE ,
595- limit = 1024 ,
596634 env = _server_env (request ),
597635 )
598636 stdout = proc .stdout
@@ -617,17 +655,18 @@ def _find_free_port():
617655 return s .getsockname ()[1 ]
618656
619657
658+ Server = Literal ["daphne" , "granian" , "gunicorn" , "hypercorn" , "uvicorn" ]
659+
660+
620661class Args (argparse .Namespace ):
621662 mode : Literal ["sync" , "async" ]
622- server : Literal [ "granian" , "hypercorn" , "uvicorn" ]
663+ server : Server
623664
624665
625666async def main () -> None :
626667 parser = argparse .ArgumentParser (description = "Conformance server" )
627668 parser .add_argument ("--mode" , choices = ["sync" , "async" ])
628- parser .add_argument (
629- "--server" , choices = ["granian" , "gunicorn" , "hypercorn" , "uvicorn" ]
630- )
669+ parser .add_argument ("--server" , choices = get_args (Server ))
631670 args = parser .parse_args (namespace = Args ())
632671
633672 stdin , stdout = await create_standard_streams ()
@@ -663,6 +702,13 @@ async def main() -> None:
663702 with cleanup :
664703 port_future : asyncio .Future [int ] = asyncio .get_event_loop ().create_future ()
665704 match args .server :
705+ case "daphne" :
706+ if args .mode == "sync" :
707+ msg = "daphne does not support sync mode"
708+ raise ValueError (msg )
709+ serve_task = asyncio .create_task (
710+ serve_daphne (request , certfile , keyfile , cafile , port_future )
711+ )
666712 case "granian" :
667713 serve_task = asyncio .create_task (
668714 serve_granian (
0 commit comments