민서네집

Spring 에서 404 error page를 Thymeleaf로 처리하기. 본문

Spring

Spring 에서 404 error page를 Thymeleaf로 처리하기.

브라이언7 2015. 3. 24. 09:19

HTTP 404 error 페이지를 Custom하게 바꾸고 싶었다.


다음과 같이 spring에서 SimpleMappingExceptionResolver 로 Exception을 처리하도록 해도 404 에러는 이것을 통해 처리되지 않는다.


   <bean id="exceptionMapping"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"
p:defaultErrorView="exception/springError">
<property name="exceptionMappings">
<props>
<prop key="DataAccessException">exception/springError</prop>
<prop key="TypeMismatchException">exception/springError</prop>
<prop key="DefaultHandlerExceptionResolver">exception/springError</prop>
<prop key="MissingServletRequestParameterException">exception/springError</prop>
</props>
</property>
</bean>

springError.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Error Page</title>
<link rel="stylesheet" th:href="@{/css/main.css}" type="text/css" />
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
    <body class="error">
        <h1 th:text="${exception}">Error Message</h1>
        <a href="index" th:href="@{/index}">Back to Main Page</a>
    </body>
</html>

* Server에서 발생하는 Error 는 Spring Framework 가 SimpleMappingExceptionResolver 를 처리하면서 exception 이라는 이름으로 Exception 객체를 넣어준다.

404 에러 페이지를 Customize 하기 위해서는 web.xml 을 아래와 같이 설정하면 되지만, 이렇게 하면 Thymeleaf 라이브러리로 처리되지는 않는다.


 <error-page>

  <error-code>404</error-code>

  <location>/WEB-INF/views/exception/resourceNotFound.html</location>

</error-page>



404 에러를 Thymeleaf 라이브러리로 처리하기 위해서는, web.xml을 다음과 같이 설정하고,

<error-page>
 <error-code>404</error-code>
 <location>/exception/resourceNotFound.html</location>
</error-page>


java 파일에서 web.xml에서 설정한 location 값으로 Controller에 request mapping을 다음과 같이 설정해 준다.


/**

 * Exception Homepage

 */

@Controller

public class ExceptionController {

    /**

     * web.xml 에서 정의한 404 error 페이지의 Handler.

     * 이렇게 하면 Thymeleaf Library를 통해서 처리된다.

     * @return

     */

    @RequestMapping("/exception/resourceNotFound.html")

    @ResponseStatus(value=HttpStatus.NOT_FOUND)

    public String resourceNotFound() {

    return "exception/resourceNotFound";

    }

}


Controller 에 다음과 같이 Parameter를 받는 Request Mapping Hander를 추가했는데, 역시나 spring 의 SimpleMappingExceptionResolver 에서는 처리하지 못했다. (Parameter 로 "number" 를 넘겨줘야 하는데, request에서 이 파라미터를 넘겨주지 않을 경우 HTTP 400 Error가 발생한다.)


    @RequestMapping("/test")

    public String test(@RequestParam(value="number") Long number) {

    System.out.println("test..." + number);

    return "exception/error";

    }




HTTP Status 400번 Error Page도  Customize 하기 위해서는 


web.xml 에서


    <!-- Error pages -->

<error-page>

 <error-code>404</error-code>

 <location>/exception/resourceNotFound.html</location>

</error-page>

<error-page>

 <location>/exception/error.html</location>

</error-page>


위와 같이 404 error-code 의 <error-page> 다음에 <error-page> 태그를 추가해야 한다.

그런데 Controller 안에서 에러 코드를 알 수 있는 방법이 없을까?

에러 코드마다 View를 따로 만들어 줄 것이 아니라면 Controller 안에서 에러 코드를 알 수 있는 방법이 있어야 할 것 같다.


    @RequestMapping("/exception/error.html")

    public String error(Model model, HttpServletResponse response) {

model.addAttribute("response", response );

     return "exception/error";

    }


error.html


<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

<title>Error Page</title>

<link rel="stylesheet" th:href="@{/css/main.css}" type="text/css" />

<!--[if lt IE 9]>

<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>

<![endif]-->

</head>

    <body class="error">

        <h1 th:text="${'Error Code: ' + response.status}">Error Code: 400</h1>

        <a href="index" th:href="@{/index}">Back to Main Page</a>

    </body>

</html>


Controller의 error() 함수 안에서 debugging을 해 봤는데, response 안에 error message가 있기는 하지만, public field 도 아니고, getter 함수도 없어서 reflection 해서 꺼내야 한다.

다음 코드와 같이 request의 속성에서 에러 코드와 에러 메시지를 뽑아내서 model에 넣어서 view에서 출력할 수 있다.

    /** Error page. */
    @RequestMapping("/error")
    public String error(HttpServletRequest request, Model model) {
        model.addAttribute("errorCode", "Error " + request.getAttribute("javax.servlet.error.status_code"));
        Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception");
        StringBuilder errorMessage = new StringBuilder();
        errorMessage.append("<ul>");
        while (throwable != null) {
            errorMessage.append("<li>").append(escapeTags(throwable.getMessage())).append("</li>");
            throwable = throwable.getCause();
        }
        errorMessage.append("</ul>");
        model.addAttribute("errorMessage", errorMessage.toString());
        return "error";
    }

    /** Substitute 'less than' and 'greater than' symbols by its HTML entities. */
    private String escapeTags(String text) {
        if (text == null) {
            return null;
        }
        return text.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
    }

error.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Error page</title>
        <meta charset="utf-8" />
        <link rel="stylesheet" href="css/main.css" th:href="@{/css/main.css}" />
    </head>
    <body>
        <h1 th:text="${errorCode}">404</h1>
        <p th:utext="${errorMessage}">Error java.lang.NullPointerException</p>
        <a href="index" th:href="@{/index}">Back to Home Page</a>
    </body>
</html>

web.xml

  <error-page>
 <location>/error</location>
</error-page>

404 에러의 경우, errorMessage 에 아무 것도 출력 안되기 때문에 


    <!-- Error pages -->

<error-page>

 <error-code>404</error-code>

 <location>/exception/resourceNotFound.html</location>

</error-page>

<error-page>

 <location>/error</location>

</error-page>


이렇게 에러 코드를 404로 분리해 놓는게 좋을 것 같다.

400번 에러도 아무 것도 찍히지 않는 것은 마찬가지다.

ServletRequest Interface의 getAttributeNames() 메서드로 loop 돌면서 에러 메시지가 담겨 있는 Attribute가 있는지 찾아볼 수도 있겠다.


일부러 Runtime Exception을 발생시켜 보면... 아래처럼 보인다.


    /** Simulation of an exception. */

    @RequestMapping("/simulateError")

    public void simulateError() {

        throw new RuntimeException("This is a simulated error message");

    }




Comments