views:

3752

answers:

7

If I map my spring application to process all incoming requests ('/*'), then requests for static content return 404's. For example, a request for "myhost.com/css/global.css" would return a 404, even though the resource exists as Spring intercepts the request.

The alternative is to map SpringMVC to a subdirectory (for example '/home/'), but in this case, you must pass this directory in all links within the application. Is there a way to map SpringMVC to '/' and exclude a set of directories from processing?

My current web.xml configuration is:

<servlet>
    <servlet-name>springApp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>

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

Idealy I would like to have the mapping be something like the following:

 <servlet-mapping>
    <servlet-name>springApp</servlet-name>
    <url-pattern>/*</url-pattern>
    <exclude>/css/*,/js/*</exclude>
 </servlet-mapping>

Is this type of thing possible?

A: 

What are you using to serve your static images? If it's Apache then you could configure Apache to not pass css/js requests to your app server.

If you are using Tomcat you'd put something like this in your httpd.conf:

JkUnMount /*.css  webapp

Where 'webapp' is the entry from your workers.properties.

Sorry I can't give you a pure Spring solution, but this is how I do it.

Darren Greaves
This is a great tip, but I am looking for something portable; So when the assembled WAR is placed into a servlet container, it will work without external configuration being necessary.
Rich Kroll
A: 

One way to do it would be with Filters. You'd have to write a little bit of custom code but it's not bad. Here's an example if you don't want to pass *.css or *.js files to your Spring servlet:

web.xml:

<filter-mapping>
    <filter-name>fileTypeFilter</filter-name>
    <filter-class>foo.FileTypeFilter</filter-class>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Java class:

public class FileTypeFilter implements Filter {
     public void init(FilterConfig conf) {
         // init logic here
     }

     public void destroy() {
        // release resources here
     }

     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
          if(shouldExclude(req)) {
              chain.doFilter(req, res);
              //some logic so the request doesnt go to the servlet

              //maybe you could just forward
              //the request directly to the file getting accessed.  not sure if that would work
          }

          //file should be passed to the servlet; you can do some logic here
          //if you want         
     }
     private boolean shouldExclude(ServletRequest req) {
         if(req instanceof HttpServletRequest) {
             HttpServletRequest hreq = (HttpServletRequest) req;
             return (hreq.getRequestURI().endsWith(".css") ||
                     hreq.getRequestURI().endsWith(".js"));
         }
         return false;
    }
}

I haven't tested this, but I think it will work.

EDIT: There's isn't any exclude functionality in the servlet spec. I don't think there is a good way to do this within Spring, but it essentially achieves the same thing in your post.

EDIT 2: If you want to be able to easily change what gets filtered, you could just use Spring to inject something into the Filter at runtime.

EDIT 3: I just realized if you forward directly to the file, it'll do the filter again and you'll get caught in an infinite loop. There might be another way to do this with filters, but I'm honestly not sure what it is.

Alex Beardsley
A: 

Do you have a consistent extension(s) for the requests you want processed by the Spring dispatcher (I believe most of the Spring examples use a *.htm)? In that case, you could map to the extensions you wish to have processed which would bypass your css and js files.

Otherwise I'd agree with Nalandial, the Filter approach is probably the best work around at this point.

jridley
I'm using restful urls, so there is no extension for anything in the application, so suffix mapping won't work for me.
Rich Kroll
+4  A: 

If you want to do this with Spring only, it's possible but a bit messy:

  1. You'll either need to use a SimpleUrlHandlerMapping for which you can explicitly specify URL patterns which should be mapped to controllers OR extend it to support "ignore" URLs like "css/**".
  2. You'll need to write your own HttpRequestHandler implementation that would basically consist of "getServletContext().getRequestDsipatcher().include()" call to return the requested resource as is.
  3. You'll have to register that handler as defaultHandler for the above SimpleUrlHandlerMapping.

Once all that is done, all requests that can't be mapped to your controllers will be forwarded to your HttpRequestHandler and served "as is".

ChssPly76
I'm currently using DefaultAnnotationHandlerMapping to do annotation based mapping of controllers. Based upon your suggestion, I am going to extend it and add support for path based exclusions. Thanks for pointing me in the right direction.
Rich Kroll
Awesome answer!
Alex Beardsley
+2  A: 

I solved by serving static content through the 'default' servlet, that just serve the content to the client. So my web.xml looks like this:

<servlet>
    <servlet-name>MyApp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>MyApp</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping> <!-- The 'dynamic' content -->

<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>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
</servlet-mapping> <!-- The 'static' content -->

Hope this helps.

namero999
I came across this solution as well - my only problems with it are a: I don't believe it's portable and b: I had trouble mapping the "/" path via annotations. If these two could be solved, this would be the perfect solution for me.
Rich Kroll
Sorry if i'm late answering, I was out of town. Yes probably you'r right about the portability issue. I don't have this problem because I'm stuck on tomcat. Probably u can include in the war the DefaultServlet. About the "/" path via annotation, in my configuration (method-level annotated IndexController) it works with @RequestMapping(value = "/"). With class-level annotated controllers sometimes I needed to annotate this way @RequestMapping(value = "")
namero999
A: 

What is default servlet name in the above web.xml. Can you post the complete web.xml

I believe the response you are referring to is only targeting tomcat, which has a default servlet named 'default', which by default maps to '/'. Once you override the default servlet, you can remap it as namero999 has done to respond to other url patterns.
Rich Kroll
A: 

It's cleaner to use UrlRewriteFilter to redirect the request to your servlet, here an example of urlrewrite.xml

<urlrewrite>
    <rule>
        <from>^/img/(.*)$</from>
        <to>/img/$1</to>
    </rule>
    <rule>
        <from>^/js/(.*)$</from>
        <to>/js/$1</to>
    </rule>
    <rule>
        <from>^/css/(.*)$</from>
        <to>/css/$1</to>
    </rule>
    <rule>
        <from>^/(.*)$</from>
        <to>/app/$1</to>
    </rule>
    <outbound-rule>
        <from>/app/(.*)$</from>
        <to>/$1</to>
    </outbound-rule>
</urlrewrite>

NOTES:

  • It's important the last <rule> is in the bottom so img, js, css will be caught first
  • The <outbound-rule> is optional and is just to make the existing
    <c:url value="/app/some" /> render /some instead of /app/some
victor hugo