基于NodeJS的HTTP server Plus 1:Range (范围请求)

本文介绍如何使用NodeJS实现HTTP服务端的Range请求处理,通过Range头实现文件的分段下载,以及客户端如何配合实现断点续传功能。文章详细解释了服务端代码实现,包括异步操作的Promise化,以及客户端逻辑设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本篇使用 NodeJS 的 HTTP 服务创建客户端,使用 Range 请求实现下载功能,并通过本篇的 Demo 扩展在业务中实现断点续传等功能的思路。

服务端的实现

我们通过 http 模块创建服务器处理 Range 请求,在服务器代码中我们为了减少回调嵌套使用 async 函数,所以需要将异步的操作方法转换成 Promise,以往我们使用 utilpromisify 来一个一个转换异步方法,比较麻烦,我们这次使用第三方模块 mz 并直接引入转换好的替代模块。

使用 mz 之前需要先安装:

npm install mz

服务端代码如下:

文件:server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
复制代码
const http = require("http");
const path = require("path");
const url = require("url");

// 引入 mz 模块转换成 Promise 的 fs 模块
const fs = require("mz/fs");

// 请求处理函数
async function listener(req, res) {
    // 获取 range 请求头,格式为 Range:bytes=0-5
    let range = req.headers["range"];

    // 下载文件路径
    let p = path.resovle(__dirname, url.parse(url, true).pathname);

    // 存在 range 请求头将返回范围请求的数据
    if (range) {
        // 获取范围请求的开始和结束位置
        let [, start, end] = range.match(/(\d*)-(\d*)/);

        // 错误处理
        try {
            let statObj = await fs.stat(p);
        } catch (e) {
            res.end("Not Found");
        }

        // 文件总字节数
        let total = statObj.size;

        // 处理请求头中范围参数不传的问题
        start = start ? ParseInt(start) : 0;
        end = end ? ParseInt(end) : total - 1;

        // 响应客户端
        res.statusCode = 206;
        res.setHeader("Accept-Ranges", "bytes");
        res.setHeader("Content-Range", `bytes ${start}-${end}/${total}`);
        fs.createReadStream(p, { start, end }).pipe(res);
    } else {
        // 没有 range 请求头时将整个文件内容返回给客户端
        fs.createReadStream(p).pipe(res);
    }
}

// 创建服务器
const server = http.createServer(listener);

// 监听端口
server.listen(3000, () => {
    console.log("server start 3000");
});
复制代码

在上面服务端的代码中,需要兼容 Range 请求和普通请求,两种请求的区别是,如果客户端发送的是 Range 请求,会携带 Range:bytes=0-5 格式的请求头,我们可以通过 reqheaders 属性获取,在获取请求头时,原本大写字母开头 NodeJS 统一处理成小写,所以获取时应小写。

如果是 Range 请求则通过可读流读取对应的内容返回客户端,如果不是,则通过可读流读取整个文件返回客户端,在响应 Range 请求的过程中需要设置响应状态为 206,需要设置响应头 Accept-Ranges 值为 bytes,需要设置响应头 Content-Range 值为 byte 0-5/100 的格式,0 为返回数据开始的索引,5 为结束的索引(包含),100 为文件的总字节数。

在通过 urlpath 模块解析和拼接下载文件路径时,应该进行错误检测,如果文件不存在则直接返回客户端 Not Found

我们可以使用 curl 命令来检测我们的服务端代码,在命令行工具中输入下面命令,在命令窗口查看返回值是否正确。

curl -v --header “Range:bytes=0-5” http://localhost:3000

客户端的实现

在上面使用 curl 命令来访问我们的服务器时,只能请求固定范围的数据,而不是类似于下载功能,每次都下载一个范围的数据,但是想要多次下载并自动维护 Range 的范围需要借助我们自己实现的客户端逻辑。

为了简便,我们的下载客户端是在命令行窗口运行的,通过指令来模拟实际项目中的开始下载、暂停和恢复按钮,当在窗口中输入 s 指令时开始下载,输入 p 指令时暂停下载,输入 r 指令时恢复下载。

文件:client.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
复制代码
const http = require("http");
const fs = require("fs");
const path = require("path");

// 请求配置
let config = {
    host: "localhost",
    port: 3000,
    path: "/download.txt"
};

let start = 0; // 请求初始值
let step = 5; // 每次请求字符个数
let pause = false; // 暂停状态
let total; // 文件总长度

// 创建可写流
let ws = fs.createWriteStream(path.resolve(__dirname, config.path.slice(1)));

// 下载函数
function download() {
    // 配置,每次范围请求 step 个字节
    config.headers = {
        "Range": `bytes=${start}-${start + step - 1}`;
    };

    // 维护下次 start 的值
    start += step;

    // 发送请求
    http.request(config, res => {
        // 获取文件总长度
        if (typeof total !== "number") {
            total = res.headers["content-ranges"].match(/\/(\d*)/)[1];

        }

        // 读取返回数据
        let buffers = [];
        res.on("data", data => buffers.push(data));
        res.on("end", () => {
            // 合并数据并写入文件
            let buf = Buffer.concat(buffers);
            ws.write(buf);

            // 递归进行下一次请求
            if (!pause && start < total) {
                download();
            }
        });
    }).end();
}

