hypercorn
hypercorn copied to clipboard
h2/h3 trailing header support. Fixes #147
This is an initial implementation of trailing header suppport for http2/http3. This fixes #147, in theory.
Here's the thing though...I have no clue what I am doing. I would like some tips on how to do the following:
- Ensure that I am following the asgi spec properly
- Ensure that I am following the HTTP/2 HTTP/3 spec properly
I assume the underlying h2 and h3 libraries take care of (2) for me, but I'm not sure.
@pgjones can you provide feedback/comments on my stuff so far? Don't want to get to far into this if I'm going in the wrong direction. Thanks.
Thanks for this, I've adapted it a bit and merged in d8de5f28adc99b1bc9b47a6c557fe25972ab966f
I did a quick test with this implementation with python-grpc-client. The asgi app just works like a grpc server, however, the client is not happy with the server and lost the connection half way. It seems that the server send trailers after EndBody been send, which closed the connection. maybe we should avoid this. I'll dig deeper tomorrow.
Thanks both, hopefully fixed with d16b50398aaedc509f83fe2b5c6c83a0ffbfc991
Emmmm... That didn't work either. Here is my asgi app which pretend to be a grpc server(A simple grpc echo server)
async def grpcapp(scope, receive, send):
if scope["type"] == "http":
body = (await receive())["body"]
await send(
{
"type": "http.response.start",
"status": 200,
"headers": [
(b"Content-Type", b"application/grpc+proto"),
(b"Cache-Control", b"no-cache"),
(b"Trailer", b"grpc-status")
],
"trailers": True,
}
)
await send({"type": "http.response.body", "body": body})
await send({"type": "http.response.trailers", "headers": [(b"grpc-status", b"0")]})
A real grpc client would expect the same response type and value when talk to the server, but with grpclib in python, I just got
Traceback (most recent call last):
File "D:\conda\envs\py310\lib\site-packages\grpclib\client.py", line 468, in recv_trailing_metadata
trailers = await self._stream.recv_trailers()
File "D:\conda\envs\py310\lib\site-packages\grpclib\protocol.py", line 351, in recv_trailers
await self.trailers_received.wait()
File "D:\conda\envs\py310\lib\asyncio\locks.py", line 214, in wait
await fut
asyncio.exceptions.CancelledError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "E:\pyproject\nonecorn\src\pclient.py", line 28, in <module>
asyncio.run(main())
File "D:\conda\envs\py310\lib\asyncio\runners.py", line 44, in run
return loop.run_until_complete(main)
File "D:\conda\envs\py310\lib\asyncio\base_events.py", line 649, in run_until_complete
return future.result()
File "E:\pyproject\nonecorn\src\pclient.py", line 23, in main
reply = await greeter.SayHello(Test(id=2, data="xsndjasndsa"))
File "D:\conda\envs\py310\lib\site-packages\grpclib\client.py", line 902, in __call__
async with self.open(timeout=timeout, metadata=metadata) as stream:
File "D:\conda\envs\py310\lib\site-packages\grpclib\client.py", line 563, in __aexit__
raise exc_val
File "D:\conda\envs\py310\lib\site-packages\grpclib\client.py", line 553, in __aexit__
await self._maybe_finish()
File "D:\conda\envs\py310\lib\site-packages\grpclib\client.py", line 523, in _maybe_finish
await self.recv_trailing_metadata()
File "D:\conda\envs\py310\lib\site-packages\grpclib\client.py", line 467, in recv_trailing_metadata
with self._wrapper:
File "D:\conda\envs\py310\lib\site-packages\grpclib\utils.py", line 70, in __exit__
raise self._error
grpclib.exceptions.StreamTerminatedError: Connection lost
And here is the client side:
import asyncio
import os
import ssl
from datetime import datetime
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
from grpclib.client import Channel
from test_grpc import TestServerStub
# generated by protoc, it doens't matter since you can generate your own
from test_pb2 import Test
async def main():
async with Channel("127.0.0.1", 9001, ssl=False) as channel:
greeter = TestServerStub(channel)
reply = await greeter.SayHello(Test(id=2, data="xsndjasndsa"))
print(reply)
if __name__ == "__main__":
asyncio.run(main())
Besides, grpcurl also failed with ERROR: Code: Internal Message: server closed the stream without sending trailers