Koa 学习总结
前言
Koa是基于Node.js的下一代web框架,由Express团队打造,特点:优雅、简洁、灵活、体积小。几乎所有功能都需要通过中间件实现。
准备
- 检查Node版本,至少在7.6.0以上,因为Koa采用很多Es7的语法,比如async/await,
- 创建项目、安装依赖
mkdir study_koa
cd study_koa
npm init -y
npm install koa
一、基本用法
- 创建一个应用程序 新建app.js
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
// ctx.body 即服务端响应的数据
ctx.body = 'Hello Koa';
})
// 监听端口、启动程序
app.listen(3000, err => {
if (err) throw err;
console.log('runing...');
})
- 启动
node app.js
- 访问 127.0.0.1:3000 会看到页面显示Hello Koa
即便没有给ctx.body 设置响应数据,或访问不存在的路由,页面也会显示Not Found,这是koa底层做了处理,不像原生Node或Express一样页面会一直处于响应状态。
二、Context
Koa将Node的request 和 response对象都封装到了context中,每次请求都会创建一个ctx,并且在中间件中作为接收器使用。
以下是刚才访问时的ctx对象
let ctx = {
// 请求
request: {
method: 'GET',
url: '/',
// request header
header: {
host: '127.0.0.1:3030',
connection: 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
cookie: 'connect.sid=s%3AnQtQApNcQ55RmpjnkmQvWNTrdjYhZnlh.1FQUbVqpwpdRj8N6wjv8nOarf8hyzIpcxXN2LPYXGy0'
}
},
// 响应
response: {
status: 200,
message: 'ok',
header: {
'content-type': 'text/plain; charset=utf-8',
'content-length': '9'
}
},
app: {
subdomainOffset: 2,
proxy: false,
env: 'development'
},
originalUrl: '/',
// 原生Node的request对象
req: '<original node req>',
// 原生Node的reponse对象
res: '<original node res>',
socket: '<original node socket>'
}
剖析ctx
- 区分request、response、req、res
- requset ctx的请求对象
- response ctx的响应对象
- req Node的请求对象
- res Node的响应对象
注意:绕过Koa的response是不被处理的,避免使用Node的属性和方法,比如:
res.statusCode
res.writeHead()
res.write()
res.end()
即便使用ctx.res.write()也不会得到预期结果,比如:ctx.res.write('hello'),结果是hellook,会把message的值拼接上。
- ctx.state
推荐的命名空间,用于通过中间件传递信息到你的前端视图。比如每个页面都要用到用户信息,那就可以挂载在ctx.state。类似于添加全局属性。
ctx.state.userInfo = {
name: 'Jack',
age: 18
}
- ctx.app
应用程序实例引用
有关cookie和session单独介绍用法。
三、路由
Koa中的路由和Express不同,Express是把路由集成在Express中,Koa则需要通过kao-router模块使用。
- 安装:
npm i koa-router
- 使用
const Koa = require('koa');
// 直接调用的方式
const router = require('koa-router')();
// 或 单独创建router的实例
const Router = require('koa-router');
const router = new Router();
router.get('/', async ctx => {
ctx.body = 'Hello Router';
})
// 启动路由
app.use(router.routes()).use(router.allowedMethods())
// 以上为官方推荐方式,allowedMethods用在routes之后,作用是根据ctx.status设置response header.
app.listen(3000, err => {
if (err) throw err;
console.log('runing...');
});
四、中间件
Koa最大的特色和最优的设计就是中间件,就是在匹配路由之前和匹配路由之后执行函数。
使用app.use()加载中间件。每个中间件接收两个参数,ctx对象和next函数,通过调用next将执行权交给下一个中间件。
中间件分为:
- 应用级中间件
- 路由级中间件
- 错误处理中间件
- 第三方中间件
- 应用级中间件
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// 应用级中间件
app.use(async (ctx, next) => {
await next();
})
router.get('/', async ctx => {
ctx.body = 'hello koa';
})
// 启动路由
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, err => {
if (err) throw err;
console.log('runing...')
})
任何路由都会先经过应用级中间件,当执行完成next后再去匹配相应的路由。
- 路由中间件
router.get('/user', async (ctx, next) => {
console.log(111)
await next();
})
router.get('/user', async (ctx, next) => {
console.log(222)
await next();
})
router.get('/user', async ctx => {
console.log(333)
ctx.body = 'Hello'
})
// 依次打印
111
222
333
路由匹配过程中,对于相同路由会从上往下依次执行中间件,直到最后一个没有next参数的中间件为止。
- 错误处理中间件
app.use(async (ctx, next)=> {
await next();
if(ctx.status === 404){
ctx.body="404页面"
}
});
路由在匹配成功并执行完相应的操作后还会再次进入应用级中间件执行 next 之后的逻辑。所以对于404、500等错误可以在最外层的(第一个)应用级中间件的next之后做相应的处理。
如果只有一个应用级中间件的话,顺序就无所谓所有路由中间件之前和之后了。
-
第三方中间件
类似于koa-router、koa-bodyparser等就是第三方中间件。 -
中间件的合成
koa-compose
模块可以将多个中间件合成为一个。
const compose = require('koa-compose')
const first = asycn (ctx, next) => {
await next();
}
const second = async ctx => {
ctx.body = 'Hello';
}
const middle = compose([first, second]);
app.use(middle);
- 中间件的执行顺序
多个中间件会形成堆栈结构,按先进后出顺序执行
app.use(async (ctx, next) => {
console.log('1中间件第1次执行')
await next();
console.log('7中间件第7次执行')
})
app.use(async (ctx, next) => {
console.log('2中间件第2次执行');
await next();
console.log('6中间件第6次执行')
})
router.get('/user', async (ctx, next) => {
console.log('3中间件第3次执行')
await next()
console.log('5中间件第5次执行')
})
router.get('/user', (ctx, next) => {
console.log('4中间件第4次执行')
ctx.body = 'Hello Koa';
})
// 1中间件第1次执行
// 2中间件第2次执行
// 3中间件第3次执行
// 4中间件第4次执行
// 5中间件第5次执行
// 6中间件第6次执行
// 7中间件第7次执行
洋葱图由此可以看出中间件的执行顺序是先进后出的方式。类似于洋葱图。
五、获取请求数据
- GET 传值
-
Koa 中 GET传值通过request接收,有两种方式: query 和 querystring
query:返回的是参数对象。 {name: 'jack', age: 12}
querystring:返回的是请求字符串。 name=jack&age=12 -
query和querystring可以从request中获取,也可以直接从ctx中获取。
let request = ctx.request;
let query = request.query;
let querystring = request.querystring;
// 直接ctx获取
ctx.query
ctx.querystring
- POST 传值
通过post传递的值我们可以通过原生Node封装,也可以通过第三方模块接收。
- 自定义封装
const querystring = require('querystring');
module.exports = ctx => {
return new Promise((resolve, reject) => {
try {
let data = '';
// ctx.req实际上就是原生node中的req
ctx.req.on('data', (chunk) => {
data += chunk;
})
ctx.req.on('end', () => {
data = querystring.parse(data);
resolve(data);
})
}
catch(err) {
reject(err);
}
})
}
- 使用koa-bodyparser模块
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());
// 获取
ctx.request.body
六、处理静态资源
对于诸如js、css、img等静态资源采用koa-static中间件处理。
npm i koa-static
配置:
比如静态目录为static:
// 静态资源配置
// app.use(require('koa-static')('static'))
// or
// app.use(require('koa-static')('./static'))
// or
// app.use(require('koa-static')(__dirname + '/static'))
// or 使用path.join() 的时候,static前面的/可加可不加,该方法会内部会做处理
app.use(require('koa-static')(path.join(__dirname, 'static')));
在模板中即可访问:
<link rel="stylesheet" href="/css/header.css">
<img src="/image/account.eb695dc.png"/>
七、模板引擎
koa生态的模板引擎挺多的,比如ejs、art-template等。
1. koa-ejs
npm i koa-ejs
const render = require('koa-ejs');
- 配置
render(app, {
// views 视图根目录
root: path.join(__dirname, 'views'),
layout: 'template', // 公用文件 若要禁用,设置为false即可
viewExt: 'html', // 扩展名
cache: false, // 缓存 default true
debug: false // 如果开启debug模式,则会在终端实时打印信息 default false
});
- 使用
/**
* 参数1: 模板名
* 参数2: 数据(可选)
*/
await ctx.render(templateName, data);
2. art-template
npm install --save art-template
npm install --save koa-art-template
- 配置
const render = require('koa-art-template');
// 配置模板引擎
render(app, {
root: path.join(__dirname, 'views'), // 视图目录
extname: '.html',
debug: process.env.NODE_ENV !== 'production'
});
使用方式和ejs一样。
性能上相比,art-template比ejs快很多,开发中用的最多的还是art-template。
八、cookie 和 session
cookie
- cookie简单介绍
http是无状态的。比如访问淘宝首页并登录账号后,当再打开淘宝其他页面时,因为每一次的访问都是独立的,服务器并不知道你已经登录,所以还是不能下单或者加购物车之类的操作。
cookie是当第一次访问服务器的时候,服务器在下行HTTP报文时给浏览器分配一个具有特殊标识的字段,此后当浏览器再次访问同一域名的,将该字段t通过请求头携带到服务器。第一次访问服务器是不可能携带cookie的。
- Koa中使用cookie
- 设置cookie
ctx.cookies.get(name, [options])
通过 options 获取 cookie name:
signed 所请求的cookie应该被签名
- 设置cookie
ctx.cookies.set(name, value, [options])
通过 options 设置 cookie name 的 value:
- maxAge: 一个数字表示从 Date.now() 得到的毫秒数
- signed: cookie 签名值
- expires: cookie 过期的 Date
- path: cookie 路径, 默认是'/'
- domain: cookie 域名
- secure: 安全 cookie
- httpOnly: 服务器可访问 cookie, 默认是 true
- overwrite: 一个布尔值,表示是否覆盖以前设置的同名的 cookie (默认是 false). 如果是 true, 在同一个请求中设置相同名称的所有 Cookie(不管路径或域)是否在设置此Cookie 时从 Set-Cookie 标头中过滤掉。
设置中文cookie
通过buffer转成base64存进去,取出来是再转回中文。
// 转base64
Buffer.from('张三').toString('base64');
// 转回中文
Buffer.from(buf, 'base64').toString();
session
- seeion简单介绍
session是另一种记录客户状态的机制,不同的是cookie保存在客户端浏览器中,而session保存在服务器上。
cookie 是存放在客户端,不是很安全,用户可以自己手动把cookie种在客户端以欺骗服务器。而session是存储在服务端的,所以对于较重要的数据存储在session。
-
session 的工作机制
当浏览器第一次请求服务器时,服务器端会创建一个session对象,生成一个类似于key-value的键值对, 然后将key(cookie)下发到浏览器(客户)端,浏览器再访问时,携带key(cookie),找到对应的session(value)。 生产中用户的信息都保存在session中。 -
koa-session 的使用
npm install koa-session
const Koa = require('koa');
const session = require('koa-session');
const app = new Koa();
app.keys = ['some secret hurr'];
const CONFIG = {
key: 'koa:sess', // 返给浏览器 cookie 的key 默认是 'kao:sess'
maxAge: 86400000, // cookie的过期时间 maxAge in ms (default is 1 days)
autoCommit: true, // (boolean) 自动给客户端下发cookie 并设置session
overwrite: true, // 是否可以覆盖之前同名的cookie (默认default true)
httpOnly: true, // cookie是否只有服务器端可以访问 httpOnly or not (default true)
signed: true, // 签名默认true
rolling: false, // 在每次响应时强制设置session标识符cookie,到期时被重置设置过期倒计时。(默认为false)
renew: false, // 当session快过期时更新session,这样就可以始终保持用户登录 默认是false
};
以上配置选项常用的就是key、maxAge、httpOver。
renew应用:比如我们登录账号写一篇博客,写了一半cookie过期了,当我们提交的时候就会退出登录,体验很不好,而且写好的博客丢失。
九、重定向
301和302重定向状态码区别。302为临时重定向,301永久重定向。Koa中默认为302。详细信息查看这篇博客 301和302重定向介绍
字符串 “back” 是特别提供Referrer支持的,当Referrer不存在时,使用 alt 或“/”。
ctx.redirect('back');
ctx.redirect('back', '/login');
ctx.redirect('/login');
要更改 “302” 的默认状态,只需在该调用之前或之后分配状态。要变更主体请在此调用之后:
ctx.status = 301;
ctx.redirect('/cart');
ctx.body = 'Redirecting to shopping cart';