cardician
cardician

Reputation: 2491

Spring Controllers using REST getting 405 errors

----------------UPDATE----------------

I've entirely re-written my question in attempt to clarify what was apparently not well written. I've also included even more code in the hopes that someone can offer some help. My apologies for the confusing stuff provided earlier.

Basically my problem seems to be that I don't entirely understand Spring and REST. So I'm hoping someone can perhaps clarify things for me and perhaps look over my code and inform me of specifically why it doesn't work. Though I have some idea of the cause I don't understand why it is the way it is.

I've got a very basic Spring app. The user is displayed a page that lists (from the DB) a table made up of two columns filled with usernams and a boolean of whether or not they're enabled.

@RequestMapping(value="/admin/modifyUser", method=RequestMethod.GET)
public void showModifyUser() {}

Clicking on a link in the enabled column simply switches their status. The link is created by sending the user to /admin/access and appending the username and access variable. So, an example would be http://localhost:8080/myApp/admin/access?username=test&access=true (or whatever the exact syntax is). That code was:

@RequestMapping(value="/admin/access",method=RequestMethod.GET)
public String submitModifyAccess(@RequestParam("username")String username,
                                @RequestParam("access")String access) {
    ....
    return "redirect:/admin/modifyUser";
}

That worked fine. It would update the user's access and return to the page with the table and user data. (Maybe not the best way to implement it?) Later on I wanted to populate data in a Dojo grid and therefore needed the data put into JSON format. Hence I read up in my Spring book on REST and such. So, to start out easy, I decided to make the above Handler RESTful. So I changed it to:

@RequestMapping(value="/admin/access/{username}/{access}",method=RequestMethod.GET)
public String submitModifyAccess(@PathVariable String username,
                                @PathVariable String access) {
    ....
    return "redirect:/admin/modifyUser";
}

I also updated the JSP to make the link go to /admin/access/username/access, so for example: http://localhost:8080/myApp/admin/access/test/true. And voila, things still worked. I assumed I had made it RESTful. A couple of things did strike me as odd though.

First, when clicking on the link, it did update the status properly, but when returning to the /admin/modifyUser page (which is where it sends you), the two variables would be appended to the URL. So instead of showing http://localhost:8080/myApp/admin/modifyUser, it showed http://localhost:8080/myApp/admin/modifyUser?username=test&access=true. Pretty sure that wasn't supposed to be happening.

Second, I realized that the RequestMethod for submitModifyAccess should be POST (or perhaps PUT).

But as I said, it still worked so I didn't worry about it too much.

Next I tried to modify the other link, the username link. When clicking on that link the user is taken to a form populated with the data of that person. Originally that was called by just appending the username to the URL with a GET request to display the form. So the code was:

@RequestMapping(value="/admin/editUser", method=RequestMethod.GET)
public void showEditUser(Model model, @RequestParam("username") String username) {
    NFIUser user = userService.getUser(username);
    UserDetails userDetails = userDetailsManager.loadUserByUsername(username);

    ....
    model.addAttribute("user", user);
}

Worked fine. So I updated the JSP so the username links called the proper URL and then I tried to RESTify this method by changing it to:

@RequestMapping(value="/admin/editUser/{username}", method=RequestMethod.GET)
public String showEditUser(Model model, @PathVariable String username) {
    NFIUser user = userService.getUser(username);
    UserDetails userDetails = userDetailsManager.loadUserByUsername(username);

    ....
    model.addAttribute("user", user);
    return "redirect:/admin/editUser";
}

Upon doing that I started to see 405 errors and I now realize I'm clearly not understanding something. First, I believe that in order to do a REST PUT or POST you have to have a GET of that exact same URL. Is that correct? What do people think I should do in this situation?

Oh, and in case anyone wants it, the form I was sending people to is as follows (though it's not ever getting loaded as when the user clicks on the link they get the 405 error):

<div align="center">
<b>If you change the Username you MUST change the password as well.</b>
<s:url value="/admin/editUser" var="edit_url" />
<sf:form method="POST" modelAttribute="user" dojoType="dijit.form.Form" action="${edit_url}">
<script type="dojo/method" event="onSubmit">
    if (!this.validate()) {
        return false;
    }
    return true;
</script>
<sf:hidden path="username"/>
<table>
<tr>
<td align="right">Username: </td>
<td>
    <sf:input path="newUsername" dojoType="dijit.form.ValidationTextBox" trim="true" required="true" value="${user.username}"/>
</td>
</tr>
<tr>
<td align="right">Password: </td>
<td>
    <sf:input path="password" type="password" dojoType="dijit.form.ValidationTextBox" required="true"/>
</td>
</tr>
<tr>
<td align="right">Enabled: </td>
<td>
    Yes<sf:radiobutton path="enabled" value="true" dojoType="dijit.form.RadioButton"/> 
    No<sf:radiobutton path="enabled" value="false" dojoType="dijit.form.RadioButton"/>
</td>
</tr>
<tr>
<td align="right">Admin: </td>
<td>
    Yes<sf:radiobutton path="isAdmin" value="true" dojoType="dijit.form.RadioButton"/> 
    No<sf:radiobutton path="isAdmin" value="false" dojoType="dijit.form.RadioButton"/>
</td>
</tr>
<tr>
<td align="right" colspan="2">
    <button dojoType="dijit.form.Button" type="submit">Submit</button>
</td>
</tr>
</table>
</sf:form>
</div>

So hopefully that makes things more clear. Again, if you've got some idea of what I'm doing wrong, if you can explain what I clearly don't get about REST, or any other comments that would improve my code, by all means let me know. Thank you very much.

Upvotes: 1

Views: 4016

Answers (2)

SingleShot
SingleShot

Reputation: 19131

First the main issue, your 405 error. 405 means "method not supported", i.e. the HTTP method (GET/PUT/POST/etc.) is not supported. Your redirect to /admin/editUser will return a 302 to the client (browser) with a header indicating the redirect URL. The browser will then issue a GET against the URL (i.e. it will redirect itself). From your mappings it looks like the closest matching request you can handle is GET /admin/editUser/{username} - but you are redirecting to GET /admin/editUser. My guess is that is the problem - your redirect does not match any endpoints you've declared.

Note I'm assuming your /admin/editUser/username URL in your example should really have been /admin/editUser/{username} because you need to use curly braces for @PathVariable's.

Also note that it is a bit uncommon to redirect from a GET. You just got something, presumably to return to the client - why redirect to something else?

Regarding whether something like http://localhost:8080/myApp/admin/access/test/true is "RESTful" may be an opinion, but my take is that it is not. The resource to me is the user's "access". To grant access I would probably do a PUT /users/{username}/access. To deny access I would probably do a DELETE /users/{username}/access. Note the uniform interface: different HTTP methods operating on the same URL.

If you have verbs in your URLs (e.g. "modifyUser") or pass data in your URLs (e.g. the true in access=true or /access/true) you are probably not adhering to REST principles.

Lastly, I feel you could benefit from a good book on REST rather than what I am guessing you are using is blogs/articles found online. I find Rest in Practice to be the best so far.

Upvotes: 1

Ralph
Ralph

Reputation: 120761

modifyUser vs. editUser ?

You wrote that the link is: http://localhost:8080/myApp/admin/modifyUser but you mapped to /admin/editUser/{username}

If this is not the problem, then try to make your question a bit more clear - especially what is the current implementation and what does not work currently.

Upvotes: 0

Related Questions