Сохранение файла из blob:http с использованием javascript без перенаправления

Я уже прошел через некоторые из предложенных решений здесь, в SO, но безуспешно. Это проблема. Когда большой двоичный объект создается с использованием данных, полученных из конечной точки API, я хочу заставить браузер загрузить большой двоичный объект. До сих пор я пробовал три решения, но ни одно из них не работает. Далее следует код. Обратите внимание, что я добавил несколько комментариев в код для дальнейшего объяснения.

const FILE_NAME_REGEX = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
    
    export function Download(url) {
    	return APICall.get(url).then(response => {
    		const disposition = response.request.getResponseHeader('Content-Disposition');
    		//^This line gives the 'Refused to get unsafe header "Content-Disposition"' error, so next few lines won't execute and part with generating anchor is not used, but the part of code under the 'else' branch.
    
    		if (disposition && disposition.indexOf('attachment') !== -1) {
    			const matches = FILE_NAME_REGEX.exec(disposition);
    
    			if (matches != null && matches[1]) {
    				filename = matches[1].replace(/['"]/g, '');
    			}
    		}
    
    		const type = response.request.getResponseHeader('Content-Type');
    
    		const blob = new Blob([response.data], { type });
    
    		if (typeof window.navigator.msSaveBlob !== 'undefined') {
    			window.navigator.msSaveBlob(blob, filename);
    		} else {
    			const URL = window.URL || window.webkitURL;
    			const downloadUrl = URL.createObjectURL(blob);
    
    			if (filename) {
    				const a = document.createElement('a');
    
    				if (typeof a.download === 'undefined') {
    					window.location = downloadUrl;
    				} else {
    					a.href = downloadUrl;
    					a.download = filename;
    					document.body.appendChild(a);
    					a.click();
    					document.body.removeChild(a);
    				}
    			} else {

 // 1. soultion 
   window.location = downloadUrl;

 }
    
    			setTimeout(() => {
    				URL.revokeObjectURL(downloadUrl);
    			}, 100);
    		}
    	});
    }

// 2. решение

        const ifrmDownloader = document.createElement('iframe');
        ifrmDownloader.setAttribute('src', downloadUrl);
        ifrmDownloader.style.width = '0px';
        ifrmDownloader.style.height = '0px';
        document.body.appendChild(ifrmDownloader);

// 3. решение

window.open(downloadUrl,_blank);
  1. Решение не работает, потому что оно открывает другую вкладку и вместо файла возвращает пустой квадратик. Вероятно, это связано с потерей временного блоба при открытии новой вкладки.

  2. Решение просто не работает, и я не знаю, почему. Iframe добавляется в dom, и запрос записывается на вкладке «Сеть» в консоли разработчика, но загрузка не запускается. Вероятно, та же причина, что и в решении 1. Кроме того, это сообщение регистрируется:

Ресурс интерпретируется как документ, но передается с типом MIME image/png: "blob:https://example.com/e53bf47b-7f39-4758-b3dd-3cc2df5889ad".

  1. Решение тоже не работает. Во-первых, браузер заблокировал всплывающее окно, а после того, как всплывающее окно разрешено, загрузка файла не начинается.

Я чувствую, что здесь чего-то не хватает, но чего?


person Fikret Pasovic    schedule 05.02.2019    source источник


Ответы (2)


Ваш код настроен на загрузку файла только в том случае, если установлена ​​переменная filename.

Эта переменная устанавливается только в том случае, если вы можете прочитать заголовок Content-Disposition (т. е. только при том же происхождении).

Таким образом, простым исправлением будет установка фиктивного значения для filename, если вам не удалось получить его из заголовков, таким образом, filename всегда установлено, и вы всегда пытаетесь загрузить его, а не просто открывать его на новой странице.
Разумнее было бы попытаться разобрать имя файла из переменной url, но, не зная формата возможных URL-адресов, сделать это для вас сложно.

const FILE_NAME_REGEX = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;

export function Download(url) {
  return APICall.get(url).then(response => {
      const disposition = response.request.getResponseHeader('Content-Disposition');
      if (disposition && disposition.indexOf('attachment') !== -1) {
        const matches = FILE_NAME_REGEX.exec(disposition);

        if (matches != null && matches[1]) {
          filename = matches[1].replace(/['"]/g, '');
        }
      }
      if (!filename) {
        // getFileNameFromURL(url);
        filename = 'dummy.png';
      }
      const type = response.request.getResponseHeader('Content-Type');

      const blob = new Blob([response.data], {
        type
      });

      if (typeof window.navigator.msSaveBlob !== 'undefined') {
        window.navigator.msSaveBlob(blob, filename);
      } else {
        const URL = window.URL || window.webkitURL;
        const downloadUrl = URL.createObjectURL(blob);

//     if (filename) { // always truthy
        const a = document.createElement('a');

        if (typeof a.download === 'undefined') {
          window.location = downloadUrl;
        } else {
          a.href = downloadUrl;
          a.download = filename;
          document.body.appendChild(a);
          a.click();
          document.body.removeChild(a);
        }
//    }
/*  else { // useless
    window.location = downloadUrl;
    }
*/
      setTimeout(() => {
        URL.revokeObjectURL(downloadUrl);
      }, 100);
    }
  });
}
person Kaiido    schedule 06.02.2019

Вот что я нашел в отношении проблемы, которую я опубликовал. На самом деле было две проблемы:

  1. Заголовок Content-Disposition был отклонен, поэтому ни имя файла, ни информация об удалении не были доступны. Согласно ответу @Kaiido, это было устранено путем извлечения имени файла из URL-адреса и добавления «, png». НАПРИМЕР.

    const chunks = extendedURl.split('/');
    const pngExtension = '.png';
    const baseName = chunks[chunks.length - 1].split('?')[0];
    filename = `${baseName}${pngExtension}`;
    
  2. Более серьезная проблема, которую я не осознавал до сегодняшнего дня, заключалась в том, что при вызове метода axios Get для responseType не было установлено значение 'arraybuffer'.

    Поэтому я смог загрузить файлы, которые были «сломаны». Этот вопрос помог мне понять, в чем заключалась настоящая проблема: https://stackoverflow.com/questions/38936645/file-is-corrupted-while-downloading-using-angular-http

Как только вызывающему методу Get был предоставлен параметр 'responseType: "arraybuffer"', файлы стали выглядеть нормально, а не "испорченными".

Подводя итог, при вызове конечной точки .net Core Web API, которая возвращает FileStreamResult, чтобы иметь возможность создать Blob в JavaScript FE, вы должны явно установить для responseType значение «буфер массива» следующим образом:

axios.get(extendedURl, { responseType: 'arraybuffer' }).then(response =>...
person Fikret Pasovic    schedule 13.02.2019