fetch获取线上图片引发的思考

背景

起因是想下载一个套图但是貌似要 VIP 会员才能下载的样子,于是抱着侥幸的心理点开 F12 看了一眼,发现居然 src 就是图片的真实 uri,并且整个套图文件名数字是累加的, 嘴角疯狂上扬的同时决定写个脚本白嫖一下.

实现过程

该过程主要还是对 fetch 这个 api 的探究, 使用了能够运行在 node 中的node-fetch, 以下三种方法对图片音频进行下载测试均正常

stream

使用fetch返回的是一个Promise对象且resolve状态下的res.body是一个readablestream可读流,可以直接利用管道接到创建的可写流中.

1
2
3
4
5
6
7
8
fetch(url).then(res => {
return new Promise((resolve, reject) => {
const dest = fs.createWriteStream(path.resolve(__dirname, "./images", `${no}-0${num}.jpg`))
res.body.pipe(dest)
res.body.on("end", resolve)
res.body.on("error", reject)
})
})

buffer()

利用node-fetch相较浏览器fetch特有的 buffer()方法

1
2
3
4
5
6
7
fetch(url)
.then(res => res.buffer())
.then(image => {
fs.writeFile(path.resolve(__dirname, "./images", `${no}-0${num}.jpg`), image, function(err) {
console.log(err)
})
})

arrayBuffer()

我们知道 node 中传输二进制数据需要通过buffer进行存储, 而 fetch 只提供了res.arrayBuffer()的方法, 所以在使用arrayBuffer()后还需要使用Buffer.fromarrayBuffer转化为buffer,这也是第二种方法简化的操作

1
2
3
4
5
6
7
fetch(url)
.then(res => res.arrayBuffer())
.then(image => {
fs.writeFile(path.resolve(__dirname, "./images", `test-${no}-0${num}.jpg`), Buffer.from(image), function(err) {
console.log(err)
})
})

到这里已经对于获取远程文件的方法已经讲完了,下面是个人对其中不理解的部分的笔记

fetch 对比 ajax

之前一直觉得 fetch 与 $.ajax,axios 一样,对上面案例实践后发现还是自己菜了.
根据 MDN 上的描述:

Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。它还提供了一个全局 fetch() 方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。这种功能以前是使用 XMLHttpRequest 实现的。

也就是说 fetch 实际上是在Promise出现来解决callback hell前提下的一个新的异步获取资源的方案.
fetch与传统 ajax 的区别是

  • 除非出现网络故障或请求被阻止的情况下, fetch会将Promise状态置为reject,其余像响应状态码404,500, fetch都会将状态标记为resolve,但会将resolve的响应对象的ok属性置为false
  • fetch不会接收跨域的cookie,即跨域响应头中的set-cookie将被忽略
  • fetch默认不会跨域时发送cookie,(ajax 也是一样的), 默认fetchcredentialssame-origin

fetch 跨域

fetch 跨域需要后端配合CORS,后端需要对以下响应头字段进行设置,否则会报错

1
2
3
4
// Koa框架设置响应头
ctx.set("Access-Control-Allow-Origin", "*")
ctx.set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
ctx.set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")

之后,fetch设置fetch(url, {mode: 'cors'})即可进行跨域
若设置为{mode: 'no-cors'}, fetch不会进行报错,但会把Promiseresolve会返回ok: false, type: 'opaque'表明你没有权限访问
resolve内容

  • Q: 提问! 后端没有提供CORS, fetch需要怎么跨域?
  • A: 那就不要使用fetch api, 建议JSONP.

arrayBuffer, Buffer, typedArray

对于arrayBuffertypedArray都属于二进制数组,对于详细内容可以参考掘金的这篇文章:
掘金文章
这里做简述:

1
2
3
4
5
6
7
let a1 = new ArrayBuffer(4)
// 这里创建了四个格子, 一个格子一个字节
let u1 = new Uint8Array(a1)
// 这里就利用arraybuffer创建了一个typedArray, 由于数组的每个元素同样是8bit1个字节, 所以输出为[0,0,0,0]
u1.buffer === a1
// true
// 而通过typedArray.buffer方法又能够的到ArrayBuffer

下面这张图也能看出两者的关系:
arrayBuffer

Buffer 与 TypedArray

编写脚本时便有一个困扰,BufferarrayBuffer有啥关系? 这里记录一下.

  • Buffer是对Uint8Array的实现
    BufferUint8Array的相关 API 进行了实现,但 node 对Buffer类进行了优化,使之更适合在 node 下运行
  • Buffer并不是完全兼容TypedArray实现
    Buffer同样是一个Uint8Array类型数组实例。但它与 ES6 中的类型数组规范并不完全兼容,如:ArrayBuffer#slice()会创建一个分隔部分数据的拷贝,而 Buffer#slice()会创建一个从 Buffer 中拷贝数据的视图,相对来说 Buffer#slice()更高效。
  • Buffer可以与类型数组共享内存区
    可以从TypedArray.buffer属性或new ArrayBuffer()创建一个 Buffer 对象。该对象会与类型数组共享内存区:
1
2
3
4
5
6
7
8
9
const arr = new Uint16Array(2)
arr[0] = 5000
arr[1] = 4000

const buf1 = Buffer.from(arr) // 复制 buffer, 开辟新内存区
const buf2 = Buffer.from(arr.buffer) // 与 arr 共享内存空间

console.log(buf1) // <Buffer 88 a0>,由于Buffer'等同于'Uint8Array所以对于16bit的类型数组,会截取8bit
console.log(buf2) // <Buffer 88 13 a0 0f>

回到开头那个困扰, 除了底层规范不完全兼容, 我们可以以Uint8Array来看待Buffer,并可以通过Buffer.buffer的方式获得ArrayBuffer.

延伸的实践

谈了那么多关于二进制数据的话题,现在对掘金文章中的一些小案例进行一下实践

获取远程图片并转换为 base64 格式

to be continue…

reference

MDN fetch

知乎 fetch 如何跨域

fetch 从 URL 提取 Blob 并写入文件

Node.js Buffer 与 JavaScript TypeArray 类型数组的异同

前端处理后端接口传递过来的图片文件


fetch获取线上图片引发的思考
https://mariana-yui.github.io/2020/03/16/2020-03-16-fetch-and-ajax/
作者
Mariana
发布于
2020年3月16日
许可协议