巧用Request Range优化下载

最近在做视频相关的项目,上次介绍了 视频的点播,今天来介绍一下视频的切片下载

通常是针对大资源,一次性请求一整个资源会导致响应时间过长,所以进行分批请求,可利用浏览器并发请求的能力

1、前置知识

(注意:应该是Content-Range)

在HTTP``request Header中,有一个请求头字段Range,用来表示范围请求

  • 即只请求目标文件的一部分
javaScript 复制代码
GET /videoFile.mp4 HTTP/1.1
Host: www.example.re
Range: bytes=0-9999

1.1 正常响应

当后端正常响应时,它对应的状态码为206--- partial content,表示部分内容;并且会在响应头添加Content-Length字段,并返回请求的范围和资源长度

如下:

javaScript 复制代码
HTTP/1.1 206 Partial Content
Content-Length: 10000
Content-Range: bytes 0-9999/25000
Accept-Ranges: bytes

1.2 预请求

客户端可以在发起范围请求之前,先发送一个head请求,看服务端是否支持range请求

  • 若支持,服务端会在响应头中返回一个字段Accept-Range: bytes
  • 如不支持,则返回Accept-Range: none

1.3 超出范围

当请求头中的范围超出资源大小时,服务端端则会返回416--- Range Not Satisfiable

2、后端

后端仍然使用koa来完成,在之前项目的基础上增加两个接口:

  • head请求获取文件的大小
  • get进行范围请求

2.1 获取文件大小

head请求基本与get请求相同(安全且幂等 ),只不过它没有响应体,只有响应头,一般用于请求元信息

这里将用于获取文件的大小,请求结果放在头部的Content-Length字段

javaScript 复制代码
router.head('/file/size', async (ctx) => {
  const { originPath } = ctx.query
  const filePath = path.join(__dirname, '../uploadFiles', originPath)
  // 读取文件信息
  const { size } = fs.statSync(filePath)

  ctx.set('Content-Length', size)
  ctx.status = 200
})

注意: 无需返回响应体信息,即使返回,客户端也不会接收到

2.2 进行范围请求

进行范围请求首先会先检查请求头是否有range字段,如果没有,仍然返回一整个文件

javaScript 复制代码
/**
 * 下载文件
 */
router.get('/file/download', async (ctx) => {
  const { originPath } = ctx.query
  const filePath = path.join(__dirname, '../uploadFiles', originPath)
  
  const { size } = fs.statSync(filePath)

  const range = ctx.headers.range
  
  if (!range) {
    // 告诉客户端 服务端事支持range请求的
    ctx.set('Accept-Range', 'bytes')
    // 流式返回
    ctx.body = fs.createReadStream(filePath);
    return
  }
})

createReadStream接收第二个配置参数,可用于配置读取的起始、终止位置

javaScript 复制代码
router.get('/file/download', async (ctx) => {
  ...

  const range = ctx.headers.range  // 其结构为:bytes=0-999
  
  const [start, end] = gerRange(range)

  // 表示超出范围
  if (start > size || end > size) {
    ctx.status = 416
    ctx.body = ''
    return
  }

  ctx.set('Accept-Ranges', 'bytes')
  ctx.set('Content-Length', end - start + 1)
  ctx.set('Content-Range', `bytes ${start}-${end}/${size}`)
  ctx.status = 206
  
  ctx.body = fs.createReadStream(filePath, {start, end})  

})

3、前端

步骤:

  1. head请求获取文件大小
  2. 请求range文件的blob
  3. 下载文件
javaScript 复制代码
const downloadFile = async (path: string) => {
  try {
    const size = await fetchFileSize(path);
    if (!size) return alert('下载失败');

    const sizeQueue = getFileSlice(+size.headers['content-length']);
    const peomiseQueue = sizeQueue.map(item => {
      const [start, end] = item;
      return fetchFileBlob(path, start, end);
    });

    Promise.all(peomiseQueue)
      .then(res => {
        const blob = new Blob(res, { type: 'video/mp4' });
        const href = URL.createObjectURL(blob);

        // 下载
        const a = document.createElement('a');
        a.download = path.split('/').pop() as string;
        a.href = href;
        a.click();
        URL.revokeObjectURL(blob);
      })
  } catch (error) {
    console.error(error);
  }
};

下载一个大小为130+M的MP4文件,时间大概为1.2s

详细代码已上传github


参考:

相关推荐
云浪5 小时前
Go 并发实战:从零实现一个高并发安全的任务调度器
javascript·后端·go
OpsEye13 小时前
大模型账单对账难:不同厂商 Token 统计口径差异怎么处理
javascript·ai编程
全栈项目管理程序猿13 小时前
ArcGIS JS 基础教程(32):体元系列 - VoxelSlice 体元切片
前端·javascript
Hilaku15 小时前
AI 流式输出都用 SSE,什么时候必须换 WebSocket?
前端·javascript·程序员
奇牙coding16 小时前
Codex CLI 升级后多文件编辑静默失效:config.yaml 枚举变更排查与修复指南
java·前端·javascript·ai
黄俊懿16 小时前
【架构师从入门到进阶】第六章:服务集群优化——第八节:线程数公式
java·服务器·数据库·分布式·性能优化·架构·系统架构
不简说16 小时前
AI 帮你画模板、点一下就云打印:sv-print 这三个月都干了啥
前端·javascript·人工智能
大明二代17 小时前
从零构建 Mastra RAG MVP:DeepSeek、SiliconFlow 与 PostgreSQL/pgvector 实战
javascript·数据库·人工智能
结局无敌17 小时前
React Native for OpenHarmony 实战:三方库 react-native-paper 的鸿蒙化适配指南
javascript·人工智能·深度学习·react native·react.js·ecmascript·harmonyos
铁皮饭盒17 小时前
CPU就行, 老照片变清晰, 本地部署582mb腾讯的模型, 本地部署, 过节了给长辈们变个小魔术
前端·javascript·后端