Koa2.x
1. Koa是基于Node.js的下一代web开发框架,也是由Express团队开发的;
1. Node.js是一个异步的世界,官方API支持的都是callback形式的异步编程模型;
2. Koa的开发思路与Express差不多,最大的特点就是,可以避免异步嵌套(回调地狱);
3. Koa不在内核方法中绑定任何中间件,仅仅提供一个轻量优雅的函数库,所以体积更小,更健壮
4. 阿里是业界最早一批使用Node.js做线上大流量应用的公司,并基于koa开发了egg.js
2. 环境搭建:koa2.0开始才算是稳定版,npm install koa --save
1. koa2.x要求Node.js的版本大于v7.6,因为node7.6开始完全支持async/await
2. 简单使用:const Koa = require('koa'); const app = new Koa();
app.use( async (ctx)=>{ -----> 配置中间件
ctx.body = 'hello koa2';
})
app.listen(3000, 'Ip') ---> 默认Ip为127.0.0.1
3. koa的工作内容
1. 接收请求(request) --> 处理数据,生成数据(middleware) --> 发送数据(response)
2. koa已经处理request和response,开发者只需要专心处理数据即可;
3. 通过 use() 注册中间件,处理数据、生成数据。
4. 热重载工具:npm i supervisor -g,启动:supervisor app.js
四大对象
1. koa的四大对象:Application -> Context -> Request、Response
2. Application:当前应用程序对象,由 new Koa() 创建的实例对象,Context是其子类;
let app = new Koa();
1. app.listen():启用监听;
2. app.use(callback):启用中间件的方法,callback(ctx, next)
3. ctx 就是 Context对象,next 是迭代器,实现手动控制执行过程;
4. 异步中间件:use(async(ctx, next) => { ... });
5. 监听错误:app.on('error', (err, ctx) => { ...//统计错误处理 });
app.use((ctx, next) => {
throw new Error(); ---> 把错误抛给error事件去处理
})
3. Context:每次请求都会包装成一个Context对象,它进一步封装了node的request和response
1. koa把Context传入到中间件函数的第一个参数ctx中;
2. ctx.req、ctx.res:分别是Node的request对象和response对象(太原始,并不推荐使用)
3. ctx.request、ctx.response:分别是Koa的request对象和response对象,包含更多信息
4. ctx.state:用户数据存储空间,ctx.username会污染Context对象,所以用ctx.state
app.use((ctx, next) => {
ctx.state.username = 'abc';
next();
});
app.use((ctx, next) => { --> 同一个请求的Context对象也都是同一个
console.log(ctx.state.username);
});
5. ctx.app:当前应用程序的Application对象
6. ctx.throw():抛出错误时不建议使用 throw new Error(); 它属于JS,而throw()带有
http的错误信息,如抛出404错误:ctx.throw(404, '错误信息', {附带参数});
4. Request、Response
1. ctx.request、ctx.response分别获取本次请求的Request对象和Response对象;
2. Request和Response可以获取/设置本次请求和响应的各种信息;
3. Request和Response的很多方法会被映射到 Context 上,通过 ctx 直接调用;
4. 重定向:ctx.redirect('/login'); -->ctx.response.redirect('/login');
5. 设置下载文件头:ctx.attachment(filename); -->ctx.response.attachment();
6. ctx.query 同 ctx.request.query,ctx.url 同 ctx.request.url ...
路由
1. 安装路由模块:cnpm install koa-router --save
2. 使用方式:let Router = require('koa-router');
let router = new Router(); ---> 等效于:let router = Router();
router.get('/', async(ctx, next) => {
ctx.body = 'Index'; ---------> 相当于 res.writeHead(); res.end();
})
router.get('/news', async(ctx, next) => {
ctx.body = 'News';
}) -----------------------> 可以使用链式调用:router.get().get().get();
app.use(router.routes());
app.use(router.allowedMethods()); ---->可以用链式调用,app.use().use();
1. app.use(router.routes()):启用路由;
2. app.use(router.allowedMethods()):不是必须,但官方推荐,写在所有路由后面;
3. allowedMethods() 的作用是会根据 ctx.status 设置response的响应头。
3. router.redict():路由重定向
1. router.redict('/admin', '/user', 301);
2. 访问 /admin 时,会被重定向到 /user
4. URL生成器:Router.url();
1. Router.url('/list', {page: 1}, {query: {order:'desc'}});
2. 生成路由:/list/1?order=desc
参数传递
1. get传值
router.get('/news', async(ctx, next) => {
ctx.body = 'News';
}) --> http://localhost:3000/news?aid=123&uname=Jack
1. 接收get传值的方式有两种:query(格式化的参数对象)和querystring(请求字符串)
2. ctx.query:获取的是参数对象,{ aid:'123', uname:'Jack' }
3. ctx.querystring:请求字符串的形式,aid=123&uname=Jack
4. ctx.url:请求的路由地址,/news?aid=123&uname=Jack
2. 动态路由
router.get('/news/:aid', async(ctx, next) => {
ctx.body = 'News';
}) --> http://localhost:3000/news/123
1. ctx.params:获取动态路由的参数对象,{ aid: '123' }
2. /news/:aid/:uname:多个占位符,http://localhost:3000/news/123/Mack
中间件
1. 在express中,中间件必须写在所有路由之前,而在koa中不管写在哪个位置,都会优先匹配;
2. 应用级中间件
app.use(async (ctx)=>{
ctx.body = '应用级中间件'
})
1. 用于匹配所有路由,一旦匹配成功,将不再自动向下匹配;
2. 手动让路由继续向下匹配
app.use(async (ctx, next)=>{
...
await next(); ---> 继续向下匹配
})
3. 路由中间件
router.get('/news', async(ctx, next) => {
console.log('首次匹配news')
await next(); ---> 继续向下匹配
})
router.get('/news', async(ctx, next) => {
ctx.body = 'News';
})
4. 错误处理中间件
app.use(async (ctx, next)=>{
console.log('1', '中间件')
await next(); ----------------->去匹配路由,不会再继续向下执行,等待匹配结果
console.log('2', ctx.url)
if(ctx.status == 404) { ---> 路由不存在,匹配失败,进行错误处理
console.log('3', '404')
ctx.body = '404页面'
} else { ---------------------> 匹配成功
console.log('4', ctx.url)
}
})
router.get('/news', async(ctx, next) => {
console.log('5', 'news')
ctx.body = 'News'
})
1. 访问http://localhost:3000/xxx --> 1 --> 不存在此路由,匹配失败 --> 2 --> 3
2. 访问http://localhost:3000/news --> 1 --> 5 --> 2 --> 4
5. koa的洋葱模型
1. koa的匹配过程(request-->response)类似于一个洋葱
koa2.png
2. 由外而内,再由内而外:
中间件-1 --> 中间件2 --> 匹配路由,返回匹配结果 --> 中间件2 --> 中间件1
app.use(async(ctx, next)=>{ ----------> 中间件-1
console.log('1-1')
await next();
console.log('1-2')
})
app.use(async(ctx, next)=>{ ----------> 中间件-2
console.log('2-1')
await next();
console.log('2-2')
})
router.get('/news', async(ctx, next) => {
console.log('news')
ctx.body = 'News'
})
3. 匹配过程:1-1 ==> 2-1 ==> news ==> 2-2 ==> 1-2
POST
1. 原生NodeJs获取POST提交的数据:<input type="text" name="uname" />
function parserPost(ctx) {
return new Promise((resolve, reject) => {
try{
let postData = "";
ctx.req.on('data', (chunk)=>{
postData += chunk.toString();
});
ctx.req.on('end', ()=>{ ---> 数据传输完成
resolve(postData);
});
} catch(error) {
reject(error);
}
});
}
router.post('/dologin', async(ctx) => {
let data = await parserPost(ctx);
ctx.body = '登录成功!'
})
2. 中间件处理POST提交的数据:npm i koa-bodyparser --save
1. 配置中间件:let parser = require('koa-bodyparser');
app.use(parser());
2. 获取post提交的数据
router.post('/dologin', async (ctx)=>{
let postData = ctx.request.body;
});
3. 在url中,? 后的内容称为querystring
1. querystring是url的一部分,与提交方式(无论是post还是get)没有任何关系;
2. url的长度有限,所以在数据量较大时,不推荐使用querystring的方式传输,跟get没关系;
3. post请求也可以在url上使用querystring,在后台也可以获取此参数;
4. 不同的是,get方式不能操作正文,而post可以把参数写入正文(请求体)
静态服务
1. 静态资源中间件:npm install koa-static --save
2. 配置中间件:const static = require('koa-static');
1. app.use(static('static')); ----> 托管 根目录/static 下的资源文件
2. 在模板中访问static/css/base.css:
<link rel="stylesheet" href="css/base.css" /> ---> href="/css/base.css"
3. koa静态资源的目录也可以配置多个:
app.use(static('static')); ---> 根目录/static
app.use(static(path(__dirname, 'public'))); ---> 根目录/public
1. 在匹配资源时,会先在static目录下查找,如果查找失败,再去public目录下查找;
2. app.use(static('.')); -->表示去根目录下去查找
<img src="public/img/a.png" /> --> 根目录/public/img/a.png
4. app.use(static('.'))是不安全的,它能访问根目录下的所有资源,如app.js、package.json
模板引擎
适用于koa的模板引擎有很多,如ejs、jade、nunjucks、art-template...
koa与ejs
1. 安装ejs之后,安装koa-views:npm install koa-views --save
2. 多种方式配置koa-views中间件,const views = require('koa-views');
1. app.use(views('views', { map:{ html:'ejs' } })); -->模板的后缀名为html
2. app.use(views('views', { extension:'ejs' })); -->模板的后缀名为ejs
3. views()的第一个参数表示ejs模板存放的目录,'views'表示在根目录/views
3. 在根目录/views中创建index.ejs
router.get('/', async(ctx) => {
await ctx.render('index', {key: value}) --->渲染index.ejs
})
4. 公共数据(全局变量)
1. 在koa中,通过 ctx.state 管理公共数据,在所有模板中都能访问;
2. 在中间件中设置公共数据
app.use(async(ctx, next) => {
ctx.state = { uname: 'Mack', age: 20 } ----> 设置公共数据uname和age
await next();
})
3. 在模板中使用:<h2><%= uname %></h2>
art-template
1. art-template是一个简约、超快的模板引擎,它采用作用域预声明的技术来优化模板渲染速度;
1. art-template的模板渲染速度接近JavaScript极限地运行性能,且同时支持NodeJs和浏览器
2. art-template支持ejs语法,也可以用类似angular数据绑定地语法
3. 由腾讯开发的,同时支持Express、Koa
2. 渲染速度:art-template(15-25ms) > jade(51ms) > ejs(141ms)
3. 安装:npm install art-template --save,npm install koa-art-template --save
const render = require('koa-art-template');
render(app, {
root: path.join(__dirname, 'views'), ---> 配置模板的位置:根目录/view
extname: '.art', ---> 模板的扩展名,也可以设置为'.html'
debug: process.env.NODE_ENV !== 'production' --> 开发环境下开启调试模式
})
app.use(async (ctx)=>{
await ctx.render('index', {key: value}); --> 根目录/views/index.art
})
4. art-template支持类似ejs的语法(原始语法),也支持类似angular的语法(标准语法)
1. 原始语法:<%= value %>,<%= a?b:c %> ,<%= a+b %>,<%- value %> ...
2. 引入子模板的方式与ejs不同:<% include('./public/header.art') %>
<% include('./public/header.art', data) %>
5. 标准语法:{{value}},{{a?b:c}},{{a+b}},{{@value}},{{if a}} ... {{/if}}
1. {{@value}}:表示原样输出value,如value="<p>123</p>",原样输出的结果就是<p>标签
2. 循环数据:{{each a}} {{$index}} {{$value}} {{/each}}
3. 引入子模板:{{include './header.art'}},{{include './header.art', data}}
4. 引入不在同一目录下的子模板时,不能使用相对路径,而是直接相对于模板的根目录;
{{include 'admin/public/header.html'}} --->views/admin/public/header.html
6. 在标签内直接使用模板语言
<input type="checkbox" {{if list.check==1}} checked {{/if}} />
Cookie
1. Koa可以直接操作cookie:ctx.cookies.set(name, value, [options])
router.get('/', async (ctx)=>{
ctx.cookies.set('uname', 'Jack', {
maxAge: 60*1000*60 ---> 60s后过期
})
})
1. 获取cookie:ctx.cookies.get('uname')
2. options的可选参数:maxAge、expires、path、domain、secure、httpOnly、overwrite
1. httpOnly:是否只允许服务器访问cookie,默认为true,不允许浏览器端的JS访问cookie
2. secure:安全的cookie,默认为false,设置为true表示只允许https可以访问;
3. overwrite:是否覆盖以前设置的同名cookie,默认为false
ctx.cookies.set('uname', 'Jack', {
path: '/news', ---> 只允许 /news 路由页面可以访问此cookie
domain: '.baidu.com' -->设置允许访问cookie的域名,默认当前域名下的所有路由页面
}) -------------------------> 都可以访问,所以正常情况下不会设置
3. 在koa中无法直接设置中文的cookie,如ctx.cookies.set('uname', '李蕾');
1. 可以把中文转为base64编码:new Buffer('李蕾').toString('base64');
2. 解码:new Buffer('base64编码数据', 'base64').toString();
Session
1. 安装中间件:npm i koa-session --save
2. 配置session中间件
1. const session = require('koa-session');
2. app.keys = ['some secret']; ---> cookie的签名/加密,可以是任意字符串
2. app.use(session(CONFIG, app));
3. CONFIG表示session的配置
const CONFIG = {
key: 'koa:sess', ---> 也类似于cookie的key,使用默认配置即可
maxAge: 86400000, ---> cookie的过期时间
overwrite: true,
httpOnly: false, ---> 默认为true,表示只允许服务器端访问cookie
signed: true, ----> 启用签名
rolling: false,
renew: false
}
1. rolling:true --> 浏览器每次访问都强制设置cookie,每次都会重置过期时间
2. renew:true --> 每次访问时会检查cookie的过期时间,在将要过期时才会重置过期时间
4. 设置/获取:ctx.session.uname='Mack'; let uname=ctx.session.uname;
网友评论