使用 REST 风格提交请求时,Content-Type 规范的来说应该用 application/json,但是服务器端获取请求的参数时必须从 Request Body 中获取,有些框架对从 Request Body 中获取数据支持不好,虽然 SpringMVC 中能够使用注解 @RequestBody 从 Request Body 中获取数据,但这时不能使用 Filter 进行 XSS 过滤,总是感觉不太方便,推荐尽可能的使用 Content-Type 为 application/x-www-form-urlencoded,原因下面进行解释。
这里使用 SpringMVC 作为后端处理请求进行介绍,SpringMVC 提供了一个 Filter HiddenHttpMethodFilter,把 Content-Type 为 application/x-www-form-urlencoded 的 POST 请求,参数中 _method 值为 PUT 的请求分发为 PUT 请求,为 DELETE 请求分发为 DELETE 请求,实现了普通表单的 REST 风格提交,这样同时可以使用 @RequestParam 获取参数的值:
- Content-Type 为 application/x-www-form-urlencoded + HiddenHttpMethodFilter
- 优点: 服务器端处理 GET, PUT, POST, DELETE 时可直接把参数映射为对象,或则都使用 @RequestParam 获取参数,使用形式一致、简洁
- 缺点:
- 不符合标准的 REST 规范
- 参数是按照 key/value 的形式发送的,和普通表单的参数形式一样,有兴趣的可以在 Chrome 的 Network 中查看请求的 Headers
- 不方便传递复杂对象,例如 value 又是一个 Json 对象,不过估计 90% 的情况简单的 key/value 就够了
- PUT 时参数中需要带上
_method=PUT
,DELETE 时参数中需要带上_method=DELETE
- Content-Type 为 application/json
- 优点: 符合标准的 REST 规范,GET 处理和上面的一样,但是 POST, PUT, DELETE 的参数是序列化后的 JSON 字符串,能够传递复杂的对象
- 缺点:
- 服务器端直接参数映射为对象,或则 GET 时使用 @RequestParam 获取参数,POST, PUT, DELETE 使用 @RequestBody 获取参数到 Map 中,然后再从 Map 中获取一个一个的参数,非常繁琐
- GET 和 POST, PUT, DELETE 获取参数的形式不统一,一个用 @RequestParam,其他的用 @RequestBody,需要脑子转换一下
- 浏览器端 PUT, POST, DELETE 请求传递的 JSON 对象需要序列化后才能传给服务器端
总结下来,在 SpringMVC 中推荐使用 application/x-www-form-urlencoded + HiddenHttpMethodFilter
的方式实现 REST 的请求,就是为了获取参数时形式统一,当需要传递复杂的参数时,例如属性是多层嵌套的对象,Json 对象的数组,这时再使用 application/json 的方式。
为了简化 Rest Ajax 的访问,对 jQuery 的 Ajax 进行了简单的封装成插件 jQuery.rest
,下面的例子展示了更新用户名的原始实现和使用 jQuery.rest
简化后的代码:
1 | $.ajax({ |
如果每个 REST 的请求都像上面这样写一遍:
GET
时 data 不能序列化PUT
,POST
,DELETE
时 data 需要序列化:JSON.stringify(data)
- 请求不同时 method 也不同
- dataType 和 contentType 是固定的
这么多参数,很容易出错。使用下面实现的 rest 插件后,简化如下,只需要关心参数和回调,不需要处理其他额外信息,而且 $.rest.update
名字也更语义化,一看就知道是更新操作:
1 | $.rest.update({ |
更多例子:
1 |
|
输出:
1 | {code: 0, data: "Alice", message: "GET handled", success: true} |
REST 插件 jquery.rest.js:
1 | (function($) { |
服务器端
添加下面的 Filter 到 web.xml, servlet-name 为 DispatcherServlet 的 servlet-name,根据自己的配置进行修改:
1 | <!-- 浏览器的 form 不支持 put, delete 等 method, 由该 filter 将 /blog?_method=delete 转换为标准的 http delete 方法 --> |
Controller 的实现:
1 | package com.xtuer.controller; |
Result.java
Result 用于统一服务器端返回的 JSON 格式,例如:
1 | { |
1 | import com.alibaba.fastjson.JSON; |
Content-Type 注意事项
- 为 application/x-www-form-urlencoded 时:
- GET, POST 请求的 data 不能够序列化,必须是一个普通的 JSON 对象:
{ username: 'Alice', password: 'Passw0rd' }
- 如果请求是 PUT, DELETE,后端不管使用 @RequestParam 还是 @RequestBody 都不能获取参数,无参数时可以正常处理请求
- GET, POST 请求的 data 不能够序列化,必须是一个普通的 JSON 对象:
- 为 application/json 时:
- GET 请求的 data 不能够序列化
- POST, PUT, DELETE 的 data 都需要序列化:
JSON.stringify({ username: 'Alice', password: 'Passw0rd' })
参考资料
为了理解 Content-Type 为 application/x-www-form-urlencoded 和 application/json 的区别,可以参考四种常见的 POST 提交数据方式 https://imququ.com/post/four-ways-to-post-data-in-http.html。