使用 REST 的 Spring 控制器出现 405 错误

发布于 2024-12-11 09:56:27 字数 5068 浏览 0 评论 0原文

----------------更新----------------

我已经完全重写了我的问题尝试澄清显然写得不好的内容。我还包含了更多代码,希望有人可以提供一些帮助。对于之前提供的令人困惑的内容,我深表歉意。

基本上我的问题似乎是我不完​​全理解 Spring 和 REST。所以我希望有人能为我澄清一些事情,也许可以检查我的代码并告诉我具体为什么它不起作用。虽然我对原因有一些了解,但我不明白为什么会这样。

我有一个非常基本的 Spring 应用程序。将向用户显示一个页面,其中列出(来自数据库)由两列组成的表,其中填充了用户名和是否启用的布尔值。

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

单击启用列中的链接只需切换其状态。该链接是通过将用户发送到 /admin/access 并附加用户名和访问变量来创建的。因此,一个例子是 http://localhost:8080/myApp/admin /access?username=test&access=true (或任何确切的语法)。该代码是:

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

效果很好。它将更新用户的访问权限并返回包含表和用户数据的页面。 (也许不是实现它的最佳方法?)后来我想在 Dojo 网格中填充数据,因此需要将数据放入 JSON 格式。因此,我阅读了有关 REST 等的 Spring 书籍。因此,为了简单起见,我决定将上述处理程序设为 RESTful。所以我将其更改为:

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

我还更新了 JSP 以使链接转到 /admin/access/username/access,例如: http://localhost:8080/myApp/admin/access/test/true。瞧,事情仍然有效。我以为我已经让它变得 RESTful 了。不过,有几件事确实让我觉得奇怪。

首先,当单击链接时,它确实正确更新了状态,但是当返回到 /admin/modifyUser 页面(这是它发送给您的位置)时,这两个变量将被附加到 URL 中。因此,它没有显示 http://localhost:8080/myApp/admin/modifyUser,而是显示 < a href="http://localhost:8080/myApp/admin/modifyUser?username=test&access=true" rel="nofollow">http://localhost:8080/myApp/admin/modifyUser?username=test&access=true。很确定这不应该发生。

其次,我意识到submitModifyAccess 的RequestMethod 应该是POST(或者可能是PUT)。

但正如我所说,它仍然有效,所以我并没有太担心。

接下来我尝试修改另一个链接,即用户名链接。单击该链接时,用户将进入一个填充有该人数据的表单。最初,只需将用户名附加到 URL 并使用 GET 请求来显示表单即可调用该方法。所以代码是:

@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);
}

工作正常。所以我更新了 JSP,以便用户名链接调用正确的 URL,然后我尝试通过将其更改为 RESTify 该方法:

@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";
}

这样做后,我开始看到 405 错误,现在我意识到我显然不理解某些内容。首先,我相信为了执行 REST PUT 或 POST,您必须拥有完全相同的 URL 的 GET。这是正确的吗?人们认为在这种情况下我应该做什么?

哦,如果有人想要它,我发送给人们的表单如下(尽管它永远不会被加载,因为当用户单击链接时,他们会收到 405 错误):

<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>

所以希望这能让事情变得更加清晰。再说一次,如果你知道我做错了什么,如果你能解释我对 REST 的明显不明白的地方,或者任何其他可以改进我的代码的评论,请务必让我知道。非常感谢。

----------------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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

魄砕の薆 2024-12-18 09:56:27

首先是主要问题,您的 405 错误。 405 表示“方法不支持”,即不支持 HTTP 方法(GET/PUT/POST/等)。您重定向到 /admin/editUser 将会向客户端(浏览器)返回 302,其中包含指示重定向 URL 的标头。然后,浏览器将针对该 URL 发出 GET(即,它将自行重定向)。从您的映射来看,您可以处理的最接近的匹配请求是 GET /admin/editUser/{username} - 但您正在重定向到 GET /admin/editUser。我的猜测是这就是问题所在 - 您的重定向与您声明的任何端点都不匹配。

请注意,我假设示例中的 /admin/editUser/username URL 实际上应该是 /admin/editUser/{username} 因为您需要使用大括号@PathVariable 的。

另请注意,从 GET 重定向有点不常见。你刚刚得到了一些东西,大概是要返回给客户端 - 为什么要重定向到其他东西?

关于 http://localhost:8080/myApp/admin/access/test/true 之类的东西是否是“RESTful”可能是一种观点,但我的看法是它不是。对我来说,资源就是用户的“访问权限”。要授予访问权限,我可能会执行 PUT /users/{username}/access。要拒绝访问,我可能会执行DELETE /users/{username}/access。注意统一的接口:不同的 HTTP 方法在同一个 URL 上运行。

如果您的 URL 中有动词(例如“modifyUser”)或在 URL 中传递数据(例如 access=true/access/true< 中的 true /code>)您可能没有遵守 REST 原则。

最后,我觉得您可以从一本关于 REST 的好书中受益,而不是我猜您正在使用的是在线找到的博客/文章。我发现在练习中休息,成为迄今为止最好的。

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.

Saygoodbye 2024-12-18 09:56:27

modifyUsereditUser

您写道,链接是:
http://localhost:8080/myApp/admin/modifyUser
但你映射到
/admin/editUser/{username}

如果这不是问题,那么请尝试使您的问题更清楚一些 - 特别是当前的实现是什么以及当前不起作用。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文