koa-static

koa-static能够解决动态资源,参数是动态资源文件夹门路,官网的实现蕴含了更多的参数配,具体能够查看 koajs/static

实现剖析:

  • 获取申请url门路,查找动态文件夹下的门路是否能匹配
  • 如果是门路是文件夹,查找文件夹下index.html文件
  • 设置响应头文件类型(mime)
  • gzip压缩,设置压缩类型并返回可读流
  • 出错或文件不存在next持续下一个中间件
const fs = require("fs")const path = require("path")const mime = require("mime")const zlib = require("zlib")function static(dir) {  return async (ctx, next) => {    try {      let reqUrl = ctx.path      let abspath = path.join(dir, reqUrl)      let statObj = fs.statSync(abspath)      // 如果是文件夹,拼接上index.html      if (statObj.isDirectory()) {        abspath = path.join(abspath, "index.html")      }      // 判断门路是否精确      fs.accessSync(abspath);      // 设置文件类型      ctx.set("Content-Type", mime.getType(abspath) + ";charset=utf8")      // 客户端容许的编码格局,判断是否须要gzip压缩      const encoding = ctx.get("accept-encoding")      if (/\bgzip\b/.test(encoding)) {        ctx.set("Content-Encoding", "gzip")        ctx.body = fs.createReadStream(abspath).pipe(zlib.createGzip())      } else if (/\bdeflate\b/.test(encoding)) {        ctx.set("Content-Encoding", "bdeflate")        ctx.body = fs.createReadStream(abspath).pipe(zlib.createDeflate())      } else {        ctx.body = fs.createReadStream(abspath)      }    } catch (error) {      await next()    }  }}module.exports = static

koa-bodyparser

koa-bodyparser能够解决POST申请的数据,将form-data数据解析到ctx.request.body,官网地址:koa-bodyparser

实现剖析:

  • 读取申请数据
  • 设置响应头类型application/json
  • 判断客户端申请头content-type类型
  • 解析数据并绑定到ctx.request.body
function bodyParser() {  return async (ctx, next) => {    await new Promise((resolve, reject) => {      let data = []      ctx.req.on("data", (chunk) => {        data.push(chunk)      })      ctx.req.on("end", () => {        let ct = ctx.get("content-type")        let body = {}        ctx.set("Content-Type", "application/json")        if (ct === "application/x-www-form-urlencoded") {          body = require("querystring").parse(Buffer.concat(data).toString())        }        if (ct === "application/json") {          body = JSON.parse(Buffer.concat(data).toString())        }        ctx.request.body = body        resolve()      })      ctx.req.on("error", (error) => {        reject(error)      })    })    await next()  }}module.exports = bodyParser

koa-router

koa-router能够让koa像express一样管制路由,源码koa-router实现比较复杂,这里实现一个简略版本。

实现剖析:

  • 先将申请保留到数组middlewares
  • routes返回中间件函数,获取ctxnext
  • 从数组middlewares过滤出门路/办法雷同的数据
  • 中间件解决,传入ctx和封装后的next,解决完结才调用真正的next办法
class Router {  constructor() {    // 保留中间件办法的数组    this.middlewares = []  }  get(path, handler) {    // get、post、delete、put这些办法都要解决,这里只实现get    this.middlewares.push({ path, handler })  }  compose(routes, ctx,next) {    // 派发每一个存储的中间件办法    const dispatch = (index) => {      // 解决实现才执行上下文的next      if (routes.length === index) return next();      // 将中间件的next包装成下个执行的dispath,避免屡次执行上下文的next办法      routes[index].handler(ctx, () => dispatch(++index))    }    dispatch(0)  }  routes() {    return async (ctx, next) => {      // 过滤出雷同的存储对象      let routes = this.middlewares.filter((item) => item.path === ctx.url)      this.compose(routes, ctx,next)    }  }}module.exports = Router;

koa-better-body

koa外面解决文件上传应用的是koa-better-body,这里须要保障表单中带有multipart/form-data

<form action="/submiturl" method="POST" enctype="multipart/form-data">

上面是通过表单enctype为multipart/form-data提交后盾拿到的数据:

------WebKitFormBoundaryfCunWPksjjur83I5Content-Disposition: form-data; name="username"chenwl------WebKitFormBoundaryfCunWPksjjur83I5Content-Disposition: form-data; name="password"1234567------WebKitFormBoundaryfCunWPksjjur83I5Content-Disposition: form-data; name="avatar"; filename="test.txt"Content-Type: text/plain这里是文件内容------WebKitFormBoundaryfCunWPksjjur83I5--

实现剖析:

  • 获取申请信息,申请头须要有multipart/form-data
  • 切割申请信息,提取有用的信息
  • 蕴含filename的为文件,写入到对应门路
  • 提取其它信息保留到ctx.request.fields
const fs = require("fs");const path = require("path");Buffer.prototype.split = function(sep){    let arr = [];    let offset = 0;    let len = Buffer.from(sep).length;    let current = this.indexOf(sep,offset);    while (current !== -1) {        let data=this.slice(offset,current)        arr.push(data);        offset = current+len;        current = this.indexOf(sep, offset)    }    arr.push(this.slice(offset));    return arr;}module.exports = function ({ uploadDir }) {  return async (ctx, next) => {    // 后果放到 req.request.fields    await new Promise((resolve, reject) => {      let data = []      ctx.req.on("data", (chunk) => {        data.push(chunk)      })      ctx.req.on("end", () => {        // multipart/form-data; boundary=----WebKitFormBoundaryvFyQ9QW1McYTqHkp        const contentType = ctx.get("content-type")        if (contentType.includes("multipart/form-data")) {          const boundary = "--"+contentType.split("=")[1];          const r = Buffer.concat(data);          const arr = r.split(boundary).slice(1,-1);          const fields = {};          arr.forEach(line=>{              let [head,body] = line.split("\r\n\r\n");              body = body.slice(0,-2); // 取出无效的内容              head = head.toString();                if(head.includes("filename")){                // 解决文件                 // 申请头长度  = 总共的内容长度 - 头部长度 - 4个换行符长度                 const filecontent = line.slice(head.length+4,-2);                const filenanme = head.match(/filename="(.*?)"/)[1] || uid();                const uploadPath = path.join(uploadDir, filenanme)                fs.writeFileSync(uploadPath, filecontent)              }else{                fields[head.match(/name="(.*?)"/)[1]] = body.toString();              }          })          ctx.request.fields = fields        }        resolve()      })      ctx.req.on("error", (error) => {        reject(error)      })    })    await next()  }}function uid(){    return Math.random().toString(32).slice(2);}