async def open(self, session, backoff_delay=0): # exponential backoff for consequent connections if necessary if backoff_delay: await sleep(backoff_delay) if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'connecting to', self.url, 'with timeout', self.connectionTimeout, 'ms') self.connectionStarted = Exchange.milliseconds() try: coroutine = self.create_connection(session) self.connection = await wait_for( coroutine, timeout=int(self.connectionTimeout / 1000)) self.connecting = False if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'connected') self.connected.resolve(self.url) # run both loops forever await gather(self.ping_loop(), self.receive_loop()) except TimeoutError as e: # connection timeout error = RequestTimeout('Connection timeout') if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'RequestTimeout', error) self.on_error(error) except Exception as e: # connection failed or rejected (ConnectionRefusedError, ClientConnectorError) error = NetworkError(e) if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'NetworkError', error) self.on_error(error)
async def receive_loop(self): if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'receive loop') while not self.closed(): try: message = await self.receive() # self.print(Exchange.iso8601(Exchange.milliseconds()), 'received', message) self.handle_message(message) except Exception as e: error = NetworkError(str(e)) if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'receive_loop', 'Exception', error) self.reset(error)
def on_close(self, code): if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'on_close', code) if not self.error: self.reset(NetworkError(code)) self.on_close_callback(self, code) if not self.closed(): ensure_future(self.close(code))
def on_error(self, error): if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'on_error', error) self.error = error self.reset(error) self.on_error_callback(self, error) if not self.closed(): ensure_future(self.close(1006))
def handle_text_or_binary_message(self, data): if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'message', data) if isinstance(data, bytes): data = data.decode() decoded = json.loads(data) if Exchange.is_json_encoded_object( data) else data self.on_message_callback(self, decoded)
def handle_message(self, message): # self.print(Exchange.iso8601(Exchange.milliseconds()), message) if message.type == WSMsgType.TEXT: self.handle_text_or_binary_message(message.data) elif message.type == WSMsgType.BINARY: data = message.data if self.gunzip: data = gunzip(data) elif self.inflate: data = inflate(data) self.handle_text_or_binary_message(data) # autoping is responsible for automatically replying with pong # to a ping incoming from a server, we have to disable autoping # with aiohttp's websockets and respond with pong manually # otherwise aiohttp's websockets client won't trigger WSMsgType.PONG elif message.type == WSMsgType.PING: if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'ping', message) ensure_future(self.connection.pong()) elif message.type == WSMsgType.PONG: self.lastPong = Exchange.milliseconds() if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'pong', message) pass elif message.type == WSMsgType.CLOSE: if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'close', self.closed(), message) self.on_close(message.data) elif message.type == WSMsgType.CLOSED: if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'closed', self.closed(), message) self.on_close(1000) elif message.type == WSMsgType.ERROR: if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'error', message) error = NetworkError(str(message)) self.on_error(error)
def on_close(self, code): if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'on_close', code) if not self.error: self.reset( NetworkError( 'Connection closed by remote server, closing code ' + str(code))) self.on_close_callback(self, code) if not self.closed(): ensure_future(self.close(code))
async def ping_loop(self): if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'ping loop') while self.keepAlive and not self.closed(): now = Exchange.milliseconds() self.lastPong = now if self.lastPong is None else self.lastPong if (self.lastPong + self.keepAlive * self.maxPingPongMisses) < now: self.on_error( RequestTimeout( 'Connection to ' + self.url + ' timed out due to a ping-pong keepalive missing on time' )) # the following ping-clause is not necessary with aiohttp's built-in ws # since it has a heartbeat option (see create_connection above) # however some exchanges require a text-type ping message # therefore we need this clause anyway else: if self.ping: await self.send(self.ping(self)) else: await self.connection.ping() await sleep(self.keepAlive / 1000)
async def close(self, code=1000): if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'closing', code) if not self.closed(): await self.connection.close()
def send(self, message): if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'sending', message) return self.connection.send_str(message if isinstance( message, str) else json.dumps(message, separators=(',', ':')))
async def ping_loop(self): if self.verbose: self.print(Exchange.iso8601(Exchange.milliseconds()), 'ping loop') pass