如何在Django Channels中实现socket.io-stream式文件下载功能?
Django Channels替代socket.io-stream实现文件流式下载
Absolutely, Django Channels can totally replicate the file streaming functionality you had with socket.io-stream! Let's break down how to build this step by step, matching your original Node.js setup closely.
1. 环境准备
First, install the required packages:
pip install django channels channels-redis django-cors-headers
2. Django项目配置
Update your settings.py to enable Channels and CORS:
2.1 添加INSTALLED_APPS
INSTALLED_APPS = [ # ... 其他默认应用 'channels', 'corsheaders', ]
2.2 配置ASGI和Channel Layers
# 替换WSGI_APPLICATION为ASGI_APPLICATION ASGI_APPLICATION = 'your_project_name.asgi.application' # 配置Channel Layers(用Redis作为后端) CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { "hosts": [('127.0.0.1', 6379)], }, }, } # 配置CORS中间件(放在最前面) MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # ... 其他中间件 ] # 允许所有跨域请求(根据实际需求调整) CORS_ALLOW_ALL_ORIGINS = True
3. 配置ASGI应用
Modify asgi.py in your project root to handle WebSocket connections:
import os from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter from channels.auth import AuthMiddlewareStack import your_app_name.routing os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project_name.settings') application = ProtocolTypeRouter({ "http": get_asgi_application(), "websocket": AuthMiddlewareStack( URLRouter( your_app_name.routing.websocket_urlpatterns ) ), })
4. 创建WebSocket路由
Create routing.py in your app directory:
from django.urls import re_path from . import consumers websocket_urlpatterns = [ re_path(r'ws/download/$', consumers.FileDownloadConsumer.as_asgi()), ]
5. 核心:编写FileDownloadConsumer
This is the equivalent of your socket.io connection handler. Create consumers.py in your app:
import os import aiofiles from channels.generic.websocket import AsyncWebsocketConsumer from django.conf import settings class FileDownloadConsumer(AsyncWebsocketConsumer): async def connect(self): await self.accept() print("New Client Connected") async def disconnect(self, close_code): print("Client disconnected") # 处理客户端发送的download消息 async def receive(self, text_data): # 解析客户端发送的文件名 filename = text_data.strip() file_path = os.path.join(settings.BASE_DIR, 'public', filename) # 检查文件是否存在 if not os.path.exists(file_path): await self.send(text_data='{"error": "File not found"}') return # 获取文件大小 file_size = os.path.getsize(file_path) # 先发送文件元数据(对应原来的size参数) await self.send(text_data=f'{{"metadata": {{"size": {file_size}}}}}') # 异步读取文件并分块发送 chunk_size = 1024 * 1024 # 1MB块大小,可调整 async with aiofiles.open(file_path, 'rb') as f: while chunk := await f.read(chunk_size): # 发送二进制文件块 await self.send(bytes_data=chunk) # 发送完成标记 await self.send(text_data='{"status": "complete"}')
6. 前端对应实现(替代socket.io-stream)
Your frontend will now use native WebSocket instead of socket.io. Here's a JavaScript example:
const socket = new WebSocket('ws://localhost:8000/ws/download/'); // 请求下载文件 function requestFileDownload(filename) { socket.send(filename); } let fileChunks = []; let totalSize = 0; let receivedSize = 0; socket.onmessage = function(event) { if (event.data instanceof Blob) { // 接收文件块 fileChunks.push(event.data); receivedSize += event.data.size; console.log(`Received ${receivedSize}/${totalSize} bytes`); } else { // 处理文本消息(元数据或错误) const message = JSON.parse(event.data); if (message.metadata) { totalSize = message.metadata.size; console.log(`Starting download of ${totalSize} bytes`); } else if (message.status === 'complete') { // 拼接所有块并下载 const blob = new Blob(fileChunks); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'downloaded_file'; // 可替换为原文件名 a.click(); URL.revokeObjectURL(url); fileChunks = []; totalSize = 0; receivedSize = 0; } else if (message.error) { console.error(message.error); } } }; // 调用示例 requestFileDownload('your_file_name.pdf');
关键说明
- 异步文件读取:我们用
aiofiles来异步读取文件,避免阻塞事件循环,这对Channels的性能很重要。 - 分块传输:和socket.io-stream一样,我们把文件分成小块发送,避免一次性加载大文件到内存。
- 元数据传递:先发送文件大小,让前端可以显示下载进度。
- 错误处理:添加了文件不存在的判断,及时给客户端返回错误信息。
测试步骤
- 确保Redis服务器正在运行:
redis-server - 在Django项目根目录创建
public文件夹,并放入测试文件 - 启动Django服务器:
python manage.py runserver - 运行前端代码,测试文件下载功能
内容的提问来源于stack exchange,提问作者Michael Hansen
相关产品推荐
相关产品推荐

