Announcement Announcement Module
Collapse
No announcement yet.
Request method 'HEAD' not supported Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Request method 'HEAD' not supported

    G'day everyone,

    I'm working on a Spring 3.0 project, and we want to update our Selenium-based test suite. Unlike the version we're currently using, Selenium 1.0.3 sends HEAD requests, which causes the following exception each time, and for the client to receive an HTTP 500 response:

    Code:
    Oct 26, 2010 09:59:53 org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/].[SpringMVC] invoke
    ERROR: Servlet.service() for servlet SpringMVC threw exception
    org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'HEAD' not supported
    This occurs even when I add HEAD to the supported request methods. The code looks like this:

    Code:
        @RequestMapping(value = {URIs.STRING_CONSTANT_1, URIs.STRING_CONSTANT_2},
                        method = {RequestMethod.GET, RequestMethod.HEAD})
        public String getPageWeWant(ModelMap model) {
            debug(this, "log statement to prove I'm calling the right thing");
    When I curl the URL, it works, and that debug message shows up in the logs. But when I do a curl -I, which sends a HEAD request instead, the exception above occurs.

    What I really want is for HEAD requests to just work wherever GET requests do. I agree with this poster on StackOverflow: explicitly adding HEAD support to our 277 RequestMappings isn't an elegant solution, and right now it seems that wouldn't even work. This thread in this forum also discusses this exception, but the post is quite old; the solution of removing the doHead() method from FrameworkServlet doesn't seem to apply anymore, since that method doesn't exist in Spring 3.0.5.

    Any clues as to why I'm seeing this exception, and any better ideas on getting HEAD requests to just work across the board?

    Thanks,
    Michael Scheper.

  • #2
    There are probably a few ways to address this, but if you really want all mappings that support GET requests to support HEAD requests as well, then here's one solution...

    I'm drawing some inspiration from Spring's own HiddenHttpMethodFilter. In case you're unaware of its function, this filter exists to handle the "overloaded POST" pattern. Since most browsers don't natively support PUT and DELETE requests, it's common for MVC frameworks to use POSTs in place of PUT or DELETE with a hidden _method field added to the form. This hidden field specifies what the method is really supposed to be. HiddenHttpMethodFilter is always on the lookout for POST requests with a hidden _method parameter, and when it sees such a request, it takes action.

    Of course HttpServletRequest objects are immutable, so there's not setMethod() method that the filter can call. What it has to do instead is apply the decorator pattern and wrap the HttpServletRequest with a decorator that implements the same interface and will intercept calls to the getMethod() method while delegating all other method calls to the target object.

    You could use this same approach. The filter below should turn incoming HEAD requests into GET requests. Just remember that once a HEAD request makes it through this filter, it looks like a legitimate GET request from there on out.

    Code:
    public class HeadToGetFilter extends OncePerRequestFilter {
    
      @Override
      protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if ("HEAD".equals(request.getMethod()))
          filterChain.doFilter(new HttpMethodRequestWrapper("GET", request), response);
        else
          filterChain.doFilter(request, response);
      }
    	
      private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
    
        private final String method;
    
        public HttpMethodRequestWrapper(String method, HttpServletRequest request) {
          super(request);
          this.method = method;
        }
    
        @Override
        public String getMethod() {
          return this.method;
        }
    		
      }
    	
    }
    Hope this helps.

    Comment


    • #3
      It worked indeed, krancour. Thank you. I had to do a bit more mucking around to work out how to hook it up; I ended up using the joy that is DelegatingFilterProxy for that.

      The bad news is that the latest Selenium doesn't solve the problems we're having doing automated testing against IE7, but I'll whinge about that in another forum.

      Thanks,
      MS.

      Comment


      • #4
        Kent, that's a great solution, but in order to adhere to RFC 2616 shouldn't you also prevent the response from being written to the client, as the StackOverflow guys details?

        "The HEAD method is identical to GET except that the server MUST NOT
        return a message-body in the response. "


        What do you think about:

        Code:
        if ("HEAD".equals(request.getMethod())) {
            filterChain.doFilter(new HttpMethodRequestWrapper("GET", request),
        			 new NullHttpServletResponseWrapper(response));
        } else {
            filterChain.doFilter(request, response);
        }

        Code:
        private static class NullHttpServletResponseWrapper extends HttpServletResponseWrapper {
        
        	private static final PrintWriter writer = new PrintWriter(new NullOutputStream());
        
        	private static final ServletOutputStream out = new ServletOutputStream() {
        	    @Override
        	    public void write(int b) throws IOException {
        		//noop
        	    }
        	    @Override
        	    public void write(byte[] b) throws IOException {
        		//noop
        	    }
        	    @Override
        	    public void write(byte[] b, int off, int len) throws IOException {
        		//noop
        	    }
        	    @Override
        	    public void flush() throws IOException {
        		//noop
        	    }
        	};
        
        	private NullHttpServletResponseWrapper(HttpServletResponse response) {
        	    super(response);
        	}
        
        	@Override
        	public ServletOutputStream getOutputStream() throws IOException {
        	    return out;
        	}
        
        	@Override
        	public PrintWriter getWriter() throws IOException {
        	    return writer;
        	}
        }

        Comment


        • #5
          Is it really necessary to write custom code to suppress writing a response in the case of a HEAD request?

          My gut feeling is that Spring MVC bypasses view-resolution for HEAD requests, but I'm not positive.

          It's worth looking into.

          Also, most HTTP clients may not care if the response from a HEAD request has a body (even though it's not supposed to).

          Comment


          • #6
            Is it necessary? Don't know. Don't know what these bots or browsers are doing issuing the request. But, since I'm already writing custom code to handle the HEAD request in the first place, might as well adhere to the spec and emit no response. (Spring will happily return the full response.)

            Thanks for the filter.

            Comment


            • #7
              Originally posted by krancour View Post
              Is it really necessary to write custom code to suppress writing a response in the case of a HEAD request?

              My gut feeling is that Spring MVC bypasses view-resolution for HEAD requests, but I'm not positive.

              It's worth looking into.

              Also, most HTTP clients may not care if the response from a HEAD request has a body (even though it's not supposed to).
              1) It appears it is needed (otherwise I'm getting a 405, at least with Tomcat). This is bad because people are lazy and by default will do the wrong thing.

              2) Sending a response body with HEAD is a severe bug. The client won't expect the response body, and thus will not read it. This will mess up the connection.

              I think this needs a JIRA issue.

              Comment

              Working...
              X