koa的原理与洋葱模型 koa是什么 express原班人马开发的极简web框架,为什么称为极简,因为只做了4件事 处理HTTP的request 处理http的response 中间件的加载 中间件的执行 所以我们在使用koa开发时,只需要做两件事:1、框架的设计2、中间件的选择 koa的文件结构 application.js 封装了context,request,response,中间件处理流程

koa的原理与洋葱模型

  • koa是什么

    express原班人马开发的极简web框架,为什么称为极简,因为只做了4件事

    1. 处理HTTP的request
    2. 处理http的response
    3. 中间件的加载
    4. 中间件的执行

    所以我们在使用koa开发时,只需要做两件事:1、框架的设计2、中间件的选择

  • koa的文件结构

    1. application.js 封装了context,request,response,中间件处理流程
    2. context.js 处理应用上下文
    3. request.js 处理http请求
    4. resonse.js 处理http响应
  • koa的异步处理

    koa是基于async/await操作中间件,node版本需要大于7 他是async/await generator/yield的衍生,但本质还是Promise

  • koa的AOP设计

    • APO设计:面向切面编程
    • 面向切面编程:在程序或者声明周期中,加入或减去一个或多个功能,不影响原有功能
    • koa的切面:使用中间件实现,一个中间件有两个切面,切面执行顺序,类似栈,先进后出
    • koa是洋葱模型HTTP中间件处理流程,处理流程就像剥洋葱,按照生命周期,执行中间件,有前置操作、其他操作和后置操作
    • koa的切面 koa的洋葱切面
  • 代码实现

let context = {}
async function m1(ctx,next){
  console.log('1')
  await next();
  console.log('2')
}
async function m2(ctx,next){
  console.log('3')
  await next();
  console.log('4')
}
async function m3(ctx,next){
  console.log('5')
  await next();
  console.log('6')
}
Promise.resolve(m1(context,async()=>{
  return Promise.resolve(m2(context,async()=>{
    return Promise.resolve(m3(context,async()=>{
      return Promise.resolve();
    }))
  }))
})).then(()=>{
  console.log("end");
})

输出的值分别是 1 3 5 6 4 2 ,其中生命周期就是Promise 中间件是m1 m2 m3 next前为前置操作,next后为后置操作,类似一个大回环,先前置操作的最后后置操作,就是这个道理

koa的服务器搭建

上篇博客里已经介绍了koa如何开启服务器,这里面就不说了,可以看上篇nodejs熟悉,http,fs,path,Buffer的使用

koa通过use导入中间件

// log.js
function log(ctx) {
  console.log(ctx)
}
module.exports = function () {
  return async function (ctx, next) {
    log(ctx)
    await next()
  }
}
// index.js
const Koa  = require("koa");
const Log = require("./logger");
const app = new Koa();
app.use(Log())
app.use((ctx)=>{
  ctx.body = "hello world"
})
app.listen(1111)

koa实现自定义路由

  • 访问127.0.0.1:1111
const Koa = require("koa");
const fs = require("fs")
const app = new Koa();

function render(page) {
  return new Promise((resolve, reject) => {
    let ViewUrl = `./test/${page}`;
    fs.readFile(ViewUrl, 'binary', (err, data) => {
      if (err) {
        reject(err)
      } else {
        resolve(data)
      }
    })
  })
}
async function route(url) {
  let view = "404.html"
  switch (url) {
    case "/":
      view = 'index.html';
      break;
    default:
      view = view;
      break;
  }
  let html = await render(url);
  return html;
}
app.use(async (ctx) => {
  let url = ctx.request.url;
  let html = await route(url);
  ctx.body = html;
})
app.listen(1111);

koa-router的使用

  • 访问127.0.0.1:1111
  • 访问127.0.0.1:1111/page/page1
  • 访问127.0.0.1:1111/page/page2
const Koa = require("koa");
const fs = require("fs");
const app = new Koa();
const Router = require("koa-router");

// 子路由1
let home = new Router();
home.get("/", async (ctx) => {
  ctx.body = "<h1>hello Router</>";
})
// 子路由2
let page = new Router();
page.get("/page1", async (ctx) => {
  ctx.body = "page1"
}).get("/page2", async (ctx) => {
  ctx.body = "page2"
})
// 装载路由
let router = new Router();
router.use('/', home.routes(), home.allowedMethods())
router.use('/page', page.routes(), page.allowedMethods())
// 加载路由中间件
app.use(router.routes()).use(router.allowedMethods())
app.listen(1111)

koa获取get请求参数

  • 访问127.0.0.1:1111/page/page1?id=1
const Koa = require("koa");
const app = new Koa();
app.use( async (ctx)=>{
  let url = ctx.url;
  let query = ctx.request.query;
  let querystring = ctx.request.querystring;

  let _query = ctx.query;
  let _querystring = ctx.querystring;

  ctx.body = {
    url,
    query,
    querystring,
    _query,
    _querystring
  }
})
app.listen(1111)

解析post请求上下文的表单数据

  • koa没有封装post请求参数
function parsePostData(ctx) {
  return new Promise((resolve, reject) => {
    try {
      let postdata = "";
      ctx.req.addListener('data', (data) => {
        postdata += data
      })
      ctx.req.addListener('end', () => {
        let parseData = parseQuery(postdata);
        resolve(parseData)
      })
    } catch (err) {
      reject(err)
    }
  })
}

