저번 시간에 이어 이번에는 파일 다운로드를 구현해 볼 것이다. 파일 다운로드를 구현하기 위한 방법으로는 다음 2가지 방법이 존재한다.
- URI 링크 형태로 구현
- binary 형태로 구현
이 중 URI 링크 형태로 구현하는 방법은 보안 상의 취약점으로 인해 추천하지 않는다. 따라서, 이 글에서는 binary 형태로 구현할 것이다.
파일을 다운로드하기 위한 HTML form을 포함한 JSP 파일을 작성해준다. 사용자가 클릭할 수 있는 링크를 생성하고, 해당 링크를 클릭하면 서버로부터 파일 다운로드에 필요한 정보를 전달받도록 한다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>파일 다운로드 테스트</title>
</head>
<body>
<%-- 파일 ID가 1인 파일 다운로드 --%>
<a href="/download.do?seq=1">파일 다운로드</a>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>파일 다운로드 테스트</title>
</head>
<body>
<%
for (int seq = 1; seq <= 3; seq++) {
%>
<a href="/download.do?seq=<%= seq %>">파일 다운로드</a>
<%
}
%>
</body>
</html>
파일 다운로드를 처리하는 서블릿 클래스를 작성해준다.
package com.study.controller;
import com.study.dao.FileDAO;
import com.study.vo.FileVO;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* 파일 다운로드 HTTP 요청을 처리하는 서블릿
*/
@MultipartConfig(
fileSizeThreshold = 1024 * 1024,
maxFileSize = 1024 * 1024 * 5,
maxRequestSize = 1024 * 1024 * 10
)
@WebServlet("/download.do")
public class FileDownloadController extends HttpServlet {
/**
* HTTP GET 요청 처리
* 모든 GET 요청을 processRequest 메소드로 전달
*
* @param request 클라이언트의 요청 정보를 담고 있는 HttpServletRequest 객체
* @param response 클라이언트에게 응답을 보내는 HttpServletResponse 객체
* @throws ServletException 요청 처리 중 발생하는 예외
* @throws IOException 요청 또는 응답 처리 중 입출력 예외가 발생할 경우
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
/**
* 파일을 다운로드하는 메서드
*
* @param request HTTP 요청 객체
* @param response HTTP 응답 객체
* @throws ServletException Servlet 예외 발생 시
* @throws IOException 입출력 예외 발생 시
*/
private void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 요청으로부터 파일의 일련번호 받아오기
String id = request.getParameter("seq");
// 파일 정보를 데이터베이스에서 조회
FileDAO fileDAO = new FileDAO();
FileVO fileVO = fileDAO.getFileById(Long.parseLong(id));
// 파일이 저장된 경로와 파일 이름 가져오기
String filePath = fileVO.getSavedPath();
String fileName = fileVO.getSavedName();
// 파일의 MIME 타입 가져오기
String contentType = request.getServletContext().getMimeType(fileName);
// MIME 타입이 없을 경우 기본값으로 설정
if (Objects.isNull(contentType)) {
contentType = "application/octet-stream";
}
// 파일 이름을 UTF-8 형식으로 인코딩
String encodingFileName = new String(fileName.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
// HTTP 응답의 컨텐츠 타입과 헤더 설정
response.setContentType(contentType);
response.setHeader(
"Content-Disposition",
"attachment; filename=\"" + encodingFileName + "\""
);
// 다운로드할 파일의 전체 경로 생성
String fullPath = filePath + File.separator + fileName + "." + fileVO.getExt();
File downloadFile = new File(fullPath);
// 파일을 읽고 HTTP 응답으로 전송
try (FileInputStream fileInputStream = new FileInputStream(downloadFile);
OutputStream outputStream = response.getOutputStream()) {
byte[] buffer = new byte[4096];
int read;
// 파일을 읽어서 버퍼에 저장하고, 버퍼의 내용을 출력 스트림으로 전송
while ((read = fileInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, read);
}
}
}
}
StandardCharsets.ISO_8859_1
파일 데이터와 관련된 DB 연산을 수행하는 DAO 클래스를 작성해준다.
package com.study.dao;
import com.study.util.DBUtil;
import com.study.vo.FileVO;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 파일 관련 데이터 액세스 객체
* - 파일 데이터와 관련된 데이터베이스 연산 수행
*/
public class FileDAO {
...
/**
* 주어진 파일 ID에 해당하는 파일 정보를 데이터베이스에서 조회하는 메서드
*
* @param id 파일 ID
* @return 주어진 ID에 해당하는 파일 정보를 담은 FileVO 객체
*/
public FileVO getFileById(long id) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
FileVO vo = null;
try {
// 데이터베이스 연결을 위한 Connection 객체 가져오기
conn = DBUtil.getConnection();
// 파일 정보를 조회하기 위한 SQL 문 작성
String sql = "SELECT * FROM tb_file WHERE file_id = ?";
// PreparedStatement를 생성하여 SQL 문을 실행
pstmt = conn.prepareStatement(sql);
// 파일 ID 설정
pstmt.setLong(1, id);
// SQL 문을 실행하고 결과를 ResultSet으로 받음
rs = pstmt.executeQuery();
// ResultSet에서 파일 정보를 읽어와 FileVO 객체 생성
while (rs.next()) {
vo = FileVO.builder()
.id(rs.getInt("file_id"))
.savedName(rs.getString("saved_name"))
.savedPath(rs.getString("saved_path"))
.build();
}
} catch (SQLException e) {
// SQL 예외가 발생한 경우 예외 정보를 출력
e.printStackTrace();
} finally {
// 사용한 데이터베이스 리소스를 반환
DBUtil.release(rs, pstmt, conn);
}
// 조회한 파일 정보가 담긴 FileVO 객체를 반환
return vo;
}
}
웹 브라우저에 접속하여 파일을 다운로드한다.
파일이 다운로드 된 디렉토리로 이동하여 확인한다.
📖 참고