Search code examples
pythonwebsockettornado

Python websocket object has no attribute 'write_message' error


I got this sample code for python websocket client from here and I am running into a problem below. I edited this code a bit from the original one in couple of places since I was getting into a problem:

  • Removed the connect method and merged it into constructor. I was getting 'self._ws_connection' not found issue.

  • Changed the WebSocketClient __init__()method to accept 'url' and removed the '' argument. I am not sure what passing of '' means.

Problem1:

Traceback (most recent call last):
  File "websocketcli.py", line 138, in <module>
    main()
  File "websocketcli.py", line 129, in main
    client.send('Hello world!')
  File "websocketcli.py", line 49, in send
    self._ws_connection.write_message(escape.utf8(json.dumps(data)))
  File "/users/anjangam/pyvenv/venv/lib/python2.7/site-packages/tornado/websocket.py", line 970, in write_message
    return self.protocol.write_message(message, binary)
AttributeError: 'NoneType' object has no attribute 'write_message'

Problem 2: When I remove the '*' from the WbSocketClient constructor, I get the following error, almost same as the one encountered in problem 1. Note: I am using python version 2.7.8.

Traceback (most recent call last):
  File "websocketcli.py", line 138, in <module>
    main()
  File "websocketcli.py", line 129, in main
    client.send('Hello world!')
  File "websocketcli.py", line 52, in send
    self._ws_connection.write_message(escape.utf8(json.dumps(data)))
  File "/users/anjangam/pyvenv/venv/lib/python2.7/site-packages/tornado/websocket.py", line 970, in write_message
    return self.protocol.write_message(message, binary)
AttributeError: 'NoneType' object has no attribute 'write_message'

Client code:

from tornado import escape
from tornado import gen
from tornado import httpclient
from tornado import httputil
from tornado import ioloop
from tornado import websocket

import functools
import json
import time


APPLICATION_JSON = 'application/json'

DEFAULT_CONNECT_TIMEOUT = 60
DEFAULT_REQUEST_TIMEOUT = 60


class WebSocketClient():
"""Base for web socket clients.
"""
def __init__(self, connect_timeout=DEFAULT_CONNECT_TIMEOUT,
             request_timeout=DEFAULT_REQUEST_TIMEOUT):

    self.connect_timeout = connect_timeout
    self.request_timeout = request_timeout

def connect(self, url):
    """Connect to the server.
    :param str url: server URL.
    """
    headers = httputil.HTTPHeaders({'Content-Type': APPLICATION_JSON})
    request = httpclient.HTTPRequest(url=url,
                                     connect_timeout=self.connect_timeout,
                                     request_timeout=self.request_timeout,
                                     headers=headers)
    self._ws_connection = websocket.WebSocketClientConnection(ioloop.IOLoop.current(),
                                                  request)
    self._ws_connection.connect_future.add_done_callback(self._connect_callback)
    print 'Connection: ', self._ws_connection

    def send(self, data):
        """Send message to the server
        :param str data: message.
        """
        if not self._ws_connection:
            raise RuntimeError('Web socket connection is closed.')

        self._ws_connection.write_message(escape.utf8(json.dumps(data)))

    def close(self):
        """Close connection.
        """

        if not self._ws_connection:
            raise RuntimeError('Web socket connection is already closed.')

        self._ws_connection.close()

    def _connect_callback(self, future):
        if future.exception() is None:
            self._ws_connection = future.result()
            self._on_connection_success()
            self._read_messages()
        else:
            self._on_connection_error(future.exception())

    @gen.coroutine
    def _read_messages(self):
        while True:
            msg = yield self._ws_connection.read_message()
            if msg is None:
                self._on_connection_close()
                break

            self._on_message(msg)

    def _on_message(self, msg):
        """This is called when new message is available from the server.
        :param str msg: server message.
        """

        pass

    def _on_connection_success(self):
        """This is called on successful connection ot the server.
        """

        pass

    def _on_connection_close(self):
        """This is called when server closed the connection.
        """
        pass

    def _on_connection_error(self, exception):
        """This is called in case if connection to the server could
        not established.
        """

        pass


class TestWebSocketClient(WebSocketClient):

    def __init__(self, url):
        WebSocketClient.__init__(self, url)

    def _on_message(self, msg):
        print(msg)
        deadline = time.time() + 1
        ioloop.IOLoop().instance().add_timeout(
            deadline, functools.partial(self.send, str(int(time.time()))))

    def _on_connection_success(self):
        print('Connected!')
        self.send(str(int(time.time())))

    def _on_connection_close(self):
        print('Connection closed!')

    def _on_connection_error(self, exception):
        print('Connection error: %s', exception)


def main():
    client = TestWebSocketClient()
    client.connect('ws://localhost:8888/ws')
    client.send('Hello world!')


    try:
        ioloop.IOLoop.instance().start()
    except KeyboardInterrupt:
        client.close()


if __name__ == '__main__':
    main()

Solution

  • The original code you referenced works just fine on python 3.5, however I suspect you are using some version of python 2. The reason it would break would be due to the asterisk you mentioned, which is not a feature of python 2. PEP 3102 talks about this, but in short that asterisk will just force function calls to pass in keyword arguments as opposed to relying on the order that the parameters were supplied in.

    So take for example the following functions:

    # we can use positional arguments on this one
    def test(a, b):
        print(a, b)
    
    # this will require keyword arguments to be supplied on function calls
    def test_keywords_args_required(*, a, b):
        print(a, b)
    
    test(1, 2) # test can be called using positional arguments
    test_keywords_args_required(1, 2) # this will error out. Needs keywords args!
    test_keywords_args_required(a=1, b=2) # this will work!
    

    If you use the code originally supplied from that link, and remove the asterisk, the code should work (confirmed with python 2.7).