Node.js-开发实践:下载文件
【前言】
下载文件是Node.js中最常见的功能,
但实际开发中下载文件也会隐藏各种各样的坑。
【原始代码】
如果在网络搜索Node.js下载文件代码,
大概会搜到类似下面的代码片段,
本文从这里开始,陆续优化下载文件这个功能。
const http = require("http");
const https = require("https");
const fs = require("fs");
function downloadFile(url, dest) {
let protocol = url.startsWith("https") ? https : http;
let file = fs.createWriteStream(dest);
protocol
.get(url, (res) => {
res.pipe(file);
file.on("finish", () => {
file.close();
console.log("Download complete.");
});
})
.on("error", (err) => {
fs.unlink(dest);
console.error(`Download failed: ${err.message}`);
});
}
上面的代码片段可以看到:
1.兼容了Node.js原生的http和https请求
2.通过get请求获取url内容后pipe到本地文件写流中
3.捕获了请求的异常,异常时删除已经下载的文件
【校验url合法性】
可以看到上面代码是直接使用url的,
其实这里应该校验一下url的合法性,
代码如下:
// url
import { URL } from "url";
/**
* check url
* @param {*} checkUrl
* @returns
*/
export const checkUrl = (inputUrl) => {
// check
if (!inputUrl) return;
// check url
const url = new URL(inputUrl);
if (!url) return;
// check protocol
return url.protocol === "http:" || url.protocol === "https:";
};
这里使用了Node.js原生的url模块进行校验
【校验dest合法性】
同样的上面代码直接使用dest目标路径,
这里同样的需要进行一些校验,
代码如下:
// fs
import { path, mkdir } from "qiao-file";
/**
* check dest
* @param {*} dest
* @returns
*/
export const checkDest = async (dest) => {
// check dest
if (!dest) return;
// absolute dest
const destIsAbsolute = path.isAbsolute(dest);
if (!destIsAbsolute) dest = path.resolve(__dirname, dest);
// mkdir
const dir = path.dirname(dest);
const mkdirRes = await mkdir(dir);
if (!mkdirRes) return;
// return
return dest;
};
1. 这里兼容了dest为相对路径的情况
2. 以及如果dest的文件夹不存在则自动创建
添加url和dest校验后的代码如下:
// http
import http from "http";
import https from "https";
// fs
import { fs } from "qiao-file";
// check
import { checkUrl } from "./check-url.js";
import { checkDest } from "./check-dest.js";
/**
* download
* @param {*} url
* @param {*} dest
* @returns
*/
export const download = async (url, dest) => {
// check url
if (!checkUrl(url)) return Promise.reject(new Error("url is not valid"));
// check dest
const newDest = await checkDest(dest);
if (!newDest) return Promise.reject(new Error("dest is not valid"));
// p and file
const file = fs.createWriteStream(newDest);
const protocol = url.startsWith("https") ? https : http;
//
return new Promise((resolve, reject) => {
protocol
.get(url, (res) => {
// pipe
res.pipe(file);
// file
file.on("finish", () => {
file.close();
resolve(newDest);
});
file.on("error", (err) => {
fs.unlink(newDest);
reject(err);
});
})
.on("error", (err) => {
fs.unlink(newDest);
reject(err);
});
});
};
【校验文件流异常】
上面的代码中文件流的写入异常监听,
放在了res.pipe后,
如果写入的是一个没权限的路径,
则会抛出一个未捕获的异常,

