복붙노트

[SPRING] 스프링 부트 : Apache Commons FileUpload를 사용하는 대규모 스트리밍 파일 업로드

SPRING

스프링 부트 : Apache Commons FileUpload를 사용하는 대규모 스트리밍 파일 업로드

'스트리밍'Apache Commons File Upload API를 사용하여 큰 파일을 업로드하려고합니다.

기본 Spring Multipart 업 로더가 아닌 Apache Commons File Uploader를 사용하는 이유는 매우 큰 파일 크기 (~ 2GB)를 업로드 할 때 실패하기 때문입니다. 그런 파일 업로드가 꽤 흔한 GIS 응용 프로그램을 만들고 있습니다.

내 파일 업로드 컨트롤러의 전체 코드는 다음과 같습니다.

@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

/ uploader에 대한 JSP보기는 다음과 같습니다.

<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>

나는 무엇을 잘못하고있을 것인가?

해결법

  1. ==============================

    1.M.Deinum의 매우 유용한 의견 덕분에 문제를 해결할 수있었습니다. 내 원래 게시물 중 일부를 정리하고 나중에 참조 할 수 있도록 완전한 답변으로 게시하고 있습니다.

    M.Deinum의 매우 유용한 의견 덕분에 문제를 해결할 수있었습니다. 내 원래 게시물 중 일부를 정리하고 나중에 참조 할 수 있도록 완전한 답변으로 게시하고 있습니다.

    내가 처음 만들었던 실수는 Spring이 제공하는 기본 MultipartResolver를 비활성화하는 것이 아닙니다. 이것은 HttpServeletRequest를 처리하는 resolver에서 끝나기 때문에 컨트롤러가 컨트롤러를 사용하기 전에 HttpServeletRequest를 소비하게됩니다.

    M. Deinum 덕분에이 기능을 비활성화하는 방법은 다음과 같습니다.

    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. ==============================

    2.최신 버전의 스프링 부트 (2.0.0.M7을 사용하고 있습니다)를 사용하는 경우 속성 이름이 변경되었습니다. Spring은 기술 특정 이름을 사용하여 시작되었습니다.

    최신 버전의 스프링 부트 (2.0.0.M7을 사용하고 있습니다)를 사용하는 경우 속성 이름이 변경되었습니다. Spring은 기술 특정 이름을 사용하여 시작되었습니다.

    여러 구현이 활성화되어 있기 때문에 StreamClosed 예외가 발생하는 경우 마지막 옵션을 사용하면 기본 스프링 구현을 비활성화 할 수 있습니다

  3. ==============================

    3.application.properties 파일에 spring.http.multipart.enabled = false를 추가하십시오.

    application.properties 파일에 spring.http.multipart.enabled = false를 추가하십시오.

  4. ==============================

    4.나는 kindeditor + springboot를 사용합니다. 내가 (MultipartHttpServletRequest) 요청을 사용할 때. 파일을 가져올 수 있지만 appeche-common-io를 사용합니다. upload.parse (request) 반환 값이 null입니다.

    나는 kindeditor + springboot를 사용합니다. 내가 (MultipartHttpServletRequest) 요청을 사용할 때. 파일을 가져올 수 있지만 appeche-common-io를 사용합니다. upload.parse (request) 반환 값이 null입니다.

    public BaseResult uploadImg(HttpServletRequest request,String type){
                    MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
                    MultiValueMap<String, MultipartFile> multiFileMap = multipartRequest.getMultiFileMap();
    
  5. from https://stackoverflow.com/questions/32782026/springboot-large-streaming-file-upload-using-apache-commons-fileupload by cc-by-sa and MIT license