Logo Vincent
返回文章列表

Node.js-开发实践:下载文件

Node.js
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】 要

© 2026 vincentqiao.com . 保留所有权利。