示例#1
0
    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()
示例#2
0
 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())
示例#3
0
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()
示例#4
0
 def __init__(self):
     config = H2Configuration(client_side=False, header_encoding='utf-8')
     self.conn = H2Connection(config=config)
     self.transport = None
     self.stream_data = {}
示例#5
0
文件: http2.py 项目: zkite/httpx
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()
示例#6
0
    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()
示例#7
0
    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()
示例#8
0
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()
示例#9
0
文件: hyper.py 项目: Krukov/levin
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
示例#10
0
 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 = {}
示例#11
0
 def __init__(self, sock):
     config = H2Configuration(client_side=False)
     self.sock = sock
     self.conn = H2Connection(config=config)