def test_request_with_server_push(self): with client_and_server( client_options={"alpn_protocols": H3_ALPN}, server_options={"alpn_protocols": H3_ALPN}, ) as (quic_client, quic_server): h3_client = H3Connection(quic_client) h3_server = H3Connection(quic_server) # send request stream_id = quic_client.get_next_available_stream_id() h3_client.send_headers( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), ], end_stream=True, ) # receive request events = h3_transfer(quic_client, h3_server) self.assertEqual( events, [ HeadersReceived( headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), ], stream_id=stream_id, stream_ended=True, ) ], ) # send push promises push_stream_id_css = h3_server.send_push_promise( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/app.css"), ], ) self.assertEqual(push_stream_id_css, 15) push_stream_id_js = h3_server.send_push_promise( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/app.js"), ], ) self.assertEqual(push_stream_id_js, 19) # send response h3_server.send_headers( stream_id=stream_id, headers=[ (b":status", b"200"), (b"content-type", b"text/html; charset=utf-8"), ], end_stream=False, ) h3_server.send_data( stream_id=stream_id, data=b"<html><body>hello</body></html>", end_stream=True, ) # fulfill push promises h3_server.send_headers( stream_id=push_stream_id_css, headers=[(b":status", b"200"), (b"content-type", b"text/css")], end_stream=False, ) h3_server.send_data( stream_id=push_stream_id_css, data=b"body { color: pink }", end_stream=True, ) h3_server.send_headers( stream_id=push_stream_id_js, headers=[ (b":status", b"200"), (b"content-type", b"application/javascript"), ], end_stream=False, ) h3_server.send_data(stream_id=push_stream_id_js, data=b"alert('howdee');", end_stream=True) # receive push promises, response and push responses events = h3_transfer(quic_server, h3_client) self.assertEqual( events, [ PushPromiseReceived( headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/app.css"), ], push_id=0, stream_id=stream_id, ), PushPromiseReceived( headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/app.js"), ], push_id=1, stream_id=stream_id, ), HeadersReceived( headers=[ (b":status", b"200"), (b"content-type", b"text/html; charset=utf-8"), ], stream_id=stream_id, stream_ended=False, ), DataReceived( data=b"<html><body>hello</body></html>", stream_id=stream_id, stream_ended=True, ), HeadersReceived( headers=[(b":status", b"200"), (b"content-type", b"text/css")], push_id=0, stream_id=push_stream_id_css, stream_ended=False, ), DataReceived( data=b"body { color: pink }", push_id=0, stream_id=push_stream_id_css, stream_ended=True, ), HeadersReceived( headers=[ (b":status", b"200"), (b"content-type", b"application/javascript"), ], push_id=1, stream_id=push_stream_id_js, stream_ended=False, ), DataReceived( data=b"alert('howdee');", push_id=1, stream_id=push_stream_id_js, stream_ended=True, ), ], )
def _handle_request_or_push_frame( self, frame_type: int, frame_data: Optional[bytes], stream: H3Stream, stream_ended: bool, ) -> List[H3Event]: """ Handle a frame received on a request or push stream. """ http_events: List[H3Event] = [] if frame_type == FrameType.DATA: # check DATA frame is allowed if stream.headers_recv_state != HeadersState.AFTER_HEADERS: raise FrameUnexpected( "DATA frame is not allowed in this state") if stream_ended or frame_data: http_events.append( DataReceived( data=frame_data, push_id=stream.push_id, stream_ended=stream_ended, stream_id=stream.stream_id, )) elif frame_type == FrameType.HEADERS: # check HEADERS frame is allowed if stream.headers_recv_state == HeadersState.AFTER_TRAILERS: raise FrameUnexpected( "HEADERS frame is not allowed in this state") # try to decode HEADERS, may raise pylsqpack.StreamBlocked headers = self._decode_headers(stream.stream_id, frame_data) # log frame if self._quic_logger is not None: self._quic_logger.log_event( category="http", event="frame_parsed", data=qlog_encode_headers_frame( byte_length=stream.blocked_frame_size if frame_data is None else len(frame_data), headers=headers, stream_id=stream.stream_id, ), ) # update state and emit headers if stream.headers_recv_state == HeadersState.INITIAL: stream.headers_recv_state = HeadersState.AFTER_HEADERS else: stream.headers_recv_state = HeadersState.AFTER_TRAILERS http_events.append( HeadersReceived( headers=headers, push_id=stream.push_id, stream_id=stream.stream_id, stream_ended=stream_ended, )) elif stream.frame_type == FrameType.PUSH_PROMISE and stream.push_id is None: if not self._is_client: raise FrameUnexpected("Clients must not send PUSH_PROMISE") frame_buf = Buffer(data=frame_data) push_id = frame_buf.pull_uint_var() headers = self._decode_headers(stream.stream_id, frame_data[frame_buf.tell():]) # log frame if self._quic_logger is not None: self._quic_logger.log_event( category="http", event="frame_parsed", data=qlog_encode_push_promise_frame( byte_length=len(frame_data), headers=headers, push_id=push_id, stream_id=stream.stream_id, ), ) # emit event http_events.append( PushPromiseReceived(headers=headers, push_id=push_id, stream_id=stream.stream_id)) elif frame_type in ( FrameType.PRIORITY, FrameType.CANCEL_PUSH, FrameType.SETTINGS, FrameType.PUSH_PROMISE, FrameType.GOAWAY, FrameType.MAX_PUSH_ID, FrameType.DUPLICATE_PUSH, ): raise FrameUnexpected( "Invalid frame type on request stream" if stream. push_id is None else "Invalid frame type on push stream") return http_events
def test_request_fragmented_frame(self): quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) quic_server = FakeQuicConnection(configuration=QuicConfiguration( is_client=False)) h3_client = H3Connection(quic_client) h3_server = H3Connection(quic_server) # send request stream_id = quic_client.get_next_available_stream_id() h3_client.send_headers( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), (b"x-foo", b"client"), ], ) h3_client.send_data(stream_id=stream_id, data=b"hello", end_stream=True) # receive request events = h3_transfer(quic_client, h3_server) self.assertEqual( events, [ HeadersReceived( headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), (b"x-foo", b"client"), ], stream_id=stream_id, stream_ended=False, ), DataReceived(data=b"h", stream_id=0, stream_ended=False), DataReceived(data=b"e", stream_id=0, stream_ended=False), DataReceived(data=b"l", stream_id=0, stream_ended=False), DataReceived(data=b"l", stream_id=0, stream_ended=False), DataReceived(data=b"o", stream_id=0, stream_ended=False), DataReceived(data=b"", stream_id=0, stream_ended=True), ], ) # send push promise push_stream_id = h3_server.send_push_promise( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/app.txt"), ], ) self.assertEqual(push_stream_id, 15) # send response h3_server.send_headers( stream_id=stream_id, headers=[ (b":status", b"200"), (b"content-type", b"text/html; charset=utf-8"), ], end_stream=False, ) h3_server.send_data(stream_id=stream_id, data=b"html", end_stream=True) # fulfill push promise h3_server.send_headers( stream_id=push_stream_id, headers=[(b":status", b"200"), (b"content-type", b"text/plain")], end_stream=False, ) h3_server.send_data(stream_id=push_stream_id, data=b"text", end_stream=True) # receive push promise / reponse events = h3_transfer(quic_server, h3_client) self.assertEqual( events, [ PushPromiseReceived( headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/app.txt"), ], push_id=0, stream_id=stream_id, ), HeadersReceived( headers=[ (b":status", b"200"), (b"content-type", b"text/html; charset=utf-8"), ], stream_id=0, stream_ended=False, ), DataReceived(data=b"h", stream_id=0, stream_ended=False), DataReceived(data=b"t", stream_id=0, stream_ended=False), DataReceived(data=b"m", stream_id=0, stream_ended=False), DataReceived(data=b"l", stream_id=0, stream_ended=False), DataReceived(data=b"", stream_id=0, stream_ended=True), HeadersReceived( headers=[(b":status", b"200"), (b"content-type", b"text/plain")], stream_id=15, stream_ended=False, push_id=0, ), DataReceived( data=b"t", stream_id=15, stream_ended=False, push_id=0), DataReceived( data=b"e", stream_id=15, stream_ended=False, push_id=0), DataReceived( data=b"x", stream_id=15, stream_ended=False, push_id=0), DataReceived( data=b"t", stream_id=15, stream_ended=False, push_id=0), DataReceived( data=b"", stream_id=15, stream_ended=True, push_id=0), ], )