Как получать файлы в Javascript, отправленные из Flask, с помощью send_file()

Я разрабатываю веб-приложение, используя HTML + простой Javascript во внешнем интерфейсе и Flask в качестве бэкэнда. Приложение отправляет некоторый идентификатор на сервер, сервер должен создать отчет в виде файла PDF и отправить его обратно клиенту.

Я использую Flask для серверной части и создал следующую конечную точку:

    @app.route("/download_pdf", methods=['POST'])
    def download_pdf():
        if request.method == 'POST':
            report_id = request.form['reportid']
            print(report_id) //Testing purposes.
            // do some stuff with report_id and generate a pdf file.
            return send_file('/home/user/report.pdf', mimetype='application/pdf', as_attachment=True)
// I already tried different values for mimetype and as_attachment=False

Из командной строки я могу проверить конечную точку и получить нужный файл, а консоль сервера выводит 123 report_id, как и ожидалось:

curl --form reportid=123 http://localhost:5000/download_pdf >> output.pdf

Для внешнего интерфейса я создал кнопку, которая вызывает функцию Javascript:

<button id=pdf_button onclick = "generatePdf()"> PDF </button>

Функция Javascript выглядит следующим образом:

function generatePdf(){
    var report_list = document.getElementById("report_list")

    if (report_list.selectedIndex < 0){
        alert("Please, select a report.");
    }else{
        var req = new XMLHttpRequest();

        req.open("POST", "/download_pdf", true);
        req.responseType = "document";
        req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        req.onreadystatechange = function(){
            console.info(req.readyState)
            console.info(req.status)
            console.info(req.response)
            var link = document.createElement('a')
            link.href = req.response;
            link.download = "report.pdf"
            link.click()

        }
        var selected_value = report_list.options[report_list.selectedIndex].value;
        var params = "reportid = "+selected_value;
        req.send(params);
    }

};

req.response в этом случае имеет значение null. Однако вызов конечной точки был выполнен правильно, так как внутренняя консоль печатает report_id, как и ожидалось.

Уже пробовал:

Наконец, консоль Firefox показывает эти 6 сообщений после нажатия соответствующей кнопки (пожалуйста, обратите внимание на вызовы console.info() в предыдущем коде):

2
0
null
4
0
null

Кажется, что функция Javascript вызывается дважды при нажатии кнопки.

Моя цель - загрузить PDF. Я не знаю, что я делаю неправильно; Я был бы благодарен за любую помощь в этом.

Вы получаете ответ как null. Тип ответа должен быть blob. И ваш заголовок запроса также неверен

weegee 11.06.2019 17:36

Если вы видите код состояния HTTP как 0, это обычно связано с ошибкой, касающейся CORS. Если вы запускаете этот код из локального файла (например, адрес показывает file:/// или `C:` или какую-то другую конструкцию локальной файловой системы), это, скорее всего, причина.

Heretic Monkey 11.06.2019 17:48

Не похоже. Пользователь говорит, что у него есть серверная часть, поэтому он уверен, что использует нужное расширение. Вы также можете видеть, что ОП находится на локальном хосте @HereticMonkey.

weegee 11.06.2019 20:37

@weegee Заявление curl - это изолированный тест, который они провели на своей серверной части, а не на том, как они называют свой внешний интерфейс. Это единственное место, где они ссылаются на localhost в вопросе. В любом случае код состояния 0 чаще всего связан с проблемами CORS. Это также будет соответствовать нулевому ответу. Неважно, работают ли они из файловой системы или пытаются выполнить XHR с другим источником.

Heretic Monkey 11.06.2019 21:45

Спасибо за ваши комментарии. Решение, предоставленное @weegee, не решило проблему — на самом деле в какой-то момент у меня был похожий код (я пробовал ооочень много разных вещей). Я вызываю интерфейс из браузера с помощью http://localhost:5000/reports, но ввод IP-адреса сервера вместо localhost приводит к тем же результатам. Я немного больше изучу возможные проблемы, связанные с CORS, и вернусь, если найду решение.

Alejandro Villegas 12.06.2019 08:35

Я изменил асинхронный вызов конечной точки на синхронный вызов, и он загружает файл (требуется еще несколько изменений). Однако загруженный файл пуст, но размеры (как размер файла, так и размер страницы PDF) правильные. Похоже, что сейчас проблема с кодировкой, которая позволяет правильно читать метаданные PDF, но искажает двоичные данные PDF. Я не могу установить свойство responseType, так как его установка несовместима с синхронными вызовами. Я опубликую решение, как только исправлю его.

Alejandro Villegas 12.06.2019 11:20

Ну, вам не следует использовать синхронные вызовы, поскольку они блокируют основной пользовательский интерфейс. Почему вы используете эти типы звонков

weegee 12.06.2019 15:28

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

Alejandro Villegas 12.06.2019 15:39
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
8
4 576
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваши настройки ajax неверны, они должны быть такими

req.open("POST", "/download_pdf", true);
req.responseType = "blob";

req.onreadystatechange = function() {
  console.info(req.readyState)
  console.info(req.status)
  const blob = new Blob([req.response]);
  const url = window.URL.createObjectURL(blob);

  const link = document.createElement('a')
  link.href = url
  link.download = "report.pdf"
  link.click()
}

Тип ответа должен быть blob, и когда вы получите ответ, проанализируйте его как большой двоичный объект. Через некоторое время удалите ссылку

setTimeout(() => {
    window.URL.revokeObjectURL(url);
    link.remove();
}, 100);
Ответ принят как подходящий

Наконец, я нашел, в чем проблема, и я публикую это для протокола. Я думал, что это не связано, но <button>, вызывающий функцию Javascript, был внутри <form>. Я проверил, что форма была обновлена ​​до завершения вызова конечной точки, что привело к преждевременному завершению вызова.

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

HTML (и кнопка выбора, и кнопка не являются частью <form>):

<select id = "report_list" size=20> ... </select> ... <button id = "pdf_button" onclick = "generatePdf()"> PDF </button>

Javascript:

function generatePdf(){
    var report_list = document.getElementById("report_list");
    var req = XMLHttpRequest();
    var selected_value = report_list.options[report_list.selectedIndex].value;

    req.open("POST", "/reports/"+selected_value+"/pdf", true);
    req.responseType = "blob";
    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    req.onreadystatechange = function(){
    if (this.readyState == 4 && this.status == 200){
        var blob = new Blob([this.response], {type: "application/pdf"});
        var url = window.URL.createObjectURL(blob);
        var link = document.createElement('a');
        document.body.appendChild(link);
        link.style = "display: none";
        link.href = url;
        link.download = "report.pdf";
        link.click();

        setTimeout(() => {
        window.URL.revokeObjectURL(url);
        link.remove(); } , 100);
    }
    };

    req.send();
}

Колба:

@app.route("/reports/<id>/pdf", methods=['POST'])
def get_pdf(id):
    if request.method == 'POST':
        return send_file(get_pdf_path(id), mimetype='application/pdf')

Я не уверен, что это лучший или более элегантный способ сделать это, но пока это работает для меня.

Другие вопросы по теме