JSP 파일 업로드

 

목차

    1. HTTP 메세지에서 이진 데이터와 텍스트 형태

    텍스트 기반 ( MIME TYPE - text/plain )

    POST /example HTTP/1.1
    Host: example.com
    Content-Type: text/plain
    Content-Length: 13
    ---- CLRF 빈줄 공백 --------
    Hello

     

    이진데이터 ( MIME TYPE - application/octet-stream )

    POST /example HTTP/1.1
    Host: example.com
    Content-Type: application/octet-stream
    Content-Length: 5
    ---- CLRF 빈줄 공백 --------
    01001000 01100101 01101100 01101100 01101111

    application/octet-stream 타입으로 지정된 경우, 본문 데이터는 그대로 전송 된다. 이 MIME 타입은 바이너리 데이터를 나타내며, 서버가 이를 특별한 인코딩이나 디코딩 없이 원시 데이터로 취급하게 한다.

    집중 !
    HTTP 통신에서는 데이터가 기본적으로 바이트 단위로 전송됩니다. 컴퓨터에서 모든 데이터는 결국 이진수로 표현되며, HTTP 통신에서도 마찬가지로 1과 0으로 이루어진 바이트 단위의 데이터로 전송됩니다. 그런데 왜 통신에 있어 데이터가 문자 단위로 전송 된다고도 이야기를 할까?

    처음 학습하는 학생들에게 쉽게 이해할 수 있도록 하기 위함 입니다. 예를 들어, HTML 문서나 JSON 같은 텍스트 형식의 데이터는 문자 단위로 이해할 수 있기 때문에, 이를 문자 단위로 전송한다고 설명하는 것이 쉽게 이해하는 데 도움이 주기 위함 입니다.

    하지만 조금 더 자세히 말하자면, 이 문자들은 결국 특정 인코딩(예: UTF-8)을 통해 바이트로 변환되어 전송됩니다.

    2. multipart/form-data MIME TYPE에 대해서 알아 보자.

    multipart/form-data는 주로 웹 애플리케이션에서 파일 업로드와 같은 복합적인 데이터 전송에 사용되는 형식이다.

    텍스트 데이터와 바이너리 데이터

    • 텍스트 데이터: 텍스트 데이터는 일반적으로 문자로 이루어져 있으며, 전송 과정에서 특별한 변환 없이 텍스트 형식 그대로 전송된다. 텍스트 필드의 내용은 UTF-8과 같은 문자 인코딩을 통해 바이트 시퀀스로 변환되어 전송된다.
    • 바이너리 데이터: 바이너리 데이터는 이미지, 오디오 파일, 비디오 파일 등과 같은 파일 형식으로 주로 나타난다. 이 데이터는 원래의 바이너리 형식 그대로 전송된다.

    multipart/form-data 형식의 특징은 데이터를 여러 부분(파트)으로 나누어 전송할 수 있다는 점이다. 각 파트는 자체적으로 여러개의 (헤더와 바디)를 가지고 있으며, 이 파트들이 조합되어 전체 요청 본문을 구성한다.

    POST /upload HTTP/1.1
    Host: example.com
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
    
    ----WebKitFormBoundary7MA4YWxkTrZu0gW
    Content-Disposition: form-data; name="textField"
    
    This is a text.
    ----WebKitFormBoundary7MA4YWxkTrZu0gW
    Content-Disposition: form-data; name="file"; filename="example.txt"
    Content-Type: text/plain
    
    (File content goes here)
    ----WebKitFormBoundary7MA4YWxkTrZu0gW--
    • Boundary (경계 문자열): 각 파트는 고유한 경계 문자열로 구분된다. 이 문자열은 요청 본문의 시작과 끝을 나타낸다.
    • Part Header (부분 헤더): 각 파트는 데이터에 대한 설명을 담고 있는 헤더를 포함할 수 있다. 예를 들어, 파일의 이름, 필드 이름, 데이터의 유형(Content-Type) 등을 포함할 수 있다.
    • Part Body (부분 본문): 여기에는 실제 데이터가 들어간다. 예를 들어, 텍스트 필드의 내용이나 업로드된 파일의 내용이 포함된다.
    시나리오 코드 1 (새 프로젝트 생성 - jsp_file_upload_ex)

    index.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    	<div class="main--container">
    		<!-- 파일을 전송하기 위한 설정  -->
    		<form action="/upload" method="post" enctype="multipart/form-data">
    			<label for="title"> 제목 : </label>
    			<input type="text" name="title" id="title">
    			<label for="mFile">첨부 파일 : </label>
    			<input type="file" name="mFile" id="mFile">
    			<button type="submit">전송</button>
    		</form>	
    	</div>
    </body>
    </html>
    package com.tenco.controller;
    
    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 jakarta.servlet.http.Part;
    
    import java.io.File;
    import java.io.IOException;
    import java.nio.file.Paths;
    import java.util.UUID;
    
    
    /**
     * 서블릿 스펙에서 파일 처리를 할려면 
     * 반드시 어노테이션 하나가 더 필요하다. 
     */
    @WebServlet("/upload")
    @MultipartConfig // 반드시 선언 
    public class FileUploadController extends HttpServlet {
    	private static final long serialVersionUID = 1L;
           
    
      public FileUploadController() {
          super();
      }
        
    	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    		// "mFile" 이라는 key 값으로 input 태그로 부터 파일 데이터를 가져올 수 있다.
    		// 파일은 getPart 을 활용 
    		Part filePart = request.getPart("mFile");
    		
    		
    		System.out.println(filePart.getContentType());
    		System.out.println(filePart.getSize());
    		
    		// 파일을 서버측에 업로드하는 처리 프로세스
    		
    		// 유효성 검사 
    		if(filePart == null || filePart.getSize() == 0) {
    			response.setContentType("text/html");
    			response.getWriter().println("첨부 파일을 추가해주세요");
    			return; 
    		}
    		
    		// 사용자가 올린 파일 원본 이름을 가져 온다. 
    		// System.out.println("originFileName : " + originFileName);
    		
    		String originFileName = filePart.getSubmittedFileName();
    		
    		// 1. 원본 파일명을 가져온다. 
    		// 2. 가능한 절대 중복되지 않을 이름을 만들어 준다. 
    		// UUID 를 통해서 고유한 파일명을 만들어 보자.
    		// 3. 확장자를 분리해서 원본파일명 + _ + 고유한 UUID를 생성해서 
    		// 새로운 파일명을 만들어 준다. 
    		String uniqueFileName = UUID.randomUUID().toString();
    		
    		// a.png, b.jpeg  ==> a_xhdf.png 
    		// 파일 확장자를 추출하여 고유한 파일명 뒤에 추가합니다. 
    		String extension = ""; 
    		int i = originFileName.lastIndexOf(".");
    		System.out.println("UNIQUE : " + uniqueFileName);
    		System.out.println(". 인덱스 번호 : " + i);
    		
    		if(i > 0 ) {
    			// . 포함한 확장자를 추출 
    			extension = originFileName.substring(i);
    			System.out.println("extension : " + extension);
    		}
    		uniqueFileName += extension;
    		System.out.println(uniqueFileName);
    		
    		// 4. 어디에 저장할지 경로를 설정해야 한다. 
    		// C:\work_web\jsp_file_upload_ex1\src\main\webapp\images
    		File uploadDirFile = new File("C:\\work_web\\jsp_file_upload_ex1\\src\\main\\webapp\\images");
    		
    		// 5. 해당경로에 폴더가 존재하는지 확인 -> 없다면 폴더를 코드로 생성하기 
    		if(!uploadDirFile.exists()) {
    			// 없으면 생성
    			// mkdir, mkdirs <-- 부모 폴더가 없으면 함께 생성해 
    			if(uploadDirFile.mkdirs()) {
    				System.out.println("디렉토리가 생성 되었습니다. " + uploadDirFile);
    			} else {
    				throw new ServletException("디렉토리 생성에 실패했습니다.");
    			}
    		}
    		
    		// 파일 생성... 
    		File fileToSave = new File(uploadDirFile, uniqueFileName);
    		System.out.println("fileToSave.getAbsolutePath() : " + fileToSave.getAbsolutePath());
    
    		// 파일을 서버에 저장 
    		filePart.write(fileToSave.getAbsolutePath());
    	
    		// 응답 페이지 구성 
    		response.setContentType("text/html");
    		response.getWriter().print("파일 업로드에 성공!");
    		response.getWriter().print("<br>");
    		response.getWriter().print("사용자가 올린 파일명 : " + originFileName);
    		response.getWriter().print("<br>");
    		response.getWriter().print("서버에 저장된 파일명 : " + uniqueFileName);
    		
    	}
    }

     

    이미지 뿌리기

    package com.tenco.controller;
    
    import jakarta.servlet.ServletException;
    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.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    
    @WebServlet("/imageList")
    public class ImageListController extends HttpServlet {
    	private static final long serialVersionUID = 1L;
           
    
        public ImageListController() {
            super();
        }
    
        // 주소설계 
        // http://localhost:8080/imageList
    	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		
    		// 이미지 파일이 저장된 경로를 가져 온다. 
    		String ulpoadDir = "C:\\work_web\\jsp_file_upload_ex1\\src\\main\\webapp\\images"; 
    		File dir = new File(ulpoadDir);
    		
    		// 디렉토리 내 파일 리스트를 가져 오는 방법 
    		File[] files = dir.listFiles();
    		List<String> fileNames = new ArrayList<>();
    		
    		if(fileNames != null) {
    			for(File f : files) {
    				fileNames.add(f.getName());
    			}
    		}
    		System.out.println(" file length : " + fileNames.size());
    		System.out.println(" file names : " + fileNames.toString());
    		
    		request.setAttribute("fileNames", fileNames);
    		request.getRequestDispatcher("/WEB-INF/list.jsp").forward(request, response);
    		
    	}
    	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	}
    }
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@page import="java.util.List"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    	<h1>이미지 리스트 출력하기</h1>
    	<ul>
    	<%
    	  List<String> fileNames = (List<String>) request.getAttribute("fileNames");
    	  if(fileNames != null && !fileNames.isEmpty() ) {
    		// 파일명들이 존재 한다면 
    		for(String fileName : fileNames) {
    	 %>		
    			<li><img src="images/<%=fileName%>" width="200px">
    	 <% 		
    		}
    		  
    	  }	
    	%>
    	</ul>
    	
    </body>
    </html>

    JSP 목차로 돌아가기