Koa 是⼀个新的 web 框架, 致⼒于成为 web 应⽤和 API 开发领域中的⼀个更⼩、更富有表现⼒、更健壮的基⽯。
特点
安装
npm i koa -s
简单的写法
const Koa = require('koa')
const app = new Koa();
app.use((ctx,next)=>{
ctx.body=[
{
content:'leo'
}
];
next()
})
app.use((ctx,next)=>{
if(ctx.url === '/index'){
ctx.type = 'text/html;charset=utf-8';
ctx.body=`koa
`
}
})
app.listen(3000,()=>{
console.log(`start server at localhost:3000`)
})
安装koa-router
npm i koa-router -s
const Koa = require('koa')
const app = new Koa();
const Router = require('koa-router')
const router = new Router()
app.use(router.routes())
router.get('/',async (ctx,next)=>{
ctx.body = [
{
content:'leo'
}
]
})
router.get('/index',async(ctx,next)=>{
ctx.body=[
{
content:'index page'
}
]
})
app.listen(3000,()=>{
console.log(`start server at localhost:3000`)
})
⼀个基于nodejs的⼊⻔级http服务,koa的⽬标是⽤更简单化、流程化、模块化的⽅式实现回调部分。
Koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量的函数库,几乎所有功能都需要引用第 方中间件来实现。这样不仅能够使框架更加轻量、优雅,也能够让开发者充分发挥自己的想象力,根据业务和项目定制中间件。
根据koa原理实现一个简单的类库:
index.js
const http = require('http');
class Lau{
listen(...args){
const server = http.createServer((req,res)=>{
this.callback(req,res)
})
server.listen(...args)
}
use(callback){ // 使用中间件
this.callback = callback
}
}
const app = new Lau();
app.use((req,res)=>{
res.writeHead(200);
res.end('hi lau')
})
app.listen(3000,()=>{
console.log(`start server at localhost:3000`)
})
上面的代码对请求进行了非常简单的封装。
来看一下koa中非常重要的概念:context
Koa将Node.js的Request(请求〉和 Response (响应)对象封装到 Context 对象中,所以也可以把 Context 对象称为一次对话的上下文,在context上设置getter和setter,从⽽简化操作。
封装request、response、context
request.js
module.exports = {
get url(){
return this.req.url
},
get method(){
return this.req.method.toLowerCase()
}
}
只简单展示几个,其他类似。
response.js
module.exports={
get body(){
return this._body
},
set body(val){
this._body = val
},
}
context.js
module.exports={
get url(){
return this.request.url
},
get body(){
return this.response.body
},
set body(val){
this.response.body = val
},
get method(){
return this.request.method
}
}
在index.js文件中引入上面三个文件,进行改进。
const http = require('http');
const context = require('./nativeKoa/context')
const request = require('./nativeKoa/request')
const response = require('./nativeKoa/response')
class Lau{
listen(...args){
const server = http.createServer((req,res)=>{
let ctx = this.createContext(req,res); // 创建上下文
this.callback(ctx)
res.end(ctx.body)
})
server.listen(...args)
}
use(callback){
this.callback = callback
}
createContext(req, res) {
const ctx = Object.create(context);
ctx.request = Object.create(request);
ctx.response = Object.create(response);
ctx.req = ctx.request.req = req;
ctx.res = ctx.response.res = res;
return ctx;
}
}
const app = new Lau();
app.use((ctx)=>{
if(ctx.url === '/'){
ctx.body='hello lau'
}
})
app.listen(3000,()=>{
console.log(`start server at localhost:3000`)
})
Koa中间件机制就是函数组合的概念,将⼀组需要顺序执⾏的函数复合为⼀个函数,外层函数的参数实际是内层函数的返回值。洋葱圈模型可以形象表示这种机制,是源码中的精髓和难点。
中间件函数是一个带有 ctx next 两个参数的简单函数。 ctx 就是之前章节介绍的上下文,封装了 Request Response 等对 next 用于把中间件的执行权交给下游的中间件。
在next()之前使用 await 关键字是因为 next()会返回一个 Promise 对象,而在当前中间件中位next()之后的代码会暂停执行,直到最后一个中间件执行完毕后,再自下而上依次执行每
个中间件中 next()之后的代码,类似于一种先进后出的堆栈结构 。
来看一下next()
在koa中使用
app.use(async (ctx, next) => {
console.log('one start ');
await next();
console.log(' one end ');
});
app.use(async (ctx, next) => {
console.log(' two start ');
ctx.body = 'two';
await next();
console.log('two end ');
});
app.use(async (ctx, next) => {
console.log(' three start');
await next();
console.log(' three end ');
})
// 输出
one start
two start
three start
three end
two end
one end
实现中间件调用函数,将多个中间件组合成一个单一的中间件。
function compose(middlewares) {
return function (ctx) {
return dispatch(0)
function dispatch(i) {
let fn = middlewares[i]
if (!fn) {
return Promise.resolve()
}
return Promise.resolve(
fn(ctx,function next() { // 传入一个上下文
return dispatch(i + 1)
})
)
}
}
}
将该函数添加至index.js中进行完善。
class Lau{
constructor(){
this.middlewares=[]
}
listen(...args){
const server = http.createServer(async (req,res)=>{
let ctx = this.createContext(req,res); // 创建上下文
const fn = this.compose(this.middlewares)
console.log(fn.toString())
await fn(ctx)
res.end(ctx.body)
})
server.listen(...args)
}
use(middleware){
this.middlewares.push(middleware)
}
createContext(req, res) {
const ctx = Object.create(context);
ctx.request = Object.create(request);
ctx.response = Object.create(response);
ctx.req = ctx.request.req = req;
ctx.res = ctx.response.res = res;
return ctx;
}
compose(middlewares) {
return function (ctx) {
return dispatch(0)
function dispatch(i) {
let fn = middlewares[i]
if (!fn) {
return Promise.resolve()
}
return Promise.resolve(
fn(ctx,function next() {
return dispatch(i + 1)
})
)
}
}
}
}
规范:
ctx
和next
参数next()
实现一个简单的路由中间件
router.js
class Router{
constructor(){
this.stack=[]
}
register(path,methods,middleware){
let route = {path,methods,middleware}
this.stack.push(route)
}
get(path,middleware){
this.register(path,'get',middleware)
}
post(path,middleware){
this.register(path,'post',middleware)
}
// 只实现常用的post、get方法
routes(){
let stock = this.stack;
return async function (ctx,next){
let currentPath = ctx.url;
let route;
for(let i =0;i<stock.length;i++){
let item = stock[i];
if(currentPath === item.path && item.methods.indexOf(ctx.method)>=0){
route = item.middleware;
break;
}
}
if(typeof route === 'function'){
route(ctx,next); // 执行对应操作
return;
}
await next()
}
}
}
在index.js中进行改写
const Router = require('./router/index')
const router = new Router()
app.use(router.routes())
router.get('/',async (ctx,next)=>{
ctx.body='hi router'
})
router.get('/index',async (ctx,next)=>{
ctx.body='hi index page'
})