1.koa介绍
Koa 是现在最流行的基于Node.js平台的web开发框架,由 Express 原班人马打造,利用 async 函数,Koa丢弃了回调函数,并有力地增强错误处理.体积小。
koa原理:組合comprose方法,把多个promise组合一个promise,主要靠递归的方式,先取出第一个,第一个会等待第二个方式执行。
const Koa = require('koa'); // babel
const app = new Koa(); // 创建一个app实例
// 请求到来时会执行此方法
app.use((ctx)=>{
ctx.body = 'hello'
});
app.listen(3000);
// 1.listen 方法
// 2.use 方法
// 3.ctx 上下问对象
// 4.监控错误
目录结构
- application 应用文件 默认引用的
- req/res (node中的默认的 req,res原生的)
- request response (这个属性是koa自己封装的)
- context 上下文 整合 req\res\request\response
2.三大模块
- Application: 基本服务器框架
- Context: 服务器框架基本数据结构的封装,用以 http 请求解析及响应
- Middleware: 中间件,也是洋葱模型的核心机制
3.req和request和ctx的关系
-
创建app的应用
packge.json文件路径改为自己的,
"main": "./lib/application.js"
//server.js const Koa = require('./koa'); // 创建app的应用 let app = new Koa(); app.use((ctx)=>{ res.end("hello") }); app.listen(3000,()=>{ console.log(`server start 3000`) }); //application.js const http = require('http'); module.exports = class Application { constructor() { this.handleRequest = this.handleRequest.bind(this) } use(callback) { this.callback = callback; } handleRequest(req,res){ // this //请求到来时需要执行use方法 this.callback(req, res); }; listen(...args){ //通过bind绑定这个方法,可以在构造函数中绑定 //箭头函数的方式改变this const server = http.createServer(this.handleRequest.bind(this)); server.listen(...args); } } -
实现ctx
主要考虑this指向的问题.
思考:1.req 和 request的区别 request.req = req;
-
ctx.path = ctx.request.path 为什么?
解释:ctx内部就是defineProperty 代理,它会去content.js找getPath方法,返回的就是request.path
//server.js app.use((ctx)=>{ console.log(ctx.req.url); // 原生的url // 自己封装的request上有原生的req属性 console.log(ctx.request.req.url); //上面2个是等价的,下面2个是等价的 // pathname => url.path() console.log(ctx.request.path); // 自己封装的url属性 //简写 console.log(ctx.path); // 他表示的是 ctx.request.url });// 通过请求和响应 + 自己封装的request和response 组成一个当前请求的上下文 handleRequest(req,res){ let ctx = this.createContext(req,res); this.callBack(ctx) }context.js需要很多方法例如get url ,get path,升级版的写法defineGetter('request','url');
defineGetter('request','path');
看以下代码解释
let obj = { url:{ a:1 } } // obj.a => obj.url.a // 废弃的方法 obj.__defineGetter__('a',function () { return obj.url.a }) console.log(obj.a)//完整版的context.js let context = { // get url(){ // 这里也是一个代理的机制 相当于你去 ctx.url ctx指代的并不是我们的context文件指的是我们在应用中拷贝出来的一份 ,拷贝的一份上 context.request.url // return this.request.url // }, // get path(){ // return this.request.path // } } // defineProperty proxy function defineGetter(target,key){ context.__defineGetter__(key,function () { // context.url = > context.request.url // console.log(this.__proto__.__proto__ === context); return this[target][key]; }) } function defineSetter(target,key){ context.__defineSetter__(key,function (value) { this[target][key] = value; }) } //等价于上面的get url(){return} defineGetter('request','url'); defineGetter('request','path'); defineGetter('request','query'); defineGetter('response','body'); defineSetter('response','body'); module.exports = context; -
-
request
const url = require('url'); let request = { get url() { // 这里的this 指代的是 ctx.request 因为使用的时候是通过 ctx.request.url来使用的 // ctx.request.req = req return this.req.url; }, get path() { return url.parse(this.req.url).pathname }, get query() { return url.parse(this.req.url).query } } // 导出request对象 module.exports = request; // 对象的属性访问器 -
response
let response = { _body: undefined, set body(value) { this.res.statusCode = 200; this._body = value; }, get body() { return this._body } } module.exports = response; // defineProperty
4.koa-compose方法
compose,顾名思义,相当于把所有注册的中间件组合,它规定了中间件的执行顺序,返回一个“大”中间件,这个大中间件,就在 node 的 http 服务
onRequest事件的时候执行。
难点在中间件.洋葱模型。
把多个promise 组合成一个promise 这个promise完成后 去拿 最终的结果显示给用户
//洋葱模型
const Koa = require('koa');
const app = new Koa();
// koa 里面用的是洋葱模型 把方法都套起来 co -> next
// koa中可以使用async + await方法
app.use(async function (ctx,next) {
console.log(1);
next()
console.log(2);
})
app.use(async function (ctx,next) {
console.log(3);
next();
console.log(4);
})
app.use(function (ctx,next) {
console.log(5);
next();
console.log(6);
})
// 输出1 3 5 6 4 2
app.listen(3000);
// 把多个promise 组合成一个promise 这个promise完成后 去拿 最终的结果显示给用户
-
middleware: 第一个中间件将会执行 -
next: 每个中间件将会通过 next 来执行下一个中间件。//middleware.js const Koa = require('koa'); const app = new Koa(); const sleep = async ()=>{ await new Promise((resolve,reject)=>{ setTimeout(() => { console.log('睡觉') resolve(); }, 1000); }) } // koa 里面用的是洋葱模型 把方法都套起来 co -> next // koa中可以使用async + await方法 // 我们执行koa时 每个next 方法都需要前面都要增加await 否则不会有等待效果 // koa为了统一处理错误 就将每个函数都转化成promise ,为了方便错误处理 // 整理http的header // 用reduce() 来实现函数的组合 (redux-compose) app.use(async function (ctx,next) { // console.log(1); // await next(); // 这里只是调用next函数并没有等待的效果 // next(); // next() // console.log(2); await next(); next(); }) // app.use(async function (ctx,next) { // console.log(3); // await sleep(); // await next(); // console.log(4); // }) // app.use(function (ctx,next) { // console.log(5); // // throw new Error('我错了'); // next(); // console.log(6); // }) app.on('error',function (err) { console.log(err); }) app.listen(3000); // 把多个promise 组合成一个promise 这个promise完成后 去拿 最终的结果显示给用户在application.js用
this.middlewares = []; // 存放所有的use方法 use(callback){ this.middlewares.push(callback); // [fn,fn,fn] } //组合多个函数 this.compose(ctx).then(()=>{ let body = ctx.body; } compose(ctx){ // 默认将middlewares 里的第一个执行 let index = -1; // 默认一次都每调用 const dispatch = (i)=>{ // 0 let middleware = this.middlewares[i]; // 第一次调用将值保存到了index中 if (i ==index){ return Promise.reject(new Error('next() called multiple times my ----')) } // index = 1 index = i; // 相当于第一次调用时 我把index 变成0 // 如果执行完毕后 有可能返回的不是promise if(i === this.middlewares.length){ return Promise.resolve(); } // 链式等待 默认先执行第一个 之后如果用户调用了await next() try{ // 这里需要增加try/catch 否则直接抛错 需要捕获异常 return Promise.resolve(middleware(ctx,()=>dispatch(i+1))); }catch(e){ return Promise.reject(e); } } return dispatch(0); // 默认取出第一个执行 }
5.koa返回不同的body内容
application.js组合多个函数,1默认是字符串或者buffer2可能是个流Stream3是一个对象4数字number
handleRequest(req,res){
// this
// 通过请求和响应 + 自己封装的request和response 组成一个当前请求的上下文
let ctx = this.createContext(req,res);
// 组合多个函数
res.statusCode = 404;
this.compose(ctx).then(()=>{
let body = ctx.body;
// 默认是字符串或者buffer
if(typeof body === 'string' || Buffer.isBuffer(body)){
res.end(body);
}else if(body instanceof Stream){
// 需要下载此文件
res.setHeader('Content-Disposition',`attachment;filename=${ctx.path.slice(1)}`);
body.pipe(res);
}else if(typeof body == 'object'){
res.end(JSON.stringify(body));
}else if(typeof body == 'number'){
res.end(body + '');
}else{
res.end('Not found')
}
}).catch(err=>{
this.emit('error',err);
})
}
body.js
const Koa = require('./koa');
const app = new Koa();
const fs = require('fs');
const file = fs.createReadStream('./1.server.js')
app.use((ctx,next)=>{
ctx.body = '123'
})
app.on('error',function (err) {
console.log(err);
})
app.listen(3000);
当访问 post /form的时候 我需要获取用户在表单中填写的数据
当访问 get /form 给用户显示个表单
需要一个中间件npm install koa- bodyparser
代码如下
// 当访问 post /form的时候 我需要获取用户在表单中填写的数据
// 当访问 get /form 给用户显示个表单
const Koa = require('koa');
const bodyParser = require('./koa-bodyparser');
const static = require('./koa-static')
const app = new Koa();
// 为了方便传递参数 所以把中间件 都封装了一个个函数
app.use(bodyParser());
// 中间件
app.use(async (ctx, next) => {
if (ctx.method === 'GET' && ctx.path == '/form') {
ctx.body = `<form action="/form" method="POST">
用户名<input type="text" name="username"> <br/>
密码<input type="text" name="password"> <br/>
<button>提交 </button>
</form>`
} else {
await next(); // undefined需要等待,必须加app.use(async
}
});
// koa中不能处理回调函数的方式 所有的异步需要全部封装成promise即可
// const body = (ctx)=>{
// return new Promise((resolve,reject)=>{
// let arr =[]
// ctx.req.on('data',function (chunk) {
// arr.push(chunk);
// });
// ctx.req.on('end',function () {
// resolve(Buffer.concat(arr).toString())
// })
// })
// }
app.use(async (ctx, next) => {
if (ctx.method === 'POST' && ctx.path == '/form') {
ctx.body = await ctx.request.body;
}else{
next();
}
});
app.listen(3000);
app.use(static(__dirname))
app.use(static(require('path').join(__dirname,'node_modules')))
koa- bodyparser.js
中间件的特点 可以统一处理逻辑,如果处理完毕后 需要继续向下执行next()
const bodyparser = ()=>{
return async (ctx,next)=>{
// 中间件的特点 可以统一处理逻辑
// 如果处理完毕后 需要继续向下执行
await new Promise((resolve,reject)=>{
let arr =[]
ctx.req.on('data',function (chunk) {
arr.push(chunk);
});
ctx.req.on('end',function () {
ctx.request.body = Buffer.concat(arr).toString()
resolve();
})
});
return next()//返回结果,需要后续逻辑
}
}
module.exports = bodyparser;
return next() 和await的区别:
后面没有逻辑了,return;还需要后续处理await
静态服务中间件const static = require('./koa-static')
let fs = require('fs').promises;
let path = require('path')
const static = (filePath) => {
return async (ctx, next) => {
let pathUrl = path.join(filePath,ctx.path);
try{
let statObj = await fs.stat(pathUrl);
if (statObj.isFile()) {
ctx.set('Content-Type', 'text/html;charset=utf-8');
ctx.body = await fs.readFile(pathUrl)
} else {
await next();
}
}catch(e){
await next()
}
}
}
module.exports = static;