在Angular 2+客户端和Django后端之间打开WebSocket连接

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Angular 2+客户端和Django后端之间打开WebSocket连接相关的知识,希望对你有一定的参考价值。

我正在尝试使用Django通道从我的Angular 2+应用程序和Django后端打开websocket连接。我完成了本教程:https://www.youtube.com/watch?v=RVH05S1qab8并设法使所有与javascript部分内嵌在Django html模板中的代码一起工作。但是,当我将前端聊天表单迁移到单独的angular 2应用程序时,我只是简单地打开了一个websocket连接。字体和后端都在本地托管。

前端-聊天表单模板

<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>

前端-聊天表单组件

  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 Websocket路由

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)
                ]
            )
        )
    )
)

Django Project Urls

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

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

Django WebSocket使用者

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)

Django Chat Urls

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)),
]

预期输出我期待根据消费者类中的websocket_connect方法在控制台上打印事件信息的“ CONNECTED”消息。类似地,从Angular 2+ chat.component.ts在浏览器的控制台中触发了带有事件的“打开”消息。

实际输出

我在浏览器控制台上立即收到此警告消息:

与'ws:// localhost:4200 / sockjs-node / 344 / ux0z32ma / websocket的WebSocket连接失败:在建立连接之前,WebSocket已关闭。

[等待约2分钟后...Attached image shows what is automatically output on the console.

答案

我可能错过了一些东西,因为我看不到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上公开该端口不适用,尽管我非常确定这不是您的情况。

以上是关于在Angular 2+客户端和Django后端之间打开WebSocket连接的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2 前端 django 2 REST 框架后端用户身份验证

Angular 2登录到Django Rest Framework后端

Angular2 和 Django:CSRF 令牌头痛

在一个仓库中推送和维护后端(Django)和前端(Angular)文件夹

Django Channels:如何刷新发送缓冲区

django 后端和角度前端向后端发送请求