我有一个部署到 Heroku 并使用 django Channels 3.0.4 的应用程序。过去 18 个月一直运行良好,但现在出现以下错误:
与“wss://my-app.myurl/ws/quiztest/mc/”的 WebSocket 连接失败:
与之前工作时相比,我没有更改任何代码。我还有一个在 Heroku 上运行的应用程序的测试版本,使用完全相同的代码库,并且工作正常,这表明该错误可能与我的 Heroku 配置有关,尽管我已将此应用程序的设置与测试应用程序进行了比较他们看起来还不错。我的应用程序中的其他所有内容都工作正常,只是 django 通道 websocket 问题。
我做了一个简单的测试来重现这个问题。这是尝试连接的 JS:
<script>
var loc = window.location;
var wsStart = 'ws://';
if (loc.protocol == 'https:'){
wsStart = 'wss://';
}
var endpoint = wsStart + loc.host + '/ws/quiztest/';
var username = 'mc';
var socket = new WebSocket(endpoint + username + '/');
console.log(socket);
console.log(endpoint + username + '/');
socket.onopen = function(e) {
console.log('Open Host Socket')
};
socket.onmessage = function(event) {
// Handle incoming messages from the server here
console.log('Received message:', event.data);
}
socket.onclose = function(e) {
console.log('Close',e);
};
socket.onerror = function(e) {
console.log('Error', e);
};
</script>
这是我的routing.py:
from django.urls import path
from taskmanager.consumers import MyQuizConsumer
ws_urlpatterns = [
path('ws/quiztest/<username>/', MyQuizConsumer.as_asgi()),
]
这是我的consumers.py
from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer
from base.base_helpers import log_and_print
from asgiref.sync import async_to_sync
class MyQuizConsumer(WebsocketConsumer):
def connect(self):
log_and_print('QUIZ HOST TEST connecting...')
async_to_sync(self.channel_layer.group_add)(
'id-1',
self.channel_name
)
self.accept()
我可能做错了什么,或者我需要检查 Heroku 配置的哪些方面可能会导致 websocket 失败?
所以我已经解决了这个问题。据我所知,这最初是由我的 Heroku Redis 实例达到最大可用内存引起的。我将计划从 Mini 升级到 Premium-0。然而,这也将 Redis 实例升级到 6.2,我得到:
SSLCertVerificationError:[SSL:CERTIFICATE_VERIFY_FAILED]证书验证失败:证书链中的自签名证书(_ssl.c:1123)
事实证明,运行 Redis 6+ 的 Heroku Data for Redis 的生产计划(高级版及更高版本)需要 TLS 连接。 Heroku Data for Redis 使用自签名证书,需要禁用这些证书。需要对缓存和两个通道层执行此操作。
缓存
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': REDIS_URL,
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'IGNORE_EXCEPTIONS': True,
'CONNECTION_POOL_KWARGS': {
'ssl_cert_reqs': None
},
}
}
}
通道层
import ssl
ssl_context = ssl.SSLContext()
ssl_context.check_hostname = False
heroku_redis_ssl_host = {
'address': REDIS_URL,
'ssl': ssl_context
}
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': (heroku_redis_ssl_host,)
}
},
}
REDIS_URL 并在最后添加 ?ssl_cert_reqs=none