Открытие соединения WebSocket между клиентом Angular 2+ и бэкэндом Django - PullRequest
0 голосов
/ 25 октября 2019

Я пытаюсь открыть соединение через веб-сокет из моего углового приложения 2+ с моим бэкэндом Django, используя Django Channels. Я прошел этот урок: https://www.youtube.com/watch?v=RVH05S1qab8 и сумел заставить все работать с частью JavaScript, встроенной в HTML-шаблон Django. Но у меня возникают проблемы с простым открытием соединения через веб-сокет при переносе формы чата переднего плана в отдельное угловое приложение. И шрифт, и внутренний интерфейс размещаются локально.

Front End - шаблон формы чата

<div *ngFor="let message of thread.messages">
  <div>{{message.message}}</div>
</div>

<form #formData [formGroup]="form">
  <div class="form-group">
    <input formControlName="messageInput" #msgInput type="text" class="form-control" id="newMessage" placeholder="Type a message">
  </div>
  <button (click)="onSubmit($event)" type="submit" class="btn btn-primary">Send</button>
</form>
</div>

Front End - компонент формы чата

  otherUser: User;
  me: User;
  threadId: number;
  thread: Thread;
  endpoint: string;
  socket: WebSocket;

  form = new FormGroup({
    messageInput: new FormControl("")
  });

  get messageInput() {
    return this.form.get('messageInput');
  }


  getThreadById(id: number) {
    this._chatService.getThreadById(id).subscribe(thread => {
      console.log("response: thread found", thread);
      this.thread = thread;
    },
      error => {
        console.log("error", error);
      });
  }

  getEndpoint() {
    let loc = window.location
    let wsStart = "ws://"
    if (loc.protocol == "https:") {
      wsStart = 'wss://'
    }
    this.endpoint = wsStart + loc.host + loc.pathname;
    return this.endpoint;
  }


  constructor(private _activatedRoute: ActivatedRoute) {
  }

  ngOnInit() {
    this._activatedRoute.params.subscribe(params => { this.threadId = params['id']; });
    if (this.threadId) {
      this.getThreadById(this.threadId);
    }
    this.getEndpoint();
    this.createSocket();
  }

  createSocket() {
    this.socket = new WebSocket(this.endpoint);
    console.log("endpoint ", this.endpoint);

    this.socket.onopen = (e) => {
      console.log("open", e);
    }

    this.socket.onmessage = (e) => {
      console.log("message", e);
      let chatDataMsg = JSON.parse(e.data);
      this.thread.messages.push(chatDataMsg.message);
    }

    this.socket.onerror = (e) => {
      console.log("error", e);
    }

    this.socket.onclose = (e) => {
      console.log("close", e);
    }

  }

  onSubmit($event) {
    let msgText = this.messageInput.value;
    let finalData = {
      'message': msgText
    };
    let chatMessage: ChatMessage = new ChatMessage(this.thread, this.me, new Date(), msgText);
    this.socket.send(JSON.stringify(finalData))
    this.form.reset();
  }

Настройки внутреннего проекта Django

ASGI_APPLICATION = "joole.routing.application"

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("localhost", 6379)],
            #"hosts": [os.environ.get('REDIS_URL', 'redis://localhost:6379')]
        },
    },
}


ALLOWED_HOSTS = ['joole-api.herokuapp.com', '.herokuapp.com', '127.0.0.1']

Маршрутизация веб-сокета Django

from channels.routing import ProtocolTypeRouter, URLRouter
from django.conf.urls import url
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator, OriginValidator
from chat.consumers import ChatConsumer

application = ProtocolTypeRouter({
    'websocket': AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter(
                [
                    url(r"^messages/(?P<id>[\w.@+-]+)/$", ChatConsumer)
                ]
            )
        )
    )
})

Проект DjangoURL

from django.urls import path, include
from django.conf import settings

urlpatterns = [
    path('messages/', include('chat.urls')),
]

Django WebSocket Consumer

import asyncio
import json
from users.models import CustomUser, Employee
from channels.consumer import AsyncConsumer
from channels.db import database_sync_to_async
from .models import Thread, ChatMessage


