본문 바로가기

IT/Jsp/Servlet

JSP를 활용한 파일다운로드 구현기.


웹상에서 요청된 파일다운로드 처리를 구현해볼 기회가 있어 간만에 스크립틀릿을 만져봤다.


멘토과장님께서 JSP를 활용한 파일다운로드 소스까지 찾아서 넘겨주셨고.. 

별도로 고려해야했던 이슈도 지금까지 진행해왔던 작은 경험이 있어 무리없이 해결했다.


두 가지 이슈는 다음과 같았다.

1) 파일다운로드를 구현하는 환경은 JSP로 한정.

2) 인자로 넘겨받는 파일명의 인코딩은 UTF-8로.


먼저 멘토과장님이 주신 Hong님의 소스는 다음과 같다.

감사하다고 댓글이라도 남기는게 인지상정인데 댓글란이 없다 -_-;


 제목 : (130717) FileDownload.jsp, FileDownloadProc.jsp (JSP를 이용한 파일다운로드) -- 바로가기 링크

 - FileDownload.jsp 소스
 <%@page import="java.io.File"%>
 <%@page import="java.util.Enumeration"%>
 <%@page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy"%>
 <%@page import="com.oreilly.servlet.MultipartRequest"%>
 <%@ page language="java" contentType="text/html; charset=EUC-KR" pageEncoding="EUC-KR"%>
 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 <html>
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
 <title>Insert title here</title>
 </head>
 <body>
 <center>
<h2> 파일 다운로드 </h2>
<form action = "FileDownloadProc.jsp" method = "post">

<table border = "0" cellpadding="8" cellspacing="5">

<tr align = "center">
<th> 파일명 : </th>
<td> <input type = "text" name = "fileName"> </td>
</tr>

<tr align = "center">
<td colspan = "2"> <input type = "submit" value = "다운로드"> </td>
</tr>
</table>
</form>

 </center>
 </body>
 </html>


 - FileDownloadProc.jsp 소스
 <%@page import="java.io.File"%>
 <%@page import="java.io.*"%>
 <%@ page language="java" contentType="text/html; charset=EUC-KR" pageEncoding="EUC-KR"%>
 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 <html>
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
 <title>Insert title here</title>
 </head>
 <body>

<% 
// 다운받을 파일의 이름을 가져옴
String fileName = request.getParameter("fileName");
// 다운받을 파일이 저장되어 있는 폴더 이름
String saveFolder = "File";

// Context에 대한 정보를 알아옴
ServletContext context = getServletContext(); // 서블릿에 대한 환경정보를 가져옴

// 실제 파일이 저장되어 있는 폴더의 경로
String realFolder = context.getRealPath(saveFolder);
// 다운받을 파일의 전체 경로를 filePath에 저장
String filePath = realFolder + "\\" + fileName;

try{
// 다운받을 파일을 불러옴
File file = new File(filePath);
byte b[] = new byte[4096];

// page의 ContentType등을 동적으로 바꾸기 위해 초기화시킴
response.reset();
response.setContentType("application/octet-stream");

// 한글 인코딩
String Encoding = new String(fileName.getBytes("UTF-8"), "8859_1");
// 파일 링크를 클릭했을 때 다운로드 저장 화면이 출력되게 처리하는 부분
response.setHeader("Content-Disposition", "attachment; filename = " + Encoding);

// 파일의 세부 정보를 읽어오기 위해서 선언
FileInputStream in = new FileInputStream(filePath);

// 파일에서 읽어온 세부 정보를 저장하는 파일에 써주기 위해서 선언
ServletOutputStream out2 = response.getOutputStream();

int numRead;
// 바이트 배열 b의 0번 부터 numRead번 까지 파일에 써줌 (출력)
while((numRead = in.read(b, 0, b.length)) != -1){
out2.write(b, 0, numRead);
}

out2.flush();
out2.close();
in.close();

} catch(Exception e){
}
%>
 </body>
 </html>


오더를 받은 당일엔 소스를 만질 여유가 없어 소스를 보고 구상만 하는걸로 끝냈는데

단순히 FormSubmit으로 요청이 이루어지니 페이지가 이동할거라 단정지어버렸드랬다.

그래서 출근하는대로 광속으로 Ajax 호출구문을 작성함.-_-


자랑스럽게 작성한 Javascript코드. 버튼을 누르면 Ajax로 호출하도록 했다.

인자는 다운로드를 요청할 파일명.


전자정부 프레임워크 환경이라 컨트롤러에서 받아 URL매핑으로 JSP파일을 잡아주는 구문.

fileDownload.jsp는 Hong님 소스의 fileDownloadProc.jsp와 같았다.


버튼을 클릭해 Ajax 호출을 실행하니 다음과 같은 에러메시지가 발생한다.



이젠 익셉션이 떠도 그 순간엔 크게 동요하지 않는다. 나에겐 GGG(GooGleGod)가 함께하니까!


...


구글링 결과 익셉션의 이유는 getOutputStream()메서드로 OS를 호출할 때

JSP가 Servlet으로 변환될 때 자동으로 가져왔던 write객체와 충돌한다는 것.

(출처 : 닥컴 님의 티스토리)


그럼 write객체를 호출하면 에러없이 작동하지 않을까?

하지만 이 방법은 일단 Hong님의 예제를 부분적으로 수정해야한다는 위험부담이 존재했고

마침 닥컴님도 예제의 수정 없이 write객체를 초기화 후 추-_-방하는 방법을 덧붙여놓으셨기에

