aiortc / aioquic

QUIC and HTTP/3 implementation in Python

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

event.stream_ended was not True when receving response after HEAD request

whitehatboxer opened this issue · comments

commented

I used examples http3_client.py, and modified it to send HEAD request.

But I found it don't work, it seemed like that event.stream_ended was not True after receving entrie response from quic. So the coroutine was hang.

My h3 client class looks like as below:

class H3Model(QuicConnectionProtocol):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)

        self.h3_agent = H3Connection(self._quic)
        self.request_streams = {}
        self.stream_waiters = {}
    
    def quic_event_received(self, event: QuicEvent):
        events = self.h3_agent.handle_event(event)
        for event in events:
            self.handle_h3_event(event)
    
    def handle_h3_event(self, event: H3Event):
        if isinstance(event, (DataReceived, HeadersReceived)):
            stream_id = event.stream_id
            if stream_id in self.request_streams:
                self.request_streams[stream_id].append(event)
            
            if event.stream_ended:
                waiter = self.stream_waiters.pop(stream_id)
                waiter.set_result(self.request_streams.pop(stream_id))
    
    async def request(
        self,
        uri: str,
        method: str,
        headers: dict,
        data: str | None = None,
        json: dict | None = None
    ):
        h3_headers = [
            (b":method", method.encode()),
            (b":scheme", b"https"),
            (b":path", uri.encode()),
        ]
        if json:
            h3_headers.append((b"content-type", b"application/json"))
        for k, v in headers.items():
            h3_headers.append((k.lower().encode(), v.encode()))

        stream_id = self._quic.get_next_available_stream_id()
        self.h3_agent.send_headers(stream_id, headers=h3_headers, end_stream=not data and not json)

        if json:
            self.h3_agent.send_data(stream_id, data=jsonlib.dumps(json).encode(), end_stream=True)
        elif data:
            self.h3_agent.send_data(stream_id, data=data.encode(), end_stream=True)
        
        waiter = self._loop.create_future()
        self.stream_waiters[stream_id] = waiter
        self.request_streams[stream_id] = deque()

        self.transmit()

        http_events = await asyncio.shield(waiter)

        # compose response
        status = None
        headers = {}
        body = b""
        for event in http_events:
            if isinstance(event, HeadersReceived):
                for k, v in event.headers:
                    if k == b":status":
                        status = int(v)
                    else:
                        headers[k.decode()] = v.decode()
            elif isinstance(event, DataReceived):
                body += event.data
        
        resp = Response(
            status_code=status,
            headers=headers,
            content=body
        )
        return resp

I have to change handle_h3_event(self, event: H3Event) to work through it, the modified version looks like:

def handle_h3_event(self, event: H3Event):
    if isinstance(event, (DataReceived, HeadersReceived)):
        stream_id = event.stream_id
        if stream_id in self.request_streams:
            self.request_streams[stream_id].append(event)
        
        if event.stream_ended or self.head_recorders[stream_id]:
            waiter = self.stream_waiters.pop(stream_id)
            waiter.set_result(self.request_streams.pop(stream_id))
            self.head_recorders.pop(stream_id)

I wonder whether there was some wrong code in my file or other reason?

I haven't been able to reproduce this in local testing. If I have a client send a HEAD instead of a GET, it always get a response that ends the stream, and my client sees that in the events that it gets from aioquic. What server are you talking to?