function parseQuery(queryStr) {
  let queryData = {};
  let queryStrList = queryStr.split("&");
  for (let [index, queryStr] of queryStrList.entries()) {
    let itemList = queryStr.split("=");
    queryData[itemList[0]] = decodeURIComponent(itemList[1])
  }
  return queryData
}

const Koa = require("koa");
const app = new Koa();

app.use(async (ctx) => {
  if (ctx.method == "GET") {
    let html = `
      <h1>koa2 request post demo</h1>
      <form method="POST" action="/">
        <p>userName</p>
        <input name="userName" /><br/>
        <p>nickName</p>
        <input name="nickName" /><br/>
        <p>email</p>
        <input name="email" /><br/>
        <button type="submit">submit</button>
      </form>`
    ctx.body = html;
  }else if(ctx.method=='POST'){
    let postData = await parsePostData(ctx);
    ctx.body = postData;
  }
})
app.listen(1111)

koa-bodyparse的使用

  • koa-bodyparse是解析POST和GET请求的
const Koa = require("koa");
const app = new Koa();
const bodyParser = require("koa-bodyparser");

app.use(bodyParser());

app.use(async (ctx) => {
  if (ctx.method == "GET") {
    let html = `
      <form method="POST" action="/">
        <p>userName</p>
        <input name="userName" /><br/>
        <p>nickName</p>
        <input name="nickName" /><br/>
        <p>email</p>
        <input name="email" /><br/>
        <button type="submit">submit</button>
      </form>
    `
    ctx.body = html
  } else if (ctx.method == "POST") {
    let postData = ctx.request.body
    ctx.body = postData;
  }
})
app.listen(1111)

koa实现静态服务器功能

  • 在入口文件中建一个static的文件夹,里面放有静态文件
const Koa = require('koa');
const path = require("path");
const fs = require("fs");
const url = require("url");
const app = new Koa();
// 静态文件夹相对入口文件的位置
const staticPath = "./static";
// 定义文件类型
let mimes = {
  'css': 'text/css',
  'less': 'text/css',
  'gif': 'image/gif',
  'html': 'text/html',
  'ico': 'image/x-icon',
  'jpeg': 'image/jpeg',
  'jpg': 'image/jpeg',
  'js': 'text/javascript',
  'json': 'application/json',
  'pdf': 'application/pdf',
  'png': 'image/png',
  'svg': 'image/svg+xml',
  'swf': 'application/x-shockwave-flash',
  'tiff': 'image/tiff',
  'txt': 'text/plain',
  'wav': 'audio/x-wav',
  'wma': 'audio/x-ms-wma',
  'wmv': 'video/x-ms-wmv',
  'xml': 'text/xml'
}
// 读取目录内容
function walk(reqPath) {
  let files = fs.readdirSync(reqPath);
  let dirList = [];
  let fileList = [];
  for (let i = 0; i < files.length; i++) {
    let item = files[i];
    let itemArr = item.split("\.");
    let itemMime = (itemArr.length > 1) ? itemArr[itemArr.length - 1] : "undefined";
    if (typeof mimes[itemMime] === "undefined") {
      dirList.push(files[i]);
    } else {
      fileList.push(files[i])
    }
  }
  let result = dirList.concat(fileList);
  return result;
}
// 封装目录内容
function dir(url, reqPath) {
  let contentList = walk(reqPath);
  let html = "";
  for (let [index, item] of contentList.entries()) {
    html += `<li><a href="${url==='/'?'':url}/${item}">${item}</a></li>`
  }
  return "<ul>" + html + "<ul/>"
}
// 读取文件方法
function file(filePath) {
  let content = fs.readFileSync(filePath, 'binary');
  return content;
}
// 获取静态文件内容
async function content(ctx, fullStaticPath) {
  let reqPath = path.join(fullStaticPath, ctx.url);
  let exist = fs.existsSync(reqPath);
  let content = "";
  if (!exist) {
    content = "404";
  } else {
    let stat = fs.statSync(reqPath);
    if (stat.isDirectory()) {
      content = dir(ctx.url, reqPath);
    } else {
      content = await file(reqPath);
    }
  }
  return content;
}
// 解析请求内容
function parseMime(url) {
  let extName = path.extname(url);
  extName = extName ? extName.slice(1) : "unknown";
  return mimes[extName]
}
// 使用中间件
app.use(async (ctx) => {
  let fullStaticPath = path.join(__dirname, staticPath);
  let _content = await content(ctx, fullStaticPath);
  let _mime = parseMime(ctx.url)
  if (_mime) {
    ctx.type = _mime;
  }
  if (_mime && _mime.indexOf("image/") >= 0) {
    ctx.res.writeHead(200)
    ctx.res.write(_content, 'binary')
    ctx.res.end();
  } else {
    ctx.body = _content;
  }
})
app.listen(1111);

koa-static实现静态文件夹

const Koa = require("koa");
const path = require("path");
const static = require("koa-static");
const app = new Koa();

const staticPath = "./static"

app.use(static(
  path.join(__dirname, staticPath)
))

app.use((ctx) => {
  ctx.body = "hello "
})
app.listen(1111)

总结

  • koa相对比express来说,非常轻量,灵活度很大,开发者可以针对自己的项目使用合适的中间件,极大提高了工作效率,更重要的是async/await的使用,解决了回调嵌套的问题,代码看起来不冗余,处理异步操作更加舒服。
  • 该篇只是介绍了koa的用法和原理,koa源码的学习,放在了以后,毕竟后台管理系统的重点不是koa的源码。

暂无评论