Как обрабатывать большой файл в POST запросе и какие проблемы могут возникнуть
Что проверяют
Понимаете ли вы, где на пути файла стоят лимиты и что происходит с телом запроса в Spring Boot. Следом спрашивают, как принять файл на 5 ГБ так, чтобы он вообще не проходил через память приложения.
Как отвечать
Начните с устройства MultipartFile. Контейнер сервлетов разбирает multipart и при spring.servlet.multipart.file-size-threshold, равном по умолчанию нулю, пишет содержимое во временный файл на диске, а не в память. Память ломает не сам приём, а ваш код: file.getBytes() поднимает весь файл в heap и приводит к OutOfMemoryError.
Каркас ответа:
- лимиты: по умолчанию
max-file-size1 МБ иmax-request-size10 МБ, превышение даётMaxUploadSizeExceededException. У nginx свойclient_max_body_size, по умолчанию 1 МБ, и он отвечает413раньше, чем запрос дойдёт до приложения; - потоковая обработка:
file.transferTo(path)или чтение изgetInputStream()с передачей дальше, например частями в S3 multipart upload, без сборки в массив; - очень большие файлы: отключить разбор multipart и читать тело из
HttpServletRequest.getInputStream(), а лучше выдать клиенту presigned URL, чтобы файл шёл сразу в объектное хранилище; - долгая обработка: сохранить файл, вернуть
202 Acceptedс идентификатором задачи, а разбор отдать в очередь. Держать HTTP-запрос открытым минутами значит упираться в таймауты прокси; - потоки сервера: медленный клиент занимает поток Tomcat на всё время загрузки. Виртуальные потоки Java 21 через
spring.threads.virtual.enabled=trueв Spring Boot 3.2 и новее снимают эту проблему; - обрыв связи: для гигабайтов нужна докачка, через S3 multipart или протокол tus.
Отдельно назовите проверки: размер, тип по сигнатуре файла, а не по Content-Type, контрольная сумма и место во временном каталоге.
Где спотыкаются
Вызывают getBytes() или читают поток целиком в byte[], и сервис падает на третьем параллельном файле.
Предлагают WebFlux как решение. Если внутри собрать весь Flux<DataBuffer> в память или вызвать блокирующий код, реактивный стек ничего не даст.
Доверяют getOriginalFilename() и сохраняют файл под этим именем. Имя вида ../../app.jar превращается в path traversal.