修改后如下:
return new Promise((resolve, reject) => {
// file write stream
const file = fs.createWriteStream(newDest);
file.on("error", (err) => {
reject(err);
});
file.on("finish", () => {
file.close();
resolve(newDest);
});
// get
const protocol = url.startsWith("https") ? https : http;
protocol
.get(url, (res) => {
// pipe
res.pipe(file);
})
.on("error", (err) => {
fs.unlink(newDest);
reject(err);
});
});
【校验请求异常】
上面代码中么有校验请求的异常,
例如请求响应code非200,
如果code非200,但是不会走到error的监听,
只会下载一个错误的文件,
修改代码如下:
// get
const protocol = url.startsWith("https") ? https : http;
protocol
.get(url, (res) => {
// check res
if (!res || res.statusCode !== 200) {
return reject(new Error("response status code is not 200"));
}
// pipe
res.pipe(file);
})
.on("error", async (err) => {
await rm(newDest);
reject(err);
});
【设置timeout】
接着来设置下载的超时时间,
这里并不是直接在request中设置timeout就行,
nodejs的http模块中的timeout属性,
代表的是建立连接的超时时间,
例如设置timeout 100ms,
代表建立连接超过100ms,
而不是整个下载过程超过100ms,
这里需要自己实现一下,
代码如下:
// timeout
let timeoutId;
if (timeout) {
timeoutId = setTimeout(() => {
if (res) res.destroy();
clearFile(newDest);
reject(new Error("timeout"));
}, timeout);
}
const file = fs.createWriteStream(newDest);
file.on("error", (err) => {
clearTimeoutFn(timeoutId, timeout);
reject(err);
});
file.on("finish", () => {
file.close();
clearTimeoutFn(timeoutId, timeout);
resolve(newDest);
});
// pipe
res.pipe(file);
1. 首先如果传入timeout参数,就启动timeout
2. 如果提前完成,则清理timeout
3. 如果没有提前,清理文件,并返回异常
【校验文件完整性】
下载完文件后需要校验文件的完整性,
这里简单校验一下文件大小,
代码如下:
export const checkFileSize = (res, dest) => {
// check
if (!res || !dest) return;
// res length
const resLength = res.headers["content-length"];
if (!resLength) return;
// fs length
try {
const s = fs.statSync(dest);
if (!s || !s.size) return;
return parseInt(resLength, 10) === s.size;
} catch (error) {
debug(error);
}
};
【文件下载进度】
下载文件的过程中,
实时的反馈下载进度也很重要,
这里可以监听res的data事件实现,
代码如下:
// data
let data = 0;
/**
* on progress
* @param {*} res
* @param {*} onProgress
* @returns
*/
export const onDownloadProgress = (res, onProgress) => {
// check res
if (!res) return;
// check progress
if (!onProgress) return;
// check content
const contentLength = res.headers["content-length"];
if (!contentLength) return;
// total
const total = parseInt(contentLength, 10);
// on data
res.on("data", (chunk) => {
data = data + chunk.length;
const p = (data / total).toFixed(3);
const n = parseFloat(p);
onProgress(n);
});
};
【qiao-downloader】
封装了一个npm包,欢迎使用,
https://www.npmjs.com/package/qiao-downloader
1.基础的下载文件能力
2.请求异常校验
3.文件流异常校验
4.定制timeout能力
5.下载完成后文件大小校验
6.支持下载进度回调
相关推荐
Node.js-WebServer开发实践:使用autocannon进行接口压测
【前言】 AutoCannon是基于Node.js的接口压测工具, https://www.npmjs.com/package/autocannon 【安装】 【cli使用】 AutoCannon可以通过cli的方式使用, 其中各参数的含义可以直接输入autocannon查看, 例如10个并发连接,
Node.js-开发实践:使用健壮的FS
【前言】 fs模块是nodejs中最常见的模块, 可是fs的使用经常会有各种意想不到的坑。 【高性能FS】 其中之一是没有使用高性能的fs, 导致在electron应用中造成卡顿, fs模块有3种使用方式, callback方式 1\. 书写会导致回调地狱 2\. 体现nodejs事件驱动,非阻塞i
Node.js-开发实践:高性能FS
【前言】 nodejs的fs模块相信大家都不陌生, 本文对比一下fs模块的三种使用方式。 【fs的三种使用方式】 nodejs官方提供了fs的三种使用方式, https://nodejs.org/dist/latestv18.x/docs/api/fs.htmlpromiseexample call
Node.js-WebServer开发实践:使用PM2-Cluster模式提升接口QPS
【前言】 pm2是nodejs进程管理工具, https://pm2.keymetrics.io/ 介绍详见之前的一篇文章: https://blog.csdn.net/uikoo9/article/details/79018750 , 本文介绍下pm2的cluster模式, 并使用pm2的clus
Node.js-WebServer开发实践:定时任务
【前言】 定时任务是服务端开发中的必备能力, 在nodejs web server的开发过程中, 可以使用cron实现定时任务能力, 【qiaotimer】 cron的使用可以查看官网文档, 这里封装了一个npm包,欢迎使用:https://code.insistime.com//qiaotimer
Node.js-WebServer开发实践:获取公网IP
【前言】 在nodejs server开发实践中,在一些场景下需要获取公网ip, 而nodejs中默认的获取ip的方法,只能获取本地的ip, 而无法获取公网ip,本文介绍如何获取公网ip 【服务端获取公网ip】 在服务端获取公网ip比较简单, nodejs下可以通过下述方法获取, 但是如果有使用ng
Node.js-WebServer开发实践:本地日志
【前言】 本地日志是服务端开发中必备的能力, 在nodejs web server的开发过程中, 可以使用log4js实现本地日志能力, 【qiaolog】 log4js的使用可以查看官网文档, 这里封装了一个npm包,欢迎使用: https://www.npmjs.com/package/qiao
Node.js-WebServer开发实践:上传文件
【前言】 文件上传是服务端开发中的必备能力, 在nodejs web server的开发过程中, 可以使用formidable实现文件上传能力, 【qiaozupload】 formidable的使用可以查看官网文档, 这里封装了一个npm包,欢迎使用: https://code.insistime
Node.js-开发实践:图片处理
【前言】 使用nodejs的过程中会遇到一些处理图片的场景, 比如上传图片时进行压缩, 或者nodejs开发客户端本处理图片等, 本文介绍下nodejs常见的图片处理操作。 【常见图片处理库】 nodejs常见的图片处理库如下, 可以看到sharp从各方面都遥遥领先 npm包 github地址 gi
一篇文章开发Node.js-WebServer
【前言】 Node.js的服务端框架很多,耳熟能详的有express,koa等, 本文从零到一开发一个Node.js的web server。 https://nodejs.org/en/ https://expressjs.com/ https://koa.bootcss.com/ 【http】 要