// 监控输入
process.stdin.on("data", data => {
    // 获取指令
    let ins = data.toString().match(/(\w*)\/r/)[1];
    switch (ins) {
        case "s":
        case "r":
            pause = false;
            download();
            break;
        case "p":
            pause = true;
            break;
    }
});
复制代码

在上面代码中下载的文件通过 config 中的 path 属性配置,每次调用 download 函数下载时都会重新计算当前范围请求的初始位置和结束位置,并设置 Range 请求头,下一次请求靠递归 download 来实现。

在执行时需先启动我们的服务器,在通过命令行输入 node client.js 来启动客户端,在命令窗口输入对应的指令进行开始下载、暂停下载和恢复下载操作。

总结

相信现在已经了解什么是范围请求,范围请求客户端和服务端需要做些什么,其实说白了就是对应的请求头和响应头的使用,需要注意的是范围请求的响应状态码为 206,这样的需求在一些上传、下载资源的网站也很常见,其目的就是为了让我们实现断点续传,不至于一次没有上传或下载完成的资源文件,在下一次的做同样操作时需要重新来过,可以接着上次的位置继续,范围请求在视频网站上也广泛应用,边请求边观看,不至于一次加载整个视频资源,节省流量,节省时间。

原文出自:https://www.pandashen.com


高校信息中心部门工作管理系统 软件开发需求文档 1. 引言 1.1 项目背景 随着高校信息中心业务的不断拓展,部门日常工作日益繁杂,为提高工作管理效率、规范工作流程、便于工作考核,特计划开发部门工作管理系统,实现工作任务的数字化管理。 1.2 项目目标 开发一个基于 B/S 架构的部门工作管理系统,满足系统管理员、部门负责人、部门员工三类角色的工作需求,实现工作任务的全生命周期管理,支持工作统计与信息导出,提升部门工作管理的规范化和高效化水平。 1.3 项目范围 本系统仅用于高校信息中心内部的工作管理,涵盖用户管理、工作大类管理、任务管理、统计分析等功能模块,不涉及高校其他部门的业务管理。 2. 总体描述 2.1 系统目标 实现多角色用户的权限管理,确保不同角色按权限操作。 实现工作任务的创建、下发、接收、执行、提交等全流程管理。 支持工作任务相关信息的详细记录,包括工作名称、时间、状态等。 提供工作统计分析功能,支持按时间对工作大类进行统计。 支持工作信息及佐证材料的导出,方便考核工作开展。 2.2 用户特点 系统管理员:熟悉系统管理操作,负责系统用户和基础数据管理。 部门负责人:了解部门工作规划,需要创建和下发任务,关注任务进展。 部门员工:主要执行任务,需要及时更新任务状态和提交相关材料。 2.3 运行环境 服务器操作系统:CentOS 7。 数据库:MySQL 5.7 及以上版本。 服务端:使用nodejs。 web应用:使用vue。 客户端:主流浏览器(Chrome 80 及以上、Firefox 75 及以上、Edge 80 及以上)。 3. 具体需求 3.1 功能需求 3.1.1 用户管理模块(系统管理员操作) 用户添加:录入用户基本信息(姓名、工号、所属部门、角色等),设置初始密码。 用户查询:可按姓名、工号、角色等条件查询用户信息。 用户修改:修改用户的基本信息(除工号外),可重置用户密码。 用户删除:删除不再使用系统的用户账号。 3.1.2 工作大类管理模块(系统管理员操作) 工作大类添加:录入工作大类名称、描述等信息。 工作大类查询:查询所有工作大类信息。 工作大类修改:修改工作大类的名称、描述等信息。 工作大类删除:删除不再使用的工作大类(需确保该大类下无相关任务)。 3.1.3 任务管理模块 3.1.3.1 部门负责人操作 定制任务:创建新任务,填写任务基本信息(工作名称、所属工作大类、工作开始时间、工作结束时间、任务描述等)。 下发任务:将定制的任务下发给指定的部门员工,下发后任务状态为 “未领取”。 创建个人任务:创建属于自己的工作任务,填写相关信息,任务状态可自行设置为 “进行中” 等。 查看任务:查看所有自己创建、下发的任务及任务的详细状态(包括接收人、当前状态、完成情况等)。 任务催办:对处于 “未领取” 或 “进行中” 且即将超时的任务,向相关员工发送催办通知。 3.1.3.2 部门员工操作 接收任务:查看部门负责人下发的 “未领取” 任务,选择接收或拒绝(拒绝需填写理由)。 创建个人任务:创建属于自己的工作任务,填写相关信息。 更新任务状态:根据任务进展,将任务状态从 “进行中” 更新为 “已完成” 等。 填写量化指标:针对任务填写量化指标,如完成数量、完成比例等。 上传佐证材料:上传与任务相关的佐证材料,支持文本文件、图片、pdf 等多种格式。 查看个人任务:查看自己接收的任务和创建的个人任务及其详细信息。 3.1.4 统计分析模块 按时间统计:可按年、月、日等时间维度对各工作大类的任务数量、完成情况等进行统计。 统计结果展示:以表格、图表(柱状图、饼图等)形式展示统计结果。 信息导出:导出统计结果对应的工作信息及相关佐证材料,支持 Excel、PDF 等格式。按照这个需求,给出数据库设计、前端代码、后端代码。
最新发布
07-28
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值