使用 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 2 3 4 5 6 7 8 9 10
| $.ajax({ url : '/users/1/username', data : JSON.stringify({name: 'Bob'}), method : 'PUT', dataType : 'json', contentType: 'application/json' }) .done(function(result) { console.log(result); });
|
如果每个 REST 的请求都像上面这样写一遍:
GET
时 data 不能序列化
PUT
, POST
, DELETE
时 data 需要序列化: JSON.stringify(data)
- 请求不同时 method 也不同
- dataType 和 contentType 是固定的
这么多参数,很容易出错。使用下面实现的 rest 插件后,简化如下,只需要关心参数和回调,不需要处理其他额外信息,而且 $.rest.update
名字也更语义化,一看就知道是更新操作:
1 2 3 4 5 6 7
| $.rest.update({ url : '/users/1/username', data : {name: 'Bob'}, success: function(result) { console.log(result); } });
|