SpringBoot: загрузка больших потоковых файлов с помощью Apache Commons FileUpload
Я пытаюсь загрузить большой файл с помощью API потоковой передачи файлов Apache Commons.
Причина, по которой я использую Apache Commons File Uploader, а не загрузчик Spring Multipart по умолчанию, заключается в том, что он не работает, когда мы загружаем файлы очень больших размеров (~2 ГБ). Я работаю над ГИС-приложением, где такие загрузки файлов довольно распространены.
Полный код моего контроллера загрузки файлов выглядит следующим образом:
@Controller
public class FileUploadController {
@RequestMapping(value="/upload", method=RequestMethod.POST)
public void upload(HttpServletRequest request) {
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
// Inform user about invalid request
return;
}
//String filename = request.getParameter("name");
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload();
// Parse the request
try {
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext()) {
FileItemStream item = iter.next();
String name = item.getFieldName();
InputStream stream = item.openStream();
if (item.isFormField()) {
System.out.println("Form field " + name + " with value " + Streams.asString(stream) + " detected.");
} else {
System.out.println("File field " + name + " with file name " + item.getName() + " detected.");
// Process the input stream
OutputStream out = new FileOutputStream("incoming.gz");
IOUtils.copy(stream, out);
stream.close();
out.close();
}
}
}catch (FileUploadException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
@RequestMapping(value = "/uploader", method = RequestMethod.GET)
public ModelAndView uploaderPage() {
ModelAndView model = new ModelAndView();
model.setViewName("uploader");
return model;
}
}
Беда в том, что getItemIterator(request)
всегда возвращает итератор, у которого нет элементов (т.е. iter.hasNext()
) всегда возвращается false
,
Мой файл application.properties выглядит следующим образом:
spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:19095/authdb
spring.datasource.username=georbis
spring.datasource.password=asdf123
logging.level.org.springframework.web=DEBUG
spring.jpa.hibernate.ddl-auto=update
multipart.maxFileSize: 128000MB
multipart.maxRequestSize: 128000MB
server.port=19091
Представление JSP для /uploader
как следует:
<html>
<body>
<form method="POST" enctype="multipart/form-data" action="/upload">
File to upload: <input type="file" name="file"><br />
Name: <input type="text" name="name"><br /> <br />
Press here to upload the file!<input type="submit" value="Upload">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
</body>
</html>
Что я могу делать не так?
4 ответа
Благодаря некоторым очень полезным комментариям М. Дейнм, мне удалось решить проблему. Я очистил некоторые из моих оригинальных сообщений и публикую их как полный ответ для дальнейшего использования.
Первой ошибкой, которую я сделал, было не отключение по умолчанию MultipartResolver
что обеспечивает весна. Это закончилось обработчиком резолвера HttpServeletRequest
и, таким образом, потребляя его до того, как мой контроллер сможет на него воздействовать
Способ его отключить, благодаря М. Дейну, был следующим:
multipart.enabled=false
Однако после этого меня ждал еще один скрытый подводный камень. Как только я отключил многочастный распознаватель по умолчанию, я начал получать следующую ошибку при попытке сделать загрузку:
Fri Sep 25 20:23:47 IST 2015
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported
В моей конфигурации безопасности я включил защиту CSRF. Для этого я отправил свой запрос POST следующим образом:
<html>
<body>
<form method="POST" enctype="multipart/form-data" action="/upload?${_csrf.parameterName}=${_csrf.token}">
<input type="file" name="file"><br>
<input type="submit" value="Upload">
</form>
</body>
</html>
Я также немного изменил свой контроллер:
@Controller
public class FileUploadController {
@RequestMapping(value="/upload", method=RequestMethod.POST)
public @ResponseBody Response<String> upload(HttpServletRequest request) {
try {
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
// Inform user about invalid request
Response<String> responseObject = new Response<String>(false, "Not a multipart request.", "");
return responseObject;
}
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload();
// Parse the request
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext()) {
FileItemStream item = iter.next();
String name = item.getFieldName();
InputStream stream = item.openStream();
if (!item.isFormField()) {
String filename = item.getName();
// Process the input stream
OutputStream out = new FileOutputStream(filename);
IOUtils.copy(stream, out);
stream.close();
out.close();
}
}
} catch (FileUploadException e) {
return new Response<String>(false, "File upload error", e.toString());
} catch (IOException e) {
return new Response<String>(false, "Internal server IO error", e.toString());
}
return new Response<String>(true, "Success", "");
}
@RequestMapping(value = "/uploader", method = RequestMethod.GET)
public ModelAndView uploaderPage() {
ModelAndView model = new ModelAndView();
model.setViewName("uploader");
return model;
}
}
где Response - это простой общий тип ответа, который я использую:
public class Response<T> {
/** Boolean indicating if request succeeded **/
private boolean status;
/** Message indicating error if any **/
private String message;
/** Additional data that is part of this response **/
private T data;
public Response(boolean status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
// Setters and getters
...
}
Если вы используете последнюю версию весенней загрузки (я использую 2.0.0.M7), то имена свойств изменились. Spring начал использовать названия технологий
spring.servlet.multipart.maxFileSize = -1
spring.servlet.multipart.maxRequestSize = -1
spring.servlet.multipart.enabled = ложь
Если вы получаете исключения StreamClosed, вызванные активностью нескольких реализаций, то последний параметр позволяет отключить реализацию Spring по умолчанию.
Пожалуйста, попробуйте добавить spring.http.multipart.enabled=false
в файле application.properties.
You Can simply add spring properties:
spring.servlet.multipart.max-file-size=20000KB
spring.servlet.multipart.max-request-size=20000KB
here my maximum file size is 20000KB, you can change if required.
Я пользуюсь kindeditor + springboot. Когда я использую (MultipartHttpServletRequest) запрос. Я мог бы получить файл, но я использую appeche-common-io:upload.parse(request), возвращаемое значение равно нулю.
public BaseResult uploadImg(HttpServletRequest request,String type){
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
MultiValueMap<String, MultipartFile> multiFileMap = multipartRequest.getMultiFileMap();