Sebastian Kuligowski's Home Page

REST style urls and url mapping for static content (Apache Tomcat)

August 27th, 2009

This short article describes how to create web.xml url mappings for static content when REST style of urls is used. Missing extensions at the end of URIs force to use path-prefix matching rules which cause real problems.

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)"

Page generated in 0.007s

Sebastian Kuligowski's Home Page

Copyright © Krak√≥w, 2008 All Rights Reserved