首页 > 其他分享 >http Range

http Range

时间:2024-04-13 23:55:25浏览次数:27  
标签:const res http start Range fileSize end

Range

Range 范围请求,允许服务器只发送 HTTP 消息的一部分到客户端。Range 可以一次性请求多个部分,请求范围用一个逗号分隔开,服务器会以 multipart/byteranges 文件的形式将其返回。如果服务器返回的是范围响应,需要使用 206 Partial Content 状态码。在请求的范围越界的情况下,服务器会返回 416 Range Not Satisfiable (请求的范围无法满足) 状态码,表示客户端错误。服务器允许忽略 Range,从而返回整个文件,状态码用 200 。


相关状态码: 200, 206, 416.
相关首部: Accept-Ranges, Range, Content-Range, If-Range, Transfer-Encoding.

  • index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>http range</title>
    <style>
      body {
        margin: 0;
        padding: 20px;
        box-sizing: border-box;
      }
    </style>
  </head>
  <body>
    <h1>HTTP Range</h1>
    <hr />
    <h3 class="result"></h3>

    <script>
      fetchData()
      async function fetchData() {
        const url = 'http://localhost:3000/range';
        const res = await fetch(url, {
          headers: {
            Range: `bytes=0-2048`,
          },
        }).then((resp) => resp.blob());
        console.log("res => ", res)
        // open(URL.createObjectURL(res), '_blank');
        const img = new Image()
        img.onload = () => {
          document.querySelector('.result').appendChild(img)
        }
        img.src =  URL.createObjectURL(res)

        // document.querySelector('.result').textContent = res;
      }
    </script>
  </body>
</html>
  • server.mjs
import { createServer } from 'node:http';
import { readFile, stat, createReadStream } from 'node:fs';
import { parse } from 'node:url';
import { promisify } from 'node:util';

const filePath = './favicon.ico';
const contentType = 'image/vnd.microsoft.icon';

createServer(async (req, res) => {
  const { pathname } = parse(req.url);

  res.setHeader('Access-Control-Allow-Origin', '*');

  console.log('pathname => ', req.method, pathname);

  if (pathname === '/') {
    const html = await promisify(readFile)('./range.html');
    res.setHeader('Content-Type', 'text/html;charset=utf-8');
    res.end(html);
  } else if (pathname === '/range') {
    if (req.method.toUpperCase() === 'OPTIONS') {
      res.setHeader(
        'Access-Control-Allow-Headers',
        'Accept-Ranges, Range, Content-Type, Content-Length'
      );
      res.statusCode = 200;
      return res.end();
    }

    const { size: fileSize } = await promisify(stat)(filePath);

    const headerRange = req.headers['range'];
    console.log('headerRange => ', headerRange);

    res.setHeader('Accept-Ranges', 'bytes');
    res.setHeader('Content-Type', contentType);

    // 分片传输
    if (headerRange) {
      // 只获取一个range
      const reg = /^bytes=\d+$/;
      if (reg.test(headerRange)) {
        const index = +headerRange.split('=')[1];
        if (index >= fileSize) return handleBoudary(res);
        return createReadStream(filePath, { start: index, end: index }).pipe(res);
      }

      const ranges = headerRange
        .replace('bytes=', '')
        .split(',')
        .map((item) => item.trim().split('-'));

      console.log('ranges => ', ranges);

      // 处理越界情况
      const boundaryStatus = ranges.some(([start, end]) => {
        return start >= fileSize || end >= fileSize;
      });
      if (boundaryStatus) return handleBoudary(res);

      // 处理多个range
      if (ranges.length > 1) {
        const range_divider = String(Math.random()).slice(2);
        res.statusCode = 206;
        res.setHeader('Content-Type', `multipart/byteranges; boundary=${range_divider}`);

        const chunksPromise = ranges.map(([start, end]) => {
          // 从结尾开始获取
          console.log("start => ", start, end)
          if (start === '' && end !== '') {
            start = fileSize - parseInt(end);
            end = fileSize - 1;
          } else {
            start = parseInt(start);
            end = end ? parseInt(end) : fileSize - 1;
          }
          return getChunk(start, end, filePath);
        });

        const chunks = await Promise.all(chunksPromise);

        let list = [];
        for (const { start, end, chunk } of chunks) {
          const str = [
            `\r\n--${range_divider}`,
            `Content-Type: ${contentType}`,
            `Content-Range: bytes ${start}-${end}/${fileSize}`,
            '',
            chunk,
          ].join('\r\n');
          list.push(str);
        }

        const result = list.join('') + `\r\n--${range_divider}--`;

        return res.end(result);
      }

      // 处理单个range (部分内容)
      let [start, end] = ranges[0];
      // 从结尾开始获取
      if (start === '' && end !== '') {
        start = fileSize - parseInt(end);
        end = fileSize - 1;
      } else {
        start = parseInt(start);
        end = end ? parseInt(end) : fileSize - 1;
      }

      res.writeHead(206, {
        'Content-Length': end - start + 1,
        'Content-Range': `bytes ${start}-${end}/${fileSize}`,
      });
      const readStream = createReadStream(filePath, { start, end });
      return readStream.pipe(res);
    }

    // 完整的内容
    res.statusCode = 200;
    res.setHeader('Content-Length', fileSize);
    createReadStream(filePath).pipe(res);
  } else {
    res.statusCode = 404;
    res.end('Not found');
  }
}).listen(3000, () => {
  console.log('server runnning in 3000');
});

