Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

405 JSP error with Put Method

Tags:

spring-mvc

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;
    }
like image 996
Richard G Avatar asked Jul 10 '14 09:07

Richard G


2 Answers

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.

like image 88
BerGer Avatar answered Oct 05 '22 02:10

BerGer


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.

like image 21
ryanp Avatar answered Oct 05 '22 00:10

ryanp