class ChatConsumer(AsyncConsumer):

    async def websocket_connect(self, event):
        print("CONNECTED", event)

        thread_id = self.scope['url_route']['kwargs']['id']
        thread_obj = await self.get_thread(thread_id)
        self.thread_obj = thread_obj
        chat_room = f"thread_{thread_obj.id}"
        self.chat_room = chat_room
        await self.channel_layer.group_add(
            chat_room,
            self.channel_name
        )
        await self.send({
            "type": 'websocket.accept'
        })

    async def websocket_receive(self, event):
        print("receive", event)
        front_text = event.get('text', None)
        if front_text is not None:
            loaded_dict_data = json.loads(front_text)
            msg = loaded_dict_data.get('message')
            user = self.scope['user']
            username = 'default'
            if user.is_authenticated:
                username = user.email
            myResponse = {
                'message': msg,
                'username': user.email
            }
            await self.create_chat_message(user, msg)
            # broadcasts the message event to be sent
            await self.channel_layer.group_send(
                self.chat_room,
                {
                    "type": "chat_message",
                    "text": json.dumps(myResponse)
                }
            )

    async def chat_message(self, event):
        # send the actual message event
        print("message", event)
        await self.send({
            "type": "websocket.send",
            "text": event["text"]
        })

    async def websocket_disconnect(self, event):
        print("disconnected", event)

    @database_sync_to_async
    def get_thread(self, id):
        return Thread.objects.get(id=id)

    @database_sync_to_async
    def create_chat_message(self, me, msg):
        thread_obj = self.thread_obj
        return ChatMessage.objects.create(thread=thread_obj, user=me, message=msg)

Модели чата Django

class Thread(models.Model):
    first = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chat_thread_first')
    second = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chat_thread_second')
    updated = models.DateTimeField(auto_now=True)
    timestamp = models.DateTimeField(auto_now_add=True)

    @property
    def room_group_name(self):
        return f'chat_{self.id}'

    def broadcast(self, msg=None):
        if msg is not None:
            broadcast_msg_to_chat(msg, group_name=self.room_group_name, user='admin')
            return True
        return False

class ChatMessage(models.Model):
    thread = models.ForeignKey(Thread, null=True, blank=True, on_delete=models.SET_NULL, related_name="messages")
    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='sender', on_delete=models.CASCADE)
    message = models.TextField()
    timestamp = models.DateTimeField(auto_now_add=True)

URL-адреса чата Django

from django.urls import include, path
from rest_framework import routers
from .views import ThreadViewSet, ChatMessageViewSet

router = routers.DefaultRouter()
router.register('thread', ThreadViewSet)
router.register('chatMessage', ChatMessageViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Ожидаемый вывод Я ожидал сообщение "CONNECTED" с информацией о событии, напечатанной на консоли в соответствии с методом websocket_connect в классе потребителей,Точно так же «открытое» сообщение с событием, запущенным в консоли моего браузера от Angular 2+ chat.component.ts.

Фактический вывод

Я немедленно получаю это предупреждениесообщение на консоли браузера:

Сбой соединения WebSocket с 'ws: // localhost: 4200 / sockjs-node / 344 / ux0z32ma / websocket': WebSocket закрывается до установления соединения.

Примерно через 2 минуты ожидания ... Прикрепленное изображение показывает, что автоматически выводится на консоль.

1 Ответ

0 голосов
/ 26 октября 2019

Возможно, я что-то пропустил, потому что не вижу конфигурации Django, но, насколько я понял, вы написали, что вы используете внешний и внутренний сервер отдельно. Вы можете видеть, что веб-интерфейс пытается установить соединение с localhost:4200. Я считаю, что это угловой сервер, который не имеет смысла, вы должны указать свой WebSocket на приложение Django, поэтому, по моему мнению, вы должны изменить метод ниже:

SERVER_URL = "localhost:8000"

getEndpoint() {
  let wsStart = "ws://"
  if (window.location.protocol == "https:") {
    wsStart = 'wss://'
  }
  this.endpoint = wsStart + SERVER_URL + window.location.pathname;
  return this.endpoint;
}

Но если вы хостинги приложения на Django, и показ на порт 4200, который не применяется, хотя я совершенно уверен, что это не ваш случай.

...