본문 바로가기
프로그래밍

web.xml url-pattern / 와 /* 의 차이점

by 탁구치는 개발자 2012. 12. 6.

[*.do에서 /* 로 바꾸게 된 이유]

스프링 3.1 샘플 프로젝트의 web.xml(DD) 구성 시 url-pattern을 *.do와 같이 설정하였다.
이유는 단순하다.

프로젝트를 진행할 때 항상 *.do를 사용했기 때문이다.


하지만 REST 방식의 웹 어플리케이션을 구성하기 위해서는 다음과 같은 URL 형식을 제공해야 하는데 현재는 *.do와 같이 되어 있기 때문에 pattern을 /* 와 같이 변경해야만 했다.

http://localhost:8080/user/list



[현상]

이제 내가 만들어 놓은 프로젝트는 REST 방식을 지원하는 spring web application이 되었다.

허나 controller mapping url을 호출하면 404 에러가 발생하면서 아래와 같은 로그가 찍힌다.

No mapping found for HTTP request with URI [/WEB-INF/view/common/layout/default/layout.jsp] in 

DispatcherServlet with name 'dispatcher'




[원인]
controller mapping url을 못 찾아서 발생하는 404가 아닌 /WEB-INF/view/common/layout/default/layout.jsp 의 매핑 URL을 찾을 수 없어서 발생하는 오류였다.

이 오류가 발생하는 원인을 알기 위해서는 서블릿 컨테이너와 웹 어플리케이션간의 연동 방법을 알아야 한다.
일단 url-pattern에 등록할 수 있는 URL은 다음과 같다.
"/"로 시작하고 "/*"로 끝나는 패턴은 path로 인식
"*."으로 시작하는 경우 확장자 매칭
"/"만 정의한 경우 디폴트 서블릿 의미
그 외의 경우 동치 매칭

위의 패턴 매칭에 존재하지는 않지만 /*는 요청 받는 모든 URL을 처리한다는 의미다. (아래와 같은 유형의 패턴 모두)
/user/list
/user/list.do
/user/userList.jsp
/img/test.png

결국 모든 요청을 DispatcherServlet에서 처리하겠다고 지정했으니 jsp에 대한 호출도 DispatcherServlet이 처리를 하려고 했던 것이다.
이로 인하여 jsp에 해당하는 mapping url을 찾을 수 없어 HTTP 404 오류가 발생하게 된 것이다.


[해결 방안]
그럼 해결 방법은 뭘까?
결론적으로 말하자면 url-pattern을 "/" 로 지정하면 된다.
해결 방법은 간단하지만 왜 이렇게 설정하면 잘 되는지 궁금해 졌다.
위에서 언급했지만 "/"로 정의한 경우 디폴트 서블릿을 의미한다고 하였다.

이게 뭔 말인가?
이 말의 의미를 찾아 보니 디폴트 서블릿은 서블릿 매핑 URL에 걸리지 않는 요청들을 처리한다고 한다.

또 의문이 생긴다.
난 서블릿 매핑을 "/" 와 같이 한 개만 만들었는데...결국 거르는 작업 없이 내가 만들어 놓은 서블릿 매핑에 모두 걸리는 것 아닌가?
그리고 디폴트 서블릿은 뭔가?

위의 의문들을 해결하기 위해서 이제 tomcat 서블릿 컨테이너에 대해서 언급할 때가 왔다.
${TOMCAT_HOME}/conf/web.xml 파일을 열어 보면 다음과 같이 세 개의 서블릿 매핑이 존재한다.
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>

*.jsp, *.jspx와 같은 url 패턴은 JspServlet이 처리하고, DefaultServlet은 spring Controller mapping과 jsp 패턴에 걸리지 않는 요청 들을 처리한다는 결론이 나온다.
즉, DefaultServlet은 png, jpg, js, html등 정적인 content를 처리한다는 말이다.

정리하면 
http://localhost:8080/user/userList.jsp <- JspServlet 요청 처리
http://localhost:8080/img/button.jpg <- DefaultServlet 요청 처리
http://localhost:8080/user/list <- DispatcherServlet 요청 처리


[스프링에서 정적 content 호출 시 404 에러 발생]
문제가 생겼다.
http://localhost:8080/user/list 는 정상적으로 동작하는데 
http://localhost:8080/img/button.jpg 로 호출 할 때 404 에러가 발생한다.

DispatcherServlet with name 'dispatcher' processing GET request for [/img/button.jpg]

로그를 보면 *.jpg 호출 시 DefaultServlet이 처리하는 것이 아닌 DispatcherServlet이 처리를 하게 되는 것을 볼 수 있다.
당연히 DispatcherServlet이 처리를 하게 되면 controller mapping URL이 존재하지 않으니 404 오류가 발생한다.

DispatcherServlet은 url-pattern을 "/" 와 같이 설정하게 되면서 tomcat의 server.xml에 정의되어 있는 url-pattern "/"을 무시하기 때문이다.
결국 DispatcherServlet url-pattern을 재정의하게 되어서 DefaultServlet은 더이상 동작할 수 없게 된 것이다.

스프링에서는 이를 해결하기 위해서 <mvc:default-servlet-handler /> 설정을 지원한다.

<mvc:default-servlet-handler /> 설정은 내부적으로 DefaultServletHttpRequestHandler가 담당하게 되고, 이 핸들러(컨트롤러)는 /**로 매핑되어 있다고 한다. (아래 토비님 글 참고)

DefaultServletHttpRequestHandler가 하는 역할은 DispatcherServlet이 처리 못하는 매핑 url을 DefaultServlet으로 넘기는 것이다.

드디어 정리 끝...
이라고 생각하면 오산.
<mvc:default-servlet-handler /> 설정을 추가하니 http://localhost:8080/user/list 호출 시 404에러가 발생한다.
로그를 확인해 보니 이상한 부분이 있었다. 
아래와 같이 SimpleUrlHandlerMapping에서 처리를 하고 있는 것이다.
SimpleUrlHandlerMapping - Matching patterns for request [/user/list] are [/**]

디폴트 핸들러 매핑인 DefaultAnnotationHandlerMapping이 처리해야 하는데 왜 SimpleUrlHandlerMapping이 처리를 하게 된 것일까?
이유는 두 개 이상의 핸들러 매핑이 등록되었을 경우에는 디폴트 전략이 무시되기 때문이다.

결국 <mvc:annotation-driven /> 설정을 통해 DefaultAnnotationHandlerMapping이 자동 등록되게 설정하였고, 이로써 모든 요청이 정상적으로 동작하였다.

단지 url-pattern만 정리하려고 했는데 꼬리에 꼬리를 물어서 스프링까지 설정까지 오게 되었다.

지금까지 url-pattern에 대해서 대수롭지 않게 생각했는데 까면 깔수록 공부해야 할 것들이 산더미이다.
그래도 오늘 하루 내가 뭔가를 배울 수 있었다는 것에 보람을 느낀다.