[스프링/Spring] 서블릿과 파일 업로드 구현하기(2)

dongbrown·2024년 8월 30일

Spring

목록 보기
8/23

파일 업로드 기능 구현을 위해 서블릿이 제공하는 Part 인터페이스의 사용법과 실제 파일 업로드 구현 방법에 대해 알아보겠습니다.

1. 파일 저장 경로 설정

먼저 업로드된 파일이 저장될 경로를 설정해야 합니다.

# application.properties
file.dir=/Users/kimyounghan/study/file/

주의사항
1. 실제 파일이 저장될 폴더를 미리 생성해두어야 합니다.
2. 경로 설정 시 마지막에 슬래시(/)를 포함해야 합니다.

2. 파일 업로드 컨트롤러 구현

@Slf4j
@Controller
@RequestMapping("/servlet/v2")
public class ServletUploadControllerV2 {
    
    @Value("${file.dir}")
    private String fileDir;
    
    @GetMapping("/upload")
    public String newFile() {
        return "upload-form";
    }
    
    @PostMapping("/upload")
    public String saveFileV1(HttpServletRequest request) throws ServletException, IOException {
        log.info("request={}", request);
        String itemName = request.getParameter("itemName");
        log.info("itemName={}", itemName);
        
        Collection<Part> parts = request.getParts();
        log.info("parts={}", parts);
        
        for (Part part : parts) {
            log.info("==== PART ====");
            log.info("name={}", part.getName());
            
            // 모든 헤더 정보 출력
            Collection<String> headerNames = part.getHeaderNames();
            for (String headerName : headerNames) {
                log.info("header {}: {}", headerName, part.getHeader(headerName));
            }
            
            // 편의 메서드
            log.info("submittedFileName={}", part.getSubmittedFileName());
            log.info("size={}", part.getSize());
            
            // 파일 데이터 저장
            if (StringUtils.hasText(part.getSubmittedFileName())) {
                String fullPath = fileDir + part.getSubmittedFileName();
                log.info("파일 저장 fullPath={}", fullPath);
                part.write(fullPath);
            }
        }
        
        return "upload-form";
    }
}

3. Part 인터페이스 주요 메서드

Part 인터페이스는 멀티파트 형식의 데이터를 편리하게 다룰 수 있는 다양한 메서드를 제공합니다:

메서드설명
getSubmittedFileName()클라이언트가 전송한 파일명 반환
getInputStream()Part의 전송 데이터를 읽기 위한 InputStream 제공
write(String filename)Part를 통해 전송된 데이터를 파일로 저장
getHeaderNames()Part의 모든 헤더 이름을 컬렉션으로 반환
getHeader(String name)지정된 헤더의 값을 반환
getSize()Part의 데이터 크기 반환

4. 실행 결과

예를 들어, "상품A"라는 이름과 "스크린샷.png" 파일을 업로드하면 다음과 같은 로그가 출력됩니다:

==== PART ====
name=itemName
header content-disposition: form-data; name="itemName"
submittedFileName=null
size=7
body=상품A

==== PART ====
name=file
header content-disposition: form-data; name="file"; filename="스크린샷.png"
header content-type: image/png
submittedFileName=스크린샷.png
size=112384
파일 저장 fullPath=/Users/kimyounghan/study/file/스크린샷.png

5. 성능 최적화 팁

대용량 파일 업로드 테스트 시에는 다음 설정들을 비활성화하는 것이 좋습니다:

  1. HTTP 요청 로그 비활성화:
# application.properties에서 제거
logging.level.org.apache.coyote.http11=trace
  1. 바이너리 데이터 로그 출력 제거:
// 컨트롤러에서 제거
// log.info("body={}", body);

6. Part 데이터 구조

멀티파트 요청은 다음과 같은 구조로 전송됩니다:

  1. 폼 데이터 Part

    • 일반 텍스트 형식의 데이터
    • content-disposition 헤더만 존재
  2. 파일 Part

    • 파일 데이터
    • content-disposition 헤더에 filename 정보 포함
    • content-type 헤더로 파일 타입 정보 포함

정리

  • 서블릿의 Part 인터페이스를 사용하면 멀티파트 파일 업로드를 쉽게 구현할 수 있습니다.
  • Part의 편의 메서드들을 통해 파일명, 헤더 정보, 데이터 크기 등을 쉽게 확인할 수 있습니다.
  • 실제 파일 저장은 Part의 write() 메서드를 통해 간단히 구현할 수 있습니다.
  • 대용량 파일 처리 시에는 불필요한 로깅을 제거하여 성능을 최적화할 수 있습니다.

0개의 댓글