Notes

范围请求的使用

需求:在线传输视频,并且可以按需加载,即不一次性请求所有视频数据,并且,可以实现自由的快进和后退

一般做法

在前端页面中播放视频,可以使用 <mp4> 标签,并将 src 的值设置为视频的 URL, 但这种做法在一般情况可行,但是最少有下面一个缺点:

不能在进度条上实现快进或后退,一旦用户移动了进度条,整个视频会回到原点重新加载

因此,需要的是,按需加载,给服务器发送一个范围值,然后服务器发送该段数据给客户端,而这一个需求的实现,需要使用到 HTTP 协议中头部的 Content-Range 字段

实现

实现数据的切片传输,我们需要用到的 HTTP 头部字段有

HTTP 状态码 206

需要知道的

额外的

Range 字段

需要知道的

Range 值的语法

Range: <unit>=<range-start>-
Range: <unit>=<range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>

unit 范围值所采用的单位,一般为 bytes range-start 整数,范围的起始值 range-end 整数,范围的结束值,也是一个可选值,如果不存在,则表示一直到文件的结束

额外的

Accept-Ranges 字段

Content-Range 字段

Content-Ranges 语法

Content-Range: <unit> <range-start>-<range-end>/<size>
Content-Range: <unit> <range-start>-<range-end>/*
Content-Range: <unit> */<size>

unit 范围值所采用的单位,一般为 bytes range-start 范围的起始值 range-end 范围的结束值 size 整个文件的大小,若文件大小未知,则用*表示

Content-Length 字段

Content-Type 字段

需要知道的

额外的

Content-Type 语法

Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=something

Content-Type: <media-type>; [charset=<charset> | boundary=<boundary>]

media-type 资源类型 charset 字符编码 boundary 若消息体中存在多个部分,则 boundary 时必须的,用于分隔这多个部分的内容

通过上面描述的 HTTP 头部字段,可以知道,范围请求的实现侧重于服务端的实现

Node.js 实现的服务端支持范围请求

下面的代码是在 Express 框架中实现的,代码来自 References 中 「Node.js发送视频流」代码

  const stat = fs.statSync(filePath); // 获取文件信息
  let fileSize = stat.size;
  let range = req.headers.range;
  if (range) {
    //有range头才使用206状态码
    
    // 使用正则表达式截取范围
    let parts = range.replace(/bytes=/, "").split("-");
    let start = parseInt(parts[0], 10);
    let end = parts[1] ? parseInt(parts[1], 10) : start + 999999;

    // end 在最后取值为 fileSize - 1 
    end = end > fileSize - 1 ? fileSize - 1 : end;

    let chunksize = (end - start) + 1;
    let file = fs.createReadStream(filePath, { start, end });
    
    // 范围请求状态码为 206
    res.statusCode = 206;
    res.set({
      'Content-Range': `bytes ${start}-${end}/${fileSize}`,
      'Accept-Ranges': 'bytes',
      'Content-Length': chunksize,
      'Content-Type': 'video/mp4',
    });
    file.pipe(res);
  } else {
      let head = {
          'Content-Length': fileSize,
          'Content-Type': 'video/mp4',
      };
      res.statusCode = 200;
      res.set({
        'Content-Length': fileSize,
        'Content-Type': 'video/mp4',
      });
      fs.createReadStream(filePath).pipe(res);
  }

References