从服务器获得错误的SSE响应
我正在用Django开发一个项目,已经安装了DRF(Django REST框架)。后台有个进程在生成日志文件。我想在文件的末尾查找特定的字符串。当找到这些字符串时,它们需要在HTML用户页面上显示出来。整个过程都是动态进行的。
这是我的urls.py文件:
path('api/log-stream/', LogStreamAPIView.as_view(), name='log_stream'),
这是LogStreamAPIView:
class LogStreamAPIView(APIView):
def get(self, request):
def stream_log():
f = subprocess.Popen(['tail', '-F', rcvenot_log], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = select.poll()
p.register(f.stdout)
while TEST_IN_PROGRESS:
if p.poll(0.1):
new_line_log = f.stdout.readline().decode('utf-8').strip()
for target_line in RCV_SCENARIO:
if target_line in new_line_log:
yield f"event: {new_line_log}\n\n"
return StreamingHttpResponse(stream_log(), content_type='text/event-stream')
这是用户HTML页面上的脚本:
<div style="width: 200px; height: 200px; overflow-y: scroll;">
<ul id="logList"></ul>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
var logList = document.getElementById('logList');
function updateLog(newLine) {
var listItem = document.createElement('li');
listItem.textContent = newLine;
logList.appendChild(listItem);
}
var source = new EventSource('/api/log-stream/');
source.onmessage = function(event) {
updateLog(event.data);
};
});
</script>
我通过API mysite.ru/api/log-stream/
看到了字符串。一些字符串的格式是这样的:event: 2024.03.22 16:16:13:016[INFO ][Thread-0] ************************* - 2
但是我在HTML页面上看不到这些字符串。
在浏览器控制台中,我看到响应代码是406:GET 127.0.0.1:8000/api/log-stream 406 (Not Acceptable)
。响应内容是{"detail":"The \"Accept\" request header could not be satisfied."}
我尝试过用不同的格式发送数据,比如f"event: {new_line_log}\n\n"
或者f"data: {new_line_log}\n\n"
,但是都不行。
我还尝试在return StreamingHttpResponse(stream_log(), content_type='text/event-stream', status=200)
中添加状态码,试过status=202
,结果也是一样。
这里有一些浏览器控制台的截图。
我阅读了SSE(服务器发送事件)的文档,服务器应该返回Content-Type: text/event-stream
,但在浏览器控制台中我看到的是Content-Type: application/json
。我怀疑问题可能出在这里……
请帮忙找出问题所在。
1 个回答
你遇到错误的原因是因为 Django Rest Framework(DRF)在尝试进行内容协商。默认情况下,它会查看 DEFAULT_RENDERER_CLASSES
设置,以确定可以返回哪些内容类型,如果没有匹配的类型,就会返回406错误。
考虑到你的这个视图似乎并没有使用任何 DRF 特有的功能,你可以选择将它转换成一个普通的视图,这样就不需要使用 DRF 了。
另一种解决方案是设置一个自定义内容协商类,这个类会简单地忽略所有的内容协商。以下是 DRF 文档中的一个示例:
from rest_framework.negotiation import BaseContentNegotiation class IgnoreClientContentNegotiation(BaseContentNegotiation): def select_parser(self, request, parsers): """ Select the first parser in the `.parser_classes` list. """ return parsers[0] def select_renderer(self, request, renderers, format_suffix): """ Select the first renderer in the `.renderer_classes` list. """ return (renderers[0], renderers[0].media_type)
然后你需要修改你的视图,以使用这个内容协商:
class LogStreamAPIView(APIView):
content_negotiation_class = IgnoreClientContentNegotiation
# Rest of your view
...