【问题标题】:how does axios handle blob vs arraybuffer as responseType?axios 如何将 blob 与 arraybuffer 处理为 responseType?
【发布时间】:2020-06-12 16:40:53
【问题描述】:

我正在下载带有axios 的压缩文件。为了进一步处理,我需要获取已下载的“原始”数据。据我所知,在 Javascript 中有两种类型:Blobs 和 Arraybuffers。两者都可以在请求选项中指定为responseType

在下一步中,需要解压缩 zip 文件。我为此尝试了两个库:js-zip 和 adm-zip。两者都希望数据成为 ArrayBuffer。到目前为止一切顺利,我可以将 blob 转换为缓冲区。在这种转换之后,adm-zip 总是很高兴地提取 zip 文件。但是,js-zip 会抱怨文件损坏,除非该 zip 已使用 'arraybuffer' 作为 axios responseType 下载。 js-zip 不适用于取自 blobbuffer

这让我很困惑。我认为ArrayBufferBlob 本质上只是对底层内存的看法。将某些内容下载为 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'));
        //...

这里有什么不同,我如何获得“真实”的下载数据?

【问题讨论】:

    标签: javascript node.js axios blob arraybuffer


    【解决方案1】:

    来自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",我猜当没有获取可解析的 JSON 数据时,它会回退到 "text"

    以文本形式获取二进制数据很容易产生损坏的数据。 因为Body.text() 和许多其他 API 返回的文本是USVStrings(它们不允许不成对的代理代码点)并且因为响应被解码为 UTF-8,所以二进制文件中的某些字节无法映射到字符正确,因此将被替换为 � (U+FFDD) 替换字符,无法恢复之前的数据:您的数据已损坏。

    这里有一个 sn-p 解释这一点,以 .png 文件的标题 0x89 0x50 0x4E 0x47 为例。

    (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 对象,因此 axios 没有对其进行猴子补丁是有道理的,只是为了返回一个其他人无法使用的响应。

    在浏览器中,您会得到完全相同的响应:

    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 一起使用,我没有在 API 调用中传递正确的 responseType。
    • 感谢这篇文章!我有同样的问题。但是即使以这种方式创建了一个新的 Blob,我的 excel 文件在下载后仍然是未知格式:(
    猜你喜欢
    • 2021-09-17
    • 2019-01-18
    • 1970-01-01
    • 2021-04-12
    • 2020-07-13
    • 2018-04-29
    • 2011-12-08
    • 2015-10-12
    • 2021-11-03
    相关资源
    最近更新 更多