(책) 자바 웹을 다루는 기술

Chap 15 JSP 페이지를 풍부하게 하는 오픈 소스 기능

EunaSon 2023. 9. 1. 00:44

15.1 JSP에서 파일 업로드 (606p)

15.2 JSP에서 파일 다운로드 (615p)

 


15.1 JSP에서 파일 업로드

606p

여태까지는 JSP와 직접 관련 있는 기능들에 대해 알아보았음

JSP는 대부분의 기능을 오픈소스로 제공함

파일 업로드, 파일 다운로드, 이메일 등 수많은 오픈 소스 라이브러리를 제공함

 

* 서블릿3.0 환경에서는 Part API 제공, 이를 이용해 파일 업로드 구현,

이전 버전에서는 Apache Commons의 FileUpload 라이브러리를 많이 사용함

 

FileUpload 라이브러리 설치

(Apache Commons는 Apache Jakarta project의 일부로, java 환경에서 공통적으로 사용되는 코드 모음(라이브러리)를 개발하여 오픈소스로 배포하는 프로젝트임)

Apache Jakarta 공식 사이트인 http://jakarta.apache.org에 접속 > 왼쪽 메뉴에서 Commons를 클릭 > 페이지 왼쪽 중간 쯤에 있는 FileUpload를 클릭 > 버전 1.3.3을 찾아서 here을 클릭 > commons-fileupload-1.3.3-bin.zip을 클릭해 다운로드 함 > 다운받은 zip의 압축을 품 > 하위폴더인 commons-fileupload-1.3.3-bin에 위치한 commons-fileupload-1.3.3.jar 파일을 복사 > 이클립스에서 프로젝트의 WEB-INF 하위의 lib 폴더에 붙여넣기

 

commons-io-2.6.jar 파일 설치

http://commons.apache.org/proper/commons-io/download_io.cgi 링크에 접속해 commons-io-2.6-bin.zip를 클릭해 다운 > 로컬pc의 원하는 폴더에 zip 파일의 압축을 해제 > 하위 폴더인 commons-io-2.6.jar 파일을 복사 > 마찬가지로 이클립스에서 프로젝트의 WEB-INF 하위의 lib 폴더에 붙여넣음

 

파일 업로드 관련 API

파일 업로드 라이브러리에서 제공하는 클래스에는 DistFileItemFactory, ServletFileUpload가 있음

 

▼DiskFileItemFactory 클래스가 제공하는 메서드

메서드 기능
setRepository() 파일을 저장할 디렉터리를 File 객체로 지정함
setSizeThreshold() 저장소에 임시파일을 생성할 한계 크기를 byte 단위로 설정함.
이 값을 1024로 지정한 경우, 1024byte 이상의 파일을 업로드하면
메모리에 있던 파일의 바이너리 데이터를 저장소에 임시 파일로 잠시 저장함

임시 파일로 저장하는 이유

: 대용량 파일을 업로드 했을 때 그정도 크기의 데이터를 웹 애플리케이션이 동작하는 JVM 메모리 상에 모두 로드하는 것은 부담이 되기 때문. 임시파일로 저장되었던 파일은 이후에 FileItem#write() 메서드를 통해 실제 파일로 변경되거나 FileItem#delete() 메서드를 통해 제거됨 

 

▼ServletFileUpload 클래스가 제공하는 메서드

메서드 기능
parseRequest() 전송된 매개변수를 List 객체로 얻음
getItemIterator() 전송된 매개변수를 Iterator 타입으로 얻음

 

JSP 페이지에서 파일 업로드

이제 설치한 라이브러리를 이용해 파일을 업로드해보자

 

프로젝트 pro15의 src 폴더에 sec01.ex01 패키지를 만들고 FileUpload 클래스를 생성함,

WebContent 폴더에 test01 폴더를 만들고 uploadForm.jsp를 추가함,

파일을 업로드할 때 사용할 저장소를 C드라이브 아래에 만듦, 이름은 file_repo로 했음

 

[ uploadForm.jsp ]

파일 업로드창.

파일을 업로드할 때 <form> 태그의 encType 속성은 반드시 multipart/form-data로 지정해야함(encType 속성은 <form>의 method 속성값이 post인 경우에만 사용할 수 있음)

 

* encType의 속성값

encType 속성은 <form> 태그의 데이터들을 전송할 때 데이터들을 어떤 형식으로 변환할 것인지에 대한 값을 지정하는 속성임. 

