Я разрабатываю веб-приложение, используя 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. Я не знаю, что я делаю неправильно; Я был бы благодарен за любую помощь в этом.
Если вы видите код состояния HTTP как 0, это обычно связано с ошибкой, касающейся CORS. Если вы запускаете этот код из локального файла (например, адрес показывает file:/// или `C:` или какую-то другую конструкцию локальной файловой системы), это, скорее всего, причина.
Не похоже. Пользователь говорит, что у него есть серверная часть, поэтому он уверен, что использует нужное расширение. Вы также можете видеть, что ОП находится на локальном хосте @HereticMonkey.
@weegee Заявление curl - это изолированный тест, который они провели на своей серверной части, а не на том, как они называют свой внешний интерфейс. Это единственное место, где они ссылаются на localhost в вопросе. В любом случае код состояния 0 чаще всего связан с проблемами CORS. Это также будет соответствовать нулевому ответу. Неважно, работают ли они из файловой системы или пытаются выполнить XHR с другим источником.
Спасибо за ваши комментарии. Решение, предоставленное @weegee, не решило проблему — на самом деле в какой-то момент у меня был похожий код (я пробовал ооочень много разных вещей). Я вызываю интерфейс из браузера с помощью http://localhost:5000/reports, но ввод IP-адреса сервера вместо localhost приводит к тем же результатам. Я немного больше изучу возможные проблемы, связанные с CORS, и вернусь, если найду решение.
Я изменил асинхронный вызов конечной точки на синхронный вызов, и он загружает файл (требуется еще несколько изменений). Однако загруженный файл пуст, но размеры (как размер файла, так и размер страницы PDF) правильные. Похоже, что сейчас проблема с кодировкой, которая позволяет правильно читать метаданные PDF, но искажает двоичные данные PDF. Я не могу установить свойство responseType, так как его установка несовместима с синхронными вызовами. Я опубликую решение, как только исправлю его.
Ну, вам не следует использовать синхронные вызовы, поскольку они блокируют основной пользовательский интерфейс. Почему вы используете эти типы звонков
Я знаю, что не должен использовать синхронные вызовы. Я намерен заставить его работать с синхронным вызовом, чтобы убедиться, что проблема не возникает из-за асинхронного метода, а затем двигаться дальше и реализовать асинхронную версию.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Ваши настройки 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')
Я не уверен, что это лучший или более элегантный способ сделать это, но пока это работает для меня.
Вы получаете ответ как null. Тип ответа должен быть blob. И ваш заголовок запроса также неверен