[웹 풀스택] 부스트코스 - 웹 프로그래밍 - 5. 웹 앱 개발: 예약서비스 4/4

1. 파일 업로드 - FE

1) file upload방법의 이해

form 전송 시에 이미지와 같은 파일을 서버로 올리는 경우가 간혹 있습니다.일반적인 값을 서버로 보내는 것과 다르게 처리해야 할 부분이 존재합니다.

file upload

form 데이터를 그대로 보낸다면, file도 input 방식으로 업로드가 가능합니다.
file type에도 역시 name으로 이름을 지정해야 서버에서 이를 인식해서 데이터를 얻을 것입니다. 아래는 input type이 file인 경우 예제입니다.

<input type="file" name="reviewImg" id="reviewImageFileOpenInput" accept="image/*">

지금까지만 보면 클라이언트 입장에서는 file을 올린다고 해서 크게 다르진 않습니다.
type을 file로 선언해두면 되고, name설정을 해서 클라이언트/서버 간의 보낼 데이터의 이름을 지어주면 됩니다. accept는 허용 가능한 file type을 결정지을 수 있습니다. 이와 관련해 유용한 속성은 여러 개 있는데, 브라우저 지원이 제한적인 상태이므로 참고해서 사용해야 합니다. 마지막 id는 클라이언트에서 어떤 제어를 해야 하는 경우에만 사용합니다. 일반적인 form 데이터를 전송 시에 HTTP Header에는 기본적으로, 'application/x-www-form-urlencoded' 라는 정보로 노출 됩니다.

  • Content-Type:application/x-www-form-urlencoded

그런데, file을 전송할 때는 좀 다릅니다.
아래처럼 Form 태그의 속성으로, enctypemultipart/form-data로 지정해야 합니다.

  • Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7rKYKhIWaTrjvGi1

여기서 다루지는 않지만, 서버에서는 multipart/form-data를 처리할 수 있도록 합니다. multipart/form-data의 실제 데이터를 서버로 어떻게 전송이 될까요?
크롬 개발자도구의 network탭에서 요약된 정보를 통해서 이를 이해할 수 있습니다.

------WebKitFormBoundaryiUIOhJXAwxorM25j
Content-Disposition: form-data; name="email"

[email protected]
------WebKitFormBoundaryiUIOhJXAwxorM25j
Content-Disposition: form-data; name="password"

werwerwerwerwer
------WebKitFormBoundaryiUIOhJXAwxorM25j
Content-Disposition: form-data; name="reviewImg"; filename="A_Pen_by_younjisu.png"
Content-Type: image/png


------WebKitFormBoundaryiUIOhJXAwxorM25j--

특이한 점은, 아래와 같이 **WebKitFormBoundaryiUIOhJXAwxorM25j **라는 어떤 **구분정보**를 기준으로 데이터가 노출되고 있습니다. 다른 input 데이터들과 같이 서버로 보내야 한다면, 아래와 같이 html페이지를 구성할 수 있을 겁니다.

