def handle_event(self, event: QuicEvent) -> List[H3Event]: http_events: List[H3Event] = [] if isinstance(event, StreamDataReceived) and (event.stream_id % 4) == 0: data = self._buffer.pop(event.stream_id, b"") + event.data if not self._headers_received.get(event.stream_id, False): if self._is_client: http_events.append( HeadersReceived(headers=[], stream_ended=False, stream_id=event.stream_id)) elif data.endswith(b"\r\n") or event.end_stream: method, path = data.rstrip().split(b" ", 1) http_events.append( HeadersReceived( headers=[(b":method", method), (b":path", path)], stream_ended=False, stream_id=event.stream_id, )) data = b"" else: # incomplete request, stash the data self._buffer[event.stream_id] = data return http_events self._headers_received[event.stream_id] = True http_events.append( DataReceived(data=data, stream_ended=event.end_stream, stream_id=event.stream_id)) return http_events
def handle_event(self, event: QuicEvent) -> List[H3Event]: http_events: List[H3Event] = [] if isinstance(event, StreamDataReceived) and (event.stream_id % 4) == 0: data = event.data if not self._headers_received.get(event.stream_id, False): if self._is_client: http_events.append( HeadersReceived( headers=[], stream_ended=False, stream_id=event.stream_id ) ) else: method, path = data.rstrip().split(b" ", 1) http_events.append( HeadersReceived( headers=[(b":method", method), (b":path", path)], stream_ended=False, stream_id=event.stream_id, ) ) data = b"" self._headers_received[event.stream_id] = True http_events.append( DataReceived( data=data, stream_ended=event.end_stream, stream_id=event.stream_id ) ) return http_events
def test_blocked_stream(self): quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) h3_client = H3Connection(quic_client) h3_client.handle_event( StreamDataReceived( stream_id=3, data=binascii.unhexlify( "0004170150000680020000074064091040bcc0000000faceb00c"), end_stream=False, )) h3_client.handle_event( StreamDataReceived(stream_id=7, data=b"\x02", end_stream=False)) h3_client.handle_event( StreamDataReceived(stream_id=11, data=b"\x03", end_stream=False)) h3_client.handle_event( StreamDataReceived(stream_id=0, data=binascii.unhexlify("01040280d910"), end_stream=False)) h3_client.handle_event( StreamDataReceived( stream_id=0, data=binascii.unhexlify( "00408d796f752072656163686564206d766673742e6e65742c20726561636820" "746865202f6563686f20656e64706f696e7420666f7220616e206563686f2072" "6573706f6e7365207175657279202f3c6e756d6265723e20656e64706f696e74" "7320666f722061207661726961626c652073697a6520726573706f6e73652077" "6974682072616e646f6d206279746573"), end_stream=True, )) self.assertEqual( h3_client.handle_event( StreamDataReceived( stream_id=7, data=binascii.unhexlify( "3fe101c696d07abe941094cb6d0a08017d403971966e32ca98b46f" ), end_stream=False, )), [ HeadersReceived( headers=[ (b":status", b"200"), (b"date", b"Mon, 22 Jul 2019 06:33:33 GMT"), ], stream_id=0, stream_ended=False, ), DataReceived( data= (b"you reached mvfst.net, reach the /echo endpoint for an " b"echo response query /<number> endpoints for a variable " b"size response with random bytes"), stream_id=0, stream_ended=True, ), ], )
def test_request_with_server_push_max_push_id(self): with h3_client_and_server() 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 for i in range(0, 8): h3_server.send_push_promise( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", "/{}.css".format(i).encode("ascii")), ], ) # send one too many with self.assertRaises(NoAvailablePushIDError): 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"/8.css"), ], )
async def send(self, message: Dict) -> None: print("In example server :class HttpRequestHandler send") if message["type"] == "http.response.start": self.connection.send_headers( stream_id=self.stream_id, headers=[ (b":status", str(message["status"]).encode()), (b"server", SERVER_NAME.encode()), (b"date", formatdate(time.time(), usegmt=True).encode()), ] + [(k, v) for k, v in message["headers"]], ) elif message["type"] == "http.response.body": self.connection.send_data( stream_id=self.stream_id, data=message.get("body", b""), end_stream=not message.get("more_body", False), ) elif message["type"] == "http.response.push" and isinstance( self.connection, H3Connection): request_headers = [ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", self.authority), (b":path", message["path"].encode()), ] + [(k, v) for k, v in message["headers"]] # send push promise print( "In example server :class HttpRequestHandler send push promise" ) try: push_stream_id = self.connection.send_push_promise( stream_id=self.stream_id, headers=request_headers) except NoAvailablePushIDError: return # fake request print("In example server :class HttpRequestHandler fake request") cast(HttpServerProtocol, self.protocol).http_event_received( HeadersReceived(headers=request_headers, stream_ended=True, stream_id=push_stream_id)) self.transmit()
async def _create_server_push(self, stream_id: int, path: bytes, headers: List[Tuple[bytes, bytes]]) -> None: request_headers = [(b":method", b"GET"), (b":path", path)] request_headers.extend(headers) request_headers.extend(self.config.response_headers("h3")) try: push_stream_id = self.connection.send_push_promise( stream_id=stream_id, headers=request_headers) except NoAvailablePushIDError: # Client does not accept push promises or we are trying to # push on a push promises request. pass else: event = HeadersReceived(stream_id=push_stream_id, stream_ended=True, headers=request_headers) await self._create_stream(event) await self.streams[event.stream_id ].handle(EndBody(stream_id=event.stream_id))
async def send(self, message: Dict): if message["type"] == "http.response.start": self.connection.send_headers( stream_id=self.stream_id, headers=[ (b":status", str(message["status"]).encode("ascii")), (b"server", b"aioquic"), (b"date", formatdate(time.time(), usegmt=True).encode()), ] + [(k, v) for k, v in message["headers"]], ) elif message["type"] == "http.response.body": self.connection.send_data( stream_id=self.stream_id, data=message.get("body", b""), end_stream=not message.get("more_body", False), ) elif message["type"] == "http.response.push" and isinstance( self.connection, H3Connection): request_headers = [ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", self.authority), (b":path", message["path"].encode("utf8")), ] + [(k, v) for k, v in message["headers"]] # send push promise try: push_stream_id = self.connection.send_push_promise( stream_id=self.stream_id, headers=request_headers) except NoAvailablePushIDError: return # fake request self.protocol.http_event_received( HeadersReceived(headers=request_headers, stream_ended=True, stream_id=push_stream_id)) self.transmit()
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 _make_request(self, h3_client, h3_server): quic_client = h3_client._quic quic_server = h3_server._quic # 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"", 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"", stream_id=stream_id, stream_ended=True), ], ) # send response h3_server.send_headers( stream_id=stream_id, headers=[ (b":status", b"200"), (b"content-type", b"text/html; charset=utf-8"), (b"x-foo", b"server"), ], ) h3_server.send_data( stream_id=stream_id, data=b"<html><body>hello</body></html>", end_stream=True, ) # receive response events = h3_transfer(quic_server, h3_client) self.assertEqual( events, [ HeadersReceived( headers=[ (b":status", b"200"), (b"content-type", b"text/html; charset=utf-8"), (b"x-foo", b"server"), ], stream_id=stream_id, stream_ended=False, ), DataReceived( data=b"<html><body>hello</body></html>", stream_id=stream_id, stream_ended=True, ), ], )
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 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), ], )
def test_request_headers_only(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"HEAD"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), (b"x-foo", b"client"), ], end_stream=True, ) # receive request events = h3_transfer(quic_client, h3_server) self.assertEqual( events, [ HeadersReceived( headers=[ (b":method", b"HEAD"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), (b"x-foo", b"client"), ], stream_id=stream_id, stream_ended=True, ) ], ) # send response h3_server.send_headers( stream_id=stream_id, headers=[ (b":status", b"200"), (b"content-type", b"text/html; charset=utf-8"), (b"x-foo", b"server"), ], end_stream=True, ) # receive response events = h3_transfer(quic_server, h3_client) self.assertEqual( events, [ HeadersReceived( headers=[ (b":status", b"200"), (b"content-type", b"text/html; charset=utf-8"), (b"x-foo", b"server"), ], stream_id=stream_id, stream_ended=True, ) ], )
def test_request_with_trailers(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 with trailers 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=False, ) h3_client.send_headers( stream_id=stream_id, headers=[(b"x-some-trailer", b"foo")], 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=False, ), HeadersReceived( headers=[(b"x-some-trailer", b"foo")], stream_id=stream_id, stream_ended=True, ), ], ) # 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=False, ) h3_server.send_headers( stream_id=stream_id, headers=[(b"x-some-trailer", b"bar")], end_stream=True, ) # receive response events = h3_transfer(quic_server, h3_client) self.assertEqual( events, [ 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=False, ), HeadersReceived( headers=[(b"x-some-trailer", b"bar")], stream_id=stream_id, stream_ended=True, ), ], )
def _make_session(self, h3_client, h3_server): quic_client = h3_client._quic quic_server = h3_server._quic # send request stream_id = quic_client.get_next_available_stream_id() h3_client.send_headers( stream_id=stream_id, headers=[ (b":method", b"CONNECT"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), (b":protocol", b"webtransport"), ], ) # receive request events = h3_transfer(quic_client, h3_server) self.assertEqual( events, [ HeadersReceived( headers=[ (b":method", b"CONNECT"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), (b":protocol", b"webtransport"), ], stream_id=stream_id, stream_ended=False, push_id=None, ) ], ) # send response h3_server.send_headers( stream_id=stream_id, headers=[ (b":status", b"200"), ], ) # receive response events = h3_transfer(quic_server, h3_client) self.assertEqual( events, [ HeadersReceived( headers=[ (b":status", b"200"), ], stream_id=stream_id, stream_ended=False, ), ], ) return stream_id