从0-1实现文件下载CLI工具(1)

简介: 前言在日常学习/生活中,下载资源时,大部分情况是通过别人分享的资源站点,找到下载入口然后触发下载。当资源通过url传播的时候,一般也是直接打开,通过浏览器触发下载。资深的冲浪选手,一般会用一些客户端工具(还记得Win上的各种下载器),Mac上笔者有时候会使用 NeatDownloadManager,无 🪜 时也能拥有不错的下载速度

前言

在日常学习/生活中,下载资源时,大部分情况是通过别人分享的资源站点,找到下载入口然后触发下载。

当资源通过url传播的时候,一般也是直接打开,通过浏览器触发下载。

资深的冲浪选手,一般会用一些客户端工具(还记得Win上的各种下载器),Mac上笔者有时候会使用 NeatDownloadManager,无 🪜 时也能拥有不错的下载速度

Coder们用命令行下载文件的方式就很多了,比如最常使用的内置库 curl

下面是最常用的拉取资源的例子

# 链接是第三方服务缩短后的
# -L 参数表明自动对资源进行重定向
curl -L http://mtw.so/6647Rc -o 码上掘金logo.image
# 通过管道
curl -L http://mtw.so/6647Rc >码上掘金logo.image
# 原图链接 https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/759e2aa805c0461b840e0f0f09ed05fa~tplv-k3u1fbpfcp-zoom-1.image

当然 curl 也支持上传下载,以及多种传输协议,具体用法这里就不展开了,感兴趣的读者可以前往Quick Reference: Curl 备忘清单 进一步了解。

本文从 0-1 使用Node实现一个 url文件下载 工具,读者可以收获包含但不限于如下知识点,

Node实现下载文件如何通过Proxy(🪜)代理下载资源通用的Node本地持久化存储方法fs/path/http等模块的常见用法等。

对包含文件下载场景的CLI提供一个实践参考。

下面是简单的使用演示,对实现感兴趣的读者可以接着往下阅读

npx efst http://mtw.so/66eO7c

image.png

url资源下载

先是纯 url资源下载 的场景,本小节将详细展开相关小功能的实现。

Node原生实现

基于读写流操作,可以看到代码还是十分的简洁

import https from 'https'
import fs from 'fs'
import path from 'path'
function downloadByUrl(url: string, filename?: string) {
  const filepath = path.resolve(filename || randomName())
  https.get(url, (response) => {
    // 创建1个可写流
    const writeStream = fs.createWriteStream(filepath)
    response.pipe(writeStream).on('close', () => {
      console.log(`file save to ${filepath}`)
    })
  })
}
// sourceUrl 为前面的原图链接
downloadByUrl(sourceUrl,'test.image')

image.png

示例代码1

下载进度获取

大一点的文件肯定无法实现秒下载,需要获取一下进度,了解现在下载了多少

资源的总大小可以一般可以通过response headers中的content-length字段获取

const sumSize = +response.headers['content-length']

流的传输进度可以通过on data事件间接获取

在不通过response.setEncoding(BufferEncoding)修改的编码时,chunk默认是Buffer类型

let receive = 0
response.on('data', (chunk: Buffer) => {
  receive += chunk.length
  const percentage = receive / sumSize
})

到此进度percentage就可以获取到了

对上面的方法进行稍加改造,增加progressend两个方法(支持链式调用的丐版实现)

function downloadByUrl(url: string, filename?: string) {
  let receive = 0
  // 支持链式调用相关逻辑
  let progressFn: (cur: number, rec: number, sum: number) => void
  let endFn: (filepath: string) => void
  const thisArg = {
    progress: (fn: typeof progressFn) => {
      progressFn = fn
      return thisArg
    },
    end: (fn: typeof endFn) => {
      endFn = fn
      return thisArg
    }
  }
  https.get(url, (response) => {
    // 输出文件路径
    const filepath = path.resolve(filename || randomName())
    // 创建一个可写流
    const writeStream = fs.createWriteStream(filepath)
    const sumSize = +response.headers['content-length']! || 0
    response.on('data', (chunk: Buffer) => {
      receive += chunk.length
      progressFn && progressFn(chunk.length, receive, sumSize)
    })
    response.pipe(writeStream).on('close', () => {
      endFn && endFn(filepath)
    })
  })
  return thisArg
}
// 调用示例
downloadByUrl(sourceUrl, 'test.image')
  .progress((current, receive, sum) => {
    console.log(receive, ((receive / sum) * 100).toFixed(2), '%')
  })
  .end((filepath) => {
    console.log('file save:', filepath)
  })

image.png

示例代码2

重定向处理

部分资源在对外直接暴露时,可能是一个短链,此时就需要做重定向处理

重定向的状态码常见301302,当然还有其它的3开头的这里不赘述

除了状态码,重定向的目标url由response.headers.location表示

这里稍微改造一下之前的代码,添加一个重定向逻辑即可