function handleBoudary(res) {
  res.statusCode = 416
  res.end('请求范围不符合要求')
}

function getChunk(start, end, filePath) {
  return new Promise((resolve) => {
    const readStream = createReadStream(filePath, { start, end });
    let chunk = '';
    readStream.on('data', (data) => {
      chunk += data;
    });
    readStream.on('end', () => {
      resolve({ start, end, chunk });
      // console.log('end');
    });
  });
}

标签:const,res,http,start,Range,fileSize,end
From: https://www.cnblogs.com/chlai/p/18133602

相关文章

  • Docker+Net8运行https
    环境:win11,docker4.28.0,Net8。使用windows版docker 跑老外的run-aspnetcore-microservices 这个分布式项目时,最开始直接运行会遇到这个问题。中间也试了几种方法,有ok也有不行的,有些较为麻烦。Net8开始Docker 端口 默认端口8080了下面是我的1生成pfx文件d......
  • httprunner4.x学习03-变量
    前言声明变量和引用是经常用到的,引用变量方式有两种:$var或 ${var} 通过关键字variables 定义变量主要在config和step中,这两者的区别如下:1.在config中定义的变量为全局变量的,范围比较大,也就是整个测试用例(testcase)的所有地方都可以引用;2.在step中定义的变量为局部变量......
  • conda install sometools报错:CondaHTTPError: HTTP 000 CONNECTION FAILED for url <h
    把该错误投入chatgpt中会反映网络问题,重试几次但我重试了好几天也没安上,重新搜索该报错发现:ThatHTTPerrorhappenedwhenIupdatedthecondawith condaupdateconda.ItriedalloptionsdiscussedherebutitonlywassolvedwhenIdowngradedthecondaversion(I......
  • Asp .Net Core 系列:集成 Refit 和 RestEase 声明式 HTTP 客户端库
    背景.NET中有没有类似Java中Feign这样的框架?经过查找和实验,发现在.NET平台上,虽然没有直接的Feign框架的端口,但是有一些类似的框架和库,它们提供了类似的功能和设计理念。下面是一些在.NET中用于声明式HTTP客户端的框架和库:Refit:Refit是一个用于构建声明式、类型......
  • 敌人——创建敌人_Ranger
    目的敌人Actor的前置准备工作(略)编写Ranger的Task逻辑制作Bomb蓝图在Ranger中编写Ranger的逻辑核心思路Ranger行为逻辑:漂浮在空中的敌人具有两种攻击模式(核心)向玩家发射3颗散射的子弹当玩家在其轰炸检测范围内会发射三颗自由落体的炸弹轰炸玩家1.编写Ranger的Task逻......
  • 安装nginx时报错解决(configure error: the HTTP gzip module requires the zlib libra
    安装nginx时报错解决下载地址nginx源码包下载地址:https://nginx.org/en/download.html安装环境Ubuntu20.04LTSnginx-1.23.4安装步骤#解压缩包tar-zxvfnginx-1.23.4.tar.gz#进入包目录cdnginx-1.23.4#生成makefile./configure就在./configure这一步出现了依......
  • httpsok-谷歌免费SSL证书如何申请
    ......
  • v1.9.2-httpsok快速申请免费谷歌SSL证书
    ......
  • Asp .Net Core 系列:集成 Refit 和 RestEase 声明式 HTTP 客户端库
    背景.NET中有没有类似Java中Feign这样的框架?经过查找和实验,发现在.NET平台上,虽然没有直接的Feign框架的端口,但是有一些类似的框架和库,它们提供了类似的功能和设计理念。下面是一些在.NET中用于声明式HTTP客户端的框架和库:Refit:Refit是一个用于构建声明式、类......
  • docker nginx监听80端口 同一 IP 多域名配置方法--多子配置文件包含 https
    下载nginx镜像文件dockerpullnginx:1.24.0宿主机上创建nginx_80目录htmlcertconflogs创建配置文件nginx.conf一、Nginx配置文件nginx.conf操作:在http模块增加(子配置文件的路径和名称):include/etc/nginx/conf.d/*.conf;usernginx;worker_processes1;err......