什么是REST
REST 是 Representational State Transfer 的缩写. 它是一种架构的风格, 这种风格基于一套预定义的规则, 这些规则描述了网络资源是如何定义和寻址的.
一个实现了REST这些规则的服务就叫做RESTful的服务.
REST的原则/约束
REST有6大原则/约束, 每一个原则都是对API有正面或负面影响的设计决定.
RESTful API 最关心的有这几方面: 性能, 可扩展性, 简洁性, 互操作性, 通讯可见性, 组件便携性和可靠性.
这些方面被封装在REST的6个原则里, 它们是:
-
客服端-服务端约束
: 客户端和服务端是分离的, 它们可以独自的进化. -
无状态
: 客户端和服务段的通信必须是无状态的, 状态应包含在请求里的. 也就是说请求里要包含服务端需要的所有的信息, 以便服务端可以理解请求并可以创造上下文. -
分层系统
: 就像其它的软件架构一样, REST也需要分层结构, 但是不允许某层直接访问不相邻的层. -
统一接口
: 这里分为4点, 他们是: 资源标识符(URI), 资源的操作(也就是方法Method, HTTP动词), 自描述的响应(可以认为是媒体类型Media-Type), 以及状态管理(超媒体作为应用状态的引擎 HATEOAS, Hypermedia as the Engine of Application State). -
缓存
: 缓存约束派生于无状态约束, 它要求从服务端返回的响应必须明确表明是可缓存的还是不可缓存的. -
按需编码
: 这允许客户端可以从服务端访问特定的资源而无须知晓如何处理它们. 服务端可以扩展或自定义客户端的功能.
只有满足了这6个原则的系统才可以真正称得上是RESTful的, 其实大部分系统的RESTful API并不是RESTful的, 但这样并不代表这些API就不好, 利弊需要开发人员去衡量.
# Richardson 成熟度模型
Richardson 成熟度模型代表着你的API是否足够成熟, 分为4个级别, 0代表最差, 3代表最好.
0级, Plain Old XML沼泽:
这里HTTP协议只是被用来进行远程交互, 协议的其余部分都用错了, 都是RPC风格的实现(例如SOAP, 尤其是使用WCF的时候).
例如:
POST (查询数据信息)
http://host/myapi
POST (创建数据)
http://host/myapi1级, 资源:
这级里, 每个资源都映射到一个URI上了, 但是HTTP方法并没有正确的使用, 结果的复杂度不算太高.
例如这两个查询:
POST
http://host/api/authors
POST
http://host/api/authors/{id}2级, 动词:
正确使用了HTTP动词, 状态码也正确的使用了, 同时也去掉了不必要的变种.
例如:
GET
http://host/api/authors
Ok (authors)
POST (author representation)
http://host/api/authors
Created (author)3级, 超媒体:
API支持超媒体作为应用状态的引擎 HATEOAS, Hypermedia as the Engine of Application State, 引入了可发现性.
例如:
GET
http://host/api/authors
200 Ok (返回了authors 和 驱动应用程序的超链接)
API 资源命名
- 资源应该使用名词, 它是个东西, 不是动作
GET api/users 就是正确的
GET api/users/{userId}. - 层次结构
例如api/department/{departmentId}/emoloyees
, 这就表示了department (部门)和员工(employee)之前是主从关系.
而api/department/{departmentId}/emoloyees/{employeeId}
, 就表示了该部门下的某个员工. - 过滤排序
过滤和排序, 不是资源, 应作为参数.例如api/users?orderby=username
- ID
资源的URI应该永远都是一样的.
推荐GUID应该作为ID来使用.
自增int类型的ID, 在迁移到新数据库时需要特殊设定, 保证ID值不会发生变化.
HTTP方法与资源交互
注意:
-
HEAD
: 和GET差不多, 但是它不应该返回响应的body, 所以没有响应的payload. 它主要使用来获取资源的一些信息, 例如查看资源是否可用等. -
OPTIONS
: 它是用来查询某个资源URI的可交互方式有哪些, 换句话说就是, 使用它可以知道某个URI是否可以执行GET或者POST动作, 这些结果通常是在响应的Headers里面而不是body里, 所以也没有响应的payload.
状态码
- 状态码会告诉API的消费者:
- 请求是否如预期的成功,或者失败
- 如果出现了错误,谁该为这个错误负责
- API主要用到:
-
200
级别, 表示成功.
* 200 - OK
* 201 - Created,表示资源创建成功了
* 204 - No content,成功执行,但是不应该返回任何东西 -
400
级别, 表示客户端引起的错误.
* 400 - Bad request,表示API的消费者发送到服务器的请求是错误的
* 401 - Unauthorized,表示没有权限
* 403 - Forbidden,表示用户验证成功,但是该用户仍然无法访问该资源
* 404 - Not found,表示请求的资源不存在
* 405 - Method not allowed,这就是当我们尝试发送请求给某个资源时,使用的HTTP方法却是不允许的,例如使用POST api/countries, 而该资源只实现了 GET,所以POST不被允许
* 406 - Not acceptable,这里涉及到了media type,例如API消费者请求的是application/xml格式的media type,而API只支持application/json
* 409 - Conflict,表示该请求无法完成,因为请求与当前资源的状态有冲突,例如你编辑某个资源数据以后,该资源又被其它人更新了,这时你再PUT你的数据就会出现409错误;有时也用在尝试创建资源时该资源已存在的情况。
* 415 - Unsupported media type,这个和406正好返回来,比如说我向服务器提交数据的media type是xml的,而服务器只支持json,那么就会返回415
* 422 - Unprocessable entity,表示请求的格式没问题,但是语义有错误,例如实体验证错误。 -
500
级别, 表示服务器错误.
* 500 - Internal server error,这表示是服务器发生了错误
-
HTTP GET
- 单个数据
找到了: 200
没找到: 404
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var post = await _postRepository.GetPostByIdAsync(id);
if (post == null)
{
return NotFound();//404
}
var postResource = _mapper.Map<Post, PostResource>(post);
return Ok(postResource); //200
}
- 集合数据
至少有一条数据, 200
没有数据, 也是200
[HttpGet]
public async Task<IActionResult> Get()
{
var posts = await _postRepository.GetAllPostsAsync();
var postResources = _mapper.Map<IEnumerable<Post>, IEnumerable<PostResource>>(posts);
return Ok(postResources);
}
内容协商
如果资源支持多种展现格式,那么消费者可以选择它想要的格式
- 在请求的Accept Header指定Media Type
* application/json, application/xml
* 若未指定, 返回默认 application/json - 请求的media type不可用时, 并且消费者不支持默认格式, 返回406
- ASP.NET Core支持输出和输入两种格式化器.
* 用于输出的media type放在Accept Header里, 表示客户端接受这种格式的输出.
* 用于输入的media type放Content-Type Header里, 表示客户端传进来的数据是这种格式.
* ReturnHttpNotAcceptable设为true, 就会返回406.
services.AddMvc(
options=>
{
options.ReturnHttpNotAcceptable = true; //开启406
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
});