// 通过url 简单区分一下 资源是 https 还是 http 
const _http = url.startsWith('https') ? https : http
_http.get(
  url,
  {
    // 添加一个UA,避免404
    // 部分短链服务网站没有UA会响应404
    headers: {
      'User-Agent': 'node http module'
    }
  },
  (response) => {
    const { statusCode } = response
    // 判断状态码是否3开头
    if (Math.floor(statusCode! / 100) === 3) {
      // 且存在 location
      if (response.headers.location) {
        // 递归
        downloadByUrl(response.headers.location, filename)
          // 透传事件
          .progress(progressFn)
          .end(endFn)
        return
      }
      // 不存在抛出错误
      throw new Error(
        `url:${url} status ${statusCode} without location header`
      )
    }
  }
)

image.png

示例代码3

为了防止无限重定向,还需要加个次数限制,再简单改造一下上述代码,添加一个配置属性作为入参

interface Options {
  filename: string
  maxRedirects: number
}
function downloadByUrl(url: string, option?: Partial<Options>) {
  const ops: Options = { filename: randomName(), maxRedirects: 10, ...option }
  // 省略一些重复代码
  _http.get(
    url,
    (response) => {
      const { statusCode } = response
      if (Math.floor(statusCode! / 100) === 3 && ops.maxRedirects) {
        ops.maxRedirects -= 1
        // 递归调用
        if (response.headers.location) {
          downloadByUrl(response.headers.location, ops)
          return
        }
      }
    }
  )
  return thisArg
}

示例代码4

请求超时

部分资源由于网络原因可能出现超时,为了避免长时间无反馈等待,可以设置超时时间

http模块支持timeout属性设置

// 接着之前的例子修改部分代码即可
const request = _http.get(
  url,
  {
    // 设置超时时间,单位ms
    timeout: ops.timeout || 300000,
  },
  (response) => {
    // 省略response 逻辑
  }
)
request.on('timeout', () => {
  // 中断请求,输出错误
  request.destroy()
  console.error(`http request timeout url:${url}`)
})

下面是请求 google logo 失败示例

image.png

从0-1实现文件下载CLI工具(2):https://developer.aliyun.com/article/1395069?spm=a2c6h.13148508.setting.29.55964f0ez7IHhI


相关文章
|
19天前
|
前端开发 Java 测试技术
性能工具之 JMeter 上传与下载脚本编写
【4月更文挑战第3天】性能测试工作中,文件上传也是经常见的性能压测场景之一,那么 JMeter 文件上传下载脚本怎么做?
39 2
性能工具之 JMeter 上传与下载脚本编写
|
19天前
|
JavaScript 前端开发 数据安全/隐私保护
NodeJS 下构建 命令行工具(CLI) 与 交互式命令界面 的实践
NodeJS 下构建 命令行工具(CLI) 与 交互式命令界面 的实践
265 1
|
19天前
|
存储
从0-1实现文件下载CLI工具(3)
参数转换传递 下面是defaultCommand的逻辑,只需要将相关参数处理后透传给定义的download方法即可,option 不支持 number 所以需要对数字字符串做一些转换
|
19天前
从0-1实现文件下载CLI工具(2)
示例代码5 Proxy 部分资源访问不顺畅的时候,通常会走服务代理(🪜) 以谷歌的logo资源链接https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png 要让前面的方法downloadByUrl顺利执行,就需要其走代理服务
|
19天前
|
安全 网络安全 数据安全/隐私保护
Mendelson AS2 介绍下载和配置
Mendelson AS2 介绍下载和配置
|
19天前
uniapp下载文件安装
uniapp下载文件安装
|
6月前
|
JavaScript 小程序 前端开发
nodejs管理包工具nvm的安装与基本使用
有好的建议,请在下方输入你的评论。 欢迎访问个人博客 guanchao.site 欢迎访问我的小程序:打开微信->发现->小程序->搜索“时间里的”
67 1
|
8月前
|
数据安全/隐私保护 iOS开发
如何使用 altool 命令行工具上传 IPA 包:
如何使用 altool 命令行工具上传 IPA 包:
29 0
|
缓存 前端开发
前端下载并生成文件
前端下载并生成文件
|
Java Android开发 数据安全/隐私保护
ApkScan-PKID 查壳工具下载与使用
一、 关于壳的介绍 1、壳的功能:壳最本质的功能就是实现加载器,壳是指在一个程序的外面再包裹上另外一段代码,保护里面的代码不被非法修改或反编译的程序。它们一般都是先于程序运行,拿到控制权,然后完成它们保护软件的任务,深入点就是在apk外面再套一层壳,在运行的时候这层壳会把真正的apk的dalvik指令集释放出来,为apk加壳是目前主流的防护方案,真正的源码是隐藏在壳之下的,要想拿到源码研究其逻辑就
583 0
ApkScan-PKID 查壳工具下载与使用