axios 如何将 blob 与 arraybuffer 作为 responseType 处理?
how does axios handle blob vs arraybuffer as responseType?
我正在使用 axios 下载 zip 文件。为了进一步处理,我需要获取已下载的 "raw" 数据。据我所知,在 Javascript 中有两种类型:Blob 和 Arraybuffers。两者都可以在请求选项中指定为 responseType
。
下一步,需要解压缩 zip 文件。我为此尝试了两个库:js-zip 和 adm-zip。两者都希望数据是 ArrayBuffer。到目前为止一切顺利,我可以将 blob 转换为缓冲区。在此转换之后,adm-zip 总是会愉快地提取 zip 文件。但是,js-zip 抱怨文件已损坏,除非已使用 'arraybuffer'
作为 axios responseType
下载 zip。 js-zip 不适用于从 blob
.
中获取的 buffer
这让我很困惑。我认为 ArrayBuffer
和 Blob
本质上只是对底层内存的看法。以 blob 形式下载内容与以缓冲区形式下载内容之间的性能可能存在差异。但是结果数据应该是一样的吧?
好吧,我决定试验一下,发现了这个:
如果指定responseType: 'blob'
,axios 会将response.data
转换为字符串。假设您对这个字符串进行哈希处理并获得哈希码 A。然后将其转换为缓冲区。对于此转换,您需要指定一种编码。根据编码的不同,你会得到各种新的哈希值,我们称它们为 B1,B2,B3,......当指定 'utf8' 作为编码时,我回到原来的哈希值 A.
所以我猜当下载数据为 'blob'
时,axios 隐式地将其转换为使用 utf8 编码的字符串。这似乎很有道理。
现在您指定 responseType: 'arraybuffer'
。 Axios 为您提供了一个缓冲区 response.data
。哈希缓冲区,你得到一个哈希码 C。此代码不对应于 A、B1、B2、...
中的任何代码
所以当以 'arraybuffer'
形式下载数据时,您会得到完全不同的数据?
如果数据作为 'blob'
下载,解压缩库 js-zip 会抱怨,这对我来说现在很有意义。它可能实际上以某种方式损坏了。但是 adm-zip 是如何提取它的呢?我检查了提取的数据,它是正确的。这可能只适用于这个特定的 zip 存档,但仍然让我感到惊讶。
这是我用于实验的示例代码:
//typescript import syntax, this is executed in nodejs
import axios from 'axios';
import * as crypto from 'crypto';
axios.get(
"http://localhost:5000/folder.zip", //hosted with serve
{ responseType: 'blob' }) // replace this with 'arraybuffer' and response.data will be a buffer
.then((response) => {
console.log(typeof (response.data));
// first hash the response itself
console.log(crypto.createHash('md5').update(response.data).digest('hex'));
// then convert to a buffer and hash again
// replace 'binary' with any valid encoding name
let buffer = Buffer.from(response.data, 'binary');
console.log(crypto.createHash('md5').update(buffer).digest('hex'));
//...
这里有什么不同,如何获取 'true' 下载的数据?
来自 axios docs:
// `responseType` indicates the type of data that the server will respond with
// options are: 'arraybuffer', 'document', 'json', 'text', 'stream'
// browser only: 'blob'
responseType: 'json', // default
'blob'
是“仅限浏览器”选项。
所以从 node.js 开始,当你设置 responseType: "blob"
时,"json"
将实际使用,我猜当没有 parse-able 时回退到 "text"
JSON数据已获取。
以文本形式获取二进制数据很容易生成损坏的数据。
因为文本 return 由 Body.text() and many other APIs are USVStrings 编辑(它们不允许不成对的代理代码点)并且因为响应被解码为 UTF-8,所以二进制文件中的某些字节无法正确映射到字符因此将被替换为 � (U+FFDD) 替换字符,无法取回之前的数据:您的数据已损坏。
这里有一段解释这一点的片段,以 .png 文件 0x89 0x50 0x4E 0x47
的 header 为例。
(async () => {
const url = 'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png';
// fetch as binary
const buffer = await fetch( url ).then(resp => resp.arrayBuffer());
const header = new Uint8Array( buffer ).slice( 0, 4 );
console.log( 'binary header', header ); // [ 137, 80, 78, 61 ]
console.log( 'entity encoded', entityEncode( header ) );
// [ "U+0089", "U+0050", "U+004E", "U+0047" ]
// You can read more about (U+0089) character here
// https://www.fileformat.info/info/unicode/char/0089/index.htm
// You can see in the left table how this character in UTF-8 needs two bytes (0xC2 0x89)
// We thus can't map this character correctly in UTF-8 from the UTF-16 codePoint,
// it will get discarded by the parser and converted to the replacement character
// read as UTF-8
const utf8_str = await new Blob( [ header ] ).text();
console.log( 'read as UTF-8', utf8_str ); // "�PNG"
// build back a binary array from that string
const utf8_binary = [ ...utf8_str ].map( char => char.charCodeAt( 0 ) );
console.log( 'Which is binary', utf8_binary ); // [ 65533, 80, 78, 61 ]
console.log( 'entity encoded', entityEncode( utf8_binary ) );
// [ "U+FFDD", "U+0050", "U+004E", "U+0047" ]
// You can read more about character � (U+FFDD) here
// https://www.fileformat.info/info/unicode/char/0fffd/index.htm
//
// P (U+0050), N (U+004E) and G (U+0047) characters are compatible between UTF-8 and UTF-16
// For these there is no encoding lost
// (that's how base64 encoding makes it possible to send binary data as text)
// now let's see what fetching as text holds
const fetched_as_text = await fetch( url ).then( resp => resp.text() );
const header_as_text = fetched_as_text.slice( 0, 4 );
console.log( 'fetched as "text"', header_as_text ); // "�PNG"
const as_text_binary = [ ...header_as_text ].map( char => char.charCodeAt( 0 ) );
console.log( 'Which is binary', as_text_binary ); // [ 65533, 80, 78, 61 ]
console.log( 'entity encoded', entityEncode( as_text_binary ) );
// [ "U+FFDD", "U+0050", "U+004E", "U+0047" ]
// It's been read as UTF-8, we lost the first byte.
})();
function entityEncode( arr ) {
return Array.from( arr ).map( val => 'U+' + toHex( val ) );
}
function toHex( num ) {
return num.toString( 16 ).padStart(4, '0').toUpperCase();
}
node.js 中本来就没有 Blob object,所以 axios 没有 monkey-patch 是有道理的,这样他们就可以 return 响应 no-one 否则无论如何都可以消费。
在浏览器中,您会得到完全相同的响应:
function fetchAs( type ) {
return axios( {
method: 'get',
url: 'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png',
responseType: type
} );
}
function loadImage( data, type ) {
// we can all pass them to the Blob constructor directly
const new_blob = new Blob( [ data ], { type: 'image/jpg' } );
// with blob: URI, the browser will try to load 'data' as-is
const url = URL.createObjectURL( new_blob );
img = document.getElementById( type + '_img' );
img.src = url;
return new Promise( (res, rej) => {
img.onload = e => res(img);
img.onerror = rej;
} );
}
[
'json', // will fail
'text', // will fail
'arraybuffer',
'blob'
].forEach( type =>
fetchAs( type )
.then( resp => loadImage( resp.data, type ) )
.then( img => console.log( type, 'loaded' ) )
.catch( err => console.error( type, 'failed' ) )
);
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<figure>
<figcaption>json</figcaption>
<img id="json_img">
</figure>
<figure>
<figcaption>text</figcaption>
<img id="text_img">
</figure>
<figure>
<figcaption>arraybuffer</figcaption>
<img id="arraybuffer_img">
</figure>
<figure>
<figcaption>blob</figcaption>
<img id="blob_img">
</figure>
我正在使用 axios 下载 zip 文件。为了进一步处理,我需要获取已下载的 "raw" 数据。据我所知,在 Javascript 中有两种类型:Blob 和 Arraybuffers。两者都可以在请求选项中指定为 responseType
。
下一步,需要解压缩 zip 文件。我为此尝试了两个库:js-zip 和 adm-zip。两者都希望数据是 ArrayBuffer。到目前为止一切顺利,我可以将 blob 转换为缓冲区。在此转换之后,adm-zip 总是会愉快地提取 zip 文件。但是,js-zip 抱怨文件已损坏,除非已使用 'arraybuffer'
作为 axios responseType
下载 zip。 js-zip 不适用于从 blob
.
buffer
这让我很困惑。我认为 ArrayBuffer
和 Blob
本质上只是对底层内存的看法。以 blob 形式下载内容与以缓冲区形式下载内容之间的性能可能存在差异。但是结果数据应该是一样的吧?
好吧,我决定试验一下,发现了这个:
如果指定responseType: 'blob'
,axios 会将response.data
转换为字符串。假设您对这个字符串进行哈希处理并获得哈希码 A。然后将其转换为缓冲区。对于此转换,您需要指定一种编码。根据编码的不同,你会得到各种新的哈希值,我们称它们为 B1,B2,B3,......当指定 'utf8' 作为编码时,我回到原来的哈希值 A.
所以我猜当下载数据为 'blob'
时,axios 隐式地将其转换为使用 utf8 编码的字符串。这似乎很有道理。
现在您指定 responseType: 'arraybuffer'
。 Axios 为您提供了一个缓冲区 response.data
。哈希缓冲区,你得到一个哈希码 C。此代码不对应于 A、B1、B2、...
所以当以 'arraybuffer'
形式下载数据时,您会得到完全不同的数据?
如果数据作为 'blob'
下载,解压缩库 js-zip 会抱怨,这对我来说现在很有意义。它可能实际上以某种方式损坏了。但是 adm-zip 是如何提取它的呢?我检查了提取的数据,它是正确的。这可能只适用于这个特定的 zip 存档,但仍然让我感到惊讶。
这是我用于实验的示例代码:
//typescript import syntax, this is executed in nodejs
import axios from 'axios';
import * as crypto from 'crypto';
axios.get(
"http://localhost:5000/folder.zip", //hosted with serve
{ responseType: 'blob' }) // replace this with 'arraybuffer' and response.data will be a buffer
.then((response) => {
console.log(typeof (response.data));
// first hash the response itself
console.log(crypto.createHash('md5').update(response.data).digest('hex'));
// then convert to a buffer and hash again
// replace 'binary' with any valid encoding name
let buffer = Buffer.from(response.data, 'binary');
console.log(crypto.createHash('md5').update(buffer).digest('hex'));
//...
这里有什么不同,如何获取 'true' 下载的数据?
来自 axios docs:
// `responseType` indicates the type of data that the server will respond with // options are: 'arraybuffer', 'document', 'json', 'text', 'stream' // browser only: 'blob' responseType: 'json', // default
'blob'
是“仅限浏览器”选项。
所以从 node.js 开始,当你设置 responseType: "blob"
时,"json"
将实际使用,我猜当没有 parse-able 时回退到 "text"
JSON数据已获取。
以文本形式获取二进制数据很容易生成损坏的数据。 因为文本 return 由 Body.text() and many other APIs are USVStrings 编辑(它们不允许不成对的代理代码点)并且因为响应被解码为 UTF-8,所以二进制文件中的某些字节无法正确映射到字符因此将被替换为 � (U+FFDD) 替换字符,无法取回之前的数据:您的数据已损坏。
这里有一段解释这一点的片段,以 .png 文件 0x89 0x50 0x4E 0x47
的 header 为例。
(async () => {
const url = 'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png';
// fetch as binary
const buffer = await fetch( url ).then(resp => resp.arrayBuffer());
const header = new Uint8Array( buffer ).slice( 0, 4 );
console.log( 'binary header', header ); // [ 137, 80, 78, 61 ]
console.log( 'entity encoded', entityEncode( header ) );
// [ "U+0089", "U+0050", "U+004E", "U+0047" ]
// You can read more about (U+0089) character here
// https://www.fileformat.info/info/unicode/char/0089/index.htm
// You can see in the left table how this character in UTF-8 needs two bytes (0xC2 0x89)
// We thus can't map this character correctly in UTF-8 from the UTF-16 codePoint,
// it will get discarded by the parser and converted to the replacement character
// read as UTF-8
const utf8_str = await new Blob( [ header ] ).text();
console.log( 'read as UTF-8', utf8_str ); // "�PNG"
// build back a binary array from that string
const utf8_binary = [ ...utf8_str ].map( char => char.charCodeAt( 0 ) );
console.log( 'Which is binary', utf8_binary ); // [ 65533, 80, 78, 61 ]
console.log( 'entity encoded', entityEncode( utf8_binary ) );
// [ "U+FFDD", "U+0050", "U+004E", "U+0047" ]
// You can read more about character � (U+FFDD) here
// https://www.fileformat.info/info/unicode/char/0fffd/index.htm
//
// P (U+0050), N (U+004E) and G (U+0047) characters are compatible between UTF-8 and UTF-16
// For these there is no encoding lost
// (that's how base64 encoding makes it possible to send binary data as text)
// now let's see what fetching as text holds
const fetched_as_text = await fetch( url ).then( resp => resp.text() );
const header_as_text = fetched_as_text.slice( 0, 4 );
console.log( 'fetched as "text"', header_as_text ); // "�PNG"
const as_text_binary = [ ...header_as_text ].map( char => char.charCodeAt( 0 ) );
console.log( 'Which is binary', as_text_binary ); // [ 65533, 80, 78, 61 ]
console.log( 'entity encoded', entityEncode( as_text_binary ) );
// [ "U+FFDD", "U+0050", "U+004E", "U+0047" ]
// It's been read as UTF-8, we lost the first byte.
})();
function entityEncode( arr ) {
return Array.from( arr ).map( val => 'U+' + toHex( val ) );
}
function toHex( num ) {
return num.toString( 16 ).padStart(4, '0').toUpperCase();
}
node.js 中本来就没有 Blob object,所以 axios 没有 monkey-patch 是有道理的,这样他们就可以 return 响应 no-one 否则无论如何都可以消费。
在浏览器中,您会得到完全相同的响应:
function fetchAs( type ) {
return axios( {
method: 'get',
url: 'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png',
responseType: type
} );
}
function loadImage( data, type ) {
// we can all pass them to the Blob constructor directly
const new_blob = new Blob( [ data ], { type: 'image/jpg' } );
// with blob: URI, the browser will try to load 'data' as-is
const url = URL.createObjectURL( new_blob );
img = document.getElementById( type + '_img' );
img.src = url;
return new Promise( (res, rej) => {
img.onload = e => res(img);
img.onerror = rej;
} );
}
[
'json', // will fail
'text', // will fail
'arraybuffer',
'blob'
].forEach( type =>
fetchAs( type )
.then( resp => loadImage( resp.data, type ) )
.then( img => console.log( type, 'loaded' ) )
.catch( err => console.error( type, 'failed' ) )
);
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<figure>
<figcaption>json</figcaption>
<img id="json_img">
</figure>
<figure>
<figcaption>text</figcaption>
<img id="text_img">
</figure>
<figure>
<figcaption>arraybuffer</figcaption>
<img id="arraybuffer_img">
</figure>
<figure>
<figcaption>blob</figcaption>
<img id="blob_img">
</figure>