[英]Why an event handler function in consumers.py is called multiple times and then web-socket connection is killed forcefully?
我使用 web-socket 创建了一个实时通知程序,使用 Django-Channels 实现。 每当任何用户喜欢作者的帖子时,它都会向“帖子作者”发送实时通知。
问题是,对于单次点击按钮,多个通知被保存在数据库中。 这是因为在consumer.py中定义的 function (like_notif())在发生“like button click”事件时将通知数据保存到数据库被多次调用(当它应该只被调用一次时),直到 web-socket断开连接。
在接下来的几行中,我将详细介绍幕后实际发生的事情(据我所知)。
<a class="like-btn" id="like-btn-{{ post.pk }}" data-likes="{{ post.likes.count }}" href="{% url 'like_toggle' post.slug %}">
Like
</a>
$('.like-btn').click(function (event) {
event.preventDefault();
var this_ = $(this);
var url = this_.attr('href');
var likesCount = parseInt(this_.attr('data-likes')) || 0;
$.ajax({
url: url,
method: "GET",
data: {},
success: function (json) {
// DOM is manipulated accordingly.
},
error: function (json) {
// Error.
}
});
});
# In urls.py
urlpatterns = [
path('<slug:slug>/like/', views.post_like_toggle, name="like_toggle"),
]
# In views.py
@login_required
def post_like_toggle(request, slug):
"""
Function to like/unlike posts using AJAX.
"""
post = Post.objects.get(slug=slug)
user = request.user
if user in post.likes.all():
# Like removed
post.likes.remove(user)
else:
# Like Added
post.likes.add(user)
user_profile = get_object_or_404(UserProfile, user=user)
# If Post author is not same as user liking the post
if str(user_profile.user) != str(post.author):
# Sending notification to post author is the post is liked.
channel_layer = get_channel_layer()
text_dict = {
# Contains some data in JSON format to be sent.
}
async_to_sync(channel_layer.group_send)(
"like_notif", {
"type": "notif_like",
"text": json.dumps(text_dict),
}
)
response_data = {
# Data sent to AJAX success/error function.
}
return JsonResponse(response_data)
# In consumers.py
class LikeNotificationConsumer(AsyncConsumer):
async def websocket_connect(self, event):
print("connect", event)
await self.channel_layer.group_add("like_notif", self.channel_name)
await self.send({
"type": "websocket.accept",
})
async def websocket_receive(self, event):
print("receive", event)
async def websocket_disconnect(self, event):
print("disconnect", event)
await self.channel_layer.group_discard("like_notif", self.channel_name)
async def notif_like(self, event):
print("like_notif_send", event)
await self.send({
"type": "websocket.send",
"text": event.get('text')
})
json_dict = json.loads(event.get('text'))
recipient_username = json_dict.get('recipient_username')
recipient = await self.get_user(recipient_username)
sender_username = json_dict.get('sender_username')
sender = await self.get_user(sender_username)
post_pk = json_dict.get('post_pk', None)
post = await self.get_post(post_pk)
verb = json_dict.get('verb')
description = json_dict.get('description', None)
data_dict = json_dict.get('data', None)
data = json.dumps(data_dict)
await self.create_notif(recipient, sender, verb, post, description, data)
@database_sync_to_async
def get_user(self, username_):
return User.objects.get(username=username_)
@database_sync_to_async
def get_post(self, pk_):
return Post.objects.get(pk=pk_)
@database_sync_to_async
def create_notif(self, recipient, sender, verb, post=None, description=None, data=None, *args, **kwargs):
return Notification.objects.create(recipient=recipient, sender=sender, post=post, verb=verb, description=description, data=data)
注意: notif_like() function 被多次调用,因此数据被多次保存在数据库中。 为什么这个 function 被多次调用?
<script type="text/javascript">
var loc = window.location
var wsStart = 'ws://'
if(loc.protocol == 'https:') {
wsStart = 'wss://'
}
var endpoint = wsStart + loc.host + "/like_notification/"
var likeSocket = new ReconnectingWebSocket(endpoint)
likeSocket.onmessage = function (e) {
console.log("message LikeNotificationConsumer", e)
var data_dict = JSON.parse(e.data)
/*
DOM manipulation using data from data_dict.
NOTE: All the intended data is coming through perfectly fine.
*/
};
likeSocket.onopen = function (e) {
console.log("opened LikeNotificationConsumer", e)
}
likeSocket.onerror = function (e) {
console.log("error LikeNotificationConsumer", e)
}
likeSocket.onclose = function (e) {
console.log("close LikeNotificationConsumer", e)
}
</script>
# In models.py
class Post(models.Model):
title = models.CharField(max_length=255, blank=False, null=True, verbose_name='Post Title')
post_content = RichTextUploadingField(blank=False, null=True, verbose_name='Post Content')
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
default=None,
blank=True,
null=True
)
likes = models.ManyToManyField(User, blank=True, related_name='post_likes')
published = models.DateTimeField(default=datetime.now, blank=True)
tags = models.ManyToManyField(Tags, verbose_name='Post Tags', blank=True, default=None)
slug = models.SlugField(default='', blank=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.title + str(self.pk))
super(Post, self).save()
def get_like_url(self):
return reverse("like_toggle", (), {'slug', self.slug})
def __str__(self):
return '%s' % self.title
# In models.py
class Notification(models.Model):
recipient = models.ForeignKey(User, blank=False, on_delete=models.CASCADE, related_name="Recipient",)
sender = models.ForeignKey(
User,
blank=False,
on_delete=models.CASCADE,
related_name="Sender"
)
# Post (If notification is attached to some post)
post = models.ForeignKey(
Post,
blank=True,
default=None,
on_delete=models.CASCADE,
related_name="Post",
)
verb = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
data = models.TextField(blank=True, null=True)
timestamp = models.DateTimeField(default=timezone.now)
unread = models.BooleanField(default=True, blank=False, db_index=True)
def __str__(self):
return self.verb
# In settings.py
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("localhost", 6379)]
}
}
}
我希望每次单击like按钮时只保存一次通知,但它会被保存多次,因为在consumers.py中定义的notif_like() function每次点击都会被多次调用。
此外,在此之后,web-socket 会突然断开连接,并在终端中显示以下警告 -
Application instance <Task pending coro=<SessionMiddlewareInstance.__call__() running at /home/pirateksh/DjangoProjects/ccwebsite/ccenv/lib/python3.7/site-packages/channels/sessions.py:183> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fb7603af1c8>()]>> for connection <WebSocketProtocol client=['127.0.0.1', 52114] path=b'/like_notification/'> took too long to shut down and was killed.
我已经为此苦苦挣扎了很长一段时间,所以如果有人能指导我完成这件事,那将非常有帮助。
将近一年后,我从事了一个非常相似的项目,我能够重现此错误,并找出导致此错误的原因。
我很确定这是由ReconnectingWebSocket引起的。
原因是当我切换回使用香草 WebSocket 时,一切正常。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.