koa
Koa 是⼀個新的 web 框架, 致⼒於成爲 web 應⽤和 API 開發領域中的⼀個更⼩、更富有表現⼒、更健壯的基⽯。
特點
- 輕量,無捆綁
- 中間件架構
- 優雅的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=`<h1>koa</h1>`
}
})
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`)
})
koa原理
⼀個基於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
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'
})