views:

9590

answers:

11

I deploy a webapp on two different containers (Tomcat and Jetty), but their default servlets for serving the static content have a different way of handling the URL structure I want to use (details).

I am therefore looking to include a small servlet in the webapp to serve its own static content (images, CSS, etc.). The servlet should have the following properties:

  • No external dependencies
  • Simple and reliable
  • Support for If-Modified-Since header (i.e. custom getLastModified method)
  • (Optional) support for gzip encoding, etags,...

Is such a servlet available somewhere? The closest I can find is example 4-10 from the servlet book.

Update: The URL structure I want to use - in case you are wondering - is simply:

    <servlet-mapping>
            <servlet-name>main</servlet-name>
            <url-pattern>/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
            <servlet-name>default</servlet-name>
            <url-pattern>/static/*</url-pattern>
    </servlet-mapping>

So all requests should be passed to the main servlet, unless they are for the static path. The problem is that Tomcat's default servlet does not take the ServletPath into account (so it looks for the static files in the main folder), while Jetty does (so it looks in the static folder).

+4  A: 

I had the same problem and I solved it by using the code of the 'default servlet' from the Tomcat codebase.

http://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/catalina/servlets/DefaultServlet.java

The DefaultServlet is the servlet that serves the static resources (jpg,html,css,gif etc) in Tomcat.

This servlet is very efficient and has some the properties you defined above.

I think that this source code, is a good way to start and remove the functionality or depedencies you don't need.

  • References to the org.apache.naming.resources package can be removed or replaced with java.io.File code.
  • References to the org.apache.catalina.util package are propably only utility methods/classes that can be duplicated in your source code.
  • References to the org.apache.catalina.Globals class can be inlined or removed.
Panagiotis Korros
It seems to depend on a lot of stuff from `org.apache.*`. How can you use it with Jetty?
Bruno De Fraine
You are right, this version has too many depedencies to the Tomcat (caand it also supports many things you might not want. I will edit my answer.
Panagiotis Korros
+6  A: 

I ended up rolling my own StaticServlet. It supports If-Modified-Since, gzip encoding and it should be able to serve static files from war-files as well. It is not very difficult code, but it is not entirely trivial either.

The code is available: StaticServlet.java. Feel free to comment.

Update: Khurram asks about the ServletUtils class which is referenced in StaticServlet. It is simply a class with auxiliary methods that I used for my project. The only method you need is coalesce (which is identical to the SQL function COALESCE). This is the code:

public static <T> T coalesce(T...ts) {
    for(T t: ts)
        if(t != null)
            return t;
    return null;
}
Bruno De Fraine
Don't name your inner class Error. That might cause confusion as you can mistake it for java.lang.ErrorAlso, is your web.xml the same ?
Leonel
Thanks for the Error warning. web.xml is the same, with "default" replaced by the name of the StaticServlet.
Bruno De Fraine
A: 

Just confused, where did the ServletUtilities class come from?

Hello Khurram, please see my updated answer about ServletUtils.
Bruno De Fraine
A: 

Use org.mortbay.jetty.handler.ContextHandler. You don't need additional components like StaticServlet.

At the jetty home,

$ cd contexts

$ cp javadoc.xml static.xml

$ vi static.xml

...

<Configure class="org.mortbay.jetty.handler.ContextHandler">
<Set name="contextPath">/static</Set>
<Set name="resourceBase"><SystemProperty name="jetty.home" default="."/>/static/</Set>
<Set name="handler">
  <New class="org.mortbay.jetty.handler.ResourceHandler">
    <Set name="cacheControl">max-age=3600,public</Set>
  </New>
 </Set>
</Configure>

Set the value of contextPath with your URL prefix, and set the value of resourceBase as the file path of the static content.

It worked for me.

yogman
+4  A: 

There is no need for completely custom implementation of the default servlet in this case, you can use this simple servlet to wrap request to the container's implementation:


package com.example;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

public class DefaultWrapperServlet extends HttpServlet
{   
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
     throws ServletException, IOException
    {
     RequestDispatcher rd = getServletContext().getNamedDispatcher("default");

     HttpServletRequest wrapped = new HttpServletRequestWrapper(req) {
      public String getServletPath() { return ""; }
     };

     rd.forward(wrapped, resp);
    }
}
axtavt
A: 

See StaticFile in JSOS: http://www.servletsuite.com/servlets/staticfile.htm

Coldbeans Software
Can you download the source code for that somewhere?
Bruno De Fraine
+2  A: 

I found great tutorial on the web about some workaround. It is simple and efficient, I used it in several projects with REST urls styles approach:

http://www.kuligowski.pl/java/rest-style-urls-and-url-mapping-for-static-content-apache-tomcat,5

Samaritan
+1  A: 

I did this by extending the tomcat DefaultServlet (src) and overriding the getRelativePath() method.

package com.example;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.servlets.DefaultServlet;

public class StaticServlet extends DefaultServlet
{
   protected String pathPrefix = "/static";

   public void init(ServletConfig config) throws ServletException
   {
      super.init(config);

      if (config.getInitParameter("pathPrefix") != null)
      {
      pathPrefix = config.getInitParameter("pathPrefix");
      }
   }

   protected String getRelativePath(HttpServletRequest req)
   {
      return pathPrefix + super.getRelativePath(req);
   }
}

... And here are my servlet mappings

<servlet>
 <servlet-name>StaticServlet</servlet-name>
 <servlet-class>com.example.StaticServlet</servlet-class>
 <init-param>
  <param-name>pathPrefix</param-name>
  <param-value>/static</param-value>
 </init-param>  
</servlet>

<servlet-mapping>
 <servlet-name>StaticServlet</servlet-name>
 <url-pattern>/static/*</url-pattern>
</servlet-mapping>
delux247
+3  A: 

I've had good results with FileServlet, as it supports pretty much all of HTTP (etags, chunking, etc.).

Will Hartung
A: 

To serve all requests from a Spring app as well as /favicon.ico and the JSP files from /WEB-INF/jsp/* that Spring's AbstractUrlBasedView will request you can just remap the jsp servlet and default servlet:

  <servlet>
    <servlet-name>springapp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>/WEB-INF/jsp/*</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/favicon.ico</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>springapp</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

We can't rely on the *.jsp url-pattern on the standard mapping for the jsp servlet because the path pattern '/*' is matched before any extension mapping is checked. Mapping the jsp servlet to a deeper folder means it's matched first. Matching '/favicon.ico' exactly happens before path pattern matching. Deeper path matches will work, or exact matches, but no extension matches can make it past the '/*' path match. Mapping '/' to default servlet doesn't appear to work. You'd think the exact '/' would beat the '/*' path pattern on springapp.

The above filter solution doesn't work for forwarded/included JSP requests from the application. To make it work I had to apply the filter to springapp directly, at which point the url-pattern matching was useless as all requests that go to the application also go to its filters. So I added pattern matching to the filter and then learned about the 'jsp' servlet and saw that it doesn't remove the path prefix like the default servlet does. That solved my problem, which was not exactly the same but common enough.

clish
A: 

I came up with a slightly different solution. It's a bit hackish, but here is the mapping:

<servlet-mapping>   
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
 <servlet-name>default</servlet-name>
    <url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>myAppServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

This basically just maps all content files by extension to the default servlet, and everythign else to "myAppServlet".

It works in both Jetty and Tomcat.

Taylor Gautier