본문 바로가기

프로그래밍/Spring

Spring Framework의 메시지 컨버터를 이용한 FileDownload 구현(1)

토비의 Spring 3를 보다가 메시지 컨버터의 ByteArrayHttpMessageConverter를 보게 되었다. 설명을 요약하자면 Controller에서 함수가 @ResponseBody 를 사용하여 byte []를 리턴하면 컨텐트 타입을 application/octet-stream으로 설정하여 Response에 byte []를 쏴준다는거다. 어라? 이거 파일 다운로드랑 같네. 이런 생각이 들어서 이 부분을 좀더 보게 되었다.

기존에 내가 갖고 있는 Spring 에서의 파일 다운로드 방법은 AbstractView를 상속받은 파일 다운로드 전용 View를 만들어서 컨트롤러에서 이 View를 생성해서 리턴하는 것이었다. 그럼 이 View에서는 Model에 저장되어 있는 다운로드 할 파일 정보를 이용해서 Response 객체의 OutputStream에 쏴주는 방식이다.(물론 다른 방법도 있었을수 있지만 일단은 내가 가지고 있는 것이 그러해서..) 사실 이 방법은 갠적으로는 맘에 안들은것은 있었다. 왜냐면 최종 결과물은 파일(정확하게는 File 클래스 객체)인데 그것과는 좀 거리가 멀은 View객체를 만든뒤에 그 View에서 처리하기 때문에 직접적으로 이를 처리하는 방식이 아니다. 즉 컨트롤러에서 바로 처리할수 있어야 했는데 불필요하게 그것을 처리하는 별도의 View를 만들어서 떠넘기는 식이어서 조금 껄끄러웠다. 그래서 파일을 byte []로 바꾸어 리턴하는것은 조금 더 직관적인 의미로 받아들였다.

근데 ByteArrayHttpMessageConverter로 구현하는 과정에서 조금 불합리한 부분이 있었다. 나의 내공 부족인 면도 있었지만 File을 FileInputStream으로 변환을 하고 여기서 바로 byte []로 변환해서 쏴줘야 했는데 이 부분이 매끄럽질 못했다. 그래서 질문을 올렸었는데 어느 분이 댓글로 ByteArrayHttpMessageConverter 소스를 보니 이것과 비슷하게 File을 처리할 수 있을것 같다 하여 실제로 ByteArrayHttpMessageConverter 소스를 보니 File에 대한 것도 만들수 있을것 같았다. 그래서 이에 대한 구현을 해보고자 한다.

일단 생각은 다음과 같다. Controller의 @RequestMapping으로 Url과 연결이 되어있는 함수에서 @ResponseBody로 File 객체를 리턴하면 실제로 파일을 다운받는 것이다. 다음과 같은 식이다.

@RequestMapping(value="/download.do", method=RequestMethod.GET)
@ResponseBody
public File filedownload(@RequestParam("fileidx") int idx, HttpServletResponse response) throws Exception{
	String filename="title.png";
	File file = new File("d:/" + filename);
		
	// response.setContentLength((int)file.length());
	response.setHeader("Content-Length", Long.toString(file.length()));
	response.setHeader("Content-Transfer-Encoding", "binary");
	response.setHeader("Content-Disposition", "attachment;fileName=\"" + filename + "\";");

	return file;
}

 
함수의 파라미터에서 int 형 값을 받은 것은 다운로드 받을 파일을 조회하기 위한 키 값이 넘어간다고 보면 된다. 이 부분은 프로젝트 상황에 따라 직접 파일명이 파라미터로 전달되게끔 해도 된다. 그리고 response 객체의 setContentLength 함수를 사용한 부분에 주석을 달았는데 이 함수를 쓰지 않은 이유는 setContentLength 함수에 파일의 크기가 들어가야 하는데 이 부분에 들어갈수 있는 것이 int형이다. 파일 크기가 작을 경우 문제가 없지만 대용량 파일일 경우는 int 형이 아닌 Long 형이 될 가능성이 있기 때문에 이 부분을 달리 구현했다. 즉 setHeader 함수를 이용하여 Content-Length 헤더값을 직접 기록하는 식으로 setContentLength 함수가 하는 일을 대신 하도록 한것이다. 함수를 보면 다운로드 할 파일객체를 만들고 몇가지 헤더를 지정한 다음에 그 파일을 리턴하는 것이다. 파일을 다운로드 하는 Url에서 다운로드 할 파일을 return 하는 개념이니 개인적으론 참 직관적이란 느낌이 든다. 이제 이렇게 컨트롤러 함수를 만들면 실제로 파일이 다운로드 되게끔 할려면 어떻게 해야 하는지의 부가적인 작업에 대해 설명하겠다.