как ax ios обрабатывает blob против arraybuffer как responseType? - PullRequest
0 голосов
/ 28 февраля 2020

Я загружаю zip-файл с ax ios. Для дальнейшей обработки мне нужно получить «сырые» данные, которые были загружены. Насколько я вижу, в Javascript есть два типа для этого: Blobs и Arraybuffers. Оба параметра можно указать как responseType в параметрах запроса.

На следующем шаге файл zip необходимо распаковать. Я попробовал две библиотеки для этого: js -zip и adm-zip. Оба хотят, чтобы данные были ArrayBuffer. Пока все хорошо, я могу преобразовать BLOB-объект в буфер. И после этого преобразования adm-zip всегда с радостью извлекает zip-файл. Однако js -zip жалуется на поврежденный файл, если zip не был загружен с 'arraybuffer' в качестве топора ios responseType. js -zip не работает на buffer, который был взят из blob.

Это меня очень смутило. Я думал, что и ArrayBuffer, и Blob - это просто взгляды на основную память. Может быть разница в производительности между загрузкой чего-либо в виде буфера BLOB-объекта или буфера. Но результирующие данные должны быть одинаковыми, верно?

Ну, я решил поэкспериментировать и нашел это:

Если вы укажете responseType: 'blob', ax ios преобразует response.data в строка. Допустим, у вас есть sh эта строка и вы получили хэш-код A. Затем вы конвертируете ее в буфер. Для этого преобразования вам нужно указать кодировку. В зависимости от кодировки, вы получите множество новых хэшей, назовем их B1, B2, B3, ... При указании в качестве кодировки utf8 я возвращаюсь к исходному ha sh A.

Так что я думаю, при загрузке данных как 'blob', ax ios неявно преобразует их в строку, закодированную с помощью utf8. Это кажется очень разумным.

Теперь вы указываете responseType: 'arraybuffer'. Ax ios предоставляет вам буфер как response.data. Ха sh буфер и вы получите хеш-код C. Этот код не соответствует ни одному коду в A, B1, B2, ...

Так что при загрузке данных как 'arraybuffer' вы получаете совершенно другие данные?

Теперь это имеет смысл мне, что библиотека распаковки js -zip жалуется, если данные загружаются как 'blob'. Это, вероятно, на самом деле как-то повреждено. Но тогда как adm-zip может извлечь его? И я проверил извлеченные данные, это правильно. Это может иметь место только в этом конкретном c 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'));
        //...

Что создает разницу здесь, и как я могу получить «истинные» загруженные данные?

1 Ответ

1 голос
/ 29 февраля 2020

С топор ios документы :

// `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", что, как я полагаю, дает откат к "text", когда не извлекаются данные JSON с возможностью разбора.

Выборка двоичных данных как текст склонен генерировать поврежденные данные. Поскольку текст, возвращаемый Body.text () и многими другими API, является USVStrings (они не допускают непарные суррогатные кодовые точки) и поскольку ответ декодируется как UTF-8, некоторые байты из двоичного файла не могут быть правильно сопоставлены с символами и, таким образом, будут заменены символом замены U (U + FFDD), без возможности вернуть то, что было раньше: ваши данные повреждены.

Вот фрагмент, объясняющий это, используя в качестве примера заголовок файла .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, поэтому имеет смысл, что ax ios не сделал обезьяньего патча, чтобы они могли вернуть ответ, который никто другой не сможет получить.

Из браузера вы получите точно такие же ответы:

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>
...