``` html
<div class="formWrap">
    <form action="/join" method="post" id="myform" enctype="multipart/form-data">
        <div class="inputWrap">
            <div class="email">
                <span> Email </span> <input type="text" name="email"><br/>
            </div>
            <div class="password">
                <span> Password </span> <input type="password" name="password"><br/>
            </div>
        <input type="file" name="reviewImg" accept="image/*">
        </div>
        <input class="sendbtn" type="submit">
    </form>
</div>

실제로는 파일을 보낼 때는, 보통 다른 데이터와 별도로, 먼저 보내는 경우도 많습니다.

참고
약간 더 복잡한 처리를 해야하지만, FormData라는 속성을 이용하면 좀더 쉽게 구현할 수가 있습니다.

https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects#sending_files_using_a_formdata_object

2) file upload의 확장자검사 및 썸네일노출

파일을 업로드 할 때, 몇 가지 확인이 필요한 경우가 있습니다. 서버에서 모든 것을 체크하지 않고 클라이언트에서 체크하면 더 빠르게 유효성을 판단할 수 있습니다. 이미지 파일의 확장자가 올바른지, 썸네일 이미지를 화면에 노출하는 방법을 알아볼 것입니다.

이미지 파일 확장자 유효성 검사

file의 확장자 검사는 HTML input 태그의 accept 속성으로 쉽게 필터링할 수 있습니다.

<input type="file" name="reviewImg" id="reviewImageFileOpenInput" accept="image/png, image/jpeg">

이렇게 하면, file선택 창에서 지정한 확장자 파일만 자동으로 선택할 수 있게 브라우저가 돕습니다. 그런데, accept 속성의 브라우저 지원상황이 깔끔한 상태가 아니므로, 더 많은 브라우저에서 동작하는 코드를 알아보겠습니다. file을 업로드 하면, change 이벤트를 통해서 이를 감지할 수 있습니다. 그리고 나면 file객체를 얻을 수 있습니다. file 객체는 event 객체의 target으로부터 얻을 수 있습니다.

	const elImage = document.querySelector("#reviewImageFileOpenInput");
	elImage.addEventListener("change", (evt) => {
		const image = evt.target.files[0];
		if(!validImageType(image)) { 
			console.warn("invalide image file type");
			return;
		}
	})

그리고 나면 다음과 같이, validImageType메서드를 만들고, 확장자 검사를 하면 됩니다.

function valideImageType(image) {
	const result = ([ 'image/jpeg',
					  'image/png',
					  'image/jpg' ].indexOf(image.type) > -1);
	return result;
}

image 객체의 type 값을 통해서 결과를 확인하는 코드입니다.

이미지 썸네일 노출

이미지를 올리고 나면, 썸네일 이미지가 노출되곤 합니다. 사용자가 올린 이미지가 어떤 것인지 썸네일로 인식시켜주는 것이죠. 원래는 Ajax로 image와 같은 파일을 먼저 서버로 올린 후, 잘 올라갔으면 어떤 응답 값을 받습니다. 예를 들어 썸네일이미지 주소를 받을 수 있을 겁니다. 그 정보를 받아서 화면에 썸네일이미지를 노출하는 것이 일반적인 과정입니다.

우리는 편의상 서버로 이미지를 실제로 올리기도 전에, createObjectURL를 사용한 썸네일 노출 방법을 알아봅니다. createObjectURL 라는 URL에 있는 정보를 활용해서 image 정보를 화면에 넣어볼 겁니다.

const elImage = document.querySelector("#reviewImageFileOpenInput");
elImage.addEventListener("change", (evt) => {
    const image = evt.target.files[0];
     if(!valideImageType(image)) { 
        console.warn("invalide image file type");
        return;
     }
     //이렇게 넣으면 이미지 정보가 화면에 노출됩니다.
     const elImage = document.querySelector(".thumb_img");
     elImage.src = window.URL.createObjectURL(image);
})

'thumb_img' 클래스를 가진 image 엘리먼트에 속성으로 image객체를 추가하면 됩니다.

HTML에 미리 썸네일이 들어갈 이미지 태그를 만들어 놔야한다.

<input type="file" name="reviewImg" id="reviewImageFileOpenInput" accept="image/png, image/jpeg">
<img class="thumb_img" src= "" width="130">

2. 로깅 - BE

1) 로깅이란?

로깅(Logging)이란?

  • 정보를 제공하는 일련의 기록인 로그(log)를 생성하도록 시스템을 작성하는 활동

  • 프린트 줄 넣기(printlining)는 간단한, 보통은 일시적인, 로그를 생성하기만 한다.

  • 시스템 설계자들은 시스템의 복잡성 때문에 로그를 이해하고 사용해야 한다.

  • 로그가 제공하는 정보의 양은, 이상적으로는 프로그램이 실행되는 중에도, 설정 가능해야 한다.

  • 일반적으로 로그 기록의 이점

    • 로그는 재현하기 힘든 버그에 대한 유용한 정보를 제공할 수 있다.
    • 로그는 성능에 관한 통계와 정보를 제공할 수 있다.
    • 설정이 가능할 때, 로그는 예기치 못한 특정 문제들을 디버그하기 위해, 그 문제들을 처리하도록 코드를 수정하여 다시 적용하지(redeploy) 않아도, 일반적인 정보를 갈무리할 수 있게 한다.

로그를 출력하는 방법

  • System.out.print() 이용
  • 로깅 라이브러리 이용

로그 라이브러리 종류

  • java.util.logging
    • JDK 1.4부터 포함된 표준 로깅 API
    • 별도 라이브러리 추가 불필요
    • 기능이 많이 부족해 다른 로그 라이브러리를 더 많이 사용
  • Apache Commons logging
    • 아파치 재단에 Commons 라이브러리 중에 로그 출력을 제공하는 라이브러리
  • Log4j
    • 아파치 제단에서 제공하며 가장 많이 사용되는 로깅 라이브러리
  • Logback
    • Log4j를 개발한 Ceki Gulcu가 Log4j의 단점 개선 및 기능을 추가하여 개발한 로깅 라이브러리

2) slf4j 설정하기

SLF4J란?

  • logging 관련 라이브러리는 다양하다.
  • 이러한 라이브러리들을 하나의 통일된 방식으로 사용할 수 있는 방법을 SLF4J는 제공한다.
  • SLF4J는 로깅 Facade이다.
  • 로깅에 대한 추상 레이어를 제공하는 것이고 interface의 모음이다.

SLF4J를 이용해 로깅 라이브러리 사용하기

SLF4J를 이용해 로깅 라이브러리 사용하기

maven에 SLF4J와 logback의존성 추가하기

maven에 SLF4J와 logback의존성 추가하기

  • 참고로 logback-classic 1.2.3은 이미 slf4j-api 1.7.25에 대한 의존성을 가지고 있기 때문에 slf-j-api를 추가할 필요는 없다.

  • Spring은 기본적으로 아파치 재단의 commons-logging을 사용한다.

  • logback라이브러리를 사용하려면 제거를 해야한다.

  • Spring라이브러리에서 commons-logging을 제거하면, Spring을 사용할 때 commons-logging라이브러리를 찾으면서 오류가 발생한다.

  • 이러한 오류를 제거하기 위해서 jcl-over-slf4j를 추가한다.

logback 설정

  • logback.xml
  • Appender 설정
  • logger 설정
  • root 설정

Appender

  • ConsoleAppender : 콘솔에 로그를 어떤 포맷으로 출력할지를 설정할 때 사용한다.
  • FileAppender : 파일에 로그를 어떤 포맷으로 출력할지를 설정한다.
  • RollingFileAppender : 로그의 양이 많아지면, 하나의 파일로 관리하기 어려워지는 경우가 생긴다. 이런 문제를 해결하기 위해 하루 단위로 로그를 관리하고자 할 경우 사용된다.

ConsoleAppender 설정

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
        </encoder>
    </appender>

RollingFileAppender 설정

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>access.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>access-%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
        </encoder>
    </appender>

Log Level

  • trace : debug보다 세분화된 정보
  • debug : 디버깅하는데 유용한 세분화된 정보
  • info : 진행상황 같은 일반 정보
  • warn : 오류는 아니지만 잠재적인 오류 원인이 될 수 있는 경고성 정보
  • error : 요청을 처리하는 중 문제가 발생한 오류 정보

로그 레벨 설정과 root 설정

    <logger name="org.springframework" level="info"/>
    <logger name="kr.or.connect" level="debug"/>
    <root level="debug">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>

Logger 객체 선언
로그를 남기고자 하는 클래스에 로거 객체를 필드로 선언한다.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
.......
private Logger logger = LoggerFactory.getLogger(this.getClass());

로그 출력 메소드

  • 문자열 결합을 위해 '+'연산자를 사용하지 않는다.
  • 로그로 남길 변수의 수만큼 {} 를 이용한다.
  • 로그의 수준에 따라 debug(), info(), warn(), error()메소드를 이용한다.
logger.trace("{} {} 출력", "값1", "값2");
logger.debug("{} {} 출력", "값1", "값2");
logger.info("{} {} 출력", "값1", "값2");
logger.warn("{} {} 출력", "값1", "값2");
logger.error("{} {} 출력", "값1", "값2");

3) slf4j를 이용한 로그남기기

pom.xml

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
</dependency>

위의 부분을 아래와 같이 수정합니다.

        <!-- 로깅 관련 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

resource폴더에 logback.xml 파일을 작성합니다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/tmp/access.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>/tmp/access-%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>

        <encoder>
            <Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
        </encoder>
    </appender>

    <logger name="org.springframework" level="info"/>
    <logger name="kr.or.connect" level="debug"/>

    <root level="debug">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

LogInterceptor.java를 아래와 같이 수정합니다.

package kr.or.connect.guestbook.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class LogInterceptor extends HandlerInterceptorAdapter{
	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
//		System.out.println(handler.toString() + " 가 종료되었습니다.  " + modelAndView.getViewName() + "을 view로 사용합니다.");
		logger.debug("{} 가종료되었습니다. {} 를 view로 사용합니다.", handler.toString(), modelAndView.getViewName());
	}

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
//		System.out.println(handler.toString() + " 를 호출했습니다.");
		logger.debug("{} 를 호출했습니다.", handler.toString());
		return true;
	}

	
}

3. 파일 업로드 & 다운로드 - BE

1) 파일업로드 컨셉설명

Multipart?

Multipart
웹 클라이언트가 요청을 보낼 때 HTTP프로토콜의 바디 부분에 데이터를 여러 부분으로 나눠서 보내는 것입니다.

보통 파일을 전송할 때 사용합니다.

HttpServletRequest는 파일 업로드를 지원 안 함

  • HttpServletRequest는 웹 클라이언트가 전달하는 Multipart데이터를 쉽게 처리하는 메소드를 제공하지 않습니다.

  • 서블릿에서 파일 업로드를 처리하려면 별도의 라이브러리를 사용해야 한다.

  • 대표적인 라이브러리가 아파치 재단의 commons-fileupload입니다.

Spring MVC에서의 파일 업로드

  • Spring MVC에서 파일을 업로드 하려면 몇 가지 라이브러리와 설정을 추가해야 합니다.

  • commons-fileupload, commons-io 라이브러리 추가

  • MultipartResolver Bean 추가

파일 업로드를 위한 라이브러리 추가

<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>1.4</version>
</dependency>

스프링 설정 추가

DispathcerServlet은 준비 과정에서 "multipart/form-data"가 요청으로 올 경우 MultipartResolver를 사용합니다.

@Bean
public MultipartResolver multipartResolver() {
org.springframework.web.multipart.commons.CommonsMultipartResolver multipartResolver = new org.springframework.web.multipart.commons.CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(10485760); // 1024 * 1024 * 10
return multipartResolver;
}

파일 업로드 폼

파일 업로드 시에는 form태그에 enctype설정이 되어 있어야 합니다.
post 방식으로 전송해야 합니다.

<form method="post" action="/upload"
              enctype="multipart/form-data">
......
<input type="file" name="file">
<input type="submit">
</form>

Controller에서의 업로드 처리

  • @PostMapping이 사용되야 합니다.
  • 업로드 파일이 하나일 경우 @RequestParam("file") MultipartFile file
  • 업로드 파일이 여러 개일 경우 @RequestParam("file") MultipartFile[] files
  • MultipartFile의 메소드를 이용해서 파일 이름, 파일 크기 등을 구하고 InputStream을 얻어 파일을 서버에 저장합니다.

Controller에서의 다운로드 처리

파일 다운로드와 관련된 헤더 정보를 출력합니다.

response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";");
response.setHeader("Content-Transfer-Encoding", "binary");
response.setHeader("Content-Type", contentType);
response.setHeader("Content-Length", fileLength;
response.setHeader("Pragma", "no-cache;");
response.setHeader("Expires", "-1;");

파일을 읽어 HttpServletResponse의 OutputStream으로 출력합니다.

2) 파일 업로드 구현하기

실습코드

maven pom.xml에 파일 업로드와 관련된 라이브러리를 추가해야 합니다.

     <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.2.1</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.4</version>
        </dependency>

DispathcerServlet에게 멀티파트 요청이 올경우 파일 업로드 처리가 될 수 있도록 MultipartResolver객체를 등록합니다.

최대 10메가 크기의 파일이 저장되도록 설정하였습니다.

    @Bean
    public MultipartResolver multipartResolver() {
        org.springframework.web.multipart.commons.CommonsMultipartResolver multipartResolver = new org.springframework.web.multipart.commons.CommonsMultipartResolver();
        multipartResolver.setMaxUploadSize(10485760); // 1024 * 1024 * 10
        return multipartResolver;
    }

uploadform.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!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=UTF-8">
<title>uploadform</title>
</head>
<body>
<h1>Upload Form</h1>
<br><br>
 <form method="post" action="upload" enctype="multipart/form-data">

    file :    <input type="file" name="file"><br>

        <input type="submit">
 </form>    
</body>
</html>  

파일 업로드가 성공하면 업로드 성공 메시지를 출력하는 uploadok.jsp파일을 작성

uploadok.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!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=UTF-8">
<title>uploadform</title>
</head>
<body>
<h1>파일 업로드 성공</h1>
 </form>    
</body>
</html>

파일 업로드를 처리하는 FileController를 작성합니다.

package kr.or.connect.guestbook.controller;

import java.io.FileOutputStream;
import java.io.InputStream;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

@Controller
public class FileController {

    // get방식으로 요청이 올 경우 업로드 폼을 보여줍니다.
	@GetMapping("/uploadform")
	public String uploadform() {
		return "uploadform";
	}
	
	@PostMapping("/upload")
	public String upload(@RequestParam("file") MultipartFile file) {
		
		System.out.println("파일 이름 : " + file.getOriginalFilename());
		System.out.println("파일 크기 : " + file.getSize());
		
        try(
                // 맥일 경우 
                //FileOutputStream fos = new FileOutputStream("/tmp/" + file.getOriginalFilename());
                // 윈도우일 경우
                FileOutputStream fos = new FileOutputStream("c:/tmp/" + file.getOriginalFilename());
                InputStream is = file.getInputStream();
        ){
        	    int readCount = 0;
        	    byte[] buffer = new byte[1024];
            while((readCount = is.read(buffer)) != -1){
                fos.write(buffer,0,readCount);
            }
        }catch(Exception ex){
            throw new RuntimeException("file Save Error");
        }
		
		
		return "uploadok";
	}
}

위와 같은 내용이 작성이 되었다면, http://localhost:8080/guestbook/uploadform 을 웹 브라우저로 엽니다.
connect.png파일을 업로드 합니다.
c:/tmp 폴더에 가보면 파일이 업로드 된 것을 알 수 있습니다.

한 디렉토리에 저장되는 파일의 수는 제한이 있습니다. 파일 업로드를 구현할 때는 파일이 업로드되는 디렉토리의 구조도 중요합니다. 업로드되는 파일을 저장하기 위한 디렉토리 구조를 어떻게 하는 것이 좋을까요? (힌트 : 파일이 업로드되는 날짜 정보를 이용한다.)

에러시 참고
https://2-jissun.tistory.com/8

3) 파일 다운로드 구현하기

  • 제공하는 connect.png 파일을 윈도우 사용자의 경우 c:/tmp/ 디렉토리에 복사하고 맥 사용자의 경우는 /tmp 디렉토리에 복사합니다.

  • FileController에 download메소드를 추가합니다.

    • response에 header를 설정합니다.
    • 파일을 outputStream으로 출력합니다.
  • http://localhost:8080/guestbook/download를 브라우저에서 입력하면 파일이 다운되는 것을 확인할 수 있습니다.

	@GetMapping("/download")
	public void download(HttpServletResponse response) {

        // 직접 파일 정보를 변수에 저장해 놨지만, 이 부분이 db에서 읽어왔다고 가정한다.
		String fileName = "connect.png";
		String saveFileName = "c:/tmp/connect.png"; // 맥일 경우 "/tmp/connect.png" 로 수정
		String contentType = "image/png";
		int fileLength = 1116303;
		
        response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";");
        response.setHeader("Content-Transfer-Encoding", "binary");
        response.setHeader("Content-Type", contentType);
        response.setHeader("Content-Length", "" + fileLength);
        response.setHeader("Pragma", "no-cache;");
        response.setHeader("Expires", "-1;");
        
        try(
                FileInputStream fis = new FileInputStream(saveFileName);
                OutputStream out = response.getOutputStream();
        ){
        	    int readCount = 0;
        	    byte[] buffer = new byte[1024];
            while((readCount = fis.read(buffer)) != -1){
            		out.write(buffer,0,readCount);
            }
        }catch(Exception ex){
            throw new RuntimeException("file Save Error");
        }
	}

파일 다운로드를 구현할 때는 보안에 상당히 신경을 싸야 합니다. 인자로 파일명을 받아들여, 그 파일명을 다운로드 받게 할 경우 해커들은 파일명을 "../../etc/passwd" 와 같은 형태로 전달하여 해킹을 시도하게 하는 경우도 있습니다.

좋은 웹페이지 즐겨찾기