【问题标题】:Spring MVC PUT Request returns 405 Method Not AllowedSpring MVC PUT 请求返回 405 Method Not Allowed
【发布时间】:2014-09-03 09:11:45
【问题描述】:

我正在使用spring mvc设置一个rest api,并且大部分配置都是通过spring boot项目自动设置的。在前端,我使用 angularjs 和他们的 $http 模块向服务器发出 ajax 请求以获取资源。资源 url 在我的控制器类中定义,但只有 GET url 被匹配。我试过 PUT 和 POST 但它们分别返回 405 方法不允许和 403 禁止。

我的控制器是这样的

@Controller
@RequestMapping("/api/users")
public class UserController {

    @Inject
    UserService svc;

    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public List<User> home() {
        return svc.findAll();
    }

    @RequestMapping(method = RequestMethod.GET, value = "/{id}")
    @ResponseBody
    public User findById(@PathVariable long id){
        return svc.findById(id);
    }

    @RequestMapping(method = RequestMethod.PUT, value="/{id}")
    @ResponseBody
    public User updateUser(@PathVariable long id, @RequestBody User user){
        Assert.isTrue(user.getId().equals(id), "User Id must match Url Id");
        return svc.updateUser(id, user);
    }

}

对与 url 不匹配的服务器的请求看起来像这样

$http({
            url: BASE_API + 'users/' + user.id,
            method: 'PUT',
            data:user
        })

这会向 localhost:8080/api/users/1 生成一个 PUT 请求,并且服务器会使用 405 Method Not Allowed 响应代码进行响应。

当服务器收到对 localhost:8080/api/users/1 的 HTTP GET 请求时,正确处理相同的请求映射但带有 RequestMethod.GET

任何见解都会有帮助。

PS 如果需要,包含的 Spring Boot 依赖项是

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>

谢谢