application/x-www-form-urlencoded : 기본값, 모든 문자들은 서버로 보내기 전에 인코딩됨을 명시함. 파일이 없는 폼에 사용. 단순 text 형식으로, name=value&name=value ... 의 구조임. 

multipart/form-data : 모든 문자를 인코딩하지 않음을 명시함, 이 방식은 <form> 요소가 파일이나 이미지를 서버로 전송할 때 주로 사용함(<input type="file" >이 포함된 폼)

text/plain : 공백문자(space)는 + 기호로 변환되지만, 나머지 문자는 모두 인코딩 되지 않음을 명시함. 보안성이 없어 디버깅 용도로만 사용해야 함.

 

file은 내부적으로 0과 1로만 이루어진 바이너리 형식, 일반적인 단순한 텍스트 데이터와는 성질이 다름, input 태그 중 2개는 file 타입, 3개는 text 타입임. 이는 여러가지 타입의 데이터를 한번에 모두 전송할 수 있는 인코딩 타입인 multipart/form-data로 처리할 수 있음. encType을 multipart로 변경하지 않으면 여러가지 타입의 데이터를 동시에 전송할 수 없으므로 텍스트 데이터만 전송되어 파일의 실질적인 내용은 전송되지 않고 파일명이나 사용자 컴퓨터 상의 파일 경로만 전송하게 됨. 

 

<body>

  <form action="${ contextPath }/upload.do" // upload.do 서블릿에 요청해 파일을 업로드함

            method="post" encType="multipart/form-data" >

    파일1: <input type="file" name="file1" ><br> // 파일을 업로드시에도 input 태그를 이용, type 속성에 file을 주면 됨

    파일2: <input type="file" name="file2" ><br>

    매개변수1: <input type="text" name="param1" ><br>

    ...// 매개변수 3까지

    <input type="submit" value="업로드">

  </form>

</body>

 

[ fileUpload 클래스 ]

파일 업로드를 처리하는 서블릿.

라이브러리에서 제공하는 DiskFileItemFactory 클래스를 이용해 저장위치, 업로드 가능한 최대 파일 크기를 설정함,

ServletFileUpload 클래스를 이용해 파일 업로드창에서 업로드된 파일과 매개변수에 대한 정보를 가져와서 파일을 업로드하고 매개변수 값을 출력함

 

*** DiskFileItemFactory 클래스

업로드된 파일을 저장할 저장소와 관련된 클래스.

File attacheDir = new File(ATTACHES_DIR);
DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
fileItemFactory.setRepository(attachesDir);
fileItemFactory.setSizeThreshold(LIMIT_SIZE_BYTES);

setRepository() : 업로드된 파일을 저장할 위치를 File 객체로 지정함

setSizeThreshold() : 저장소에 임시파일을 생성할 한계 크기를 byte 단위로 지정함. 이 값을 1024로 지정시 1024byte 이상의 파일을 업로드하게 되면 메모리에 있던 파일의 바이너리 데이터를 저장소에 임시파일로 잠시 저장함.

너무 대용량의 파일을 웹 애플리케이션이 동작하는 JVM 메모리상에 모두 로드하는 것은 부담되기 때문에 지정하는 것이 좋음. 임시파일은 이후 FileItem#write() 메서드를 통해 실제 파일로 변경되거나 FileItem#delete() 메서드를 통해 제거됨.

 

 

*** ServletFileUpload 클래스

HTTP 요청에 대한 HttpServletRequest 객체로부터 multipart/form-data 형식으로 넘어온 HTTP body 부분을 다루기쉽게 변환(parse)해주는 역할을 수행함. parseRequest() 메서드를 수행하면 FileItem 이라는 형식으로 변환해줌.

ServletFileUpload fileUpload = new ServletFileUpload(fileItemFactory);
List<FileItem> items = fileUpload.parseRequest(request);

  

*** FileItem

사용자가 업로드한 File 데이터나 사용자가 input text에 입력한 일반 요청 데이터에 대한 객체.

FileItem#isFormField() 메서드의 리턴값이 true이면 text 같은 일반 입력 데이터이며, false이면 파일 데이터임. 즉, false인 경우에만 업로드된 파일인 것으로 인지해 처리하면 됨.

