html 폼 전송 방식
application/x-www-form-urlencoded
가장 기본적인 방법 , form 에 입력한 전송할 항목을 HTTP Body 에 문자로 username=kim&age20 처럼 & 으로 구분한다.
파일을 업로드 하려면 파일은 문자가 아니라 바이너리 데이터를 전송해야 한다. 문자를 전송하는 이 방식으로 파일을 전송하기는 어렵다. 그리고 또 한가지 문제가 더 있는데 , 보통 폼을 전송할 때 파일만 전송하는 것이 아니라는 점이다.
문자와 바이너리를 동시에 전송해야 하는 상황이 발생한다.
mutipart/form-data
form tag에 별도로 enctype에 지정을 해줘야한다.
이런 방식으로 헤더 , 바디 가 계속 반복되고 여러개의 다른 스타일의 데이터를 한번에 보낼 수 있게 해준다.
폼의 일반 데이터는 각 항목별로 문자가 전송되고 , 파일의 경우 파일 이름과 Content-Type 이 추가되고 바이너리 데이터가 전송된다.
@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);
return "upload-form";
}
spring.servlet.mutipart.enabled=false 기본이 true이긴한데 false로하면 multipart 지원안하겠다는 뜻이다.
false로 하게되면 tomcat의 request 기본 구현체인 RequestFacade 가 나오게 된다.
true인 경우에는 DispatcherServlet에서 MutipartResolver를 실행한다.
MutipartResolver는 멀티파트 요청인 경우 서블릿 컨테이너가 전달하는 일반적인 HttpServletRequest 를 MultipartHttpServletRequest 로 변환해서 반환한다.
MutipartHttpServletRequest 는 HttpServletRequest 의 자식 인터페이스이고 멀티파트와 관련된 추가 기능을 제공한다.
@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));
}
//편의 메서드
//content-disposition; filename
log.info("submittedFilename={}", part.getSubmittedFileName());
log.info("size={}", part.getSize()); //part body size
//데이터 읽기
InputStream inputStream = part.getInputStream();
String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("body={}", body);
//파일에 저장하기
if (StringUtils.hasText(part.getSubmittedFileName())) {
String fullPath = fileDir + part.getSubmittedFileName();
log.info("파일 저장 fullPath={}", fullPath);
part.write(fullPath);
}
}
return "upload-form";
}
Part 클래스에서 알아서 다른 종류데이터 나오면 파싱해서 return 해주는 메소드가 존재한다. 쓰기도 편하다.
스프링은 MutipartFile 이라는 인터페이스로 업로드를 편하게 지원한다.
@PostMapping("/upload")
public String saveFile(@RequestParam String itemName,
@RequestParam MultipartFile file, HttpServletRequest request) throws IOException {
log.info("request={}", request);
log.info("itemName={}", itemName);
log.info("multipartFile={}", file);
if (!file.isEmpty()) {
String fullPath = fileDir + file.getOriginalFilename();
log.info("파일 저장 fullPath={}", fullPath);
file.transferTo(new File(fullPath));
}
return "upload-form";
}
내부적으로는 서블릿의 기술들을 쓰는 것이다.
파일들을 저장
@PostMapping("/items/new")
public String saveItem(@ModelAttribute ItemForm form, RedirectAttributes redirectAttributes) throws IOException {
UploadFile attachFile = fileStore.storeFile(form.getAttachFile());
List<UploadFile> storeImageFiles = fileStore.storeFiles(form.getImageFiles());
//데이터베이스에 저장
Item item = new Item();
item.setItemName(form.getItemName());
item.setAttachFile(attachFile);
item.setImageFiles(storeImageFiles);
itemRepository.save(item);
redirectAttributes.addAttribute("itemId", item.getId());
return "redirect:/items/{itemId}";
}
데이터베이스에 저장을 할시에 실제 file, image 자체를 저장하지 않는다. 이것들은 S3같은 별도의 저장소에 보관을 하고 db에는 해당 파일들의 이름및 확장자를 저장하여 S3를 참조 할 수 있도록 한다.
파일및 이미지 조회
@GetMapping("/items/{id}")
public String items(@PathVariable Long id, Model model) {
Item item = itemRepository.findById(id);
model.addAttribute("item", item);
return "item-view";
}
현재 a.txt 도 다운로드 안되고 이미지들도 다 액박으로 뜬다.
localhost:8080/images/{UUID.png} 여기에다 get 요청을 했지만 아무것도 없기에 액박으로 뜨는것이다 여기에 해당하는 Controller를 만들어줘야한다.
파일및 이미지 다운로드
@ResponseBody
@GetMapping("/images/{filename}")
public Resource downloadImage(@PathVariable String filename) throws MalformedURLException {
return new UrlResource("file:" + fileStore.getFullPath(filename));
}
url 경로에 접근해서 stream으로 반환을 한다.
@GetMapping("/attach/{itemId}")
public ResponseEntity<Resource> downloadAttach(@PathVariable Long itemId) throws MalformedURLException {
Item item = itemRepository.findById(itemId);
String storeFileName = item.getAttachFile().getStoreFileName();
String uploadFileName = item.getAttachFile().getUploadFileName();
UrlResource resource = new UrlResource("file:" + fileStore.getFullPath(storeFileName));
log.info("uploadFileName={}", uploadFileName);
String encodedUploadFileName = UriUtils.encode(uploadFileName, StandardCharsets.UTF_8);
String contentDisposition = "attachment; filename=\"" + encodedUploadFileName + "\"";
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
.body(resource);
}
다운로드를 하게 하려면 꼭 header에 contentDisposition 을 넣어줘야한다. 안그러면 해당 url에 담겨있는 정보를 그냥 브라우저에 뿌리게 된다.
encodedUploadFileName = 한글 같은것들은 깨질수 있기 때문에 해주는 작업이다. 브라우저 마다 다를 수 있다.
uploadFileName = 고객이 업로드 한 파일명 (같을 수 있음)
storeFileName = 겹치지 않게 하기위해서 존재함
'WEB > Spring MVC 2' 카테고리의 다른 글
Spring MVC 2편 스프링 타입 컨버터 (0) | 2021.10.31 |
---|---|
Spring MVC 2편 API 예외 처리 (0) | 2021.10.30 |
Spring MVC 2편 예외처리와 오류페이지 (0) | 2021.10.23 |
Spring MVC 2편 로그인 처리2 - 필터, 인터셉터 (0) | 2021.10.16 |
Spring MVC 2편 로그인 처리1 쿠키 세션 (0) | 2021.10.10 |