REST简介
REST是一种结构风格,HTTP/1.1实现了这套架构风格
REST全称是Representational State Transfer,可以说它是一种思想而不是某个具体的技术或实现,我们并不能说RESTful就是指的API,而是要说某套API实现了RESTful.但就目前来看,由于REST思想与Web的契合程度,导致HTTP是唯一与REST相关的实例
REST的基本概念
Resource
- 所谓“资源”,就是网络上的一个实体,或者说是网络上的一个具体信息
- 可以是一段文本、一张图片、一首歌、一种服务,总之就是一个具体的存在
- 可以用一个URI(统一资源定位符)指向它
- 每种资源对应一个特定的URI
- 要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符
Representation
- “资源”是一种信息实体,它可以有多种外在表现形式,我们把“资源”具体呈现出来的形式,叫做它的“表现层”(Representation)
- 比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现
- URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的html后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只表示“资源”的位置
- 具体的表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对“表现层”的描述
State Transfer
- 访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化
- 互联网通信协议HTTP协议,是一个无状态协议。这意味着所有状态都保存在服务器端。因此如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer).而这种转化是建立在表现层之上的,所以就是"表现层状态转化"
- 用表现层去改变(服务器端)资源的状态
Uniform interface
- REST要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限操作
- HTTP/1.1协议为例:
- 7个HTTP方法:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS
- HTTP头信息(可自定义)
- HTTP响应状态吗(可自定义)
- 这些就是HTTP/1.1 协议提供的统一接口
- REST还要求,对于资源执行的操作,其操作语义必须由HTTP消息体之前的部分完全表达,不能将操作语义封装在HTTP消息体内部
RESTful框架总结
- 每一个URI代表一种资源
- 客户端和服务器之间,传递这种资源的某种表现层
- 客户端通过HTTP提供的统一接口,对服务器端资源进行操作,实现“表现层“(使用HTTP动词去促使服务器端资源的)状态转化
关于应用接口
- 接口分为公共接口和私有接口
- 常见的接口方式
- WebService
- 普通HTTP请求
- RPC
- 简单的http接口设计存在的问题
- 大量的接口方法,URL地址设计复杂,需要在URL里面表示出资源及其操作
RESTful 设计
资源设计
- 路径又称"终点"(endpoint),表示API的具体网址
- 在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库表格名对应
- 一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数
动作设计(method)
- http动作说明
- GET : select
- POST : create
- PUT : update(客户端提供改变后的完整资源)
- DELETE : delete(客户端提供改变后的补丁)
- PATCH : update
- HEAD : 获得一个资源的元数据,比如一个资源的hash值或者最后修改日期
- OPTIONS : 获得客户针对一个资源能够实施的操作
返回结果(状态码)
- 200 [GET]成功
- 201 create[POST/PUT/PATCH]成功
- 204 delete[DELETE]成功
- 400 [POST/PUT/PATCH]用户发出请求有错误
- 406 [GET]用户请求格式不可用
- 410 [GET]用户请求的资源被永久删除
RESTful服务开发
Java中常见的RESTful开发服务
- Jersey
- play
- Spring MVC
使用Spring MVC 开发RESTful服务
@RequestMapping
- @ResponseBody 返回json时需要加上
- @RestController 包含了@ResponseBody 和 @Controller
- @RequestMapping属性:
- value : 映射地址,是个map,可以映射多个地址
- method : 方法方式
- headers : 映射请求头里的某个属性
- 例:接受json的请求 header="Accept=application/json"
- produces : 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
- 例:produces="application/json"
- consumes : 指定处理请求提交内容类型,服务器端返回
- 例:consumes="application/json"
public void test1(Employee e) { // 只适用于编码为 application/x-www-form-urlencoded
...
}
public void test2(@RequestBody Employee e) { // 适用于编码为 application/json
...
}
请求处理
- ajax对请求method的修改比较方便,但表单提交时,method只有get和post两种请求,此时需要在服务器端加过滤器
- 浏览器:
<form action="/xxx/xxx" method="post">
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="提交">
</form>
- 过滤器:
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<servlet-name>springmvc</servlet-name>
</filter-mapping>
RESTful的最佳实践
版本
- 项目的结构一定会随着实践和需求发生变化
- 在提供新的版本时,要保证老版本的路径同样可以访问
- 在版本更新时,要有明确的提示给老版本用户新版本发布的消息
- 监控老版本路径,如果确定没人使用了,再取消
- 如果只是增加了新的API,不需要更新版本号
- 可以通过请求头添加版本号,也可以通过URL添加版本号,URL的方式更简单
Hypermedia API
- RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么
- 例:https://api.github.com
根路径
- 根路径对于API的设计非常重要
- 一般根路径会单独提供一个二级域名
- 在根路径上放置好API文档是一个最佳实践
- 把API放在HTTPS下是一个最佳实践
数据过滤
- 让查询返回得数据有数量得限制(比如列表查询,一定需要做返回条数得限制)
- 常见的过滤设计:
- ?limit=10 限制只查询10条
- ?offset=10 从第11条查询
- ?sortby=name&order=asc 排序
- 在代码中为可以过滤或排序的列做好安全限制
访问权限
- 一些情况下,我们可以作让API作为公开得资源访问,但是在大多数情况下,我们需要监控每一个请求是由谁发出得
- 采用OAuto2.0来完成认证是一个最佳实践
应用P2P项目
用户注册接口
- POST /api/userinfos/ username=&password=
用户登陆
- 用户登陆:POST /api/tokens/ username=&password=
- 用户注销:POST /api/tokens/ username=&password=
- 一般API都是提供给移动端使用或者ajax使用,需要有效识别当前用户是否登陆;常用的办法是使用token来代替sessionid
- 实现思路:登陆之后,生成一个token,返回给客户端,之后每次客户端在访问api资源的时候,都传入token,系统根据token来识别当前访问的用户
- 两个问题:
- token和uderid存放在什么地方:可以缓存在service中,也可以直接存放在redis中
- 在请求的时候token放在什么地方:推荐放在request请求头中
登陆检查
- 检查注解: @login_required @RequireLogin
public class LoginCheckInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletRequest response, Object handler) throws Exception {
// 截获当前要请求的方法
if(handler instanceof HandlerMethod) {
// 判断方法是否有标签,如果有标签但是没有用户登录,跳转到登陆界面
if(handlerMethod.getMethodAnnotation(RequireLogin.class) != null && UserContext.getCurrent() = response.sendRedirect("/login.html")){
return false;
}
}
return true;
}
}
HandlerMethodArgumentResolver
作用:可以处理特定参数的创建方式
自定义注解的使用:
@Target(ElementType.PARAMERTER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}
- 实现:
@Component("currentUserParameterResolver")
public class CurrentUserParameterResolver implements HandlerMethodArgumentResolver {
@Autowired
private ITokenService tokenService;
// 判断当前处理的参数是不是目标参数
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(CurrentUser.class) != null;
}
// 创建并返回设置好的参数内容
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 得到请求头中的token
webRequest.getHeader(ITokenService.TOKEN_IN_HEADER);
// 从 tokenservice中获取userid
Long currentUserId = this.tokenService.findToken(token);
Logininfo currentUser = new Logininfo();
currentUser.setId(currentUserId);
return currentUser;
}
}
- bean配置
<mvc:annotation-driven>
<mvc:argument-resolvers>
<ref bean="currentUserParamenterResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
Swagger
Swagger 介绍
- 目的是为REST APIs 定义一个标准的,与语言无关的接口,使人和计算机在看不到源码或者看不到文档或者不能通过网络流量监测的情况下能发现和理解各种服务的功能
- 当服务通过Swagger定义时,消费者就能与远程的服务互动通过少量的实现逻辑
- 简单说,Swagger能够根据代码中的注释自动生成API文档,并且提供测试接口
- swagger和swagger2是两个不同的版本,现在用的比较多的是swagger和springfox,springfox就是swagger-springmvc的升级版,也叫swagger2
可视化 Swagger UI
- @Api(value="令牌资源",description="令牌资源,其实相当于用户登录")
- @ApiOperation(value="创建令牌",notes="创建令牌")
- @ApiImplicitParams({
@ApiImplicitParams(value="用户名",name="username",dataType="String",patamType="query")
})