
Client와 Server가 데이터를 주고받을 때에는 문자열, JSON뿐만 아니라
파일을 전송하기도 한다.
파일을 데이터로 주고받을 때에는 더 복잡한 과정을 수행해야만 한다.
Spring에서 파일을 처리하는 방법을 알아보자.

<form action="/send" method="post" enctype="mulipart/form-data">
HTML에서 파일을 전송할 때에는 enctype="multipart/form-data" 속성을 추가한다.
Content-Type: multipart/form-data; boundary=boundaryText
boundaryText
Context-Disposition: form-data; name="field"
value
boundaryText
Context-Disposition: form-data; name="field"
value
multipart로 데이터를 전송하는 경우
헤더에는 Content-Type: multipart/form-data가 추가되고
파일을 포함한 전송 데이터가 boudnary=에 입력된 boundaryText로 구분된다.
boundaryText는 무작위로 생성된다.
API 통신에서도 마찬가지로 Content-Type: multipart/form-data로 통신하면 된다.

// application.properties
// ** spring multipart 처리 on, off **
spring.servlet.multipart.enabled=false
// ** 파일 1개 최대 사이즈 **
spring.servlet.multipart.max-file-size=nMB
// ** 전체 파일 최대 사이즈 **
spring.servlet.multipart.max-reuqest-size=nMB
application.properties에서 multipart에 대해 다양한 설정을 할 수 있다.
spring.servlet.multipart.enabled는
Spring의 multipart 처리 기능을 on, off할 수 있는 기능으로
enabled=false인 경우 multipart 자체를 수신할 수 없다.
기본값은 true로 설정된다.
spring.servlet.multipart.max-file-size는
multipart로 전달하는 1개 파일의 최대 사이즈를 설정할 수 있고
spring.servlet.multipart.max-request-size는
multipart로 전달하는 전체 파일의 최대 사이즈를 설정할 수 있다.
// application.properties
// ** 파일 경로 설정, 이름 변경 가능 **
filePath=/url/
class Controller {
@Value("${filePath}")
String filePath;
}
filePath처럼 application.properties에 이름을 작성하고 url을 입력한 뒤,
클래스에서 @Value 애노테이션을 선언하고 '${}' 내부에 이름을 입력하면
작성한 경로를 변수에 대입하여 사용할 수 있다.

multipart 요청의 경우 DispatcherServlet은 MultipartResover를 실행하여
HttpServletRequest의 자식 인터페이스인 MultipartHttpServletRequest의 구현체로
StandardMultipartHttpServletRequest를 제공한다.
HTML에서 전송하는 파일인 multipart 형식은 Spring에서 Part라는 객체에 받아볼 수 있는데,
StandardMultipartHttpServletRequest에서 꺼내어 사용할 수 있다.
하지만 이 방식은 이렇게 HttpServletRequest에서 꺼내 사용해야 한다는 점과
파일 부분만 구분하기 번거롭다는 부분 때문에 파라미터 전달 방식을 대신해서 사용한다.

@PostMapping("/url")
public String method(@RequestParam MultipartFile file)
사용자가 보낸 파일은 HTML 형식으로 구분되어 서버에 데이터를 전송한다.
따라서 서버는 @RequestParam 애노테이션을 선언하고
MultipartFile 객체 파라미터를 통해 파일을 전달받을 수 있다.
위와 달리 API 통신에서는 @RequestPart를 사용한다.
@PostMapping("/url")
public String method(@RequestParam MultipartFile file) {
String filename = file.getOriginalFilename(); // ** 기존 파일 이름 반환 **
..
}
file.getOriginalFilename()는 사용자가 입력한 파일의 이름을 반환받는 메서드이다.
파일은 이름이 중복되는 경우 덮어씌워질 수 있다.
따라서 서버에서 파일을 저장할 때에는 파일의 원래 이름을 그대로 사용하지 않는다.
UUID와 같은 고유 이름을 원래 이름을 포함한 다른 정보와 함께 Mapping하고,
서버에 저장할 때에는 UUID와 같은 고유 이름과 확장자를 더해 저장한다.
class Controller {
@Value("${filePath}")
String filePath;
@PostMapping("/url")
public String method(@RequestParam MultipartFile file) {
String filename = file.getOriginalFilename();
String savePath = filePath + filename;
file.transferTo(new File(savePath)); // ** 파일 저장 **
..
}
}
file.transferTo(new File(filePath))는 설정한 경로에 파일을 저장하는 메서드이다.
application.properties에 설정한 파일 경로에 파일 이름을 더해 저장할 수 있다.

Client가 전달한 파일을 받아 Server가 저장했다면,
반대로 Server가 Client에게 파일을 전달할 수도 있다.
@ResponseBody
@GetMapping("/url")
public Resource method() throws MalformedURLException {
return new UrlResource("file:" + filePath);
}
Server가 Client에게 파일을 전달할 때에는 Resource 인터페이스를 사용한다.
@ResponseBody 애노테이션을 선언한 후 Resource를 반환할 때,
UrlResource에 "file:" 문자열과 함께 DB에 저장된 파일 경로를 입력하면
Client가 확인하는 웹 페이지에 이미지, 비디오, 문서 등의 파일을 출력할 수 있다.
th:src="/url"
뷰 렌더링 시에는 변수와 함께 th:src 속성을 더해
UrlResource를 반환하는 컨트롤러 url을 입력해야 화면에 파일을 출력할 수 있다.
Content-Disposition: attachment; filename=\"파일 이름"\
@ResponseBody
@GetMapping("/url")
public ResponseEntity<Resource> method() throws MalformedURLException {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"파일 이름"\")
.body(new UrlResource("file:" + filePath));
}
Server가 Content-Disposition 헤더에
attachment; filename=\"파일 이름"\을 추가하고 UrlResource를 반환하면
브라우저는 사용자가 해당 url 요청 시,
설정한 "파일 이름"으로 파일을 다운로드 하도록 처리한다.

Client와 Server의 일반적인 query Parameter, HTML Form, API 통신에 이어
파일을 주고 받는 방법에 대해 알아봤다.
일반적인 데이터만 통신하는 일은 거의 없기 때문에
파일을 주고 받는 방법은 반드시 숙지해야겠다.