Пример #1
0
    def __init__(self, logger, ip_manager, config, ssl_sock, close_cb, retry_task_cb, idle_cb):
        super(Http2Worker, self).__init__(logger, ip_manager, config, ssl_sock, close_cb, retry_task_cb, idle_cb)

        self.network_buffer_size = 128 * 1024

        # Google http/2 time out is 4 mins.
        self.ssl_sock.settimeout(240)
        self._sock = BufferedSocket(ssl_sock, self.network_buffer_size)

        self.next_stream_id = 1
        self.streams = {}
        self.last_ping_time = time.time()
        self.last_active_time = self.ssl_sock.create_time - 1
        self.continue_timeout = 0

        # count ping not ACK
        # increase when send ping
        # decrease when recv ping ack
        # if this in not 0, don't accept request.
        self.ping_on_way = 0
        self.accept_task = False

        # request_lock
        self.request_lock = threading.Lock()

        # all send frame must put to this queue
        # then send by send_loop
        # every frame put to this queue must allowed by stream window and connection window
        # any data frame blocked by connection window should put to self.blocked_send_frames
        self.send_queue = Queue.Queue()

        # keep blocked data frame in this buffer
        # which is allowed by stream window but blocked by connection window.
        # They will be sent when connection window open
        self.blocked_send_frames = []

        # Values for the settings used on an HTTP/2 connection.
        # will send to remote using Setting Frame
        self.local_settings = {
            SettingsFrame.INITIAL_WINDOW_SIZE: 16 * 1024 * 1024,
            SettingsFrame.SETTINGS_MAX_FRAME_SIZE: 256 * 1024
        }
        self.local_connection_initial_windows = 32 * 1024 * 1024
        self.local_window_manager = FlowControlManager(self.local_connection_initial_windows)

        # changed by server, with SettingFrame
        self.remote_settings = {
            SettingsFrame.INITIAL_WINDOW_SIZE: DEFAULT_WINDOW_SIZE,
            SettingsFrame.SETTINGS_MAX_FRAME_SIZE: DEFAULT_MAX_FRAME,
            SettingsFrame.MAX_CONCURRENT_STREAMS: 100
        }

        #self.remote_window_size = DEFAULT_WINDOW_SIZE
        self.remote_window_size = 32 * 1024 * 1024

        # send Setting frame before accept task.
        self._send_preamble()

        threading.Thread(target=self.send_loop).start()
        threading.Thread(target=self.recv_loop).start()
Пример #2
0
    def test_socket_error_on_readline(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)

        with pytest.raises(ConnectionResetError):
            b.readline()
