프로토콜
파일을 업로드하는 메서드는 Post 이다. 그런데 Content-Type이 좀 독특하다.
Request할때 헤더를 fiddler를 사용해서 살펴보면,
Content-Type: multipart/form-data; boundary=abcdefghhhhhhhhhhhh
body에 보내는 데이터의 content type이 form-data이긴 한데 여러부분으로 나누어 보낸다는 multipart 형태이다. 그리고 각각의 part 사이는 boundary를 이용해서 구분하는데, 앞에 –를 붙인다. 그리고 맨 마지막에 끝날때는 다시 –를 붙인다. 예를 들어 3 part를 전송한다고 가정하면 아래와 같다.
–abcdefghhhhhhhhhhhh
one part
…
–abcdefghhhhhhhhhhhh
two part
…
–abcdefghhhhhhhhhhhh
three part
…
–abcdefghhhhhhhhhhhh–
코드 구현
파일을 저장할 위치를 먼저 프라퍼티에 지정한다.(application.properties)
upload.root_folder = c:/temp/image
image_folder = /assets/upload
프라퍼티 정보를 읽어서 저장할 빈 객체를 생성한다. main 애플리케이션이 있는 위치에 ConfigConstant.java 화일을 생성한다.
@Component
public class ConfigConstant {
@Value("${upload.root_folder}")
public String uploadRootFolder;
@Value("${image_folder}")
public String image_folder;
}
@Component 어노테이션을 주었으므로 이 빈 객체도 스프링이 구동되면 스프링 컨테이너에 인스턴스가 등록이 된다.
HeroController에 post 메서드를 작성한다. /api/file 프로토콜로 작성한다.
먼저, 컨테이너에 등록된 ConfigConstant 인스턴스를 주입받는다.
@Autowired
private ConfigConstant configConstant;
@PostMapping("/file")
public Result fileUpload(@RequestPart(value="file") MultipartFile file) {
try {
// 이미지가 있는지 체크
if (file != null) {
//업로드할 디렉토리가 있는지 체크
String path = configConstant.uploadRootFolder + configConstant.image_folder;
File dir = new File(path);
if (!dir.isDirectory()) {
dir.mkdirs();
}
// 파일 저장: 파일명은 중복을 피하기 위해서 파일명 _타임스템프
String filename = file.getOriginalFilename();
String savedFilename = filename.substring(0, filename.lastIndexOf(".")) + "_" +
System.currentTimeMillis() + filename.substring(filename.lastIndexOf("."));
File saveFile = new File(path, savedFilename);
file.transferTo(saveFile);
return new Result(0, configConstant.image_folder + "/" + savedFilename);
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return new Result(500, "internal server error");
}
테스트
postman으로 테스트해본다.
http://localhost:8080/api/file 을 입력하고,
body 부분에서 form-data를 선택하고 key 부분에는 file을 입력하고 파일선택 부분에 이미지하나를 선택한다.
정상적으로 업로드가 완료되면 업로드된 경로가 리턴된다.
fiddler로 패킷을 캡쳐해보면 다음과 같다.
request부분에 Content-Type 부분을 자세히 보자. multipart/form-data가 있고, 브라우저가 자동으로 만들어주는 boundary가 있다.
Content-Type: multipart/form-data; boundary=—-WebKitFormBoundarypiNIoaXgpD7xY6SC
이 boundary가 아래에는 –가 붙여서 쓰이고 맨 마지막에 다시 이 boundary가 사용된다.