메서드 설명
public String getFieldName() 데이터의 name을 리턴함. input 태그에 설정된 name값임.
public boolean isFormField() input 태그에 입력된 데이터가 단순히 text나 checkbox 등으로 입력된 값인지의
여부를 리턴함. 첨부 파일 등 바이너리 데이터면 false를 리턴함.
public String getString
(final String charset)
첨부파일이 아닌 단순한 form 데이터인 경우,
입력된 값을 cherset에 따라 인코딩하여 리턴함.
public String getName() 데이터가 첨부파일인 경우, 파일명 또는 파일 경로를 리턴함
public long getSize() 데이터의 크기(파일의 크기)를 byte 단위로 리턴함
public void write(File file)
throws Exception
현재 데이터가 첨부파일일 때 매개변수로 넘겨준 File 객체의 경로로 출력(저장)함

 

*** File 클래스

java.io 패키지에서 제공함.

 

 

public void doHandle(...) {

  ... // request 인코딩 utf-8로 지정, "utf-8"을 변수 String encoding에 저장

  File currentDirPath = new File("C:\\file_repo");

  // File클래스 생성자 중 - 해당 경로의 파일에 대한 File 객체를 생성함 (업로드할 파일 경로를 File 객체로 지정함)

  DiskFileItemFactory factory = new DiskFileItemFactory(); // 업로드된 파일의 저장소와 관련된 클래스

  factory.setRepository(currentDirPath); // 업로드된 파일을 저장할 위치를 File 객체로 지정함

  factory.setSizeThreshold(1024*1024); // 업로드 가능한 최대 크기를 설정함(이 이상이면 임시파일로 저장소에 저장됨)

  ServletFileUpload upload = new ServletFileUpload(factory); // 업로드 파일 저장소를 인자로 하여 ServletFileUpload 객체 생성

  try {

    List items = upload.parseRequest(request);

    // 브라우저로부터 온 요청(request)에서 전달된 매개변수(첨부파일, 텍스트 등)들을 List인 items에 저장함

    for( int i=0; i <items.size(); i++) {

      FileItem fileItem = (FileItem)items.get(i); // 업로드창에서 전송된 항목들을 하나씩 가져옴

      if (fileItem.isFormField()) { // 전송된 항목이 그냥 text이면(파일이 아니면)

        System.out.println(fileItem.getFieldName() + "=" + fileItem.getString(encoding));

        // getFieldName() : input태그의 name 속성을 반환(name="param1", name="param2"에서 param1, param2),

        //getString(encoding) : 지정한 인코딩으로 파일아이템 내용을 반환

      }

      else {

        System.out.println("매개변수 이름: " + fileItem.getFieldName()); // input의 name값인 file1, file2 출력

        System.out.println("파일 이름: " + fileItem.getName()); // 클라이언트에 저장되어있던 파일명을 반환

        System.out.println("파일 크기: " + fileItem.getSize() + "bytes"); // 파일의 크기를 반환함

  

        if ( fileItem.getSize() > 0 )

          int idx = fileItem.getName().lastIndexOf("\\"); // 파일명에서 '\'의 인덱스를 찾아 반환함 (절대경로로 저장되어있으면)

         // String.lastIndexOf() : 주어진 문자열 뒤에서부터 지정한 값이 처음 발견되는 위치의 index 반환, 못찾으면 -1 반환.

          if ( idx == -1) { // getName()에 '\' 가 포함되어있지 않으면(상대경로로 저장되어있으면)

            idx = fileItem.getName().lastIndexOf("/"); 

          }

          String fileName = fileItem.getName().substring(idx + 1); // 경로명 뒤를 가져옴

          File uploadFile = new File(currentDirPath + "\\" + fileName); // 저장위치+파일명으로 저장함

          fileItem.write(uploadFile); // 인자인 File 객체대로 해당 이름으로 저장위치에  파일(fileItem) 업로드함

      } // if문 끝 ( fileItem.getSize() > 0 )

    } // else 끝 ( fieItem.isFormField() 가 false인 경우)

  } // for문 끝 ( request에서 전달된 항목들 모두 출력됨 )

...

 

=>

"utf-8"을 String 변수에 저장

업로드 된 파일을 저장할 저장소 위치(경로)를 File 객체로 생성

파일 저장 관련 설정을 할 수 있는 DiskFileItemFactory 객체 생성해서 저장소 경로 저장된 File 객체를 인수로 setRepository() 메서드 통해 저장소 경로 설정, setSizeThreshold() 메서드로 최대 업로드 가능 크기를 설정

DiskFileItemFactory 인스턴스를 인자로 하여 ServletFileUpload 객체를 생성,

parseRequest(request) 메서드로 request에서 전송된 매개변수들 가져와 list에 저장,

for문 통해 list에서 요소 하나씩 꺼내서

if문으로 해당 요소가 텍스트면

 > input태그 name값 + 입력받은 value 출력,

else로 해당요소가 첨부된 파일이면

 > input 태그 name값, 첨부파일명, 파일 크기 출력,

    첨부파일명에서 경로와 파일명을 분리해 파일명만 얻어와서 저장소위치+파일명 으로 File 객체 생성,

    생성한 File 객체를 인자로 하여 wirte() 메서드를 통해 데이터(fileItem)를 저장소에 파일명으로 저장함  

 

 

* File : 파일 및 디렉터리 경로 이름의 추상적 표현

* FileItem : multipart request로 수신된 파일과 form item - servlet/jsp에서 File 사용하기 위해 만들어짐

* MultipartFile : multipart request로 수신된 파일의 표현 - Spring에서 File 사용하기 위해 만들어짐, 메모리 혹은 디스크에 임시저장

 

15.2 JSP에서 파일 다운로드

615p

이번에는 서블릿 기능을 이용하여 업로드한 파일을 다운로드해 출력해보자

 

[  first.jsp ]

첫번째 jsp

다운로드할 이미지 파일 이름을 두번째 jsp로 전달하도록 작성함.

 

<body>

  <form method="post" action="result.jsp"> 

    <input type="hidden" name="param1" value="duke.png" /><br> // 다운로드할 파일이름을 매개변수로 전달함

    <input type="hidden" name="param2" value="duke.png" /><br> // 업로드한 파일 이름이 다르다면 value 수정해야함

    // '이미지 다운로드' 버튼(submit 타입)

  </form>

</body>

 

[ result.jsp ]

두번째 jsp.

<img> 태그의 src 속성에 다운로드를 요청할 서블릿 이름(FileDownload.java( /download.do ))과 파일이름(duke.png, duke2.png)을 GET 방식으로 전달해서 다운로드할 이미지 파일을 화면에 출력함,

<a> 태그로 링크 클릭하면 서블릿에 다운로드가 요청되어 파일(여기선 duke2.png)을 로컬pc에 다운로드함.

 

...// taglib 디렉티브 태그로 core 태그 라이브러리 사용 설정, contextPath (pro15)를 가져오는 변수 contextPath를 설정

...

<head>

  <c:set var="file1" value="${ param.param1 }" />

  <c:set var="file2" value="${ param.param2 }" /> // 다운로드할 파일 이름(duke.png, duke2.png)을 가져옴

...

</head>

<body>

  매개변수 1 : <c:out value="${ file1 }" /><br>

  매개변수 2 : <c:out value="${ file2 }" /><br>

 

  // duke.png 가져와 화면에 출력

  <c:if test="${ not empty file1 }">

    <img src="${ contextPath }/download.do?fileName=${ file1 }" width=300, height=300 /><br>

  </c:if>

 

  // duke2.png 가져와 화면에 출력

  <c:if test="${ not empty file2 }">

    <img src="${ contextPath }/download.do?fileName=${ file2 }" width=300, height=300 /><br>

  </c:if>

 

  // duke2.png 다운로드할 링크

  <a href="${ contextPath }/download.do?fileName=${ file2 }" > // 서블릿에 요청하여 duke2.png 다운로드

  파일 내려받기</a>

</body>

 

[ FileDownload 클래스 ]

파일을 다운로드 하는 기능을 하는 서블릿.

파일 다운로드 기능은 자바 IO를 이용해 구현함.

OutputStream을 가져오고,

바이트 배열로 버퍼를 만든 후 while 반복문으로 파일에서 데이터를 읽어옴,

출력스트림의 write() 메서드를 이용해 브라우저로 출력함.

 

... // 파일 다운로드할 저장소 위치 String으로 저장

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

// request에 "fileName"이라는 name으로 전송된 매개변수 가져옴

OutputStream out = response.getOutputStream(); // 출력스트림 가져옴

String downFile = file_repo + "\\" + fileName; // '저장소경로\파일명'을 String으로 저장

File f = new File(downFile); // 해당 경로의 파일에 대하여 File 객체 생성

response.setHeader("Cache-Control", "no-cache"); // 브라우저-캐시 사용안함

response.addHeader("Content-disposition", "attachment; fileName=" + fileName);

// 해당 첨부파일 다운로드 할 대화상자 뜸( '다른 이름으로 저장' 창 뜨게 됨)

 

FileInputStream in =new FileInputStream(f); // File 객체를 이용해 읽어올 파일을 지정함

byte[] buffer = new byte[1024*8]; // 파일 크기보다 넉넉한 크기로 바이트 배열 생성

while(true) {

  int count = in.read(buffer);

  // 입력스트림 in에 지정된 File 객체 f에서 buffer만큼 데이터를 읽어음. 크기를 넉넉하게 줬으므로 한번에 읽어와짐

  // 읽은 바이트 수를 count에 저장, 반복문이 한번 더 돌아오면 더 읽을 데이터가 없으므로 -1을 반환해 count에 저장

  if ( count == -1 )

    break; // 파일 데이터 다 읽어왔으면 반복문 나감

  out.write(buffer, 0, count); // 바이트배열 buffer의 0부터 읽은 바이트수(count)까지를 출력데이터에 write함

}

in.close(); // 자원 해제

out.close() // 자원 해제

 

 

 

* response 기본객체의 기능 : 헤더정보 입력, 리다이렉트 등...(여기선 기본객체 아니라 HttpServletResponse 객체지만 비슷할 것 같아서 바로 찾아진 내용으로 씀)  

addHeader(String name, String value) : name 헤더에 value를 값으로 추가함(키에 해당하는 값을 하나 더 추가함)

setHeader(String name, String value) : name 헤더의 값을 value로 지정함(새롭게 설정한 값으로 덮어씀, 이전값은 지워짐)

 

* 응답 헤더

Cache-Control : HTTP 1.1 버전에서 지원하는 헤더. 이 헤더의 값을 "no-cache"로 지정하면 웹 브라우저는 응답 결과를 캐시하지 않음, response.setHeader("Cache-Control", "no-cache ") 코드를 추가하면 브라우저가 캐시에 보관하지 않도록 설정할 수 있음( setHeader() 대신 addHeader() 써도 됨)

 

* 캐시 : 브라우저(클라이언트)가 저장해놓은 데이터

 

* Content-Disposition : HTTP Response의 Body에 오는 컨텐츠의 기질/성향을 알려주는 속성임

default 값은 inline으로, 브라우저 상에 바로 보여줄 수 있는 data라고 생각하면 됨

특수한 경우는 Content-Disposition에 attachment를 주는 경우로, 이때 filename과 함께 주게 되면 Body에 오는 값을 다운로드 받으라는 뜻이 됨. '파일 다운로드' 대화상자가 뜨도록 하는 헤더속성이라 할 수 있음

 

* FileInputStream 클래스

자바에서 파일을 바이트 단위로 읽어오기 위해 사용되는 클래스.

이 클래스를 다른 입력 관련 클래스와 연결하면 파일에서 데이터를 읽거나 쓸 수 있음.

FileInputStream 객체를 생성할 때 데이터를 읽어올 파일을 지정함, File 객체로 지정하는 방법 / 문자열 형태로 파일의 경로명을 지정하는 방법이 있음

 

int read(byte[] b) : 파일을 한번에 읽어서 바이트배열에 저장, 읽은 바이트의 수를 반환 or 파일의 끝에 도달하면 -1을 반환

void write(byte[] b, int off, int len) : b-읽어올 버퍼, off-읽어올 데이터 시작점, len-출력스트림에 write할 바이트 수

 

 

 

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

출처

https://dololak.tistory.com/720

https://dololak.tistory.com/721

 

[JSP/Servlet] JSP/Servlet 환경에서 파일 업로드 위한 Apache Commons FileUpload 라이브러리 설정 방법

연관글 [서블릿/JSP] Apache Commons FileUpload를 이용한 파일업로드 구현하기 파일 업로드시 라이브러리를 사용하는 이유 JSP/Servlet 환경에서 파일을 업로드할때에는 오픈소스 라이브러리를 사용합니

dololak.tistory.com

http://www.tcpschool.com/html-tag-attrs/form-enctype

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

https://knight76.tistory.com/entry/30042769264

 

Content-disposition 속성

Content-Disposition 은 컨텐트 타입의 옵션이기도 하며 실제로 지정된 파일명을 지정함으로써 더 자세한 파일의 속성을 알려줄 수 있다. http://www.ietf.org/rfc/rfc2183.txt 스펙을 보면.. 2.1 The Inline Disposition

knight76.tistory.com

https://lannstark.tistory.com/8

 

HTTP Content-Disposition란

간단한 이해 Disposition이란 기질, 성향, 배치, 배열 이란 뜻이다. HTTP Response Header에 들어가는 Content-Disposition은 HTTP Response Body에 오는 컨텐츠의 기질/성향을 알려주는 속성이다. default 값은 inline으

lannstark.tistory.com