Django中用FileField与HTML5视频标签播放视频时遇ConnectionResetError
首先,你推测的原因完全正确:这个ConnectionResetError: [Errno 54] Connection reset by peer是当用户在视频加载/播放过程中关闭页面、刷新或者导航到其他页面时,浏览器主动中断了和服务器的连接,导致Django服务器(尤其是开发环境的默认服务器)抛出这个错误。这本质上是正常的客户端行为,不会影响功能,但会产生烦人的日志噪音,我们可以通过以下方式解决:
1. 开发环境(Django自带runserver)
Django默认的开发服务器基于socketserver,对这类客户端主动断开连接的情况没有做异常处理,所以会把完整的栈追踪打出来。我们可以自定义一个请求处理器来忽略这个错误:
修改项目根目录下的manage.py,替换默认的请求处理器:
#!/usr/bin/env python """Django's command-line utility for administrative tasks.""" import os import sys from django.core.servers.basehttp import run, WSGIRequestHandler class QuietWSGIRequestHandler(WSGIRequestHandler): def handle(self): try: super().handle() except ConnectionResetError: # 忽略客户端连接重置的错误,避免日志刷屏 pass def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc # 检测是否是runserver命令,替换处理器 if len(sys.argv) >= 2 and sys.argv[1] == 'runserver': addr = sys.argv[2].split(':')[0] if len(sys.argv) > 2 else '127.0.0.1' port = int(sys.argv[3]) if len(sys.argv) > 3 else 8000 run(addr=addr, port=port, handler_class=QuietWSGIRequestHandler) else: execute_from_command_line(sys.argv) if __name__ == '__main__': main()
修改后,开发服务器遇到连接重置错误时就会静默处理,不会再输出冗长的错误栈。
2. 生产环境(Nginx+Gunicorn/uWSGI)
生产环境通常使用Gunicorn或uWSGI作为应用服务器,搭配Nginx反向代理。这类服务器默认会处理客户端中断连接的情况,但如果还是有日志输出,可以通过调整配置过滤该错误:
Gunicorn配置示例
创建gunicorn_config.py文件:
import logging from gunicorn import glogging class CustomGunicornLogger(glogging.Logger): def setup(self, cfg): super().setup(cfg) # 过滤包含ConnectionResetError的日志记录 access_logger = logging.getLogger("gunicorn.access") for handler in access_logger.handlers: handler.addFilter(lambda record: "ConnectionResetError" not in record.getMessage())
启动Gunicorn时指定该配置:
gunicorn --config gunicorn_config.py your_project.wsgi:application
Nginx补充配置
Nginx会把客户端主动中断的请求标记为499 Client Closed Request,你可以在Nginx日志配置中忽略这个状态码的日志:
access_log /var/log/nginx/access.log combined exclude=499;
3. 优化视频访问路径(从根源避免请求经过Django)
如果你的视频存储在S3上,当前是通过Django代理请求(比如私有桶需要签名),可以直接让前端使用S3预签名URL访问视频,这样请求直接发送到S3,不会经过Django服务器,自然就不会产生这类错误。
在DetailView中生成预签名URL:
from django.views.generic.detail import DetailView from storages.backends.s3boto3 import S3Boto3Storage from .models import YourVideoModel class VideoDetailView(DetailView): model = YourVideoModel template_name = 'video_detail.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) storage = S3Boto3Storage() # 生成有效期为1小时的预签名URL presigned_url = storage.url(self.object.video_sample.name, expire=3600) context['video_presigned_url'] = presigned_url return context
模板中使用预签名URL:
<video width='50%' controls> <source src="{{ video_presigned_url }}" type="video/mp4"> Your browser does not support the video tag. </video>
总结:这个错误本身不影响业务功能,核心是过滤日志噪音,或者让视频请求不经过Django服务器。根据你的部署环境选择对应的方案即可。
内容的提问来源于stack exchange,提问作者Shahar Gino

