Servlet mapping bases on four rules:
-
The exact match of the path. This rule has the highest priority.
-
Recursive match of the longest path-prefix. The /part1/part2/* url-mapping has higher priority than /part1/*.
-
If neither of the previous two rules result in a servlet match, the extension match is applied (eg. *.ftl)
-
If no matching rule is selected, the container will attempt to serve content for the resource placed in current context. If a "default" servlet is defined for the application, it will be used.
So, where's the problem?
Described servlet mapping rules are limited to hierarchical mappings, so if you use REST style urls, you will probably face the obstacle: how to write url-mapping for static content as css, js files. Suppose you have following configuration:
<filter> <filter-name>security-filter</filter-name> <filter-class>pl.kuligowski.example.SecurityFilter</filter-class> </filter> <filter-mapping> <filter-name>security-filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>servlet1</servlet-name> <servlet-class>pl.kuligowski.example.Servlet1</servlet-class> </servlet> <servlet-mapping> <servlet-name>servlet1</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
Servlet1 class is the Front Controller pattern and is used to dispatch all requests. There is also the security filter applied to check if user is logged or not. Using /* url mapping pattern we say that all requests should be matched to the security-filter and Servlet1 class - also static content as *.css, *.js and graphic files.
Default Servlet vain attempt
The first thought is to place our static content in "static" folder of our web application and use DefaultServlet which could be mapped to longer path-prefix than servlet1: for example /static/*. Let's try:
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/static/*</url-pattern> </servlet-mapping>
At first glance everything should be OK: /static/* path-prefix has higher precedence than /* url-mapping of servlet1. But it is not. Unfortunately after looking into DefaultServlet source I found that DefaultServlet takes only pathInfo part of requested URL, so if your request is /static/styles.css, container translates it into /styles.css. Servlet part is omitted by the DefaultServlet. If you want to access such css file you should use /static/static/styles.css request url.
The security filter is fired on every request, so *.css, *.js and graphics files will cause unnecessary filter calls.
The solution
The simple solution of our problem is to write DefaultFilter class and place it at the beginning of web.xml file. This filter will forward all static content calls to DefaultServlet. Let's take a look at the proposed DefaultFilter class:
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class DefaultFilter implements Filter {
private RequestDispatcher defaultRequestDispatcher;
@Override
public void destroy() {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
defaultRequestDispatcher.forward(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.defaultRequestDispatcher =
filterConfig.getServletContext().getNamedDispatcher("default");
}
}
Configuration of our web.xml file is as following:
<filter> <filter-name>default</filter-name> <servlet-name>default</servlet-name> <filter-class>pl.kuligowski.example.DefaultFilter</filter-class> </filter> <filter-mapping> <filter-name>default</filter-name> <url-pattern>/static/*</url-pattern> <url-pattern>*.ico</url-pattern> </filter-mapping> <filter> <filter-name>security-filter</filter-name> <filter-class>pl.kuligowski.example.SecurityFilter</filter-class> </filter> <filter-mapping> <filter-name>security-filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>servlet1</servlet-name> <servlet-class>pl.kuligowski.example.Servlet1</servlet-class> </servlet> <servlet-mapping> <servlet-name>servlet1</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
Only static content calls are matched to DefaultFilter, which simply breaks the filter chain and forwards the request to the DefaultServlet. Other calls ignore DefaultFilter and match to security filter and servlet1. Note that you can place in DefaultFilter as many url-mappings as you want, so you can create separate directories for css, img and js files. You should add *.ico extension to have favicon.ico properly handled.
3 comments to "REST style urls and url mapping for static content (Apache Tomcat)"
Tom on October 4th, 2009, 19:09
Great tut, it helps me a lot! Thanks!
Anderson Fabiano on July 2nd, 2010, 01:38
Absolutely brilliant!
Saved my day!
Cheers,
- Anderson
beniji on May 26th, 2011, 16:29
Or, without having to write any custom code, you can just:
1) list your extensions (use *.jpg, *.gif etc) and map them to the default servlet.
2) change the "/*" to be "/"
Because these extension mappings are more specific than the / match they will take priority and be sent to the default servlet.
This is much easier than implementing a custom filter. I agree there are times where you may really need your path based approach (with Tomcat at least) e.g. for unknown static extensions. This was fixed in Tomcat 6.0.30 onwards but seems broken again in version 7.