국비교육(22-23)

61일차(2)/Spring Boot(11) : 파일 저장경로 수정, 에러 페이지 응답 기능 구현

서리/Seori 2023. 1. 3. 23:40

61일차(2)/Spring Boot(11) : Error Controller, 에러 응답 기능 구현

 

 

- users 프로필이미지 저장경로 수정

- error controller, 에러 응답 페이지 생성

 


 

- users의 프로필이미지가 저장되는 경로 수정하기

 

 updateform.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/users/updateform.jsp</title>
<style>
   /* 이미지 업로드 폼을 숨긴다 */
   #imageForm{
      display: none;
   }
   #profileImage{
      width: 100px;
      height: 100px;
      border: 1px solid #cecece;
      border-radius: 50%;
   }
</style>
</head>
<body>
   <div class="container">
      <h3>회원 가입 수정 폼 입니다.</h3>
      
      <a id="profileLink" href="javascript:">
      	<c:choose>
      		<c:when test="${ empty dto.profile }">
      			<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-circle" viewBox="0 0 16 16">
	              <path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
	              <path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
	            </svg>
      		</c:when>	
      		<c:otherwise>      			
      			<img id="profileImage" src="${pageContext.request.contextPath }/users/images/${dto.profile}">
      		</c:otherwise>
      	</c:choose>         
      </a>
      <form action="${pageContext.request.contextPath}/users/update" method="post">
         <input type="hidden" name="profile" 
            value="${ empty dto.profile ? 'empty' : dto.profile }"/>
         <div>
            <label for="id">아이디</label>
            <input type="text" id="id" value="${dto.id}" disabled/>
         </div>
         <div>
            <label for="email">이메일</label>
            <input type="text" id="email" name="email" value="${dto.email}"/>
         </div>
         <button type="submit">수정확인</button>
         <button type="reset">취소</button>
      </form>   
      
      <form id="imageForm" action="${pageContext.request.contextPath}/users/profile_upload" method="post" enctype="multipart/form-data">
         프로필 사진
         <input type="file" id="image" name="image" accept=".jpg, .png, .gif"/>
         <button type="submit">업로드</button>
      </form>
               
   </div>
   <!-- sy_util.js 로딩 -->
   <script src="${pageContext.request.contextPath }/resources/js/sy_util.js"></script>
   <script>
      //프로필 이미지 링크를 클릭하면 
      document.querySelector("#profileLink").addEventListener("click", function(){
         // input type="file" 을 강제 클릭 시킨다. 
         document.querySelector("#image").click();
      });   
      
      //프로필 이미지를 선택하면(바뀌면) 실행할 함수 등록
      document.querySelector("#image").addEventListener("change", function(){
         //ajax 전송할 폼의 참조값 얻어오기
         const form=document.querySelector("#imageForm");
         //gura_util.js 에 있는 함수를 이용해서 ajax 전송하기 
         ajaxFormPromise(form)
         .then(function(response){
            return response.json();
         })
         .then(function(data){
            console.log(data);
            // input name="profile" 요소의 value 값으로 이미지 경로 넣어주기
            document.querySelector("input[name=profile]").value=data.imagePath;
            
            // img 요소를 문자열로 작성한 다음 
            let img=`<img id="profileImage"                
               src="${pageContext.request.contextPath }/users/images/\${data.imagePath}">`;
            //id 가 profileLink 인 요소의 내부(자식요소)에 덮어쓰기 하면서 html 형식으로 해석해 주세요 라는 의미 
            document.querySelector("#profileLink").innerHTML=img;
         });
      });      
   </script>
</body>
</html>

 

<c:otherwise>
   <img id="profileImage" 
      src="${pageContext.request.contextPath}${dto.profile}"/>
</c:otherwise>

- 기존 뷰 페이지 응답 방식

 

- 이렇게 이미지를 저장할 경로를 수정해준다.

 

 

UsersServiceImpl

- @Value와 필드 추가

- getRealPath 대신 fileLocation 으로 수정

 

- 저장된 파일명만! 응답되도록 한다.

 

 

updateform.jsp

- imagePath를 json으로 응답받아서 화면에 출력하려면 javascript 부분도 이렇게 수정

 

- 위쪽에 출력하는 화면에서도 경로를 표시!

 

 

- 이미지를 응답하는 컨트롤러 메소드가 필요하다.

- GalleryController에서 메소드 복사해서 추가해주기!

 

@GetMapping(
        value="/users/images/{imageName}",
        produces = {MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_GIF_VALUE}
    )
@ResponseBody
public byte[] profileImage(@PathVariable("imageName") String imageName) throws IOException {

    String absolutePath=fileLocation+File.separator+imageName;
    //파일에서 읽어들일 InputStream
    InputStream is=new FileInputStream(absolutePath);
    //이미지 데이터(byte)를 읽어서 배열에 담아서 클라이언트에게 응답한다.
    return IOUtils.toByteArray(is);
}

