8

I'm currently trying to create a backend server to communicate with some clients with a websocket. The clients makes some request to the backend and the backend responds directly to the client through a consumer.

In addition, I've got an API that needs to send some requests to the client. It has to go through the opened socket of the consumer. I'm using Django Rest Framework for the API. So I've got 2 apps for now. One for the consumer and one for the API. I want to know if it's the correct way or not.

This is actually the code I'm thinking about right now:

# mybackendapp/consumers.py

class MyConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.client_id = self.scope['url_route']['kwargs']['client_id']
        # This line I don't get it very well. It comes from:
        # [channels doc: single channels][1]
        # I don't know if I should create the Clients myself or if it's 
        # created automatically
        Clients.objects.create(channel_name=self.channel_name, 
                               self.client_id)
        self.accept()

    async def disconnect(self):
         Clients.objects.filter(channel_name=self.channel_name).delete()

    async def receive(self, text_data):
        self.recv_data = json.loads(text_data)
        if self.recv_data[0] == CLIENT_REQUEST:
            self.handler = ClientRequestHandler(self.client_id, 
                                                self.recv_data)
            await self.handler.run()
            self.sent_data = self.handler.response
            self.send(self.sent_data)
        elif self.recv_data[0] == CLIENT_RESPONSE:
            self.handler = ClientResponseHandler(self.client_id,
                                                 self.recv_data)
            channel_layer = get_channel_layer()
            # Here I'm not sure but I may have several API requests so
            # several row with the same client_id.
            # I welcome info on how to deal with that.
            api_channel_name = self.another_handler.ext_channel_name
            channel_layer.send(api_channel_name, {
                "text_data": self.handler.response,
            })

    async def message_from_api(self, event):
        self.api_channel_name = event['channel_name_answer']
        # this line is for hiding the fact that I'm only manipulating data
        # to send it to a correct format to the socket
        self.another_handler = ExternalDataHandler(event['json_data'])
        query_to_client = another_handler.get_formatted_query()
        self.send(query_to_client)

In receive, this consumer handles differently the messages from the client depending if it's initiated by the client or the rest API. You can see that with CLIENT_REQUEST and CLIENT_RESPONSE constants. Now from the API view:

# myapi/views.py

from channels.layers import get_channel_layer

def my_api_view(request, client_id):
    channel_layer = get_channel_layer()
    if request.method == 'POST':
        ext_request_data_json = request.data
        client_channel_name = Clients.objects.get(
            client_id=client_id).channel_name
        # I don't know what type is listen_channel_name. I assume it's str
        listen_channel_name = async_to_sync(channels_layers.new_channel)()
        async_to_sync(channel_layer.send)(
            client_channel_name, {
                'type': 'message.from.api',
                'json_data': ext_request_data_json,
                'channel_name_answer': listen_channel_name
            }
        )
        received_msg = channel_layer.receive(listen_channel_name)

I believe that this code should work. I want to know if it's the correct way to do it.

  • This question comes following: https://stackoverflow.com/questions/54329826/api-request-to-already-opened-django-channels-consumer/54334525#54334525 – Guillaume Geoffrey Attia Jan 25 '19 at 08:50
  • 1
    Is there a reason the API shouldn't just return a normal HTTP request to the client? What is the rationale behind sending the response to a HTTP request through a websocket? – Ken4scholars Jan 25 '19 at 13:27
  • I have no control over the client. I'm following the ocpp protocol for charging stations. – Guillaume Geoffrey Attia Jan 25 '19 at 13:53
  • So the protocol requires that the client sends a HTTP requests, then gets a websocket response? That surely is strange specification – Ken4scholars Jan 25 '19 at 14:34
  • Opening a channel between the API and the consumer is definitely not a good idea since they live on the same project. Infact a lot of other things are confusing in your code. Can you send a link to the protocol you're trying to implement? May be easier to understand that way – Ken4scholars Jan 25 '19 at 14:52
  • This is the ocpp1.6 spec: https://www.openchargealliance.org/downloads/ – Guillaume Geoffrey Attia Jan 25 '19 at 15:00
  • And no, the protocol says that the client needs to connect to a websocket (this is my consumer). The charge point (client) can issue request to the server which the consumer reponds to (this part is the easy one). The server can issue some requests to the charge point as well (that's the hard part). I want the requests coming from the server be handled by a restAPI. Then the requests are passed to the client through the consumer and the answers go back to the API through the consumer as well. – Guillaume Geoffrey Attia Jan 25 '19 at 15:07
  • If the protocol requires a duplex communication then you should use websocket all through. That's what it is meant for. Making a request to the REST API and then routing the response back through a consumer makes it very messy while being completely unnecessary – Ken4scholars Jan 26 '19 at 08:57
  • Hmm... Are you aware that the API uses the websocket in both ways ? It never talks directly to the client. If yes, how should I use only the consumer if a get an external request that needs to be asked to the client ? How would I handle such an event inside the consumer ? – Guillaume Geoffrey Attia Jan 26 '19 at 10:02
  • I'm finding it difficult to see the function of the REST API since you can receive the message from websocket and send back a message to the client through it. If you don't know how to send a message to the websocket from the client, then you should really try to follow the basic channels tutorials in their site before going forward. https://channels.readthedocs.io/en/latest/tutorial/part_1.html – Ken4scholars Jan 26 '19 at 14:02
  • Some messages may not come from the client. I've been through this tutorial several times already :) – Guillaume Geoffrey Attia Jan 26 '19 at 19:53
  • The consumer assigns a channel to any client which connects. So even if the messages don't come from an external source you should know how to handle the messages depending on who sent it. Introducing a redundant REST API here won't solve it either. Unfortunately I couldn't read the specification to see what you want achieve because it's too detailed. But it will help many understand your question if you explain what you want to achieve. It's still confusing to understand that from the code – Ken4scholars Jan 26 '19 at 20:36
  • The clients don't need to communicate together. They need to communicate with an external source. How do you do that ? If I want to ask something to a specific client but it's one time request like : "is a car connected and charging on you?" Imagine it's a one shot request. How do I ask the charging station (I repeat: the charging station is a client) that question ? I don't know what to say more... :'( – Guillaume Geoffrey Attia Jan 26 '19 at 22:00
  • First you should structure your question in the context of websocket and consumers. How do you send a message to the client? is that what you're asking? by routing a message through the consumer to the client as I already showed in the previous question. But of course, you can't do this if your client is not connected to the websocket. Overall, I think you should carefully come up with a detail description of your problem and edit your question so that people can help you – Ken4scholars Jan 26 '19 at 22:05
  • I'm so frustrated... I feel I have provided the whole frame already... If I get motivation, I may edit my question but I'm not so sure I'll do it as I believe that my solution is correct and I don't see a better way of doing it. – Guillaume Geoffrey Attia Jan 26 '19 at 22:30
  • Ok, goodluck then! – Ken4scholars Jan 26 '19 at 22:37
  • hi, @GuillaumeGeoffreyAttia did you figure out anything? I am trying to do the same as I want to make a third party API which takes in request from an HTTP request and sends it to channel. In return, the channel sends the processed message. – Hrithik Puri Apr 29 '20 at 11:53
  • It's been too long. I don't remember sorry haha – Guillaume Geoffrey Attia Apr 30 '20 at 12:02

1 Answers1

0

See djangochannelsrestframework as a possible alternative solution.

Django Channels Rest Framework provides a DRF like interface for building channels-v3 websocket consumers.

phoenix
  • 7,988
  • 6
  • 39
  • 45