【问题讨论】:

    标签: java spring spring-mvc spring-boot


    【解决方案1】:

    您在 Spring MVC 上的代码对我来说看起来不错,所以我怀疑 AngularJS 上的语法是正确的。我对AngularJS不熟悉,但是我找到了一个关于$http函数的在线教程

    $http.get(url, config)
    $http.post(url, data, config)
    $http.put(url, data, config)
    $http.delete(url, config)
    $http.head(url, config)
    

    从此链接http://tutorials.jenkov.com/angularjs/ajax.html

    希望它会有所帮助。

    【讨论】:

    • 感谢您的回复,我对角边比弹簧边熟悉得多。 $http 请求有效并产生预期结果(对 localhost/api/users/1 的 HTTP PUT 请求)。我还使用 chrome 扩展 Postman 手动点击了这个端点。我对 spring boots 自动配置不是很熟悉,我认为可能需要进行某种手动配置来启用 PUT 和 POST 请求,但是我真的不知道是不是这种情况。
    【解决方案2】:

    查看日志(在 DEBUG 中使用安全性),您会发现问题所在。很可能您没有禁用 csrf 保护,并且您的客户端没有发送 csrf 令牌。 (标准 Spring Security,而不是 Spring Boot,尽管如果您使用 Boot security autoconfig,您可以使用外部配置设置关闭 csrf。)

    【讨论】:

    • Spring Boot 的配置设置:security.enable-csrf=false
    【解决方案3】:

    首先,在 Spring 4 中,您可以在休息控制器类上使用 @RestController 注释,这允许您从方法中删除 @RequestBody 注释。让它更干净。

    其次,您可能会显示更多您的角度代码吗?我认为您应该考虑使用工厂服务向后端发出请求。比如有一个服务(这个服务已经默认提供了post、get、delete):

    app.factory('User', function ($resource) {
        return $resource("/api/users/:id", {
            update: {
                method: "PUT"
            }
        });
    });
    

    现在在您的 Angular 控制器中创建一个新的用户对象,用于调用此服务方法。

    查询全部(GET):User.query();

    查询一(GET):User.get({id: userId});

    插入(POST):User.$save();

    更新(PUT):User.$update();

    删除(删除):User.delete({id: userId}

    我也使用 Spring boot 编写了类似的 Angular 应用程序并且运行良好。

    【讨论】:

    • 我使用 jspViewResolver 来定向到特定的 jsp 页面。一旦我将我的更改为@RestController,它就不再起作用了,我是否必须创建另一个使用@Controller 指向特定jsp 页面的类?
    • 你不能同时使用它们。您要么 1) 返回 JSON 并在前端使用它 2) 将数据放入模型映射并返回视图。
    【解决方案4】:

    由于 csrf 保护,我遇到了同样的错误。如果启用 csrf 保护,则需要在请求头中发送 csrf 参数。

    您也可以查看 Spring 文档here

    我将此参数添加到我的 jsp 文件中

    <input type="hidden" id="csrfToken" value="${_csrf.token}"/>
    <input type="hidden" id="csrfHeader" value="${_csrf.headerName}"/>
    

    并修改了我的 ajax 调用,如下所示。

    var token = $('#csrfToken').val();
    var header = $('#csrfHeader').val();
    
    $.ajax({
        type : 'POST',
        url : contextPath + "/qd/translate",
        data: JSON.stringify(json),
        dataType : 'json',
        beforeSend: function(xhr) {
            xhr.setRequestHeader("Accept", "application/json");
            xhr.setRequestHeader("Content-Type", "application/json");
            xhr.setRequestHeader(header, token);
        },
        success : function(result) {
            if (result.status === 'ok') {
                $('#translationModal').modal('hide');
                alert('Error when translating: ' + result.resultMessages.succeess);
            } else {
                alert('Error when translating: ' + result.resultMessages.error);
            }
    
        },
        error : function(jqXHR, textStatus, errorThrown) {
            alert(jqXHR.status + " " + jqXHR.responseText);
        }
    });
    

    【讨论】:

    • 奥曼!你救了我的一天!这真的有效。 +1 表示非常努力。
    【解决方案5】:

    我遇到了同样的问题,并通过使用 @RestController 注释而不是控制器上的“普通”@Controller 注释来解决它。

    【讨论】:

    • 这是唯一对我有用的解决方案。有谁知道为什么?我正在使用带有安全启动器的 Spring Boot,并希望将多部分上传到@Controller。将其更改为@RestController 后,它出于某种原因开始工作。我不喜欢不知道为什么......有什么想法吗?
    • 更有趣的是,在控制器方法内部(方法退出时)成功处理请求后抛出异常。
    • 实际上我遇到了同样的问题,当我从 RestController 注释中删除前缀并在它工作的每个方法上添加整个路径时,例如:@Controller(value = "/some) 和 @GetMapping(value = " /path") 更改为 @GetMapping(value = "/some/path"),有什么想法吗?
    【解决方案6】:

    您的问题是 findById 和 updateUser 的端点相同,即 api/users/{id} 并且由于具有 GET 方法的端点首先出现,因此在您发送 PUT 请求时将首先对其进行评估,因此将在春季被拒绝。

    我建议您将更新用户的端点更改为api/users/update/{id},如下所示:

    @Controller
    @RequestMapping("/api/users")
    public class UserController {
    
        @Inject
        UserService svc;
    
        @RequestMapping(method = RequestMethod.GET)
        @ResponseBody
        public List<User> home() {
            return svc.findAll();
        }
    
        @RequestMapping(method = RequestMethod.GET, value = "/{id}")
        @ResponseBody
        public User findById(@PathVariable long id){
            return svc.findById(id);
        }
    
        @RequestMapping(method = RequestMethod.PUT, value="/update/{id}")
        @ResponseBody
        public User updateUser(@PathVariable long id, @RequestBody User user){
            Assert.isTrue(user.getId().equals(id), "User Id must match Url Id");
            return svc.updateUser(id, user);
        }
    
    }
    

    【讨论】:

    • 只要方法不同,就可以有相同的uri。
    猜你喜欢
    • 1970-01-01
    • 2016-12-04
    • 2014-04-09
    • 2014-11-26
    • 2016-10-16
    • 2018-06-29
    • 1970-01-01
    • 2015-07-29
    • 2011-05-14
    相关资源
    最近更新 更多