HTML Form Data 전송 방식
1) application/x-www-urlencoded
2) multipart/form-data 전송 방식
파일 전송 동작 방식
//application.properties
spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.enabled=true
파일 업로드 및 파일 저장 시 주의사항
@Controller
@RequiredArgsConstructor
public class FileController {
private final FileService fileService;
@GetMapping("/")
public String uploadFilesList(Model model) throws IOException {
model.addAttribute("files", fileService.loadAll().map(
path -> MvcUriComponentsBuilder.fromMethodName(FileController.class,
"serveFile", path.getFileName().toString()).build().toUri().toString())
.collect(Collectors.toList()));
return "fileUpload";
}
}
업로드한 파일들을 웹에서 조회
MvcUriComponentsBuilder -> 복잡한 파라미터를 가진 uri를 생성 , requestMapping 파일 메서드가 존재할 경우, 리스트로 반환하기 위해 files 라는 이름으로 생성하여 modelAttribute로 담아준다.
MvcUriComponentsBuilder -> fromMethodName(methodName,filePath).build().toUri().toString()->
builder 패턴으로 생성한 후, uri로 변환한 뒤, 다시 string 타입으로 반환한다.
.collect(Collectors.toList())) -> Collectors 클래스에서 제공하는 toList()메서드로 해당 uri들을 리스트로 반환
loadAll() : 생성 메서드
map() : Stream에서 제공하는 메서드
@GetMapping("files/{filename:.+}")
@ResponseBody
public ResponseEntity<Resource> serveFile(@PathVariable String filename){
Resource file = fileService.loadAsResource(filename);
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; fileName=\"" + file.getFilename() + "\"").body(file);
}
파일 다운로드
@ResponseBody -> 포워딩 필요 없이 스스로 요청에 대한 응답(REST API에서 주로 사용)
@PathVariable -> 파라미터를 url의 파라미터값으로 받을 때 사용
{filename(파일명):.+(확장자)}
ResponseEntity : HttpEntity를 상속받아서 Header와 Body를 같이 반환할 수 있다.이때 statusCode도 함께 담을 수 있다.
return 값 : attachment; -> 파일을 다운로드 받을 수 있다. 이때 fileName으로 이름을 정의한다.
@PostMapping("/")
public String handleFileUpload(@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes){
fileService.store(file);
redirectAttributes.addFlashAttribute("message",
"You successfully uploaded" + file.getOriginalFilename() + "!");
return "redirect:/";
}
@ExceptionHandler(StorageFileNotFoundException.class)
public ResponseEntity<?> handleStorageFileNotFound(StorageFileNotFoundException exc) {
return ResponseEntity.notFound().build();
}
파일 업로드
-MultipartFile 타입으로 file을 받는다.
-RedirectAttributes 에 의해 Post에서도 redirect를 사용할 수 있다.
-addFlashAttribute : flash속성에 객체를 저장.일회성으로 데이터를 전달.redirect 후 소멸.즉, post방식에서 사용해야 하기 때문에
데이터가 남는 방식은 get방식에서 사용 해야한다. 이럴때는 addAttribute를 사용한다.
파일 업로드 성공시, 메시지를 담아 view에 전달.
public interface FileService {
void init();
void store(MultipartFile file);
Stream<Path> loadAll();
Path load(String filename);
Resource loadAsResource(String filename);
void deleteAll();
}
@Override
public void store(MultipartFile file) {
String filename = StringUtils.cleanPath(file.getOriginalFilename());
//StringUtils.cleanPath :불필요한 패스를 정리. 파일 시스템에 저장된 파일 이름을 불러와서 path 주소를 path/.. 로 정리해준다.
try {
if (file.isEmpty()) {
throw new StorageException("Failed to store empty file "+ filename);
}
//유효하지 않은 주소 ->..가 들어있는 url을 반환하여 예외 처리(해킹이나 오류 방지)
if(filename.contains("..")){
throw new StorageException(
"Cannot store file with relative path outside current directory "
+ filename);
}
//실행 부분
//copy 옵션을 주어, 해당 파일의 이름으로 지정한 절대경로에 파일을 복사한다.
//이때 파일이 존재해도 덮어쓰고 싶다면, StandardCopyOption.REPLACE_EXISTING 옵션을 사용한다.
try (InputStream inputStream = file.getInputStream()) {
Files.copy(inputStream,this.rootLocation.resolve(filename),
StandardCopyOption.REPLACE_EXISTING);
}
}catch (IOException e) {
throw new StorageException("Faild to store file" + filename, e);
}
}
@Override
public Stream<Path> loadAll() {
try {
return Files.walk(this.rootLocation, 1)
.filter(path -> !path.equals(this.rootLocation))
.map(this.rootLocation::relativize);
} catch (IOException e) {
throw new StorageException("Failed to read stored files", e);
}
}
Files.walk -> 파일의 주소 중 시작 파일을 탐색, maxDepth를 1로 지정하면 자기자신과 직전 하위경로까지만 탐색
.filter -> 조건을 지정하여 해당 조건에 맞을 경우 필터링
.map(this.rootLocation::relativize) ->현재 path와 지정한 경로사이의 상대경로를 구한다.
::-> 람다식에서 함수의 결과를 반환
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("storage")
public class StorageProperties {
//파일을 저장할 위치
private String location = "upload-dir";
public String getLocation() {
return location; /* Folder location for storing files */
}
public void setLocation(String location){
this.location = location;
}
}