当前位置:网站首页>Nodejs: handwritten koa Middleware

Nodejs: handwritten koa Middleware

2020-11-09 22:19:00 Channel

koa-static

koa-static Can handle static resources , Parameter is static resource folder path , The official implementation contains more parameters , You can see koajs/static

Implementation analysis :

  • Get request url route , Find out if the path under the static folder can match
  • If it's a path, it's a folder , Look under the folder index.html file
  • Set response header file type (mime)
  • gzip Compress , Set the compression type and return the readable stream
  • Error or file does not exist next Move on to the next middleware
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)
      //  If it's a folder , Splicing index.html
      if (statObj.isDirectory()) {
        abspath = path.join(abspath, "index.html")
      }
      //  Determine whether the path is accurate 
      fs.accessSync(abspath);
      //  Set file type 
      ctx.set("Content-Type", mime.getType(abspath) + ";charset=utf8")
      //  Client allowed encoding format , To determine whether or not gzip Compress 
      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 Can handle POST Requested data , take form-data Data analysis to ctx.request.body, Official address :koa-bodyparser

Implementation analysis :

  • Read request data
  • Set response header type application/json
  • Determine the client request header content-type type
  • Parse data and bind to 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 It can make koa image express Control routing as well , Source code koa-router The implementation is more complex , Here's a simple version .

Implementation analysis :

  • Save the request to the array first middlewares in
  • routes Return middleware function , obtain ctx and next
  • From an array middlewares Filter out route / Method Same data
  • Middleware processing , Pass in ctx And packaged next, When the processing is finished, the real next Method
class Router {
  constructor() {
    //  Save an array of middleware methods 
    this.middlewares = []
  }
  get(path, handler) {
    // get、post、delete、put All these methods have to deal with , Here we only realize get
    this.middlewares.push({ path, handler })
  }
  compose(routes, ctx,next) {
    //  Dispatch every stored middleware method 
    const dispatch = (index) => {
      //  Execute context only after processing is completed next
      if (routes.length === index) return next();
      //  Will middleware's next Packaged for next execution dispath, Prevent multiple execution of context next Method 
      routes[index].handler(ctx, () => dispatch(++index))
    }
    dispatch(0)
  }
  routes() {
    return async (ctx, next) => {
      //  Filter out the same storage objects 
      let routes = this.middlewares.filter((item) => item.path === ctx.url)
      this.compose(routes, ctx,next)
    }
  }
}

module.exports = Router;

koa-better-body

koa It's used to process file uploads koa-better-body, Here you need to make sure that the form contains multipart/form-data

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

Here's through the form enctype by multipart/form-data Submit the data from the background :

------WebKitFormBoundaryfCunWPksjjur83I5
Content-Disposition: form-data; name="username"

chenwl
------WebKitFormBoundaryfCunWPksjjur83I5
Content-Disposition: form-data; name="password"

1234567
------WebKitFormBoundaryfCunWPksjjur83I5
Content-Disposition: form-data; name="avatar"; filename="test.txt"
Content-Type: text/plain

 Here is the content of the document 
------WebKitFormBoundaryfCunWPksjjur83I5--

Implementation analysis :

  • Get request information , The request header needs to have multipart/form-data
  • Cutting request information , Extract useful information
  • contain filename Is a file , Write to the corresponding path
  • Extract other information and save it to 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) => {
    //  The result is put in  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); //  Take out the valid content 
              head = head.toString();  
              if(head.includes("filename")){
                //  Processing documents  
                //  Request header length   =  Total content length  -  Head length  - 4 Newline length  
                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);
}

版权声明
本文为[Channel]所创,转载请带上原文链接,感谢