Post View

Spring HTTP 상태 코드에 따른 오류 화면 출력

이전 포스트(https://www.kurien.net/post/view/9)에서는 ExceptionHandler를 이용한 Exception 발생 시 오류 화면 출력을 적용하였습니다.
이번에는 web.xml에 HTTP 상태코드별 오류페이지 설정하는 방법을 적용해보았습니다.

ExceptionHandler를 이용한 경우 Exception이 발생되었을 때만 오류페이지가 나타나기 때문에 HTTP 상태코드에 따라서도 오류페이지가 나타나게 설정을 했습니다.
먼저 web.xml을 열고 <web-app> 내부에 아래의 코드를 추가합니다.

<error-page>
    <error-code>400</error-code>
    <location>/error/badRequest</location>
</error-page> 

<error-code> 태그에는 HTTP 상태코드의 숫자를 입력하면 되고 <location> 태그에는 RequestMapping을 입력하거나 파일명을 입력하면 됩니다.
제 경우에는 "/error/badRequest"와 같이 입력했는데 만약 400 오류가 발생하는 경우 RequestMapping이 "/error/badRequest"로 설정된 Controller로 연결됩니다.
맵핑 URL 대신 "/WEB-INF/views/error/exception.jsp"와 같이 파일경로를 입력하게 되면, 해당 파일을 바로 호출합니다.

제 경우에는 코드에서 맵핑 URL을 사용하였습니다.
RequestMapping을 사용했으니 이번에는 해당 URL에 대한 Controller를 추가해보겠습니다.

package net.kurien.blog.controller;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;

import net.kurien.blog.common.template.Template;
import net.kurien.blog.exception.handler.BasicExceptionHandler;

@Controller
@RequestMapping("/error")
public class ErrorController {
    Logger logger = LoggerFactory.getLogger(BasicExceptionHandler.class);

    @Inject
    private Template template;

    @RequestMapping
    public String defaultError() {
        return "error/exception";
    
    @RequestMapping("/badRequest")
    @ResponseStatus(value=HttpStatus.BAD_REQUEST)
    public String badRequest(HttpServletRequest request, HttpServletResponse response, Model model) {
        String requestURI = (String)request.getAttribute("javax.servlet.forward.request_uri");
        String referer = request.getHeader("referer");

        model.addAttribute("referer", referer);
        
        template.getCss().add("<link rel=\"stylesheet\" href=\"/css/module/error.css\">");
        
        logger.info("exception requestURI: " + requestURI);

        model.addAttribute("exceptionMsg", "잘못된 요청입니다.");
        model.addAttribute("exceptionDescription", "잘못된 데이터가 전송되었습니다.<br>데이터를 확인한 뒤 다시 시도하여주십시오.");

        return "error/exception";
    }
    
    @RequestMapping("/forbidden")
    @ResponseStatus(value=HttpStatus.FORBIDDEN)
    public String accessDenied(HttpServletRequest request, HttpServletResponse response, Model model) {
        String requestURI = (String)request.getAttribute("javax.servlet.forward.request_uri");
        String referer = request.getHeader("referer");

        model.addAttribute("referer", referer);
        
        template.getCss().add("<link rel=\"stylesheet\" href=\"/css/module/error.css\">");
        
        logger.info("exception requestURI: " + requestURI);

        model.addAttribute("exceptionMsg", "접근 권한이 없습니다.");
        model.addAttribute("exceptionDescription", "접근권한이 없습니다.<br>접근권한이 있는 계정으로 로그인하시기 바랍니다.");

        return "error/exception";
    }

    @RequestMapping("/notFound")
    @ResponseStatus(value=HttpStatus.NOT_FOUND)
    public String notFound(HttpServletRequest request, HttpServletResponse response, Model model) {
        String requestURI = (String)request.getAttribute("javax.servlet.forward.request_uri");
        String referer = request.getHeader("referer");

        model.addAttribute("referer", referer);
        
        template.getCss().add("<link rel=\"stylesheet\" href=\"/css/module/error.css\">");
        
        logger.info("exception requestURI: " + requestURI);

        model.addAttribute("exceptionMsg", "페이지를 찾을 수 없습니다.");
        model.addAttribute("exceptionDescription", "페이지가 이동되었거나 삭제되었습니다.<br>카테고리를 선택하시거나 아래에 표시된 버튼을 통해 이동하시기 바랍니다.");

        return "error/exception";
    }

    @RequestMapping("/internalServerError")
    @ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
    public String internalServerError(HttpServletRequest request, HttpServletResponse response, Model model) {
        String requestURI = (String)request.getAttribute("javax.servlet.forward.request_uri");
        String referer = request.getHeader("referer");

        model.addAttribute("referer", referer);
        
        template.getCss().add("<link rel=\"stylesheet\" href=\"/css/module/error.css\">");
        
        logger.info("exception requestURI: " + requestURI);

        model.addAttribute("exceptionMsg", "예상하지 못한 오류가 발생했습니다.");
        model.addAttribute("exceptionDescription", "예상하지 못한 오류가 발생했습니다.<br>카테고리를 선택하시거나 아래에 표시된 버튼을 통해 이동하시기 바랍니다.");

        return "error/exception";
    }
    
    @RequestMapping("/serviceUnavailable")
    @ResponseStatus(value=HttpStatus.SERVICE_UNAVAILABLE)
    public String serviceUnavailable(HttpServletRequest request, HttpServletResponse response, Model model) {
        String requestURI = (String)request.getAttribute("javax.servlet.forward.request_uri");
        String referer = request.getHeader("referer");

        model.addAttribute("referer", referer);
        
        template.getCss().add("<link rel=\"stylesheet\" href=\"/css/module/error.css\">");
        
        logger.info("exception requestURI: " + requestURI);

        model.addAttribute("exceptionMsg", "서비스가 원활하지 않습니다.");
        model.addAttribute("exceptionDescription", "사용자가 많거나 오류로 인하여 서비스가 원활하지 않습니다.<br>잠시 후 다시 시도해보시거나 문제가 지속되는 경우 관리자에게 문의하시기 바랍니다.");

        return "error/exception";
    }
}

Controller 부분은 다른 Controller와 동일한 형태로 작성합니다.
하지만 추가적으로 메서드 위에 @ResponseStatus가 있는데요.

이 부분은 ExceptionHandler를 이용한 오류 화면 출력에서도 나왔던 것으로 해당 로직이 실행될 때 HTTP 상태 코드를 지정된 상태 코드로 보냅니다.
이 상태코드에 따라 검색엔진 등에서 처리되는 방식에 문제가 될 수 있으니 입력해줍니다.

view는 ExceptionHandler와 동일하게 error/exception로 지정하였습니다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<section id="exception" class="error">
    <h3 class="section_subject">Error page</h3>

    <div id="exception_box">
        <h4 id="exception_header">${exceptionMsg}</h4>
        
        <div id="exception_description">
            ${exceptionDescription}
        </div>
        
        <div id="exception_button_wrap">
            <c:if test="${referer != null}"><a class="kre_btn reverse_btn signin_btn" href="${referer}"><span class="material-icons">arrow_back</span> Previous Page</a></c:if><a class="kre_btn reverse_btn signin_btn" href="${contextPath}/"><span class="material-icons">home</span> Kurien's Blog</a>
        </div>
    </div>
</section>

지정해둔 HTTP 상태 코드가 발생하면 해당 view를 호출하고 화면에 지정한 메시지를 출력해주는 역할을 합니다.
만약 이전 페이지 방문기록(referer)가 있다면 이전 페이지로 가는 버튼을 함께 보여주고 그렇지 않다면 블로그 메인으로 이동하는 버튼만 보여줍니다.

이렇게 HTTP 상태 코드에 따른 오류 화면을 출력해보았습니다.
HTTP 상태 코드는 종류가 여러가지이지만 제 경우에는 발생할 여지가 많은 몇 가지만 추가해두었습니다.
추가된 코드 이외의 문제는 문제 발생 시 추가할 생각입니다.

수정된 코드는 https://github.com/kurien92/kreBlog/commit/a945fc558892f27c09dc40dead74818f27c92b1c 와
https://github.com/kurien92/kreBlog/commit/d4e3a0d01c7954cef22dba3998a9e457d38ac4a9에 나누어져 올려져 있으며
전체적인 진행사항은 https://github.com/kurien92/kreBlog에서 확인하실 수 있습니다.

Comments