[Servlet / JSP] Servlet 5.0 (jakarta) 파일 처리 2 - 다운로드

joyful·2024년 3월 10일
1

Java/Spring

목록 보기
28/28

0. 들어가기 앞서

저번 시간에 이어 이번에는 파일 다운로드를 구현해 볼 것이다. 파일 다운로드를 구현하기 위한 방법으로는 다음 2가지 방법이 존재한다.

  1. URI 링크 형태로 구현
  2. binary 형태로 구현

이 중 URI 링크 형태로 구현하는 방법은 보안 상의 취약점으로 인해 추천하지 않는다. 따라서, 이 글에서는 binary 형태로 구현할 것이다.

🔎 관련 글 보러가기


1. 파일 다운로드

1-1. JSP

파일을 다운로드하기 위한 HTML form을 포함한 JSP 파일을 작성해준다. 사용자가 클릭할 수 있는 링크를 생성하고, 해당 링크를 클릭하면 서버로부터 파일 다운로드에 필요한 정보를 전달받도록 한다.

💻 fileDownload.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>

1-2. Servlet

파일 다운로드를 처리하는 서블릿 클래스를 작성해준다.

💻 FileDownloadController.java

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
    • 라틴 알파벳과 일부 특수 문자를 포함한 문자 집합
    • 파일 이름이 한글을 포함할 경우에 발생하는 인코딩 문제(한글 깨짐)를 해결하기 위해 설정

1-3. DAO(Data Access Object)

파일 데이터와 관련된 DB 연산을 수행하는 DAO 클래스를 작성해준다.

💻 FileDAO.java

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;
    }
}

2. 테스트

2-1. 웹 브라우저

웹 브라우저에 접속하여 파일을 다운로드한다.

2-2. 로컬

파일이 다운로드 된 디렉토리로 이동하여 확인한다.


📖 참고

profile
기쁘게 코딩하고 싶은 백엔드 개발자

0개의 댓글