Node.js学习笔记【四】
REST简介
REST是什么
REST是一种万维网软件架构风格
,用来创建网络服务的。
为何叫REST
REST全称:Re
presentational S
tate T
ransfer
- Representational:数据的表现形式(JSON、XML…)
- State:当前状态或者数据
- Transfer:数据传输
REST的6个限制
客户—服务器(Client-Server)
这个限制的本质其实是一种软件架构思想 ,叫作关注点分离
。
关注点分离
通俗地讲,就是各扫门前雪,自己管好自己的事。在这里是指服务端
专注数据存储,提升了简单性;前端
专注用户界面,提升了可移植性。
简单性
:服务端代码变简单。在过去,服务端还要渲染页面,还有数据存储,现在不用管用户界面,只要写好数据存储的逻辑;
可移植性
:一个软件可以很方便地移植到其他平台。在过去,前端都是在操作系统里写的,要管数据存储,计算和一些复杂的逻辑,现在的前端就是在浏览器工作的前端工程师不用管复杂的计算,只需要调用接口渲染用户界面,这样很多平台都可以支持,可移植性得到提升。
无状态(Stateless)
无状态
指的是所有用户会话信息都保存在客户端,当前所处的状态,服务端都不知道,所以客户端每次请求必须包括所有信息,不能依赖上下文信息。
无状态
可以使服务端不用保存会话信息,提升了简单性、可靠性、可见性。
可靠性
:指的是软件的稳定程度以及从它一次故障中恢复正常的能力。如果服务端要管理用户会话信息,一旦服务端出现故障,用户会话信息就会完全丢失,想要恢复起来几乎是不可能的;但如果不管理会话信息的话,从故障中恢复起来就会非常容易。
可见性
:指的是在软件工程中,模块和接口间的透明程度。每次请求都必须包括所有信息,接口之间就更加透明了,可见性更高了。
缓存(Cache)
这个限制的具体意思是所有服务端响应都要被标为可缓存或不可缓存。比如像前端的JS、CSS这些静态文件就可以缓存,请求一次就行,它们变化的可能性不大。
缓存的作用就是减少前后端交互,提升了性能。
统一接口(Uniform Interface)
这个限制是所有限制中最重要的一个。只有统一接口
突出了REST
的特点。
统一
指的是接口设计尽可能统一通用,提升了简单性、可见性。
接口
指的是前后端通过接口通信,前端只需要调用接口就可以了,不需要实现具体的代码,接口与实现解耦,使前后端可以独立开发迭代,两者只需要遵循同一套接口规范即可,谁也不用依赖谁。
分层系统(Layered System)
这个限制的意思是软件架构
是分很多层的,而且每层只知道相邻
的一层,后面隐藏的就不知道了,客户端不知道是和代理
还是真实服务器通信,客户端只知道自己相邻的一层,也就是接口。在这里代理
就可以算是分层系统中的一层。
分成系统还有其他层:安全层、负载均衡、缓存层等
按需代码(Code-On-Demand 可选)
这个限制是可选的。
按需代码
指的是客户端可以下载运行服务端传来的代码(比如JS);它可以通过减少一些功能,简化了客户端。
统一接口的限制
统一接口的限制
是之前介绍的REST6个限制中"统一接口"的子限制
,就是为了告诉我们REST风格的接口应该设计成什么样。
统一接口有以下限制:
资源的标识
REST风格整个是围绕资源
展开的。
-
资源是任何可以命名的事物,比如用户、评论等。
-
每个资源可以通过URI被唯一地标识
例如请求github用户列表,只能请求这一个接口,因为它被唯一标识了
通过表述来操作资源
表述
就是Representation,也就是数据的传输形式,比如JSON、XML等。
客户端不能直接操作(比如运行SQL执行增删改查)服务端资源,客户端应该通过表述
(比如JSON)来操作资源
自描述消息
每个消息(请求或响应携带的数据)必须提供足够的信息让接受者理解。
这些自描述消息具体指:
- 媒体类型(application/json、application/xml)
- HTTP方法:GET(查)、POST(增)、DELETE(删)
- 是否缓存:Cache-Control
超媒体作为应用状态引擎
- 超媒体:带文字的链接
- 应用状态:一个网页或一段JSON
- 引擎:驱动、跳转
- 合起来:点击链接跳转到另一个网页或者另一个JSON
可以让开发者更方便找到别的接口。
例如:这个接口数据还提供了别的接口,就像一个导航网站一样提供了许多链接列表
RESTful API设计最佳实践
请求设计规范
- URI使用名词,尽量用复数,如/users
- URI使用
嵌套
表示关联
关系,如/users/12/repos/5 代表id为12的用户
的id为5的仓库
,仓库是属于用户,所以要用嵌套
关系 - 使用正确的HTTP方法,如GET/POST/PUT/DELETE
- 不符合CRUD的情况:POST/action/子资源
响应设计规范
- 查询:每一个响应都是可以被查询的,可以被过滤的,加上一些限制条件,就只能返回符合查询条件的一些返回值
- 分页:本质也是一种查询,如果列表比较长的话,应该加上分页信息,比如第几页,每页有几条数据。
- 字段过滤:返回你指定的那几个字段
- 状态码:选择正确的状态码来作为响应。通常”2“开头的代表
正确
;”3“开头代表重定向
;”4“开头代表客户端错误
;”5“开头代表服务端错误
。
有关状态码的详细参考:HTTP状态码
常用HTTP状态码:
响应码 | 说明 |
---|---|
200 OK | 请求已成功 |
201 Created | 资源已创建 |
204 No Content | 请求已成功,但无返回内容 |
304 Not Modified | 缓存有效 |
400 Bad Request | 语义有误,当前请求无法被服务器理解,请求参数错误 |
401 Unauthorized | 当前请求需要用户认证(登录) |
403 Forbidden | 用户已认证(登录),但权限不足 |
404 Not Found | 请求源未在服务器上被发现 |
405 Method Not Allowed | 请求方法不能被用于请求相应的资源,如使用PUT方法访问只接受POST方法的API |
500 Internal Server Error | 服务端内部错误 |
502 Bad Gateway | 网关错误 |
504 Gateway Timeout | 网关超时 |
- 错误处理:在响应中应该加上错误处理。所谓错误处理指,如果你的请求是错的,那么应该返回错误信息并按照规范格式。
安全
- HTTPS:这个对于安全是很重要的,几乎所有的网址都加了
https
。之前使用http
的时候,网页是很容易被篡改的,因为没有加密。 - 鉴权:指的是验证用户是否拥有访问系统的权利,必须要先登录才能被允许请求某些接口,如果在不登录的情况下有些接口是无法请求的,这个是为了保证用户的数据安全。
- 限流:防止那些故意攻击网站的入侵者,它们会不停调用你的接口,让你的服务器撑不下去,最后挂掉,这就需要在接口里添加
限流
的保护措施,比如在分层系统里面专门加一个限流的层。
开发者友好
- 文档
- 超媒体
路由简介
路由是什么?
- 决定了不同URL是如何被不同地执行的
- 在
Koa
中,是一个中间件
为什么要用路由?
如果没有路由:①所有请求都做了相同的事;②所有请求都会返回相同的值
如果没有路由,可以看到无论是post请求或是get请求或者不同的URL,都返回相同的值
路由存在的意义
- 处理不同的URL
- 处理不同的HTTP方法
- 解析URL上的参数
编写Koa路由中间件
- 处理不同的URL
假如请求根目录,返回"这是主页";请求/users这个URL,返回"这是用户列表页"
const Koa = require('koa');
const app = new Koa();
app.use(async(ctx)=>{
//判断请求的是什么
if(ctx.url === '/'){
ctx.body = '这是主页'
}else if(ctx.url === '/users'){
ctx.body = '这是用户列表页'
}else{
ctx.status = 404;
}
});
app.listen(8080);
- 处理不同的http方法
假如GET users时候,返回"这是用户列表页";POST users的时候,返回"创建用户"
app.use(async(ctx)=>{
//判断请求的是什么
if(ctx.url === '/'){
ctx.body = '这是主页'
}else if(ctx.url === '/users'){
//判断http方法
if(ctx.method === 'GET'){
ctx.body = '这是用户列表页'
}else if(ctx.method === 'POST'){
ctx.body = '创建用户'
}else{
ctx.status = 405;
}
}else{
ctx.status = 404;
}
});
- 解析URL上的参数
用户请求user/${userId},返回"这是用户 "+userId
app.use(async(ctx)=>{
//判断请求的是什么
if(ctx.url === '/'){
ctx.body = '这是主页'
}else if(ctx.url === '/users'){
//判断http方法
if(ctx.method === 'GET'){
ctx.body = '这是用户列表页'
}else if(ctx.method === 'POST'){
ctx.body = '创建用户'
}else{
ctx.status = 405;
}
}else if(ctx.url.match(/\/users\/\w+/)){//请求特定用户
const userId = ctx.url.match(/\/users\/(\w+)/)[1];
ctx.body = `这是用户 ${userId}`;
}else{
ctx.status = 404;
}
});
使用koa-router实现路由
使用koa-router之前要先npm install koa-router
安装
使用koa-router可以更优雅地实现路由基本功能
const Koa = require('koa');
const Router = require('koa-router')
const app = new Koa();
const router = new Router();
router.get('/',(ctx)=>{
ctx.body = '这是主页'
});
router.get('/users',(ctx)=>{
ctx.body = '这是用户列表'
});
router.post('/users',(ctx)=>{
ctx.body = '创建用户';
});
router.get('/users/:id',(ctx)=>{
ctx.body = `这是用户 ${ctx.params.id}`;
})
//将router注册到app里
app.use(router.routes());
app.listen(8080);
在上述代码中可以看到,解析URL上的参数不像之前一样使用正则表达式,而是直接:xx
,然后通过ctx.params.xx
获取到URL上的参数值
使用koa-router还有其他高级路由功能:
- 前缀
可以通过调用 router.prefix(prefix)
来设置路由的前缀,也可以通过实例化路由的时候传递prefix参数
设置路由的前缀
使用前缀可以方便修改路由接口名,还可以简约代码,
const Koa = require('koa');
const Router = require('koa-router')
const app = new Koa();
const router = new Router();
const usersRouter = new Router({prefix:'/users'})
router.get('/',(ctx)=>{
ctx.body = '这是主页'
});
usersRouter.get('/',(ctx)=>{
ctx.body = '这是用户列表'
});
usersRouter.post('/',(ctx)=>{
ctx.body = '创建用户';
});
usersRouter.get('/:id',(ctx)=>{
ctx.body = `这是用户 ${ctx.params.id}`;
})
//将router注册到app里
app.use(router.routes());
app.use(usersRouter.routes())
app.listen(8080);
- 多中间件
在GET和POST方法里面可以传多个中间件
//仿鉴权中间件
const auth = async (ctx,next)=>{
if(ctx.url !== '/users'){
//报错
ctx.throw(401)
}
await next();
};
usersRouter.post('/',auth,(ctx)=>{
ctx.body = '创建用户';
});
usersRouter.get('/:id',auth,(ctx)=>{
ctx.body = `这是用户 ${ctx.params.id}`;
})
可以看到由于在请求用户id中间件
间添加了auth鉴权中间件
,导致用户没有权利无法访问。
HTTP options 方法的作用
- 检测服务器所支持的请求方法
可以看到这个api接口:http://example.org返回的允许的方法有OPTIONS,GET,HEAD,POST方法
- CORS中的预检请求
CORS
是用作跨域
的一门技术,这个技术可能只支持一个网站的其中一部分接口的一部分方法跨域,可以用options
方法来提前检测,如果允许跨域那么再发出真实的请求。
了解了options
方法,就可以顺水推舟讲出allowedMethods
的作用:
- 响应options方法,告诉它所支持的请求方法
用options方法请求users,结果返回的竟然是一个404
,告知方法没有实现。
使用allowedMethods(),所有接口都可以支持options请求。
app.use(usersRouter.allowedMethods());
- 相应地返回
405(不允许)
和501(没实现)
在有了allowedMethods的前提下,刚才使用options方法告诉我们users接口允许用HEAD、POST、GET方法请求,那么用其他方法如DELETE方法请求,allowedMethods
会自动返回405。
使用koa-router
不支持的请求方法,例如LINK方法,allowedMethods
会自动返回501。