当前位置:网站首页>Koa middleware implementation

Koa middleware implementation

2022-06-24 05:55:00 Uncertainty

We introduced it earlier ,Koa The core of is the middleware mechanism , The service is the same . The middleware determines the execution order from top to bottom , We can do our own operations such as authority authentication before routing , Share with me koa Implementation of several middleware , That is the use The callback function of is proposed separately to rewrite , Because we pass parameters , So it doesn't return a function directly , It is a Higher order function .

static middleware

This function is used to read the local static file , Provide a directory , When the service is started, you can directly access the internal files in the browser , Mainly used fs modular

const path = require('path') //  Path processing 

// fs  Method can be used directly  promise  call , Let's share next time  node  Implementation of internal chain function 
//  If it helps you , You can like it 、 Pay attention to ha 
const fs = require('fs').promises

//  Higher order function return function ( Closure ),  The exported function can pass parameters . If you export directly  async(ctx, next) => {}   There is no expansion operation 
module.exports = function static(staticPath) {
  return async (ctx, next) => {
    //  The path entered may not exist 
    try {
      //  Splicing , There may be... In the path  / ,node  The default root path , Use here  join
      let filePath = path.join(staticPath, ctx.path)
      //  Don't use it here  fs.exists  Judge the file ,  Use  stat  perhaps  lstat
      let statObj = awiat fs.stat(filePath)
      if (statObj.isDirection()) {
        //  By default, the folder accesses the internal  index.html
        filePath = path.join(filePath, 'index.html')
      }
       It can be in the form of flow , Here is the direct reading 
      ctx.body = await fs.readFile(filePath, 'utf-8')
    } catch(error) {
      return await next()
    }
  }
}
  • Use
app.use(static(__dirname))

http://localhost:3000/form.html

koa-rotuer middleware

Let's take a look at the official use

const Router = require('koa-router')
//  Exported is a class 
let router = new Router()

//  Yes  get  post  Method, etc. 
router.get('/login', async (ctx, next) => {
  await next()
})

//  A higher order function 
app.use(router.routes())

Realize our demo edition

//  Base frame 
class Router {
  constructor() {}
  get(path) {}
  routes() {
    return async (ctx, next) => {
    
    }
  }
}

When we use routing a method, we can call... Multiple times

router.get('/', fn)
router.get('/', fn2)
router.get('/', fn3)
router.post('/', fn1)

 So we think of using stack storage , When executing, traverse , We think of the execution mechanism of the onion model , Finally, unified call . Then what is stored in the stack is an object 
{
  path: '',//  Traversal execution to match 
  callback: , //  Match to be executed in sequence 
  method: , //  The same path may have different methods 
}

 Here we use the form of class 

rewrite

//  Define the format of a storage object 

//  Simply use   Objects can be 
class Layer {
  constructor(path, method, callback) {
    this.path = path
    this.method = method
    this.callback = callback
  }
  //  Filter the current match from the stack .  Method calls are bound to themselves , Avoid exposing attributes , Poor expansibility , Just expose a method to pass parameters , In case of special circumstances, directly in   This method can be added internally 
  match(path, method) {
    return this.path == path && this.method == method.toLowerCase()
  }
}

class Router {
  constructor() {
    //  Store all routes , Filter when executing 
    this.stack = []
  }
  
  compose(layers, ctx, next) {
    const dispatch = i => {
      if (i === layers.length) reutrn next()
      let cb = layers[i].callback
      return Promise.resolve(cb(ctx, () => dispatch(i + 1)))
    }
    return dispatch(0)
  }
  
  //  In the entrance   call 
  routes() {
    //  Real middleware execution 
    return async (ctx, next) => {
      let path = ctx.path
      let method = ctx.method
      //  Screening 
      let layers = this.stack.filter(layer => {
        return layer.match(path, method)
      })
      
      //  Filter out the  layer  了 , Follow the middleware execution pattern , Execute sequentially 
      this.compose(layers, ctx, next)
    }
  }
}

//  Here you can download  methods  library , It contains   All method names , You don't have to configure methods in classes alone , Because the internal structure is the same . Direct circulation 
['get', 'post', ...].forEach(method => {
  Router.prototype[method] = function(path, callback) {
    let layer = new Layer(path, method, callback)
    this.stack.push(layer)
  }
})

body-parser middleware

get Request that we all know from url Get parameters in , Use url.parse After the parsing , Directly in query Parameters in , Use ctx.query Can get . about post This parameter is used in body In the form of , We need to read... As a stream buffer Make a splice , And for the form of uploading pictures , And understand mulpart/form-data This form .

Submission format type

Briefly introduce the following common types

  • application/x-www-form-urlencoded

Form data transferred

  • application/json

What is passed is ordinary json data

  • mulpart/form-data

Upload files

Content-Type: multipart/form-data; boundary= Your customization boundary

Sandwiched between the separator is the transmitted data , Remember ours form The form submission will have a name attribute

<input name="username" /> ,  Match found  username  The corresponding value is converted to object form 

Some head types are application/json;charset=utf-8, There is a description after the semicolon , For the convenience of judging the data type , We use head matching to judge startsWith

receive data

Use what we wrote above static middleware , Visit a simple local page , Write a form , Prepare for the back , Let's write a post Request ordinary objects

script Execute our request directly in the script

fetch('/json', {
  method: 'post',
  headers: {
    //  The default is  text/plain
    'content-type': 'application/json'
  },
  body: JSON.stringify({a: 456})
}).then(res => res.json()).then(data => {
  console.log(data)
})

