스프링 부트 - 파일 업로드, 다운로드, 이미지 보여주기

SeungTaek·2021년 8월 19일
10
post-thumbnail

본 게시물은 스스로의 공부를 위한 글입니다.
틀린 내용이 있을 수 있습니다.

📒 HTML이 폼을 전송하는 방법 두 가지

1. 📌application/x-www-form-urlencoded

  • 가장 기본적인 전송 방법
  • Form태그에 별도의 enctype옵션이 없으면 이 방법으로 전송한다.
  • HTTP Body에 문자로 username=kim&age=20 와 같이 & 로 구분해서 전송한다.

하지만 파일을 업로드 하려면 문자가 아닌 바이너리 데이터를 전송해야 한다.

또한 파일뿐 아니라 이름, 나이 등도 함께 전송해야 한다.

즉, 첨부파일은 바이너리로, 이름과 나이는 문자로 동시에 전송해야 한다.

그래서 사용하는게 multipart/form-data이다.

2. 📌multipart/form-data

  • 위와 같은 문제를 해결하기 위해 multipart/form-data라는 전송 방식을 제공한다.
  • 방식을 사용하려면 Form 태그에 별도의 enctype="multipart/form-data" 를 지정해야 한다
  • HTTP Body를 살펴보자
------XXX
Content-Disposition: form-data; name="username"

kim
------XXX
Content-Disposition: form-data; name="file"; filename="intro.png"
Content-Type: image/png

1301390r13rjh13jr1309df13...
------XXX--
  • 위 예시에서 ------XXX로 전송 항목을 구분한것을 볼 수 있다. (실제론 무작위로 바운더리 문자가 정해지는데, HTTP 헤더를 보면 Content-Type: multipart/form-data; boundary=----xxxx에서 확인할 수 있다.)
  • 각 항목마다 항목별 헤더가 추가되어 있다.
  • 이런식으로 각각의 항목을 구분해서, 한번에 형식이 다른 여러 항목들을 전송할 수 있다.(이름 그대로 multipart!)

📒 서블릿을 이용한 파일 업로드

  • HttpServletRequestgetParts()라는 메소드를 제공한는데, multipart/form-data 전송 방식에서 각각 나누어진 부분을 받아서 확인할 수 있다.

  • 다음 예시 코드를 통해 서블릿이 제공하는 메소드 기능들을 파악해보자.