닥컴님의 조치를 따라가보기로 했다.


완성된 fileDownload.jsp는 다음과 같다.


<%@ page import="java.io.File" %>

<%@ page import="java.io.*"%>

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>파일다운로드 테스트</title>

</head>

<body>

<%

String fileName = request.getParameter("fileName");

String saveFolder = "File";

ServletContext context = getServletContext();

String realFolder = context.getRealPath("saveFolder");

String filePath = realFolder + "\\" + fileName;

try{

/* 닥컴님께서 제시하신 2줄의 코드다.

/* out(Servlet객체)를 초기화하고, Body태그 밖으로 밀어낸다고 설명하셨는데,

/* 개인적으로 Body태그 밖으로 밀어낸다는 부연설명은 잘 이해가 가지 않았다. *_*;

out.clear();

out = pageContext.pushBody();

File file = new File(filePath);

byte b[] = new byte[4096];

response.reset();

response.setContentType("application/octet-stream");

String Encoding = new String(fileName.getBytes("UTF-8"), "8859_1");

response.setHeader("Content-Disposition", "attatchment; filename = " + Encoding);

FileInputStream is = new FileInputStream(filePath);

ServletOutputStream sos = response.getOutputStream();

int numRead;

while((numRead = is.read(b,0,b.length)) != -1){

sos.write(b,0,numRead);

}

sos.flush();

sos.close();

is.close();

}catch(Exception e){

e.printStackTrace();

}

%>

</body>

</html>


과연, 두 줄을 추가하니 에러메시지가 발생하지 않는다.

버튼을 누르면! 아무런 메시지도 발생하지 않고, 아무런 일도 벌어지지 않는다.


...


혹시 몰라 Hong님의 fileDownload.jsp처럼 non-ajax FormSubmit방식으로 구성해보았다.




..페이지 이동 없이 잘 작동합니다.


FormSubmit방식을 취한다고 무조건 페이지를 이동할것이란 생각이 착각이었다.

아무래도 일반적인 요청 - 컨트롤러 - 처리의 프로세스에서 페이지이동이 이루어지는 타이밍,

왜 FileDownload 구현에선 페이지를 이동하지 않았는지 한번쯤 고민해봐야할 것 같다 -.-;


(내가 적용한) fileDownload.jsp의 Body태그 내 스크립틀릿 코드 중

response.reset(); response.setContentType("application/octet-stream")

이 부분이 아무래도 출력형식을 새 페이지로 이동하기 -> 추가창으로 출력하기 로 바꿔주는게 아닌지 의심스럽긴 한데.

저 코드가 실행되려면 먼저 페이지로 이동해야하는게 정상적인 과정 아닌가, 하는 생각때문에 알쏭달쏭.


( 추가 : 2013.11.26 )


해답을 얻어놓고 포스팅에 옮겨적는다는 것을 깜박했다.-.-;

jsp페이지는 컨트롤러에서 해당 페이지로 리턴했을 때 호출되는게 아니라

아예 프로젝트를 컴파일할 때 스크립틀릿에 해당하는 코드가 읽히며 컴파일된다.


즉, 서버에 내용물이 올라간 시점에서 이미 일반적인 view페이지로서의 jsp페이지가 아닌,

contentType이 "application/octet-steam"인 jsp페이지로 컴파일되어있는것. :)


신경써야했던 두 가지 이슈 해결은 :

멘토과장님께서 애초에 JSP소스를 주신데다가

HTML5 써버릇할때부터 인코딩 UTF-8로 하는게 습관이 들어서..ㅋㅋ


마지막으로 멘토과장님께서 넘겨주신 fileDownload.jsp 완성본이다.

스크립틀릿 내에서 메서드를 선언, 일치하는 파일명이 다수일 때 중복값이 출력되는것을 방지했다.

Java항목의 가변인자에 대한 포스팅의 계기가 된 코드이기도 하다. : )


<%@ page import="antlr.StringUtils"%>

<%@ page import="java.io.File" %>

<%@ page import="java.io.*"%>

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>파일다운로드</title>

</head>

<body>

<%!

public String getFilePath(String... filePath) {

String file = "";

for(String s : filePath) {

File f = new File(s);

if(f.exists()) {

file = s;

break;

}

}

return file;

}

%>

<%

String fileName = request.getParameter("file_name");

ServletContext context = getServletContext();

String realFolder = context.getRealPath("/upload/online/");

String realFolder2 = context.getRealPath("/upload/online/att/");

String filePath = realFolder + "/" + fileName;

String filePath2 = realFolder2 + "/" + fileName;

//System.out.println(filePath + " 파일을 다운로드");

String s = getFilePath(filePath, filePath2);

if(s==null || s.equals("") || s.equals("null")) {

out.write(fileName + " 파일이 존재하지 않습니다");

return;

}


try{

File file = new File(s);

byte b[] = new byte[4096];

response.reset();

response.setContentType("application/octet-stream");

String Encoding = new String(fileName.getBytes("UTF-8"), "8859_1");

response.setHeader("Content-Disposition", "attatchment; filename = " + Encoding);

response.setHeader("Content-Length", String.valueOf((int)file.length()));

FileInputStream is = new FileInputStream(filePath);

ServletOutputStream sos = response.getOutputStream();

int numRead;

while((numRead = is.read(b,0,b.length)) != -1){

sos.write(b,0,numRead);

}

sos.flush();

sos.close();

is.close();

} catch(Exception e){

e.printStackTrace();

}

%>

</body>

</html>