def handle_one_request(self): """ This is the main HTTP/2.0 Handler. When a browser opens a connection to the server on the HTTP/2.0 port, the server enters this which will initiate the h2 connection and keep running throughout the duration of the interaction, and will read/write directly from the socket. Because there can be multiple H2 connections active at the same time, a UUID is created for each so that it is easier to tell them apart in the logs. """ config = H2Configuration(client_side=False) self.conn = H2ConnectionGuard(H2Connection(config=config)) self.close_connection = False # Generate a UUID to make it easier to distinguish different H2 connection debug messages self.uid = str(uuid.uuid4())[:8] self.logger.debug('(%s) Initiating h2 Connection' % self.uid) with self.conn as connection: # Bootstrapping WebSockets with HTTP/2 specification requires # ENABLE_CONNECT_PROTOCOL to be set in order to enable WebSocket # over HTTP/2 new_settings = dict(connection.local_settings) new_settings[SettingCodes.ENABLE_CONNECT_PROTOCOL] = 1 connection.local_settings.update(new_settings) connection.local_settings.acknowledge() connection.initiate_connection() data = connection.data_to_send() window_size = connection.remote_settings.initial_window_size self.request.sendall(data) # Dict of { stream_id: (thread, queue) } stream_queues = {} try: while not self.close_connection: data = self.request.recv(window_size) if data == '': self.logger.debug('(%s) Socket Closed' % self.uid) self.close_connection = True continue with self.conn as connection: frames = connection.receive_data(data) window_size = connection.remote_settings.initial_window_size self.logger.debug('(%s) Frames Received: ' % self.uid + str(frames)) for frame in frames: if isinstance(frame, ConnectionTerminated): self.logger.debug('(%s) Connection terminated by remote peer ' % self.uid) self.close_connection = True # Flood all the streams with connection terminated, this will cause them to stop for stream_id, (thread, queue) in stream_queues.items(): queue.put(frame) elif hasattr(frame, 'stream_id'): if frame.stream_id not in stream_queues: queue = Queue() stream_queues[frame.stream_id] = (self.start_stream_thread(frame, queue), queue) stream_queues[frame.stream_id][1].put(frame) if isinstance(frame, StreamEnded) or (hasattr(frame, "stream_ended") and frame.stream_ended): del stream_queues[frame.stream_id] except (socket.timeout, socket.error) as e: self.logger.error('(%s) Closing Connection - \n%s' % (self.uid, str(e))) if not self.close_connection: self.close_connection = True for stream_id, (thread, queue) in stream_queues.items(): queue.put(None) except Exception as e: self.logger.error('(%s) Unexpected Error - \n%s' % (self.uid, str(e))) finally: for stream_id, (thread, queue) in stream_queues.items(): thread.join()
def _initiate_connection(self): self._connection = H2Connection( H2Configuration(client_side=False, header_encoding='utf-8')) self._connection.initiate_connection() self._sock.sendall(self._connection.data_to_send())
class AsyncHTTP2Connection(AsyncHTTPTransport): READ_NUM_BYTES = 4096 CONFIG = H2Configuration(validate_inbound_headers=False) def __init__( self, socket: AsyncSocketStream, backend: AutoBackend, ssl_context: SSLContext = None, ): self.socket = socket self.ssl_context = SSLContext() if ssl_context is None else ssl_context self.backend = backend self.h2_state = h2.connection.H2Connection(config=self.CONFIG) self.sent_connection_init = False self.streams = {} # type: Dict[int, AsyncHTTP2Stream] self.events = {} # type: Dict[int, List[h2.events.Event]] self.state = ConnectionState.ACTIVE @property def init_lock(self) -> AsyncLock: # We do this lazily, to make sure backend autodetection always # runs within an async context. if not hasattr(self, "_initialization_lock"): self._initialization_lock = self.backend.create_lock() return self._initialization_lock @property def read_lock(self) -> AsyncLock: # We do this lazily, to make sure backend autodetection always # runs within an async context. if not hasattr(self, "_read_lock"): self._read_lock = self.backend.create_lock() return self._read_lock @property def streams_semaphore(self) -> AsyncSemaphore: # We do this lazily, to make sure backend autodetection always # runs within an async context. if not hasattr(self, "_streams_semaphore"): semaphore_count = self.h2_state.remote_settings.max_concurrent_streams self._streams_semaphore = self.backend.create_semaphore( semaphore_count, PoolTimeout ) return self._streams_semaphore async def start_tls( self, hostname: bytes, timeout: Dict[str, Optional[float]] = None ): pass def mark_as_ready(self): if self.state == ConnectionState.IDLE: self.state = ConnectionState.READY async def request( self, method: bytes, url: Tuple[bytes, bytes, int, bytes], headers: List[Tuple[bytes, bytes]] = None, stream: AsyncByteStream = None, timeout: Dict[str, Optional[float]] = None, ) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], AsyncByteStream]: timeout = {} if timeout is None else timeout await self.streams_semaphore.acquire() try: async with self.init_lock: if not self.sent_connection_init: # The very first stream is responsible for initiating the connection. self.state = ConnectionState.ACTIVE await self.send_connection_init(timeout) self.sent_connection_init = True try: stream_id = self.h2_state.get_next_available_stream_id() except NoAvailableStreamIDError: self.state = ConnectionState.FULL raise NewConnectionRequired() else: self.state = ConnectionState.ACTIVE finally: self.streams_semaphore.release() h2_stream = AsyncHTTP2Stream(stream_id=stream_id, connection=self) self.streams[stream_id] = h2_stream self.events[stream_id] = [] return await h2_stream.request(method, url, headers, stream, timeout) async def send_connection_init(self, timeout: Dict[str, Optional[float]]) -> None: """ The HTTP/2 connection requires some initial setup before we can start using individual request/response streams on it. """ # Need to set these manually here instead of manipulating via # __setitem__() otherwise the H2Connection will emit SettingsUpdate # frames in addition to sending the undesired defaults. self.h2_state.local_settings = Settings( client=True, initial_values={ # Disable PUSH_PROMISE frames from the server since we don't do anything # with them for now. Maybe when we support caching? SettingCodes.ENABLE_PUSH: 0, # These two are taken from h2 for safe defaults SettingCodes.MAX_CONCURRENT_STREAMS: 100, SettingCodes.MAX_HEADER_LIST_SIZE: 65536, }, ) # Some websites (*cough* Yahoo *cough*) balk at this setting being # present in the initial handshake since it's not defined in the original # RFC despite the RFC mandating ignoring settings you don't know about. del self.h2_state.local_settings[ h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL ] self.h2_state.initiate_connection() self.h2_state.increment_flow_control_window(2 ** 24) data_to_send = self.h2_state.data_to_send() await self.socket.write(data_to_send, timeout) @property def is_closed(self) -> bool: return False def is_connection_dropped(self) -> bool: return self.socket.is_connection_dropped() async def aclose(self) -> None: if self.state != ConnectionState.CLOSED: self.state = ConnectionState.CLOSED await self.socket.aclose() async def wait_for_outgoing_flow( self, stream_id: int, timeout: Dict[str, Optional[float]] ) -> int: """ Returns the maximum allowable outgoing flow for a given stream. If the allowable flow is zero, then waits on the network until WindowUpdated frames have increased the flow rate. https://tools.ietf.org/html/rfc7540#section-6.9 """ local_flow = self.h2_state.local_flow_control_window(stream_id) connection_flow = self.h2_state.max_outbound_frame_size flow = min(local_flow, connection_flow) while flow == 0: await self.receive_events(timeout) local_flow = self.h2_state.local_flow_control_window(stream_id) connection_flow = self.h2_state.max_outbound_frame_size flow = min(local_flow, connection_flow) return flow async def wait_for_event( self, stream_id: int, timeout: Dict[str, Optional[float]] ) -> h2.events.Event: """ Returns the next event for a given stream. If no events are available yet, then waits on the network until an event is available. """ async with self.read_lock: while not self.events[stream_id]: await self.receive_events(timeout) return self.events[stream_id].pop(0) async def receive_events(self, timeout: Dict[str, Optional[float]]) -> None: """ Read some data from the network, and update the H2 state. """ data = await self.socket.read(self.READ_NUM_BYTES, timeout) events = self.h2_state.receive_data(data) for event in events: event_stream_id = getattr(event, "stream_id", 0) if hasattr(event, "error_code"): raise ProtocolError(event) if event_stream_id in self.events: self.events[event_stream_id].append(event) data_to_send = self.h2_state.data_to_send() await self.socket.write(data_to_send, timeout) async def send_headers( self, stream_id: int, headers: List[Tuple[bytes, bytes]], end_stream: bool, timeout: Dict[str, Optional[float]], ) -> None: self.h2_state.send_headers(stream_id, headers, end_stream=end_stream) self.h2_state.increment_flow_control_window(2 ** 24, stream_id=stream_id) data_to_send = self.h2_state.data_to_send() await self.socket.write(data_to_send, timeout) async def send_data( self, stream_id: int, chunk: bytes, timeout: Dict[str, Optional[float]] ) -> None: self.h2_state.send_data(stream_id, chunk) data_to_send = self.h2_state.data_to_send() await self.socket.write(data_to_send, timeout) async def end_stream( self, stream_id: int, timeout: Dict[str, Optional[float]] ) -> None: self.h2_state.end_stream(stream_id) data_to_send = self.h2_state.data_to_send() await self.socket.write(data_to_send, timeout) async def acknowledge_received_data( self, stream_id: int, amount: int, timeout: Dict[str, Optional[float]] ) -> None: self.h2_state.acknowledge_received_data(amount, stream_id) data_to_send = self.h2_state.data_to_send() await self.socket.write(data_to_send, timeout) async def close_stream(self, stream_id: int) -> None: del self.streams[stream_id] del self.events[stream_id] if not self.streams: if self.state == ConnectionState.ACTIVE: self.state = ConnectionState.IDLE elif self.state == ConnectionState.FULL: await self.aclose()
def __init__(self): config = H2Configuration(client_side=False, header_encoding='utf-8') self.conn = H2Connection(config=config) self.transport = None self.stream_data = {}
class HTTP2Connection(OpenConnection): READ_NUM_BYTES = 4096 CONFIG = H2Configuration(validate_inbound_headers=False) def __init__( self, socket: BaseSocketStream, backend: typing.Union[str, ConcurrencyBackend] = "auto", on_release: typing.Callable = None, ): self.socket = socket self.backend = lookup_backend(backend) self.on_release = on_release self.state = h2.connection.H2Connection(config=self.CONFIG) self.streams = {} # type: typing.Dict[int, HTTP2Stream] self.events = { } # type: typing.Dict[int, typing.List[h2.events.Event]] self.init_started = False @property def is_http2(self) -> bool: return True @property def init_complete(self) -> BaseEvent: # We do this lazily, to make sure backend autodetection always # runs within an async context. if not hasattr(self, "_initialization_complete"): self._initialization_complete = self.backend.create_event() return self._initialization_complete async def send(self, request: Request, timeout: Timeout = None) -> Response: timeout = Timeout() if timeout is None else timeout if not self.init_started: # The very first stream is responsible for initiating the connection. self.init_started = True await self.send_connection_init(timeout) stream_id = self.state.get_next_available_stream_id() self.init_complete.set() else: # All other streams need to wait until the connection is established. await self.init_complete.wait() stream_id = self.state.get_next_available_stream_id() stream = HTTP2Stream(stream_id=stream_id, connection=self) self.streams[stream_id] = stream self.events[stream_id] = [] return await stream.send(request, timeout) async def send_connection_init(self, timeout: Timeout) -> None: """ The HTTP/2 connection requires some initial setup before we can start using individual request/response streams on it. """ # Need to set these manually here instead of manipulating via # __setitem__() otherwise the H2Connection will emit SettingsUpdate # frames in addition to sending the undesired defaults. self.state.local_settings = Settings( client=True, initial_values={ # Disable PUSH_PROMISE frames from the server since we don't do anything # with them for now. Maybe when we support caching? SettingCodes.ENABLE_PUSH: 0, # These two are taken from h2 for safe defaults SettingCodes.MAX_CONCURRENT_STREAMS: 100, SettingCodes.MAX_HEADER_LIST_SIZE: 65536, }, ) # Some websites (*cough* Yahoo *cough*) balk at this setting being # present in the initial handshake since it's not defined in the original # RFC despite the RFC mandating ignoring settings you don't know about. del self.state.local_settings[ h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] self.state.initiate_connection() self.state.increment_flow_control_window(2**24) data_to_send = self.state.data_to_send() await self.socket.write(data_to_send, timeout) @property def is_closed(self) -> bool: return False def is_connection_dropped(self) -> bool: return self.socket.is_connection_dropped() async def close(self) -> None: await self.socket.close() async def wait_for_outgoing_flow(self, stream_id: int, timeout: Timeout) -> int: """ Returns the maximum allowable outgoing flow for a given stream. If the allowable flow is zero, then waits on the network until WindowUpdated frames have increased the flow rate. https://tools.ietf.org/html/rfc7540#section-6.9 """ local_flow = self.state.local_flow_control_window(stream_id) connection_flow = self.state.max_outbound_frame_size flow = min(local_flow, connection_flow) while flow == 0: await self.receive_events(timeout) local_flow = self.state.local_flow_control_window(stream_id) connection_flow = self.state.max_outbound_frame_size flow = min(local_flow, connection_flow) return flow async def wait_for_event(self, stream_id: int, timeout: Timeout) -> h2.events.Event: """ Returns the next event for a given stream. If no events are available yet, then waits on the network until an event is available. """ while not self.events[stream_id]: await self.receive_events(timeout) return self.events[stream_id].pop(0) async def receive_events(self, timeout: Timeout) -> None: """ Read some data from the network, and update the H2 state. """ data = await self.socket.read(self.READ_NUM_BYTES, timeout) events = self.state.receive_data(data) for event in events: event_stream_id = getattr(event, "stream_id", 0) logger.trace( f"receive_event stream_id={event_stream_id} event={event!r}") if hasattr(event, "error_code"): raise ProtocolError(event) if event_stream_id in self.events: self.events[event_stream_id].append(event) data_to_send = self.state.data_to_send() await self.socket.write(data_to_send, timeout) async def send_headers( self, stream_id: int, headers: typing.List[typing.Tuple[bytes, bytes]], timeout: Timeout, ) -> None: self.state.send_headers(stream_id, headers) self.state.increment_flow_control_window(2**24, stream_id=stream_id) data_to_send = self.state.data_to_send() await self.socket.write(data_to_send, timeout) async def send_data(self, stream_id: int, chunk: bytes, timeout: Timeout) -> None: self.state.send_data(stream_id, chunk) data_to_send = self.state.data_to_send() await self.socket.write(data_to_send, timeout) async def end_stream(self, stream_id: int, timeout: Timeout) -> None: self.state.end_stream(stream_id) data_to_send = self.state.data_to_send() await self.socket.write(data_to_send, timeout) async def acknowledge_received_data(self, stream_id: int, amount: int, timeout: Timeout) -> None: self.state.acknowledge_received_data(amount, stream_id) data_to_send = self.state.data_to_send() await self.socket.write(data_to_send, timeout) async def close_stream(self, stream_id: int) -> None: del self.streams[stream_id] del self.events[stream_id] if not self.streams and self.on_release is not None: await self.on_release()
def handle_one_request(self): """ This is the main HTTP/2.0 Handler. When a browser opens a connection to the server on the HTTP/2.0 port, Because there can be multiple H2 connections active at the same time, a UUID is created for each so that it is easier to tell them apart in the logs. """ config = H2Configuration(client_side=False) self.conn = H2ConnectionGuard(H2Connection(config=config)) self.close_connection = False # Generate a UUID to make it easier to distinguish different H2 connection debug messages self.uid = str(uuid.uuid4())[:8] self.logger.debug('(%s) Initiating h2 Connection' % self.uid) with self.conn as connection: connection.initiate_connection() data = connection.data_to_send() self.request.sendall(data) # Dict of { stream_id: (thread, queue) } stream_queues = {} try: while not self.close_connection: # This size may need to be made variable based on remote settings? data = self.request.recv(65535) with self.conn as connection: frames = connection.receive_data(data) self.logger.debug('(%s) Frames Received: ' % self.uid + str(frames)) for frame in frames: if isinstance(frame, ConnectionTerminated): self.logger.debug( '(%s) Connection terminated by remote peer ' % self.uid) self.close_connection = True # Flood all the streams with connection terminated, this will cause them to stop for stream_id, (thread, queue) in stream_queues.items(): queue.put(frame) elif hasattr(frame, 'stream_id'): if frame.stream_id not in stream_queues: queue = Queue() stream_queues[frame.stream_id] = ( self.start_stream_thread(frame, queue), queue) stream_queues[frame.stream_id][1].put(frame) if isinstance(frame, StreamEnded) or (hasattr( frame, "stream_ended") and frame.stream_ended): del stream_queues[frame.stream_id] except (socket.timeout, socket.error) as e: self.logger.error('(%s) Closing Connection - \n%s' % (self.uid, str(e))) if not self.close_connection: self.close_connection = True for stream_id, (thread, queue) in stream_queues.items(): queue.put(None) except Exception as e: self.logger.error('(%s) Unexpected Error - \n%s' % (self.uid, str(e))) finally: for stream_id, (thread, queue) in stream_queues.items(): thread.join()
def __init__( self, host: Optional[str] = None, port: Optional[int] = None, *, loop: Optional[asyncio.AbstractEventLoop] = None, path: Optional[str] = None, codec: Optional[CodecBase] = None, status_details_codec: Optional[StatusDetailsCodecBase] = None, ssl: Union[None, bool, '_ssl.SSLContext'] = None, ): """Initialize connection to the server :param host: server host name. :param port: server port number. :param loop: asyncio-compatible event loop :param path: server socket path. If specified, host and port should be omitted (must be None). :param codec: instance of a codec to encode and decode messages, if omitted ``ProtoCodec`` is used by default :param status_details_codec: instance of a status details codec to decode error details in a trailing metadata, if omitted ``ProtoStatusDetailsCodec`` is used by default :param ssl: ``True`` or :py:class:`~python:ssl.SSLContext` object; if ``True``, default SSL context is used. """ if path is not None and (host is not None or port is not None): raise ValueError("The 'path' parameter can not be used with the " "'host' or 'port' parameters.") else: if host is None: host = '127.0.0.1' if port is None: port = 50051 self._host = host self._port = port self._loop = loop or asyncio.get_event_loop() self._path = path if codec is None: codec = ProtoCodec() if status_details_codec is None and _googleapis_available(): status_details_codec = ProtoStatusDetailsCodec() self._codec = codec self._status_details_codec = status_details_codec self._config = H2Configuration(client_side=True, header_encoding='ascii') self._authority = '{}:{}'.format(self._host, self._port) if ssl is True: ssl = self._get_default_ssl_context() self._ssl = ssl or None self._scheme = 'https' if self._ssl else 'http' self._connect_lock = asyncio.Lock(loop=self._loop) self.__dispatch__ = _DispatchChannelEvents()
class SyncHTTP2Connection(SyncBaseHTTPConnection): READ_NUM_BYTES = 64 * 1024 CONFIG = H2Configuration(validate_inbound_headers=False) def __init__( self, socket: SyncSocketStream, backend: SyncBackend, ssl_context: SSLContext = None, ): self.socket = socket self.ssl_context = SSLContext() if ssl_context is None else ssl_context self.backend = backend self.h2_state = h2.connection.H2Connection(config=self.CONFIG) self.sent_connection_init = False self.streams = {} # type: Dict[int, SyncHTTP2Stream] self.events = {} # type: Dict[int, List[h2.events.Event]] self.state = ConnectionState.ACTIVE def __repr__(self) -> str: return f"<SyncHTTP2Connection state={self.state}>" def info(self) -> str: return f"HTTP/2, {self.state.name}, {len(self.streams)} streams" @property def init_lock(self) -> SyncLock: # We do this lazily, to make sure backend autodetection always # runs within an async context. if not hasattr(self, "_initialization_lock"): self._initialization_lock = self.backend.create_lock() return self._initialization_lock @property def read_lock(self) -> SyncLock: # We do this lazily, to make sure backend autodetection always # runs within an async context. if not hasattr(self, "_read_lock"): self._read_lock = self.backend.create_lock() return self._read_lock @property def max_streams_semaphore(self) -> SyncSemaphore: # We do this lazily, to make sure backend autodetection always # runs within an async context. if not hasattr(self, "_max_streams_semaphore"): max_streams = self.h2_state.local_settings.max_concurrent_streams self._max_streams_semaphore = self.backend.create_semaphore( max_streams, exc_class=PoolTimeout ) return self._max_streams_semaphore def start_tls( self, hostname: bytes, timeout: TimeoutDict = None ) -> SyncSocketStream: raise NotImplementedError("TLS upgrade not supported on HTTP/2 connections.") def get_state(self) -> ConnectionState: return self.state def mark_as_ready(self) -> None: if self.state == ConnectionState.IDLE: self.state = ConnectionState.READY def handle_request( self, method: bytes, url: URL, headers: Headers, stream: SyncByteStream, extensions: dict, ) -> Tuple[int, Headers, SyncByteStream, dict]: timeout = cast(TimeoutDict, extensions.get("timeout", {})) with self.init_lock: if not self.sent_connection_init: # The very first stream is responsible for initiating the connection. self.state = ConnectionState.ACTIVE self.send_connection_init(timeout) self.sent_connection_init = True self.max_streams_semaphore.acquire() try: try: stream_id = self.h2_state.get_next_available_stream_id() except NoAvailableStreamIDError: self.state = ConnectionState.FULL raise NewConnectionRequired() else: self.state = ConnectionState.ACTIVE h2_stream = SyncHTTP2Stream(stream_id=stream_id, connection=self) self.streams[stream_id] = h2_stream self.events[stream_id] = [] return h2_stream.handle_request( method, url, headers, stream, extensions ) except Exception: # noqa: PIE786 self.max_streams_semaphore.release() raise def send_connection_init(self, timeout: TimeoutDict) -> None: """ The HTTP/2 connection requires some initial setup before we can start using individual request/response streams on it. """ # Need to set these manually here instead of manipulating via # __setitem__() otherwise the H2Connection will emit SettingsUpdate # frames in addition to sending the undesired defaults. self.h2_state.local_settings = Settings( client=True, initial_values={ # Disable PUSH_PROMISE frames from the server since we don't do anything # with them for now. Maybe when we support caching? SettingCodes.ENABLE_PUSH: 0, # These two are taken from h2 for safe defaults SettingCodes.MAX_CONCURRENT_STREAMS: 100, SettingCodes.MAX_HEADER_LIST_SIZE: 65536, }, ) # Some websites (*cough* Yahoo *cough*) balk at this setting being # present in the initial handshake since it's not defined in the original # RFC despite the RFC mandating ignoring settings you don't know about. del self.h2_state.local_settings[ h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL ] logger.trace("initiate_connection=%r", self) self.h2_state.initiate_connection() self.h2_state.increment_flow_control_window(2 ** 24) data_to_send = self.h2_state.data_to_send() self.socket.write(data_to_send, timeout) def is_socket_readable(self) -> bool: return self.socket.is_readable() def close(self) -> None: logger.trace("close_connection=%r", self) if self.state != ConnectionState.CLOSED: self.state = ConnectionState.CLOSED self.socket.close() def wait_for_outgoing_flow(self, stream_id: int, timeout: TimeoutDict) -> int: """ Returns the maximum allowable outgoing flow for a given stream. If the allowable flow is zero, then waits on the network until WindowUpdated frames have increased the flow rate. https://tools.ietf.org/html/rfc7540#section-6.9 """ local_flow = self.h2_state.local_flow_control_window(stream_id) connection_flow = self.h2_state.max_outbound_frame_size flow = min(local_flow, connection_flow) while flow == 0: self.receive_events(timeout) local_flow = self.h2_state.local_flow_control_window(stream_id) connection_flow = self.h2_state.max_outbound_frame_size flow = min(local_flow, connection_flow) return flow def wait_for_event( self, stream_id: int, timeout: TimeoutDict ) -> h2.events.Event: """ Returns the next event for a given stream. If no events are available yet, then waits on the network until an event is available. """ with self.read_lock: while not self.events[stream_id]: self.receive_events(timeout) return self.events[stream_id].pop(0) def receive_events(self, timeout: TimeoutDict) -> None: """ Read some data from the network, and update the H2 state. """ data = self.socket.read(self.READ_NUM_BYTES, timeout) if data == b"": raise RemoteProtocolError("Server disconnected") events = self.h2_state.receive_data(data) for event in events: event_stream_id = getattr(event, "stream_id", 0) logger.trace("receive_event stream_id=%r event=%s", event_stream_id, event) if hasattr(event, "error_code"): raise RemoteProtocolError(event) if event_stream_id in self.events: self.events[event_stream_id].append(event) data_to_send = self.h2_state.data_to_send() self.socket.write(data_to_send, timeout) def send_headers( self, stream_id: int, headers: Headers, end_stream: bool, timeout: TimeoutDict ) -> None: logger.trace("send_headers stream_id=%r headers=%r", stream_id, headers) self.h2_state.send_headers(stream_id, headers, end_stream=end_stream) self.h2_state.increment_flow_control_window(2 ** 24, stream_id=stream_id) data_to_send = self.h2_state.data_to_send() self.socket.write(data_to_send, timeout) def send_data( self, stream_id: int, chunk: bytes, timeout: TimeoutDict ) -> None: logger.trace("send_data stream_id=%r chunk=%r", stream_id, chunk) self.h2_state.send_data(stream_id, chunk) data_to_send = self.h2_state.data_to_send() self.socket.write(data_to_send, timeout) def end_stream(self, stream_id: int, timeout: TimeoutDict) -> None: logger.trace("end_stream stream_id=%r", stream_id) self.h2_state.end_stream(stream_id) data_to_send = self.h2_state.data_to_send() self.socket.write(data_to_send, timeout) def acknowledge_received_data( self, stream_id: int, amount: int, timeout: TimeoutDict ) -> None: self.h2_state.acknowledge_received_data(amount, stream_id) data_to_send = self.h2_state.data_to_send() self.socket.write(data_to_send, timeout) def close_stream(self, stream_id: int) -> None: try: logger.trace("close_stream stream_id=%r", stream_id) del self.streams[stream_id] del self.events[stream_id] if not self.streams: if self.state == ConnectionState.ACTIVE: self.state = ConnectionState.IDLE elif self.state == ConnectionState.FULL: self.close() finally: self.max_streams_semaphore.release()
class Parser: config = H2Configuration(client_side=False, header_encoding="utf-8") __slots__ = ("conn", "_streams") def __init__(self): self.conn = H2Connection(config=self.config) self._streams: Dict[int, Request] = {} @property def push_support(self): return self.conn.remote_settings.enable_push def connect(self): self.conn.initiate_connection() def handle_request( self, data: bytes ) -> Tuple[Optional[bytes], Optional[List[Request]], bool]: to_send_data, requests, close = None, [], False try: events = self.conn.receive_data(data) except ProtocolError: # try parse http1.1 and send change connection to_send_data, requests = self._handle_http1(data) events = [] to_send_data = to_send_data if to_send_data is not None else self.conn.data_to_send( ) for event in events: result = self._parse_event(event) if result is False: close = True if result: requests.append(result) return to_send_data, requests, close def _parse_event(self, event): if isinstance(event, RequestReceived): self._streams[event.stream_id] = _get_request_from_event(event) elif isinstance(event, DataReceived): if event.stream_id in self._streams: self._streams[event.stream_id].body += event.data elif isinstance(event, StreamEnded): return self._streams.pop(event.stream_id) elif isinstance(event, ConnectionTerminated): # Stop all requests return False return None def _handle_http1(self, data): request, to_send_data = self.handle_http1_to_http2(data) if not to_send_data: raise ParseError() self.conn.clear_outbound_data_buffer() # clean init connection self.conn.initiate_upgrade_connection( settings_header=request.headers[b"http2-settings"]) to_send_data += b"\r\n" + self.conn.data_to_send() return to_send_data, [ request, ] def handle_http1_to_http2(self, data: bytes) -> Tuple[Optional[Request], bytes]: if b"HTTP" not in data: return None, b"" data, requests, _ = http1_parser.handle_request(data) if len(requests) == 1: request = requests[0] settings = request.headers.get(b"http2-settings") if request.headers.get(b"upgrade") != b"h2c" or not settings: return request, b"" response = Response(101, b"", headers={ b"Connection": b"Upgrade", b"Upgrade": b"h2c" }) request.stream = 1 return request, b"".join( http1_parser.handle_response(response, requests)) return None, b"" def handle_response(self, response: Response, request: Request): response.headers[b"content-length"] = str(len(response.body)).encode() response_headers = ((":status", str(response.status)), ) + tuple( response.headers.items()) stream_id = request.stream if response.push: sockname = request.get_transport_info()[1] authority = f"{sockname[0]}:{sockname[1]}" request_headers = [(b":method", request.method), (b":path", request.raw_path), (b':scheme', request.scheme), (b':authority', authority)] request_headers.extend(request.headers.items()) stream_id = self.conn.get_next_available_stream_id() try: self.conn.push_stream( stream_id=request.stream, promised_stream_id=stream_id, request_headers=request_headers, ) except ProtocolError as exc: return self.conn.end_stream(request.stream) self.conn.send_headers(stream_id, response_headers) data = response.body while True: if self.conn.local_flow_control_window(request.stream) < 1: return chunk_size = min( self.conn.local_flow_control_window(request.stream), len(data), self.conn.max_outbound_frame_size) try: self.conn.send_data(stream_id, data[:chunk_size], end_stream=not response.pushes and chunk_size >= len(data)) except (StreamClosedError, ProtocolError): # The stream got closed and we didn't get told. We're done here. break yield self.conn.data_to_send() data = data[chunk_size:] if not data: return
def __init__(self, root): config = H2Configuration(client_side=False) self.conn = H2Connection(config=config) self.known_proto = None self.root = root self._flow_control_deferreds = {}
def __init__(self, sock): config = H2Configuration(client_side=False) self.sock = sock self.conn = H2Connection(config=config)