@PostMapping("/upload")
public String saveFile(HttpServletRequest request) throws ServletException, IOException {
    //특정 항목을 따로 뽑아낼 수 있다.
	String itemName = request.getParameter("itemName");
    
    //.getParts()로 모든 항목들을 collection형식으로 받을 수 있다.
    Collection<Part> parts = request.getParts();
    for (Part part : parts) {
        //form에서 지정한 name을 받을 수 있다.
 		log.info("name={}", part.getName());
        
        //각 항목마다 따로 존재하는 헤더값들을 collection으로 받을 수 있다.
		Collection<String> headerNames = part.getHeaderNames(); 
 		for (String headerName : headerNames) { //각 항목 헤더들 출력
 			log.info("header {}: {}", headerName, part.getHeader(headerName));
        }
        
        //파일의 오리지널 이름을 얻을 수 있다. 파일이 아닌 그냥 문자의 경우 null이다.
        log.info("submittedFileName={}", part.getSubmittedFileName()); 
        log.info("size={}", part.getSize()); //각 파트의 사이즈를 얻을 수 있다.
        
        InputStream inputStream = part.getInputStream(); //데이터 읽기
        String fullPath = "/fileSave" + part.getSubmittedFileName();
 		part.write(fullPath); //경로만 지정해주면 파일을 저장할 수 있다.
	 }
        
     

📒 스프링이 지원하는 파일 업로드

  • 스프링은 MultipartFile 이라는 인터페이스로 멀티파트 파일을 매우 편리하게 지원한다.
  • HTML Form의 name에 맞추어 @RequestParam으로 각 파트들을 받을 수 있다는게 큰 메리트이다.
  • @ModelAttribute으로도 받을 수 있다.
@PostMapping("/upload")
public String saveFile(@RequestParam String itemName, @RequestParam MultipartFile file) throws IOException {
...
}
  • MultipartFile 주요 메서드
    • file.getOriginalFilename() : 업로드 파일 명
    • file.transferTo(new File("PATH")) : 파일 저장

📒 더 알아보기...

📌 업로드 파일명과 서버저장 파일명은 분리하자.

@Data
public class UploadFile {
 	private String uploadFileName;
 	private String storeFileName;
}
  • 고객이 업로드한 파일명으로 서버 내부에 파일을 저장하면 안된다.
  • 똑같은 파일명으로 업로드하면 파일을 덮어버린다.
  • 그러기 때문에 서버에서는 저장할 파일명이 겹치지 않도록 내부에서 저장하는 별도의 파일명이 필요하다. (예를 들면 UUID를 사용해서 저장.)

📌 HTML에서 파일 업로드 코드

<form action="/item"method="post" enctype="multipart/form-data">
 <ul>
 	<li>상품명 <input type="text" name="itemName"></li>
 	<li>첨부파일<input type="file" name="attachFile" ></li>
 	<li>이미지 파일들<input type="file" multiple="multiple" name="imageFiles" ></li>
 </ul>
 <input type="submit"/>
 </form>
  • multipart/form-data을 사용하려면 form태그에 enctype="multipart/form-data"가 들어가야된다.
  • input태그 속성으로 multiple="multiple" 을 넣으면 파일을 여러개 선택할 수 있다.
    • 이때 스프링에선 List<MultipartFile>@RequestParam이든 @ModelAttribute든 받으면 된다.

📌 HTML의 <img>에 이미지 보여주는 법

@ResponseBody
@GetMapping("/images/{filename}")
public Resource showImage(@PathVariable String filename) throws
MalformedURLException {
 	return new UrlResource("file:" + file.getFullPath(filename));
 }
  • HTML에서 <img src="/images/[파일이름]"> 형식으로 쓰면 된다.
  • UrlResourcefile: FULLPATH를 인자로 넘겨주면 실제 그곳에 있는 파일을 UrlResource형태로 리턴해준다. 그럼 HTML의 img에 보여질 수 있는거다.

📌 첨부파일 다운로드하게 하는 방법

@GetMapping("/attach/{itemId}")
public ResponseEntity<Resource> downloadAttach(@PathVariable Long itemId) throws MalformedURLException {
	 //...itemId 이용해서 고객이 업로드한 파일 이름인 uploadFileName랑 서버 내부에서 사용하는 파일 이름인 storeFileName을 얻는다는 내용은 생략
    
    UrlResource resource = new UrlResource("file:" + fileStore.getFullPath(storeFileName));
    
    //한글 파일 이름이나 특수 문자의 경우 깨질 수 있으니 인코딩 한번 해주기
 String encodedUploadFileName = UriUtils.encode(uploadFileName,
StandardCharsets.UTF_8);
    
    //아래 문자를 ResponseHeader에 넣어줘야 한다. 그래야 링크를 눌렀을 때 다운이 된다.
    //정해진 규칙이다.
    String contentDisposition = "attachment; filename=\"" + encodedUploadFileName + "\"";
    
    return ResponseEntity.ok()
 			.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
		 	.body(resource);
}
  • HTML 에선 <a href="/attach/[아이템이름] /> 처럼 링크형식으로, 클릭했을 때 다운로드 받게 할 수 있다.

📒 정리하기

  1. <input type=file/>로 파일을 받을 수 있다. 이때 form에는 enctype="multipart/form-data" 속성이 있어야 한다.

    • multiple="multiple"가 있으면 파일 여러개를 한번에 업로드 할 수 있다.
  2. 스프링에선 @RequestParam또는 @ModelAttribute으로 MultipartFile file을 받을 수 있다.

    • multiple="multiple"이면 List<MultipartFile file>로 받으면 된다.
  3. file.transferTo(new File("PATH") 을 이용해 파일을 저장할 수 있다.

    • 이때 사용자가 업로드한 파일명과 서버 내부에서 저장하는 파일명은 서로 다르게 저장한다. (그니깐 이 두개를 연결해주는 DB테이블이 필요하다.)
    • 사용자가 업로드한 파일명은 file.getOriginalFilename() 으로 받을 수 있다.
    • 서버 내부에서 저장하는 파일명은 절대 겹치게 설정해선 안된다.(UUID 등을 사용하자.)
  4. 이미지를 HTML에 보여줄 땐 별도의 컨트롤러가 필요하다.

    • new UrlResource("file:" + file.getFullPath(filename));을 이용해 Resource를 리턴하자.

    • html에선 <img src=~~>로 컨트롤러에서 넘어오는 Resource를 보여줄 수 있다.

  5. 파일 다운 링크를 만들기 위해서도 별도의 컨트롤러가 필요하다.

    • Response HeadercontentDisposition를 넣어줘야 한다.
    • body값으론 Resource를 넣어주면 된다.

인프런의 '스프링 MVC 2편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요

profile
I Think So!

1개의 댓글

comment-user-thumbnail
2023년 9월 17일

안녕하세요 질문 드립니다.
스프링 서버에서 클라이언트로 이미지를 여러개 전송하고 싶으면 어떻게 하나요? ResponseEntity에 Resource를 여러개 담을 수 있나요?

답글 달기