13

I'm hitting this issue regarding JSP not accepting PUT request. So I'm wondering how to fix it. I've read this related question in the stack overflow, but it doesn't explain how to fix it.

HTTP Status 405 - JSPs only permit GET POST or HEAD

Coming from Rails background, I'm trying to make it so I'm using the rails REST style like PUT for updates, and DELETE for deletes of the User resource.

But whenever there's an error in this controller it tries to return the request to the originating JSP but with Tomcat 8.0.9 isn't accepting the request and gives this error: "HTTP Status 405 - JSPs only permit GET POST or HEAD". I've tried disabling readonly in the Tomcat web.xml - it didn't have any effect and I still get the error. I've switched it to POST method and the flow works fine.

Is there a way to maybe force the forward to be POST method while still accepting PUT method for the request?

/**
     * Edit a user account.
     * @return the edit user view
     */
    @RequestMapping(value = {"/update/{userId}"}, method = RequestMethod.PUT)
    public String updateUser(@Valid @ModelAttribute("user") User user, BindingResult result, final RedirectAttributes redirectAttributes)
    {
        logger.debug(user);

        // we check for duplicate email addresses during the update operation.
        List<User> userCheckList = userRepository.findByEmail(user.getEmail());
        if (userCheckList.size() > 0)
        {
            // size of list should only ever be 1
            User userCheck = userCheckList.get(0);
            if (userCheck.getId() != user.getId())
            {
                result.rejectValue("email", "error.user", "An account already exists for this user email address.");
            }
        }


        if (result.hasErrors())
        {
            return "admin.users.edit";
        }

        // we locate the user and add it to the model
        userRepository.save(user);



        // the save operation was successful so we show the user message.
        redirectAttributes.addFlashAttribute("user", user);
        redirectAttributes.addFlashAttribute("message", "Updated successfully");


        String viewName = "redirect:/admin/users";
        logger.debug(viewName);

        return viewName;
    }
2

3 Answers 3

17

The problem is that when you return a view name from your controller method, the Spring DispatcherServlet will do a forward to the given view, preserving the original PUT method.

On attempting to handle this forward, Tomcat will refuse it, with the justification that a PUT to a JSP could be construed to mean "replace this JSP file on the server with the content of this request."

Really you want your controller to handle your PUT requests and then to subsequently forward to your JSPs as GET. Fortunately Servlet 3.0 provides a means to filter purely on the FORWARD dispatcher.

Create a filter:

public class GetMethodConvertingFilter implements Filter {

    @Override
    public void init(FilterConfig config) throws ServletException {
        // do nothing
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {

        chain.doFilter(wrapRequest((HttpServletRequest) request), response);
    }

    @Override
    public void destroy() {
        // do nothing
    }

    private static HttpServletRequestWrapper wrapRequest(HttpServletRequest request) {
        return new HttpServletRequestWrapper(request) {
            @Override
            public String getMethod() {
                return "GET";
            }
        };
    }
}

And wire it into your web.xml thusly:

<filter>
    <filter-name>getMethodConvertingFilter</filter-name>
    <filter-class>my.GetMethodConvertingFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>getMethodConvertingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

This will convert requests to GET on forward only, leaving requests through other dispatchers unchanged, so PUTs will get intercepted by your controllers as normal.

My (possibly incorrect) understanding is that Tomcat 8.0.9 introduced a fix where this is effectively done automatically for the ERROR dispatcher - see the answer in your linked question. But you're not using the container's error handling mechanism to render your error page, you're using Spring MVC to manually forward to the view, hence why you need to do this. Personally I encountered this issue under Jetty 9.2.7 where no such fix is in place, and I delegate error handling to the container, so I have <dispatcher>ERROR</dispatcher> configured in my filter mapping as well.

This all seems a bit arcane but is the only way I've discovered to successfully jump through this particular RESTful-Spring-JSP-web-application hoop.

1
  • Thanks I eneded up switching everything to Post anyway, it's not that big a deal to use Post v Put. I have been wondering if there's a way to redirect back to the new method though when there's an error, while keeping the BindingResult available to the request, that would redirect it back to GET and be neater for my code. I haven't looked too deeply into it yet though. This answer is a good solution though, so I'll mark it right.
    – Richard G
    Feb 18, 2015 at 14:08
4

I had the same issue and solved it in the end by adding

<%@ page isErrorPage="true" %>

at the beginning of my JSP page.
With apache-jsp-8.0.33, org/apache/jasper/compiler/Generator.java skips the creation of this check for pages that have this flag set, allowing the JSP to answer to any method.

0
0

If web.xml contains also Spring security config like:

<!--    springSecurityFilterChain-->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

, then - just a small improvement of described above by @ryanp, to handle DELETE and PUT methods:

public class GetMethodConvertingFilter implements Filter {

@Override
public void init(FilterConfig config) throws ServletException {
    // do nothing
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {

    chain.doFilter(wrapRequest((HttpServletRequest) request), response);
}

@Override
public void destroy() {
    // do nothing
}

private static HttpServletRequestWrapper wrapRequest(HttpServletRequest request) {
    return new HttpServletRequestWrapper(request) {
        @Override
        public String getMethod() {
            String requestMethod = request.getMethod();
            return HttpMethod.PUT.matches(requestMethod) || HttpMethod.DELETE.matches(requestMethod) ?
                    HttpMethod.GET.toString() : requestMethod;
        }
    };
  }
}

Tested on Tomcat 8.5.51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.