How to access static resources when mapping a global front controller servlet on /*

Viewed 116723

I've mapped the Spring MVC dispatcher as a global front controller servlet on /*.

<servlet>       
  <servlet-name>home</servlet-name>         
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>     
</servlet>  
<servlet-mapping>       
  <servlet-name>home</servlet-name>         
  <url-pattern>/*</url-pattern>     
</servlet-mapping>

However, this mapping stops the access to static files like CSS, JS, images etc which are all in the /res/ folder.

How can I access them anyway?

19 Answers

With Spring 3.0.4.RELEASE and higher you can use

<mvc:resources mapping="/resources/**" location="/public-resources/"/>

As seen in Spring Reference.

If you use Tomcat, you can map resources to the default servlet:

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

and access your resources with url http://{context path}/static/res/...

Also works with Jetty, not sure about other servlet containers.

I've run into this also and never found a great solution. I ended up mapping my servlet one level higher in the URL hierarchy:

<servlet-mapping>       
  <servlet-name>home</servlet-name>             
  <url-pattern>/app/*</url-pattern>     
</servlet-mapping>

And now everything at the base context (and in your /res directory) can be served up by your container.

The reason for the collision seems to be because, by default, the context root, "/", is to be handled by org.apache.catalina.servlets.DefaultServlet. This servlet is intended to handle requests for static resources.

If you decide to bump it out of the way with your own servlet, with the intent of handling dynamic requests, that top-level servlet must also carry out any tasks accomplished by catalina's original "DefaultServlet" handler.

If you read through the tomcat docs, they make mention that True Apache (httpd) is better than Apache Tomcat for handling static content, since it is purpose built to do just that. My guess is because Tomcat by default uses org.apache.catalina.servlets.DefaultServlet to handle static requests. Since it's all wrapped up in a JVM, and Tomcat is intended to as a Servlet/JSP container, they probably didn't write that class as a super-optimized static content handler. It's there. It gets the job done. Good enough.

But that's the thing that handles static content and it lives at "/". So if you put anything else there, and that thing doesn't handle static requests, WHOOPS, there goes your static resources.

I've been searching high and low for the same answer and the answer I'm getting everywhere is "if you don't want it to do that, don't do that".

So long story short, your configuration is displacing the default static resource handler with something that isn't a static resource handler at all. You'll need to try a different configuration to get the results you're looking for (as will I).

I'd recommend trying to use a Filter instead of a default servlet whenever possible.

Other two possibilities:

Write a FileServlet yourself. You'll find plenty examples, it should just open the file by URL and write its contents into output stream. Then, use it to serve static file request.

Instantiate a FileServlet class used by Google App Engine and call service(request, response) on that FileServlet when you need to serve the static file at a given URL.

You can map /res/* to YourFileServlet or whatever to exclude it from DispatcherServlets' handling, or call it directly from DispatcherServlet.

And, I have to ask, what does Spring documentation say about this collision? I've never used it.

With regard to Tomcat, a lot depends on the particular version. There was a bug fix https://bz.apache.org/bugzilla/show_bug.cgi?id=50026 which means the servlet-mapping (other than for '/') for the default servlet behaves differently in Tomcat 6.0.29 (and earlier) compared with later versions.

In section "12.2 Specification of Mappings" of the Servlet Specification, it says:

A string containing only the ’/’ character indicates the "default" servlet of the application.

So in theory, you could make your Servlet mapped to /* do:

getServletContext().getNamedDispatcher("/").forward(req,res);

... if you didn't want to handle it yourself.

However, in practice, it doesn't work.

In both Tomcat and Jetty, the call to getServletContext().getNamedDispatcher('/') returns null if there is a servlet mapped to '/*'

Related