Пример #3
0
    def test_receive_single_packet(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets.append(b'test data')

        d = b.recv(100).tobytes()
        assert d == b'test data'
Пример #4
0
    def test_socket_error_on_readline(self, monkeypatch):
        monkeypatch.setattr(
            hyper.common.bufsocket.select, 'select', dummy_select
        )
        s = DummySocket()
        b = BufferedSocket(s)

        with pytest.raises(ConnectionResetError):
            b.readline()
Пример #5
0
    def test_can_send_on_buffered_socket(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)
        b.send(b'test data')

        assert len(s.outbound_packets) == 1
        assert s.outbound_packets[0] == b'test data'
Пример #6
0
    def test_oversized_read(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets.append(b'a' * 600)

        d = b.recv(1200).tobytes()
        assert d == b'a' * 600
Пример #7
0
    def test_oversized_read(self, monkeypatch):
        monkeypatch.setattr(
            hyper.common.bufsocket.select, 'select', dummy_select
        )
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets.append(b'a' * 600)

        d = b.recv(1200).tobytes()
        assert d == b'a' * 600
Пример #8
0
    def test_receive_single_packet(self, monkeypatch):
        monkeypatch.setattr(
            hyper.common.bufsocket.select, 'select', dummy_select
        )
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets.append(b'test data')

        d = b.recv(100).tobytes()
        assert d == b'test data'
Пример #9
0
    def test_can_send_on_buffered_socket(self, monkeypatch):
        monkeypatch.setattr(
            hyper.common.bufsocket.select, 'select', dummy_select
        )
        s = DummySocket()
        b = BufferedSocket(s)
        b.send(b'test data')

        assert len(s.outbound_packets) == 1
        assert s.outbound_packets[0] == b'test data'
Пример #10
0
    def test_socket_fill_resizes_if_needed(self):
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets = [b'Here']
        b._index = 1000

        assert not len(b.buffer)

        b.fill()
        assert len(b.buffer) == 4
        assert b._index == 0
Пример #11
0
    def test_socket_readline_too_long(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)

        b._buffer_view[0:b._buffer_size] = b'0' * b._buffer_size
        b._bytes_in_buffer = b._buffer_size

        with pytest.raises(LineTooLongError):
            b.readline()
Пример #12
0
    def test_socket_fill_resizes_if_needed(self):
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets = [b'Here']
        b._index = 1000

        assert not len(b.buffer)

        b.fill()
        assert len(b.buffer) == 4
        assert b._index == 0
Пример #13
0
    def test_socket_readline_too_long(self, monkeypatch):
        monkeypatch.setattr(
            hyper.common.bufsocket.select, 'select', dummy_select
        )
        s = DummySocket()
        b = BufferedSocket(s)

        b._buffer_view[0:b._buffer_size] = b'0' * b._buffer_size
        b._bytes_in_buffer = b._buffer_size

        with pytest.raises(LineTooLongError):
            b.readline()
Пример #14
0
    def test_advancing_sockets(self):
        s = DummySocket()
        b = BufferedSocket(s)
        b._buffer_view[0:5] = b'abcde'
        b._bytes_in_buffer += 5

        assert len(b.buffer) == 5

        b.advance_buffer(3)
        assert len(b.buffer) == 2

        assert b.buffer.tobytes() == b'de'
Пример #15
0
    def test_advancing_sockets(self):
        s = DummySocket()
        b = BufferedSocket(s)
        b._buffer_view[0:5] = b'abcde'
        b._bytes_in_buffer += 5

        assert len(b.buffer) == 5

        b.advance_buffer(3)
        assert len(b.buffer) == 2

        assert b.buffer.tobytes() == b'de'
Пример #16
0
    def test_receive_small_packets(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets = [b'Here', b'begins', b'the', b'test', b'data']

        d = b''
        for _ in range(5):
            d += b.recv(100).tobytes()

        assert d == b'Herebeginsthetestdata'
Пример #17
0
    def test_receive_small_packets(self, monkeypatch):
        monkeypatch.setattr(
            hyper.common.bufsocket.select, 'select', dummy_select
        )
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets = [b'Here', b'begins', b'the', b'test', b'data']

        d = b''
        for _ in range(5):
            d += b.recv(100).tobytes()

        assert d == b'Herebeginsthetestdata'
Пример #18
0
    def test_receive_multiple_packets_at_once(self, monkeypatch):
        monkeypatch.setattr(
            hyper.common.bufsocket.select, 'select', dummy_select
        )
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets = [b'Here', b'begins', b'the', b'test', b'data', b'!']
        s.read_count = 3

        d = b''
        for _ in range(22):
            d += b.recv(1).tobytes()

        assert d == b'Herebeginsthetestdata!'
Пример #19
0
    def test_filling_the_buffer(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets = [
            b'a' * 1000,
            b'a' * 800,
        ]

        d = b''
        for _ in range(2):
            d += b.recv(900).tobytes()

        assert d == (b'a' * 1800)
Пример #20
0
    def test_receive_multiple_packets_at_once(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets = [
            b'Here', b'begins', b'the', b'test', b'data', b'!'
        ]
        s.read_count = 3

        d = b''
        for _ in range(22):
            d += b.recv(1).tobytes()

        assert d == b'Herebeginsthetestdata!'
Пример #21
0
    def test_filling_the_buffer(self, monkeypatch):
        monkeypatch.setattr(
            hyper.common.bufsocket.select, 'select', dummy_select
        )
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets = [
            b'a' * 1000,
            b'a' * 800,
        ]

        d = b''
        for _ in range(2):
            d += b.recv(900).tobytes()

        assert d == (b'a' * 1800)
Пример #22
0
    def __init__(self, ssl_sock, close_cb, retry_task_cb):
        super(HTTP2_worker, self).__init__(ssl_sock, close_cb, retry_task_cb)

        self.max_concurrent = 20
        self.network_buffer_size = 128 * 1024

        # Google http/2 time out is 4 mins.
        ssl_sock.settimeout(240)
        self._sock = BufferedSocket(ssl_sock, self.network_buffer_size)

        self.next_stream_id = 1
        self.streams = {}
        self.last_ping_time = time.time()

        # count ping not ACK
        # increase when send ping
        # decrease when recv ping ack
        # if this in not 0, don't accept request.
        self.ping_on_way = 0

        # request_lock
        self.request_lock = threading.Lock()

        # all send frame must put to this queue
        # then send by send_loop
        # every frame put to this queue must allowed by stream window and connection window
        # any data frame blocked by connection window should put to self.blocked_send_frames
        self.send_queue = Queue.Queue()

        # keep blocked data frame in this buffer
        # which is allowed by stream window but blocked by connection window.
        # They will be sent when connection window open
        self.blocked_send_frames = []

        self.encoder = Encoder()
        self.decoder = Decoder()

        # Values for the settings used on an HTTP/2 connection.
        # will send to remote using Setting Frame
        self.local_settings = {
            SettingsFrame.INITIAL_WINDOW_SIZE: 1 * 1024 * 1024,
            SettingsFrame.SETTINGS_MAX_FRAME_SIZE: 256 * 1024
        }
        self.local_connection_initial_windows = 2 * 1024 * 1024
        self.local_window_manager = FlowControlManager(self.local_connection_initial_windows)

        # changed by server, with SettingFrame
        self.remote_settings = {
            SettingsFrame.INITIAL_WINDOW_SIZE: DEFAULT_WINDOW_SIZE,
            SettingsFrame.SETTINGS_MAX_FRAME_SIZE: DEFAULT_MAX_FRAME,
            SettingsFrame.MAX_CONCURRENT_STREAMS: 100
        }

        self.remote_window_size = DEFAULT_WINDOW_SIZE

        # send Setting frame before accept task.
        self._send_preamble()

        threading.Thread(target=self.send_loop).start()
        threading.Thread(target=self.recv_loop).start()
Пример #23
0
    def test_readline_from_buffer(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)

        one = b'hi there\r\n'
        two = b'this is another line\r\n'
        three = b'\r\n'
        combined = b''.join([one, two, three])
        b._buffer_view[0:len(combined)] = combined
        b._bytes_in_buffer += len(combined)

        assert b.readline().tobytes() == one
        assert b.readline().tobytes() == two
        assert b.readline().tobytes() == three
Пример #24
0
    def test_readline_from_socket(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)

        one = b'hi there\r\n'
        two = b'this is another line\r\n'
        three = b'\r\n'
        combined = b''.join([one, two, three])

        for i in range(0, len(combined), 5):
            s.inbound_packets.append(combined[i:i + 5])

        assert b.readline().tobytes() == one
        assert b.readline().tobytes() == two
        assert b.readline().tobytes() == three
Пример #25
0
    def test_readline_from_buffer(self, monkeypatch):
        monkeypatch.setattr(
            hyper.common.bufsocket.select, 'select', dummy_select
        )
        s = DummySocket()
        b = BufferedSocket(s)

        one = b'hi there\r\n'
        two = b'this is another line\r\n'
        three = b'\r\n'
        combined = b''.join([one, two, three])
        b._buffer_view[0:len(combined)] = combined
        b._bytes_in_buffer += len(combined)

        assert b.readline().tobytes() == one
        assert b.readline().tobytes() == two
        assert b.readline().tobytes() == three
Пример #26
0
    def test_can_create_buffered_sockets(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)

        assert b is not None
        assert b._buffer_size == 1000
Пример #27
0
    def test_readline_from_socket(self, monkeypatch):
        monkeypatch.setattr(
            hyper.common.bufsocket.select, 'select', dummy_select
        )
        s = DummySocket()
        b = BufferedSocket(s)

        one = b'hi there\r\n'
        two = b'this is another line\r\n'
        three = b'\r\n'
        combined = b''.join([one, two, three])

        for i in range(0, len(combined), 5):
            s.inbound_packets.append(combined[i:i+5])

        assert b.readline().tobytes() == one
        assert b.readline().tobytes() == two
        assert b.readline().tobytes() == three
Пример #28
0
    def test_readline_both(self, monkeypatch):
        monkeypatch.setattr(hyper.common.bufsocket.select, 'select',
                            dummy_select)
        s = DummySocket()
        b = BufferedSocket(s)

        one = b'hi there\r\n'
        two = b'this is another line\r\n'
        three = b'\r\n'
        combined = b''.join([one, two, three])

        split_index = int(len(combined) / 2)

        b._buffer_view[0:split_index] = combined[0:split_index]
        b._bytes_in_buffer += split_index

        for i in range(split_index, len(combined), 5):
            s.inbound_packets.append(combined[i:i + 5])

        assert b.readline().tobytes() == one
        assert b.readline().tobytes() == two
        assert b.readline().tobytes() == three
Пример #29
0
    def test_socket_fill_basic(self):
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets = [b'Here', b'begins', b'the']

        assert not len(b.buffer)

        b.fill()
        assert len(b.buffer) == 4

        b.fill()
        assert len(b.buffer) == 10

        b.fill()
        assert len(b.buffer) == 13
Пример #30
0
    def test_readline_both(self, monkeypatch):
        monkeypatch.setattr(
            hyper.common.bufsocket.select, 'select', dummy_select
        )
        s = DummySocket()
        b = BufferedSocket(s)

        one = b'hi there\r\n'
        two = b'this is another line\r\n'
        three = b'\r\n'
        combined = b''.join([one, two, three])

        split_index = int(len(combined) / 2)

        b._buffer_view[0:split_index] = combined[0:split_index]
        b._bytes_in_buffer += split_index

        for i in range(split_index, len(combined), 5):
            s.inbound_packets.append(combined[i:i+5])

        assert b.readline().tobytes() == one
        assert b.readline().tobytes() == two
        assert b.readline().tobytes() == three
Пример #31
0
    def test_socket_fill_basic(self):
        s = DummySocket()
        b = BufferedSocket(s)
        s.inbound_packets = [b'Here', b'begins', b'the']

        assert not len(b.buffer)

        b.fill()
        assert len(b.buffer) == 4

        b.fill()
        assert len(b.buffer) == 10

        b.fill()
        assert len(b.buffer) == 13
Пример #32
0
    def connect(self):
        """
        Connect to the server specified when the object was created. This is a
        no-op if we're already connected.

        Concurrency
        -----------

        This method is thread-safe. It may be called from multiple threads, and
        is a noop for all threads apart from the first.

        :returns: Nothing.

        """
        #print("connecting to ATS")
        with self._lock:
            if self._sock is not None:
                return
            sni = self.host
            if not self.proxy_host:
                host = self.host
                port = self.port
            else:
                host = self.proxy_host
                port = self.proxy_port

            sock = socket.create_connection((host, port))

            if self.secure:
                #assert not self.proxy_host, "Proxy with HTTPS not supported."
                sock, proto = wrap_socket(sock,
                                          sni,
                                          self.ssl_context,
                                          force_proto=self.force_proto)
            else:
                proto = H2C_PROTOCOL

            log.debug("Selected NPN protocol: %s", proto)
            assert proto in H2_NPN_PROTOCOLS or proto == H2C_PROTOCOL

            self._sock = BufferedSocket(sock, self.network_buffer_size)

            self._send_preamble()
Пример #33
0
class Http2Worker(HttpWorker):
    version = "2"

    def __init__(self, logger, ip_manager, config, ssl_sock, close_cb,
                 retry_task_cb, idle_cb, log_debug_data):
        super(Http2Worker,
              self).__init__(logger, ip_manager, config, ssl_sock, close_cb,
                             retry_task_cb, idle_cb, log_debug_data)

        self.network_buffer_size = 65535

        # Google http/2 time out is 4 mins.
        self.ssl_sock.settimeout(240)
        self._sock = BufferedSocket(ssl_sock, self.network_buffer_size)

        self.next_stream_id = 1
        self.streams = {}
        self.last_ping_time = time.time()
        self.continue_timeout = 0

        # count ping not ACK
        # increase when send ping
        # decrease when recv ping ack
        # if this in not 0, don't accept request.
        self.ping_on_way = 0
        self.accept_task = False

        # request_lock
        self.request_lock = threading.Lock()

        # all send frame must put to this queue
        # then send by send_loop
        # every frame put to this queue must allowed by stream window and connection window
        # any data frame blocked by connection window should put to self.blocked_send_frames
        self.send_queue = Queue.Queue()
        self.encoder = Encoder()
        self.decoder = Decoder()

        # keep blocked data frame in this buffer
        # which is allowed by stream window but blocked by connection window.
        # They will be sent when connection window open
        self.blocked_send_frames = []

        # Values for the settings used on an HTTP/2 connection.
        # will send to remote using Setting Frame
        self.local_settings = {
            SettingsFrame.INITIAL_WINDOW_SIZE: 16 * 1024 * 1024,
            SettingsFrame.SETTINGS_MAX_FRAME_SIZE: 256 * 1024
        }
        self.local_connection_initial_windows = 32 * 1024 * 1024
        self.local_window_manager = FlowControlManager(
            self.local_connection_initial_windows)

        # changed by server, with SettingFrame
        self.remote_settings = {
            SettingsFrame.INITIAL_WINDOW_SIZE: DEFAULT_WINDOW_SIZE,
            SettingsFrame.SETTINGS_MAX_FRAME_SIZE: DEFAULT_MAX_FRAME,
            SettingsFrame.MAX_CONCURRENT_STREAMS: 100
        }

        #self.remote_window_size = DEFAULT_WINDOW_SIZE
        self.remote_window_size = 32 * 1024 * 1024

        # send Setting frame before accept task.
        self._send_preamble()

        threading.Thread(target=self.send_loop).start()
        threading.Thread(target=self.recv_loop).start()

    # export api
    def request(self, task):
        if not self.keep_running:
            # race condition
            self.retry_task_cb(task)
            return

        if len(self.streams) > self.config.http2_max_concurrent:
            self.accept_task = False

        task.set_state("h2_req")
        self.request_task(task)

    def encode_header(self, headers):
        return self.encoder.encode(headers)

    def request_task(self, task):
        with self.request_lock:
            # create stream to process task
            stream_id = self.next_stream_id

            # http/2 client use odd stream_id
            self.next_stream_id += 2

            stream = Stream(
                self.logger, self.config, self, self.ip, stream_id, task,
                self._send_cb, self._close_stream_cb, self.encode_header,
                self.decoder,
                FlowControlManager(
                    self.local_settings[SettingsFrame.INITIAL_WINDOW_SIZE]),
                self.remote_settings[SettingsFrame.INITIAL_WINDOW_SIZE],
                self.remote_settings[SettingsFrame.SETTINGS_MAX_FRAME_SIZE])
            self.streams[stream_id] = stream
            stream.start_request()

    def send_loop(self):
        while self.keep_running:
            frame = self.send_queue.get(True)
            if not frame:
                # None frame means exist
                break

            if self.config.http2_show_debug:
                self.logger.debug("%s Send:%s", self.ip, str(frame))
            data = frame.serialize()
            try:
                self._sock.send(data, flush=False)
                # don't flush for small package
                # reduce send api call

                if self.send_queue._qsize():
                    continue

                # wait for payload frame
                time.sleep(0.01)
                # combine header and payload in one tcp package.
                if not self.send_queue._qsize():
                    self._sock.flush()

                self.last_send_time = time.time()
            except socket.error as e:
                if e.errno not in (errno.EPIPE, errno.ECONNRESET):
                    self.logger.warn("%s http2 send fail:%r", self.ip, e)
                else:
                    self.logger.exception("send error:%r", e)

                self.close("send fail:%r" % e)
            except Exception as e:
                self.logger.debug("http2 %s send error:%r", self.ip, e)
                self.close("send fail:%r" % e)

    def recv_loop(self):
        while self.keep_running:
            try:
                self._consume_single_frame()
            except Exception as e:
                self.logger.exception("recv fail:%r", e)
                self.close("recv fail:%r" % e)

    def get_rtt_rate(self):
        return self.rtt + len(self.streams) * 3000

    def close(self, reason="conn close"):
        self.keep_running = False
        self.accept_task = False
        # Notify loop to exit
        # This function may be call by out side http2
        # When gae_proxy found the appid or ip is wrong
        self.send_queue.put(None)

        for stream in self.streams.values():
            if stream.task.responsed:
                # response have send to client
                # can't retry
                stream.close(reason=reason)
            else:
                self.retry_task_cb(stream.task)
        self.streams = {}
        super(Http2Worker, self).close(reason)

    def send_ping(self):
        p = PingFrame(0)
        p.opaque_data = struct.pack("!d", time.time())
        self.send_queue.put(p)
        self.last_ping_time = time.time()
        self.ping_on_way += 1

    def _send_preamble(self):
        self.send_queue.put(RawFrame(b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'))

        f = SettingsFrame(0)
        f.settings[SettingsFrame.ENABLE_PUSH] = 0
        f.settings[SettingsFrame.INITIAL_WINDOW_SIZE] = self.local_settings[
            SettingsFrame.INITIAL_WINDOW_SIZE]
        f.settings[
            SettingsFrame.SETTINGS_MAX_FRAME_SIZE] = self.local_settings[
                SettingsFrame.SETTINGS_MAX_FRAME_SIZE]
        self._send_cb(f)

        # update local connection windows size
        f = WindowUpdateFrame(0)
        f.window_increment = self.local_connection_initial_windows - DEFAULT_WINDOW_SIZE
        self._send_cb(f)

    def increase_remote_window_size(self, inc_size):
        # check and send blocked frames if window allow
        self.remote_window_size += inc_size
        #self.logger.debug("%s increase send win:%d result:%d", self.ip, inc_size, self.remote_window_size)
        while len(self.blocked_send_frames):
            frame = self.blocked_send_frames[0]
            if len(frame.data) > self.remote_window_size:
                return

            self.remote_window_size -= len(frame.data)
            self.send_queue.put(frame)
            self.blocked_send_frames.pop(0)

        if self.keep_running and \
                self.accept_task == False and \
                len(self.streams) < self.config.http2_max_concurrent and \
                self.remote_window_size > 10000:
            self.accept_task = True
            self.idle_cb()

    def _send_cb(self, frame):
        # can called by stream
        # put to send_blocked if connection window not allow,
        if frame.type == DataFrame.type:
            if len(frame.data) > self.remote_window_size:
                self.blocked_send_frames.append(frame)
                self.accept_task = False
                return
            else:
                self.remote_window_size -= len(frame.data)
                self.send_queue.put(frame)
        else:
            self.send_queue.put(frame)

    def _close_stream_cb(self, stream_id, reason):
        # call by stream to remove from streams list
        # self.logger.debug("%s close stream:%d %s", self.ssl_sock.ip, stream_id, reason)
        try:
            del self.streams[stream_id]
        except KeyError:
            pass

        if self.keep_running and \
                len(self.streams) < self.config.http2_max_concurrent and \
                self.remote_window_size > 10000:
            self.accept_task = True
            self.idle_cb()

        self.processed_tasks += 1

    def _consume_single_frame(self):
        try:
            header = self._sock.recv(9)
        except Exception as e:
            self.logger.debug("%s _consume_single_frame:%r, inactive time:%d",
                              self.ip, e,
                              time.time() - self.last_recv_time)
            self.close("ConnectionReset:%r" % e)
            return
        self.last_recv_time = time.time()

        # Parse the header. We can use the returned memoryview directly here.
        frame, length = Frame.parse_frame_header(header)

        if length > FRAME_MAX_ALLOWED_LEN:
            self.logger.error(
                "%s Frame size exceeded on stream %d (received: %d, max: %d)",
                self.ip, frame.stream_id, length, FRAME_MAX_LEN)
            # self._send_rst_frame(frame.stream_id, 6) # 6 = FRAME_SIZE_ERROR

        try:
            data = self._recv_payload(length)
        except Exception as e:
            self.close("ConnectionReset:%r" % e)
            return

        self._consume_frame_payload(frame, data)

    def _recv_payload(self, length):
        if not length:
            return memoryview(b'')

        buffer = bytearray(length)
        buffer_view = memoryview(buffer)
        index = 0
        data_length = -1

        # _sock.recv(length) might not read out all data if the given length
        # is very large. So it should be to retrieve from socket repeatedly.
        while length and data_length:
            data = self._sock.recv(length)
            self.last_recv_time = time.time()
            data_length = len(data)
            end = index + data_length
            buffer_view[index:end] = data[:]
            length -= data_length
            index = end

        return buffer_view[:end]

    def _consume_frame_payload(self, frame, data):
        frame.parse_body(data)

        if self.config.http2_show_debug:
            self.logger.debug("%s Recv:%s", self.ip, str(frame))

        # Maintain our flow control window. We do this by delegating to the
        # chosen WindowManager.
        if frame.type == DataFrame.type:

            size = frame.flow_controlled_length
            increment = self.local_window_manager._handle_frame(size)

            if increment < 0:
                self.logger.warn("increment:%d", increment)
            elif increment:
                #self.logger.debug("%s frame size:%d increase win:%d", self.ip, size, increment)
                w = WindowUpdateFrame(0)
                w.window_increment = increment
                self._send_cb(w)

        elif frame.type == PushPromiseFrame.type:
            self.logger.error(
                "%s receive push frame",
                self.ip,
            )

        # Work out to whom this frame should go.
        if frame.stream_id != 0:
            try:
                stream = self.streams[frame.stream_id]
                stream.receive_frame(frame)
            except KeyError as e:
                if frame.type not in [WindowUpdateFrame.type]:
                    self.logger.exception(
                        "%s Unexpected stream identifier %d, frame.type:%s e:%r",
                        self.ip, frame.stream_id, frame, e)
        else:
            self.receive_frame(frame)

    def receive_frame(self, frame):
        if frame.type == WindowUpdateFrame.type:
            # self.logger.debug("WindowUpdateFrame %d", frame.window_increment)
            self.increase_remote_window_size(frame.window_increment)

        elif frame.type == PingFrame.type:
            if 'ACK' in frame.flags:
                ping_time = struct.unpack("!d", frame.opaque_data)[0]
                time_now = time.time()
                rtt = (time_now - ping_time) * 1000
                if rtt < 0:
                    self.logger.error("rtt:%f ping_time:%f now:%f", rtt,
                                      ping_time, time_now)
                self.rtt = rtt
                self.ping_on_way -= 1
                #self.logger.debug("RTT:%d, on_way:%d", self.rtt, self.ping_on_way)
                if self.keep_running and self.ping_on_way == 0:
                    self.accept_task = True
            else:
                # The spec requires us to reply with PING+ACK and identical data.
                p = PingFrame(0)
                p.flags.add('ACK')
                p.opaque_data = frame.opaque_data
                self._send_cb(p)

        elif frame.type == SettingsFrame.type:
            if 'ACK' not in frame.flags:
                # send ACK as soon as possible
                f = SettingsFrame(0)
                f.flags.add('ACK')
                self._send_cb(f)

                # this may trigger send DataFrame blocked by remote window
                self._update_settings(frame)
            else:
                self.accept_task = True
                self.idle_cb()

        elif frame.type == GoAwayFrame.type:
            # If we get GoAway with error code zero, we are doing a graceful
            # shutdown and all is well. Otherwise, throw an exception.

            # If an error occured, try to read the error description from
            # code registry otherwise use the frame's additional data.
            error_string = frame._extra_info()
            time_cost = time.time() - self.last_recv_time
            if frame.additional_data != "session_timed_out":
                self.logger.warn("goaway:%s, t:%d", error_string, time_cost)

            self.close("GoAway:%s inactive time:%d" %
                       (error_string, time_cost))

        elif frame.type == BlockedFrame.type:
            self.logger.warn("%s get BlockedFrame", self.ip)
        elif frame.type in FRAMES:
            # This frame isn't valid at this point.
            #raise ValueError("Unexpected frame %s." % frame)
            self.logger.error("%s Unexpected frame %s.", self.ip, frame)
        else:  # pragma: no cover
            # Unexpected frames belong to extensions. Just drop it on the
            # floor, but log so that users know that something happened.
            self.logger.error("%s Received unknown frame, type %d", self.ip,
                              frame.type)

    def _update_settings(self, frame):
        if SettingsFrame.HEADER_TABLE_SIZE in frame.settings:
            new_size = frame.settings[SettingsFrame.HEADER_TABLE_SIZE]

            self.remote_settings[SettingsFrame.HEADER_TABLE_SIZE] = new_size
            #self.encoder.header_table_size = new_size

        if SettingsFrame.INITIAL_WINDOW_SIZE in frame.settings:
            newsize = frame.settings[SettingsFrame.INITIAL_WINDOW_SIZE]
            oldsize = self.remote_settings[SettingsFrame.INITIAL_WINDOW_SIZE]
            delta = newsize - oldsize

            for stream in self.streams.values():
                stream.remote_window_size += delta

            self.remote_settings[SettingsFrame.INITIAL_WINDOW_SIZE] = newsize

        if SettingsFrame.SETTINGS_MAX_FRAME_SIZE in frame.settings:
            new_size = frame.settings[SettingsFrame.SETTINGS_MAX_FRAME_SIZE]
            if not (FRAME_MAX_LEN <= new_size <= FRAME_MAX_ALLOWED_LEN):
                self.logger.error(
                    "%s Frame size %d is outside of allowed range", self.ip,
                    new_size)

                # Tear the connection down with error code PROTOCOL_ERROR
                self.close("bad max frame size")
                #error_string = ("Advertised frame size %d is outside of range" % (new_size))
                #raise ConnectionError(error_string)
                return

            self.remote_settings[
                SettingsFrame.SETTINGS_MAX_FRAME_SIZE] = new_size

            for stream in self.streams.values():
                stream.max_frame_size += new_size

    def get_trace(self):
        out_list = []
        out_list.append(" continue_timeout:%d" % self.continue_timeout)
        out_list.append(" processed:%d" % self.processed_tasks)
        out_list.append(" h2.stream_num:%d" % len(self.streams))
        out_list.append(" sni:%s, host:%s" %
                        (self.ssl_sock.sni, self.ssl_sock.host))
        return ",".join(out_list)

    def check_active(self, now):
        if not self.keep_running or len(self.streams) == 0:
            return

        for sid in self.streams.keys():
            try:
                stream = self.streams[sid]
                stream.check_timeout(now)
            except:
                pass

        if len(self.streams) > 0 and\
                now - self.last_send_time > 3 and \
                now - self.last_ping_time > self.config.http2_ping_min_interval:

            if self.ping_on_way > 0:
                self.close("active timeout")
                return

            self.send_ping()
Пример #34
0
class HTTP2_worker(HTTP_worker):
    version = "2"

    def __init__(self, ssl_sock, close_cb, retry_task_cb):
        super(HTTP2_worker, self).__init__(ssl_sock, close_cb, retry_task_cb)

        self.max_concurrent = 20
        self.network_buffer_size = 128 * 1024

        # Google http/2 time out is 4 mins.
        ssl_sock.settimeout(240)
        self._sock = BufferedSocket(ssl_sock, self.network_buffer_size)

        self.next_stream_id = 1
        self.streams = {}
        self.last_ping_time = time.time()

        # count ping not ACK
        # increase when send ping
        # decrease when recv ping ack
        # if this in not 0, don't accept request.
        self.ping_on_way = 0

        # request_lock
        self.request_lock = threading.Lock()

        # all send frame must put to this queue
        # then send by send_loop
        # every frame put to this queue must allowed by stream window and connection window
        # any data frame blocked by connection window should put to self.blocked_send_frames
        self.send_queue = Queue.Queue()

        # keep blocked data frame in this buffer
        # which is allowed by stream window but blocked by connection window.
        # They will be sent when connection window open
        self.blocked_send_frames = []

        self.encoder = Encoder()
        self.decoder = Decoder()

        # Values for the settings used on an HTTP/2 connection.
        # will send to remote using Setting Frame
        self.local_settings = {
            SettingsFrame.INITIAL_WINDOW_SIZE: 1 * 1024 * 1024,
            SettingsFrame.SETTINGS_MAX_FRAME_SIZE: 256 * 1024
        }
        self.local_connection_initial_windows = 2 * 1024 * 1024
        self.local_window_manager = FlowControlManager(self.local_connection_initial_windows)

        # changed by server, with SettingFrame
        self.remote_settings = {
            SettingsFrame.INITIAL_WINDOW_SIZE: DEFAULT_WINDOW_SIZE,
            SettingsFrame.SETTINGS_MAX_FRAME_SIZE: DEFAULT_MAX_FRAME,
            SettingsFrame.MAX_CONCURRENT_STREAMS: 100
        }

        self.remote_window_size = DEFAULT_WINDOW_SIZE

        # send Setting frame before accept task.
        self._send_preamble()

        threading.Thread(target=self.send_loop).start()
        threading.Thread(target=self.recv_loop).start()

    def request(self, task):
        # this is the export api
        if len(self.streams) > self.max_concurrent:
            self.accept_task = False

        task.set_state("h2_req")
        self.request_task(task)

    def request_task(self, task):
        with self.request_lock:
            # create stream to process task
            stream_id = self.next_stream_id

            # http/2 client use odd stream_id
            self.next_stream_id += 2

        stream = Stream(self, self.ip, stream_id, self.ssl_sock.host, task,
                    self._send_cb, self._close_stream_cb, self.encoder, self.decoder,
                    FlowControlManager(self.local_settings[SettingsFrame.INITIAL_WINDOW_SIZE]),
                    self.remote_settings[SettingsFrame.INITIAL_WINDOW_SIZE],
                    self.remote_settings[SettingsFrame.SETTINGS_MAX_FRAME_SIZE])
        self.streams[stream_id] = stream

    def send_loop(self):
        while connect_control.keep_running and self.keep_running:
            frame = self.send_queue.get(True)
            if not frame:
                # None frame to exist
                break

            # xlog.debug("%s Send:%s", self.ip, str(frame))
            data = frame.serialize()
            try:
                self._sock.send(data, flush=False)
                # don't flush for small package
                # reduce send api call

                # wait for payload frame
                time.sleep(0.001)
                # combine header and payload in one tcp package.
                if not self.send_queue._qsize():
                    self._sock.flush()
            except socket.error as e:
                if e.errno not in (errno.EPIPE, errno.ECONNRESET):
                    xlog.warn("%s http2 send fail:%r", self.ip, e)
                else:
                    xlog.exceptiong("send error:%r", e)

                self.close("send fail:%r", e)

    def recv_loop(self):
        while connect_control.keep_running and self.keep_running:
            try:
                self._consume_single_frame()
            except Exception as e:
                xlog.exception("recv fail:%r", e)
                self.close("recv fail:%r" % e)

    def get_rtt_rate(self):
        return self.rtt + len(self.streams) * 100

    def close(self, reason=""):
        self.keep_running = False
        # Notify loop to exit
        # This function may be call by out side http2
        # When gae_proxy found the appid or ip is wrong
        self.send_queue.put(None)

        for stream in self.streams.values():
            if stream.get_head_time:
                # after get header,
                # response have send to client
                # can't retry
                stream.close(reason=reason)
            else:
                self.retry_task_cb(stream.task)
        super(HTTP2_worker, self).close(reason)

    def send_ping(self):
        p = PingFrame(0)
        p.opaque_data = struct.pack("!d", time.time())
        self.send_queue.put(p)
        self.last_ping_time = time.time()
        self.ping_on_way += 1

    def _send_preamble(self):
        self._sock.send(b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n')
        f = SettingsFrame(0)
        f.settings[SettingsFrame.ENABLE_PUSH] = 0
        f.settings[SettingsFrame.INITIAL_WINDOW_SIZE] = self.local_settings[SettingsFrame.INITIAL_WINDOW_SIZE]
        f.settings[SettingsFrame.SETTINGS_MAX_FRAME_SIZE] = self.local_settings[SettingsFrame.SETTINGS_MAX_FRAME_SIZE]
        self._send_cb(f)

        # update local connection windows size
        f = WindowUpdateFrame(0)
        f.window_increment = self.local_connection_initial_windows - DEFAULT_WINDOW_SIZE
        self._send_cb(f)

    def increase_remote_window_size(self, inc_size):
        # check and send blocked frames if window allow
        self.remote_window_size += inc_size
        #xlog.debug("%s increase send win:%d result:%d", self.ip, inc_size, self.remote_window_size)
        while len(self.blocked_send_frames):
            frame = self.blocked_send_frames[0]
            if len(frame.data) > self.remote_window_size:
                return

            self.remote_window_size -= len(frame.data)
            self.send_queue.put(frame)
            self.blocked_send_frames.pop(0)

    def _send_cb(self, frame):
        # can called by stream
        # put to send_blocked if connection window not allow,
        if frame.type == DataFrame.type:
            if len(frame.data) > self.remote_window_size:
                self.blocked_send_frames.append(frame)
                self.accept_task = False
                return
            else:
                self.remote_window_size -= len(frame.data)
                self.send_queue.put(frame)
        else:
            self.send_queue.put(frame)

    def _close_stream_cb(self, stream_id, reason):
        # call by stream to remove from streams list
        #xlog.debug("%s close stream:%d %s", self.ssl_sock.ip, stream_id, reason)
        try:
            del self.streams[stream_id]
        except KeyError:
            pass

        if self.keep_running and len(self.streams) < self.max_concurrent and self.remote_window_size > 10000:
            self.accept_task = True

        self.processed_tasks += 1

    def _consume_single_frame(self):
        try:
            header = self._sock.recv(9)
        except Exception as e:
            xlog.warn("%s _consume_single_frame:%r", self.ip, e)
            self.close("disconnect:%r" % e)
            return

        # Parse the header. We can use the returned memoryview directly here.
        frame, length = Frame.parse_frame_header(header)

        if length > FRAME_MAX_LEN:
            xlog.error("Frame size exceeded on stream %d (received: %d, max: %d)",
                frame.stream_id, length, FRAME_MAX_LEN)
            # self._send_rst_frame(frame.stream_id, 6) # 6 = FRAME_SIZE_ERROR

        data = self._recv_payload(length)
        self._consume_frame_payload(frame, data)

    def _recv_payload(self, length):
        if not length:
            return memoryview(b'')

        buffer = bytearray(length)
        buffer_view = memoryview(buffer)
        index = 0
        data_length = -1
        # _sock.recv(length) might not read out all data if the given length
        # is very large. So it should be to retrieve from socket repeatedly.
        while length and data_length:
            data = self._sock.recv(length)
            data_length = len(data)
            end = index + data_length
            buffer_view[index:end] = data[:]
            length -= data_length
            index = end

        return buffer_view[:end]

    def _consume_frame_payload(self, frame, data):
        frame.parse_body(data)

        # xlog.debug("%s Recv:%s", self.ip, str(frame))

        # Maintain our flow control window. We do this by delegating to the
        # chosen WindowManager.
        if frame.type == DataFrame.type:

            size = frame.flow_controlled_length
            increment = self.local_window_manager._handle_frame(size)

            if increment:
                #xlog.debug("%s frame size:%d increase win:%d", self.ip, size, increment)
                w = WindowUpdateFrame(0)
                w.window_increment = increment
                self._send_cb(w)

        elif frame.type == PushPromiseFrame.type:
            xlog.error("%s receive push frame", self.ip,)

        # Work out to whom this frame should go.
        if frame.stream_id != 0:
            try:
                self.streams[frame.stream_id].receive_frame(frame)
            except KeyError:
                xlog.error("%s Unexpected stream identifier %d", self.ip, frame.stream_id)
        else:
            self.receive_frame(frame)

    def receive_frame(self, frame):
        if frame.type == WindowUpdateFrame.type:
            self.increase_remote_window_size(frame.window_increment)

        elif frame.type == PingFrame.type:
            if 'ACK' in frame.flags:
                ping_time = struct.unpack("!d", frame.opaque_data)[0]
                time_now = time.time()
                rtt = (time_now - ping_time) * 1000
                if rtt < 0:
                    xlog.error("rtt:%f ping_time:%f now:%f", rtt, ping_time, time_now)
                self.rtt = rtt
                self.ping_on_way -= 1
                #xlog.debug("RTT:%d, on_way:%d", self.rtt, self.ping_on_way)
                if self.keep_running and self.ping_on_way == 0:
                    self.accept_task = True
            else:
                # The spec requires us to reply with PING+ACK and identical data.
                p = PingFrame(0)
                p.flags.add('ACK')
                p.opaque_data = frame.opaque_data
                self._send_cb(p)

        elif frame.type == SettingsFrame.type:
            if 'ACK' not in frame.flags:
                # send ACK as soon as possible
                f = SettingsFrame(0)
                f.flags.add('ACK')
                self._send_cb(f)

                # this may trigger send DataFrame blocked by remote window
                self._update_settings(frame)

        elif frame.type == GoAwayFrame.type:
            # If we get GoAway with error code zero, we are doing a graceful
            # shutdown and all is well. Otherwise, throw an exception.

            # If an error occured, try to read the error description from
            # code registry otherwise use the frame's additional data.
            error_string = frame._extra_info()
            if frame.additional_data != "session_timed_out":
                xlog.warn("goaway:%s", error_string)

            self.close("GoAway:%s" % error_string)

        elif frame.type == BlockedFrame.type:
            xlog.warn("%s get BlockedFrame", self.ip)
        elif frame.type in FRAMES:
            # This frame isn't valid at this point.
            #raise ValueError("Unexpected frame %s." % frame)
            xlog.error("%s Unexpected frame %s.", self.ip, frame)
        else:  # pragma: no cover
            # Unexpected frames belong to extensions. Just drop it on the
            # floor, but log so that users know that something happened.
            xlog.error("%s Received unknown frame, type %d", self.ip, frame.type)

    def _update_settings(self, frame):

        if SettingsFrame.HEADER_TABLE_SIZE in frame.settings:
            new_size = frame.settings[SettingsFrame.HEADER_TABLE_SIZE]

            self.remote_settings[SettingsFrame.HEADER_TABLE_SIZE] = new_size
            self.encoder.header_table_size = new_size

        if SettingsFrame.INITIAL_WINDOW_SIZE in frame.settings:
            newsize = frame.settings[SettingsFrame.INITIAL_WINDOW_SIZE]
            oldsize = self.remote_settings[SettingsFrame.INITIAL_WINDOW_SIZE]
            delta = newsize - oldsize

            for stream in self.streams.values():
                stream.remote_window_size += delta

            self.remote_settings[SettingsFrame.INITIAL_WINDOW_SIZE] = newsize

        if SettingsFrame.SETTINGS_MAX_FRAME_SIZE in frame.settings:
            new_size = frame.settings[SettingsFrame.SETTINGS_MAX_FRAME_SIZE]
            if not (FRAME_MAX_LEN <= new_size <= FRAME_MAX_ALLOWED_LEN):
                xlog.error("%s Frame size %d is outside of allowed range", self.ip, new_size)

                # Tear the connection down with error code PROTOCOL_ERROR
                self.close("bad max frame size")
                #error_string = ("Advertised frame size %d is outside of range" % (new_size))
                #raise ConnectionError(error_string)
                return

            self.remote_settings[SettingsFrame.SETTINGS_MAX_FRAME_SIZE] = new_size

            for stream in self.streams.values():
                stream.max_frame_size += new_size
Пример #35
0
    def test_socket_fill_raises_connection_errors(self):
        s = DummySocket()
        b = BufferedSocket(s)

        with pytest.raises(ConnectionResetError):
            b.fill()
Пример #36
0
    def test_socket_fill_raises_connection_errors(self):
        s = DummySocket()
        b = BufferedSocket(s)

        with pytest.raises(ConnectionResetError):
            b.fill()