How can I add a user whose ID I know to a Django Channels channel_layer group?

心已入冬 提交于 2019-12-13 02:55:34

问题


I am writing a web-application using websockets from django-channels. In which:

  1. Customer users can make orders from store users.
  2. When a user requests an order via their communicator, I create a channel group using the order's id.
  3. The store related to this order should be added to this channel group as well. This is what I'm struggling to achieve.

This logic can be seen in my pytest below:

async def test_store_is_added_to_order_group_on_create(self, settings):
    customer = await create_customer()
    store = await create_store()
    product = await create_product(store=store)

    store_communicator = await auth_connect(store)

    # Log in as customer and create order
    customer_communicator = await auth_connect(user)
    await communicator.send_json_to({
        'type': 'create.order',
        'data': {
            'product': str(product.id),
            'customer': user.id
        }
    })
    response = await communicator.receive_json_from()
    data = response.get('data')
    await communicator.disconnect()

    # Send JSON message to new order's group.
    order_id = data['id']
    message = {
        'type': 'echo.message',
        'data': 'This is a test message.'
    }
    channel_layer = get_channel_layer()
    await channel_layer.group_send(order_id, message=message)

    # Store should receive JSON message from server.
    response = await store_communicator.receive_json_from()

    assert_equal(message, response)

    await communicator.disconnect()

The communicator function create_order should create a new channel_layer group to which both the customer and store should be added.

async def create_order(self, event):
    order = await self._create_order(event.get('data'))
    order_id = f'{order.id}'

    # Add the customer to a group identified by order's key value.
    await self.channel_layer.group_add(
        group=order_id,
        channel=self.channel_name
    )

    # Confirm creation to customer.
    await self.send_json({
        'type': 'MESSAGE',
        'data': order_data
    })

Ideally, I want to call group_add with channel set to the store communicator's channel here but I do not know the store's channel name. Is there a way to know the store's channel name from the user's communicator instance?


I have made a workaround in which I send a message to the store's communicator BEFORE the above send_json as follows:

    # HACK: Send notification to store
    await self.channel_layer.group_send(group=store_group, message={
        'type': 'notify.store',
        'data': order_data
    })

This calls a second simple communicator function for the store:

async def notify_store(self, event):
    data = event.get('data')
    await self.channel_layer.group_add(
        group=data['id'],
        channel=self.channel_name
    )

This workaround does not work. The store is added to the group after the second echo.message is sent. The await self.channel_layer.group_send... does not wait until notify_store has been executed for the second communicator. Is there a way to guarantee this behaviour?


Or is there a completely different way to approach adding to different user's to a channel_layer group from one user's communicator?

Thank you for your time.


回答1:


I think you have the idea of what you need to do. You can have a group for the store and it's only job is to get messages that say subscribe to this order group.

However, there are a few things that you aren't considering like what if the store isn't online. Then your customer's connection will block forever. Blocking on a future like that isn't very common with asyncio. If you want to do that you need to create a task. Normally you wouldn't wait for another user's session to send a response back. Make it event based and give control back to the event loop as soon as possible.

Look at doing it more event based like this:

  • Send the message telling the store to subscribe to the order id.
  • Either in JavaScript setTimeout or an asyncio task with an asyncio sleep have it eventually time out and unsubscribe from all groups and disconnect with an error message.
  • Have the store send a message to the order group that it subscribed. If you get the message from the store cancel the timeout and begin communicating.

You'll find that this stuff is kind of hard to test especially if you're testing for responses to be received. Each message is like a new request. Channels really needs a better testing framework. I have hardly any tests in my channels code because of this.



来源:https://stackoverflow.com/questions/57485030/how-can-i-add-a-user-whose-id-i-know-to-a-django-channels-channel-layer-group

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!