Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
whitehatboxer opened this issue Jun 5, 2024 · 2 comments
Labels
cannot reproduce The issue cannot be reproduced

Comments

@whitehatboxer
Copy link

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?

@rthalley
Copy link
Contributor

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?

@rthalley rthalley added the cannot reproduce The issue cannot be reproduced label Jun 15, 2024
@whitehatboxer
Copy link
Author

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?

I used Nginx 1.26.0 as my test server.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cannot reproduce The issue cannot be reproduced
Projects
None yet
Development

No branches or pull requests

2 participants