You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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一样,我们把文件分成小块发送,避免一次性加载大文件到内存。
  • 元数据传递:先发送文件大小,让前端可以显示下载进度。
  • 错误处理:添加了文件不存在的判断,及时给客户端返回错误信息。

测试步骤

  1. 确保Redis服务器正在运行:redis-server
  2. 在Django项目根目录创建public文件夹,并放入测试文件
  3. 启动Django服务器:python manage.py runserver
  4. 运行前端代码,测试文件下载功能

内容的提问来源于stack exchange,提问作者Michael Hansen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 18:57:46