- 메소드명, 경로만 변경해서 사용 가능!

 

- 변경하면 콘솔 창에 이렇게 출력된다.

 

 

cafe/detail.jsp

- 댓글의 프로필 이미지 출력을 위해!

 

- users/images/ 경로를 추가해준다.

 

- include된 ajax_Comment_List 페이지에도 필요!!

 

 

WebConfig 에서 interceptor 추가

 

- 로그인하지 않아도 users/images/ 안의 이미지가 보이도록 exclude 처리시킨다.

 

- 이렇게 하면 기존 Legacy 프로젝트를 Spring Boot 프로젝트로 변경하는 것은 끝!

 


 

- 에러 응답 기능 추가하기!

- 만약 없는 페이지를 요청한다면? (error)

 

- 불가능한 요청 처리(없는 페이지) → 404 에 해당한다.

 

- 에러 상황 발생시 이런식으로 이미지가 나오도록 처리하려고 한다!

 

 

- Root 패키지에 클래스 생성

ErrorHandler

package com.sy.boot07;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

//ExceptionController에서 처리되지 않은 예외가 발생하면 여기로 실행 순서가 온다.
@Controller
public class ErrorHandler{
	//application.properties에 server.error.path=/error 설정때문에 호출되는 컨트롤러 메소드
	@GetMapping("/error")
	public String handleError(HttpServletRequest request) {
		//에러의 코드값을 읽어온다. 404,500,403 등
		int code=(int)request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);		
		if( code == HttpStatus.NOT_FOUND.value()) { //404
			return "error/404";
		}else if( code == HttpStatus.INTERNAL_SERVER_ERROR.value()){ //500
			return "error/500";
		}else {
			return "error/info";
		}		
	}
}

 

- ErrorHandler에서 ErrorController를 꼭 구현하지는 않아도 작동한다.

- 특정 요청을 처리할 수 있는 bean으로만 만들어주면 된다. (@Controller)

 

 

application.properties

 

server.error.path=/error

- 에러가 났을때 자동으로 /error 라는 경로가 요청되도록 한다. 리다이렉트 되는 것!

 

- ErrorController 를 구현한 클래스 작성

 

- 에러 코드를 request로 읽어와서 int로 변환하여 콘솔 창에 출력하게 해본다.

- 일단 기존에 만들어둔 error/info로 들어가도록 설정했다.

 

 

- 없는 페이지를 요청하면 콘솔 창에 404 가 찍힌다.

- 찍히는 코드에 따라서 if~else로 분기할 수도 있다!!

 

 

- 에러 코드가 404, 500번일 경우 별도의 페이지에서 응답하도록 처리해준다.

- 숫자로 그대로 넣는 대신 어떤 값을 상수화하여 정한 이름이 있다.(미리 저장되어있다)

- 숫자로 직접 써도 되고, 이렇게 상수값을 가져와서 value로 읽어서 사용해도 된다.

 (상수를 사용하는 쪽이 가독성이 좋다)

 

- 이런 식으로 분기하면 에러 상황에 대한 세부적인 처리가 가능하다.

 

 

/views/error/404.jsp 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/error/404.jsp</title>
</head>
<body>
	<div class="container">
		<h3>404</h3>
		<p>
			요청하신 페이지는 존재하지 않습니다.
			<a href="${pageContext.request.contextPath}/">인덱스로</a>
		</p>
	</div>
</body>
</html>

 

- 404에러에 대해 클라이언트 브라우저에 이렇게 응답하게 할 수 있다.

 

- 경로생성, 경로요청을 처리할 컨트롤러(ErrorController를 구현)

- request 값에 에러 정보가 들어오면, 그것을 읽어내서

 에러 코드가 몇번이냐에 따라 선택적으로 작업하여 응답한다.

 


 

- 서버내에서 연산/처리를 하다가 오류가 났을 때 500번 오류를 띄운다.

- exception에서 잡히지 않으면 error/info 페이지로 기본으로 들어감.

 (미리 Exception Controller 에서 잡고 거기서 잡지 않는 것들을 이 ErrorHandler에서 처리

 

 

views/error/500.jsp 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/error/500.jsp</title>
</head>
<body>
	<div class="container">
		<h3>500</h3>
		<p>
			서버 내부에서 에러가 발생했습니다. 조속히 복구하겠습니다.
			<a href="${pageContext.request.contextPath}/">인덱스로</a>
		</p>
	</div>
</body>
</html>

 

- cafe/detail/ 을 하고, 요청하면서 파라미터 값을 전달해주지 않으면 이 에러가 발생한다.

 

 

- null 에러가 발생한다. 이것을 출력하는 대신 커스텀 에러 페이지를 출력한 것이다.