У меня есть приложение Flask, работающее на сервере Windows, использующее IIS в качестве обратного прокси-сервера для сервера приложений официантки. Он находится за локальным диспетчером трафика F5 моей организации. Ключевой функцией приложения является обработка больших (до 4 ГБ) загрузок файлов с последующей обработкой и задачами базы данных. После отправки формы процесс выглядит следующим образом:
Начать загрузку частями.
Соберите фрагменты на сервере в полный файл.
Вызовите маршрут Flask для асинхронной обработки повторно собранного файла.
Добавьте метаданные файла в базу данных.
Все работает так, как ожидалось, когда я использую сервер разработки Flask или Waitress напрямую без IIS. Однако описанный выше процесс завершается сбоем после шага 2 в рабочей функции sendMetadata
(с IIS), но только для загрузок большего размера (2+ ГБ), что приводит к ошибке блока catch Failed to fetch
и сетевой ошибке ERR_CONNECTION_RESET
.
document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById("caseForm");
const fileInput = document.getElementById("uploaded_file");
form.addEventListener("submit", async function (e) {
const file = fileInput.files[0];
await handleUpload(file);
});
async function handleUpload(file) {
try {
const uuidResponse = await fetch(
"{{ url_for('/*get_uuid*/') }}",
{
method: "POST",
headers: {
"X-CSRFToken": csrfToken,
},
}
).then((response) => response.json());
async function uploadChunk(/*args*/) {
/*get form data...*/
try {
const response = await fetch("{{ url_for('/*upload_chunk*/') }}", {
method: "POST",
body: chunkFormData,
headers: {
"X-CSRFToken": csrfToken,
},
}).then((response) => response.json());
if (response.success) {
/*success logic*/
} else {throw new Error(/*error message*/);}
} catch (error) {
/*retry logic*/
}
}
} catch (error) {
/*error handling*/
}
async function uploadFileInChunks() {
const uploadPromises = [];
for (let i = 0; i < totalChunks; i += concurrentUploads) {
const chunkPromises = [];
for (let j = 0; j < concurrentUploads && i + j < totalChunks; j++) {
chunkPromises.push(uploadChunk(i + j));
}
await Promise.all(chunkPromises);
}
}
await uploadFileInChunks();
await sendMetadata(uniqueFilename, file);
}
// problem //
async function sendMetadata(uniqueFilename, file) {
const formData = new FormData(form);
formData.append("uniqueFilename", uniqueFilename);
formData.append("fileName", file.name);
try {
pingServer(/*send_metadata_url*/); // Debugging
const response = await fetch(
"{{ url_for('/*send_metadata*/') }}",
{
method: "POST",
body: formData,
headers: {
"X-CSRFToken": csrfToken,
},
}
).then((response) => response.json());
console.info("Response: ", response); // response is never received
if (response.success) {
/*success logic*/
} else {
/*error handling*/
}
} catch (error) {
/*error handling*/
}
}
})
Функция отладки pingServer
, которая отправляет запрос на тот же URL-адрес, что и функция выборки, работает и возвращает ответ от сервера. Однако сама функция выборки никогда не генерирует ответ от сервера, и время соединения истекает.
Учитывая, что 1) код работает для всех размеров файлов только на сервере разработки Flask и сервере Waitress, и 2) он работает с IIS в качестве обратного прокси, но только для файлов меньшего размера (менее 2 ГБ), я думаю, что это это проблема конфигурации IIS. Я максимально увеличил размер файла и параметры конфигурации тайм-аута, которые смог найти в IIS, как рекомендовано здесь и в других местах. Я также подтвердил, что ошибка происходит за пределами локального диспетчера трафика F5, поэтому я не думаю, что причиной является F5.
Я не понимаю, почему работают файлы меньшего размера, и даже для больших файлов загрузка и повторная сборка фрагментов работают (я вижу неповрежденный файл на сервере), но последующая функция выборки, похоже, не срабатывает. Кто-нибудь сталкивался с чем-то подобным, прежде чем я попытаюсь найти обходной путь?
Вы можете попытаться увеличить максимальный размер загружаемого файла, изменив maxAllowedContentLength в файле web.config.
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength = "4294967295" />
</requestFiltering>
</security>
</system.webServer>
Получаете ли вы какие-либо другие сообщения об ошибках? Например, в консоли или средстве просмотра событий.
Консоль в конце концов выдает ошибку 500 (err_connection_reset), и блок catch сообщает «не удалось получить». Я не вижу ничего очевидного в средстве просмотра событий, но признаю, что не так хорошо с этим знаком.
Попробуйте использовать трассировку неудачных запросов, чтобы просмотреть подробную информацию об ошибке 500. Это создаст подробный файл журнала, который поможет вам выявить проблему.
Вот фрагмент трассировки неудачного запроса: failureReason = "TIME_TAKEN" statusCode = "200" triggerStatusCode = "0" timeTaken = "270688"
. (Что бы это ни стоило, когда я загружаю тот же файл на сервер разработки, процесс не занимает почти столько времени, поэтому я не думаю, что это проблема простого превышения лимита времени ожидания) где-то.
Одних только этих сообщений недостаточно для анализа вашей проблемы. Выложите, пожалуйста, полный лог.
Приносим извинения — файлы журналов огромны и сокращаются, и я не совсем уверен, как определить соответствующие данные. Я переконфигурировал код, чтобы сделать запрос POST заранее, прежде чем отправлять фрагменты файла на сервер. Эта стратегия работает – однако первоначальный запрос теперь достигает сервера через 2-3 минуты (но со временем это происходит). Я все еще подозреваю, что это проблема с моей конфигурацией IIS.
Обнаружена проблема... Я передал всю форму (включая загруженный файл) объекту FormData, а не извлекал только поля текстовой формы. Это привело к попытке отправить форму с огромным размером содержимого, который, насколько я понимаю, превышал максимальный размер полезной нагрузки IIS в 2 ГБ (помимо того, что это было избыточно, поскольку файл уже был загружен частями).
const formData = new FormData(form);
formData.append("uniqueFilename", uniqueFilename);
В конце концов проблема была решена путем создания пустого объекта FormData и добавления значений отдельных полей формы.
const formData = new FormData();
formData.append("uniqueFilename", uniqueFilename)
Спасибо. К сожалению, я уже превысил maxAllowedContentLength в своем web.config. Часть сценария для загрузки работает нормально... Я вижу файлы в соответствующем временном каталоге на сервере. По какой-то причине, похоже, вызов sendMetadata fetch не получает ответа от сервера.