Server receives data

router.post('/json', (ctx, next) => {
  let arr = []
  //  Native data listening is used here , Flow situation 
  ctx.req.on('data', (chunk) => {
    //  Binary form 
    arr.push(chunk)
  })

  ctx.req.on('end', () => {
    console.log(Buffer.concat(arr).toString())
  })
  ctx.body = '456'
})

Interface printing

But the delivery of our listener stream is asynchronous , When we return ctx.body when , I haven't got , So it needs to be changed to await promise situation , Then we aim at different content-type, Different sets of data processing , Take what you get body The value in , Bound to the ctx.req.body On , In this way, the middleware executed later can obtain .

Realization bodyParser middleware

The use of comments is marked in the code , You can look down from the top , It should be well understood

// dir  If you send a file   Storage directory 
function bodyParser({ dir } = {}) {
  return async (ctx, next) => {
    //  Synchronous processing returns results , It's good to assign values , The middleware in the back can be obtained 
    ctx.request.body = await new Promise((resolve, reject) => {  
      //  obtain   Data flow   on  data get data 
      let arr = []
      
      ctx.req.on('data', function(chunk){
        arr.push(chunk) // chunk buffer data 
      })
      
      //  Data received ,  Try not to use   Native req, res  operation 
      ctx.req.on('end', () => {
        //  Get the data format passed by the user  Content-Type
        let type = ctx.get('content-type') // ctx  It's packaged in  , perhaps  res.headers['content-type']
        
        //  assemble   data 
        let body = Buffer.concat(arr)
        
        //  Data type judgment 
        if (type === 'application/x-www-form-urlencoded') {
        //  Common form data 
        //  Set the response header    data type    The settings returned according to the actual situation 
          ctx.set('Content-Type', 'application/json;charset-utf-8')
          
          //  Return to object , querystring node The built-in library comes with 
          resolve(querystring.parse(body.toString()))
        } else if (type.startsWith('text/plain')) {
          //  Text type 
          resolve(body.toString())
        } else if (type.startsWith(application/json)) {
          resolve(JSON.parse(body.toString()))
        } else if (type.startsWith('multipaer/form-data')) {
          //  We introduced above  multipaer  Type of request data format , Use  boundary  Division , The head and body of each group are divided in  \r\n\r\n  Do the segmentation (http  Under the agreement , We can directly find and replace ) ( Look at the figure below, our page and node Received results )
          
          let boundary = '--' + type.split('=')[1]
          
          //  Get number of groups . This is because of our  body  yes  buffer Format ,buffer  No built-in  split  Method , We need to expand something similar   Array of  split  Method , Look below 
          let lines = body.split(boundary).slice(1, -1) //  break off both ends , After printing, you can see , Marking the beginning and end has no practical significance 
          
          //  Define the data we want to get 
          let formData = {}
          
          lines.forEach(line => {
            let [head, body] = line.split('\r\n\r\n')//  The prescribed division method 
            
            head = head.toString()
            //  We can see from the screenshot below   The format is  name=xxx
            let key = head.match(/name="(.+?)"/)[1]
            
            //  file 
            if (head.includes('filename')) {
              //  If you receive   file , We save it on the server 
              //  Get file content 
              let content = line.slice(head.length + 4, -2) // +4  because  \r\n\r\n  Segmented , Remove the tail  \r\n  -2
              
              //  Create upload directory    The directory can be uploaded during execution ,  Default  upload Catalog 
              dir = dir || path.join(__dirname, 'upload')
              
              //  Randomly generate file names 
              let filePath = uuid.v4() //uuid  Third party Library   Generate random 
              let uploadUrl = path.join(dir, filePath)
              
              fs.writeFileSync(uploadUrl, content)
              formData[key] = {
                filename: uploadUrl,
                size: content.length,
                .....  You can also add the required attributes 
              }
            } else {
              let value = body.toString()
              //  Get rid of the back  \r\n
              formData[key] = value.slice(0, -2)
            }
          })
          resolve(formData)
        } else {
          //  The default is null 
          resolve({})
        }
      })
    })
    await next() //  Continue to execute the following middleware ,  The value of the request body has been stored 
  }
}


//  expand  buffer  Of  split  Method 
Buffer.prototype.split = function(sep) { //  Separator 
  let arr = []
  let offset = 0
  //  Separator may be Chinese , Or special symbols , So we unified into  buffer  To obtain the length of the 
  let len = Buffer.from(sep).length
  //  Use  indexof  obtain  sep  The location of , Put it in the array , Returns an array of 
  while (-1 !== (index = this.indexOf(sep, offset))) { // indexOf The second parameter identifies where to start the search , Not every time from the index  0  Go back and forth 
    arr.push(this.slice(offset, index))
    offset = index + len
  }
  //  The last paragraph may not have a separator   Put as much as you have left   a|b|c   discharge  c
  arr.push(offset)
  return arr
}

( Print body.toString())

This article shares three koa Common middleware in , In fact, the forms of middleware are universal , Higher order functions return , It's all simple versions , If you are interested, you can look at the source code and learn more about . Next time I plan to share with you express Implementation mechanism , More complicated , I will sort it out smoothly and then write an article to share with you . If you have any questions about this article, you can comment and leave a message .

If you are interested, you can pay attention to bo !

原网站

版权声明
本文为[Uncertainty]所创,转载请带上原文链接,感谢
https://yzsam.com/2021/07/20210731203628894O.html

猜你喜欢

    随机推荐