Загрузка запрошенного файла с помощью XMLHttpRequest

Я знаю, что jQuery ajax-метод не может обрабатывать загрузки, и я не хочу добавлять плагин jQuery для этого.

Я хочу знать, как отправлять данные POST с помощью XMLHttpRequest для загрузки файла.

Вот что я пробовал:

var postData = new FormData();
postData.append('cells', JSON.stringify(output));

var xhr = new XMLHttpRequest();
xhr.open('POST', '/export/', true);
xhr.setRequestHeader("X-CSRFToken", csrftoken);
xhr.responseType = 'arraybuffer';
xhr.onload = function (e) {
    console.log(e);
    console.log(xhr);
}
xhr.send(postData);

Я работаю с Django, и файл, похоже, успешно отправляет клиенту. На вкладке "Сеть" в Chrome я вижу тарабарщину на вкладке предварительного просмотра (что я ожидаю). Но я хочу отправить zip файл, а не текстовое представление zip файла. Здесь конец Django:

wrapper = FileWrapper(tmp_file)
response = HttpResponse(wrapper, content_type='application/zip')
response['Content-Disposition'] = "attachment; filename=export.zip"
response['Content-Length'] = tmp_file.tell()
return response

Я искал это часами, не найдя подходящего примера, как это сделать с XMLHttpRequests. Я не хочу создавать правильную html-форму с действием POST, потому что данные формы довольно большие и динамически создаются.

Что-то не так с этим кодом? Что-то мне не хватает? Я просто не знаю, как на самом деле отправлять данные клиенту в качестве загрузки.

Ответ 1

Запрос XHR не приведет к загрузке файла. Я не могу найти явное требование, но W3C doc на XMLHttpRequest не описывает никакой специальной реакции на контент-расположение = ответы на вложения либо

Вы можете загрузить файл с помощью window.open() на отдельной вкладке, если это не был запрос POST. Здесь было предложено использовать скрытую форму с target = _blank

UPD: этот ответ не является точным с момента появления API Blob. Пожалуйста, обратитесь к Стивену за подробностями.

Ответ 2

Если перед отправкой запроса вы установите для свойства XMLHttpRequest.responseType значение 'blob', то при получении ответа он будет представлен в виде BLOB-объекта. Затем вы можете сохранить BLOB-объект во временный файл и перейти к нему.

var postData = new FormData();
postData.append('cells', JSON.stringify(output));

var xhr = new XMLHttpRequest();
xhr.open('POST', '/export/', true);
xhr.setRequestHeader('X-CSRFToken', csrftoken);
xhr.responseType = 'blob';
xhr.onload = function (this, event) {
    var blob = this.response;
    var contentDispo = this.getResponseHeader('Content-Disposition');
    // https://stackoverflow.com/a/23054920/
    var fileName = contentDispo.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)[1];
    saveOrOpenBlob(blob, fileName);
}
xhr.send(postData);

А вот пример реализации saveOrOpenBlob:

function saveOrOpenBlob(blob, fileName) {
    window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
    window.requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fs) {
        fs.root.getFile(fileName, { create: true }, function (fileEntry) {
            fileEntry.createWriter(function (fileWriter) {
                fileWriter.addEventListener("writeend", function () {
                    window.location = fileEntry.toURL();
                }, false);
                fileWriter.write(blob, "_blank");
            }, function () { });
        }, function () { });
    }, function () { });
}

Если вам не нужно, чтобы браузер переходил к файлу, когда он доступен для просмотра, тогда сделать метод, который всегда сохраняет непосредственно в файл, намного проще:

function saveBlob(blob, fileName) {
    var a = document.createElement('a');
    a.href = window.URL.createObjectURL(blob);
    a.download = fileName;
    a.dispatchEvent(new MouseEvent('click'));
}

Ответ 3

download: function(){
    var postData = new FormData();
		var xhr = new XMLHttpRequest();
		xhr.open('GET', downloadUrl, true);
		xhr.responseType = 'blob';
		xhr.onload = function (e) {
			var blob = xhr.response;
			this.saveOrOpenBlob(blob);
		}.bind(this)
		xhr.send(postData);
 }

saveOrOpenBlob: function(blob) {
		var assetRecord = this.getAssetRecord();
		var fileName = 'Test.mp4'
		var tempEl = document.createElement("a");
    	document.body.appendChild(tempEl);
    	tempEl.style = "display: none";
        url = window.URL.createObjectURL(blob);
        tempEl.href = url;
        tempEl.download = fileName;
        tempEl.click();
		window.URL.revokeObjectURL(url);
	},