Clean URLs for Static Content in Java

Sometimes a client requests development of a web site consisting primarily of static content such as a marketing website and wants clean URLs for the purposes of search engine optimization.  Clean URLs consist of only semantic information and are devoid of implementation details like .jsp file extensions and query parameters, for example http://www.anexinet.com/company/overview instead of http://www.anexinet.com/company/overview.jsp

For dynamic content, this is typically done by using an MVC approach such as Spring Framework which provides immense flexibility in mapping requested URLs to controllers with annotations.  The question here is how do you do this for something that's mostly static content and therefore not designed using an MVC framework?  There are several ways to accomplish this.

Approach 1 - Servlet Mapping

There is a good chance that all of your static content is in .jsp files (just using <jsp:include> for your common header and footer, but otherwise completely static).  If this is the case, you can simply add two entries in your deployment descriptor (web.xml): one to define a named servlet and another to map the clean URL to the servlet.  For example,


       <servlet>
              <servlet-name>companyOverview</servlet-name>
              <jsp-file>companyOverview.jsp</jsp-file>
       </servlet>
      
       <servlet-mapping>
              <servlet-name>companyOverview</servlet-name>
              <url-pattern>/company/overview</url-pattern>
       </servlet-mapping>



The downside of this approach is that you have to create separate entries for each page.  That starts getting old after a few pages.

Approach 2 - UrlRewriteFilter

Another option is to use the UrlRewriteFilter from tuckey.org.  That will give you functionality similar to Apache's mod_rewrite module.  Basically, you add some content to web.xml to define the UrlRewriteFilter as a servlet filter in your application, and then you create a urlrewrite.xml file to define the URL rewriting rules.

But if you're not familiar with mod_rewrite, or don't want to pull in yet another dependency to your project, or if you'd just rather define your rewrite rules in code rather than XML, it is not at all hard to roll your own servlet filter to perform this function.

Approach 3 - Roll Your Own Simple Filter 

A servlet filter is a plugin point that allows you to modify an HTTP request, response, or forward a request to a different URI within your application.  It is the latter kind of functionality we will use to get clean URLs for static content.  (Here we start talking about URIs instead of URLs because the relevant Java servlet APIs deal with relative URIs not the full URL).  Basically what we'll do is look at any incoming HTTP request and, if it is a clean URI, determine the real URI and forward the request there.  Any other requests, will simply be processed normally, for example we don't bother with with a clean URI for .css or .js files.  Here is the full example:

package com.anexinet;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

@WebFilter(urlPatterns = "/*")
public class UriRewriteFilter implements Filter {

    public UriRewriteFilter() { }
    public void init(FilterConfig fConfig) throws ServletException { }
    public void destroy() { }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            filterHttpRequest(request, response, chain);
        } else {
            chain.doFilter(request, response);
        }
    }

    private void filterHttpRequest(ServletRequest request, ServletResponse response,
                                   FilterChain chain)
            throws ServletException, IOException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestUri = httpRequest.getRequestURI();
        if (!requestUri.endsWith(".jsp") && !requestUri.endsWith(".css")
            && !requestUri.endsWith(".js") && !requestUri.endsWith("/")) {

            String newUri = requestUri.replaceAll(httpRequest.getContextPath(), ""
                + ".jsp";
            httpRequest.getRequestDispatcher(newUri).forward(httpRequest, response);
        } else {
            chain.doFilter(request, response);
        }
    }
}

The magic happens in filterHttpRequest().  We check for a clean URI and determine the real URI by taking off the context path portion of it, adding .jsp, and forwarding that URI to the request dispatcher.  We have to take off the context path because the request dispatcher wants the URI relative to the context path while the request URI is relative to the host.  You may want to log httpRequest.getRequestURI(), httpRequest.getContextPath(), and newUri until you get the hang of it.


Labels: ,