Channels: HTTP/WS send decode error: 'dict' object has no attribute 'encode'

Created on 30 May 2017  路  5Comments  路  Source: django/channels

It appears that Group('channel').send() is trying to call encode() on dict objects, rather than the inside strs. runserver reports the following error:

ERROR - server - HTTP/WS send decode error: 'dict' object has no attribute 'encode'

Sending an empty dict doesn't error out, but an actual (JSON-serializable) dict causes this error. To try to track it down, I subclassed dict to add my own encode() method:

class MyDict(dict):
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            self[k] = v

    def encode(self, *args, **kwargs):
        print('args: {}\tkwargs: {}'.format(args, kwargs))
        return self

The print statement shows that it's getting called as the_dict.encode('utf-8'). Is it trying to call the method on some part of the dict? All the docs say is that send() expects a dict, and when I tried passing in a str, I got an exception to the same effect. Yet, it's calling a method which doesn't exist on a normal dict.

My environment:

  • Ubuntu 16.04
  • Python 3.5.2
  • Software versions:

    • asgiref (1.1.2)

    • channels (1.1.3)

    • daphne (1.2.0)

    • Django (1.11.1)

    • Twisted (17.1.0)

    • ASGI backend: Just the default in-process one for now

  • What you expected to happen vs. what actually happened: I expected to be able to exchange messages without errors.
  • How you're running Channels: runserver
  • Console logs and full tracebacks of any errors: See above. No tracebacks were generated, unfortunately.
blockeuser-response bug

Most helpful comment

@SazhnevWIS: You can send arbitrary things by putting them in the dict:
my_data_dict = {"user": {"pk": user.pk, "name": user.name}} Group('somegroup').send({'text': json.dumps(my_data_dict)})
You could also use django's or drf's serializers.

All 5 comments

So this is happening somewhere in Daphne, and I suspect it is that one of the keys in the dictionary is a dictionary when it is not expected to be. Could you give an example dump of the message content you are trying to send that is causing the error?

You're right that one of the keys is a dictionary. Here's a sample message:

{'text': {'sync': True, 'type': 'change_slide', 'data': 1}}

It's that way because I get another error if I send just the data. If I send {'type': 'change_slide', 'sync': True, 'data': 1}, I get:

ERROR - server - HTTP/WS send decode error: Got invalid WebSocket reply message on daphne.response.SYyQvGyXkF!KxwjKNQMqk - contains unknown keys {'data', 'type', 'sync'} (looking for either {'accept', 'text', 'bytes', 'close'})

This is despite the fact that the docs say of Group.send:

send(content): Sends the dict provided as _content_ to all members of the group.

This is all quite frustrating, as according to the documentation, the only requirement is that I supply a dict. But this is quite clearly incorrect. I don't understand why I should have to speak an undocumented protocol. After all, Django doesn't require me to speak HTTP. On the JavaScript side, I can dump any JSON data into the socket.send method and it'll happily send it to the other side. Why should Channels be so picky?

Following the lead you gave in your reply, I tried sending

Group('somegroup').send({'text': json.dumps(my_data_dict)})

This didn't error out, although the data structure I received out the other end of the pipe was different from what I put into the pipe. (The output only contained my_data_dict.) So, at least I finally have some working code.

In summary, then, there are two issues:

  1. The bug I initially reported, whereby Daphne was trying to call encode on a dict, assuming that it would be a str
  2. The fact that the documentation says only that I need to pass in a dict, and says nothing about a required format for that dictionary.

Ah, yes, you need to send textual data under the text key - all the examples and docs try to show you this, and the spec makes it clear: http://channels.readthedocs.io/en/latest/asgi/www.html

There's nothing we can really do programatically here, apart from maybe a better error in Daphne when the types mismatch - I'll look into getting that in there.

What do I do to send not only text, but also some extra data (user, time of creating message, etc.)?

@SazhnevWIS: You can send arbitrary things by putting them in the dict:
my_data_dict = {"user": {"pk": user.pk, "name": user.name}} Group('somegroup').send({'text': json.dumps(my_data_dict)})
You could also use django's or drf's serializers.

Was this page helpful?
0 / 5 - 0 ratings