git

express

  • Full function, built a lot of middleware, integrated routing, static service, template engine and other functions, based on ES5, all based on callback implementation
  • Errors can only be handled within the callback
  • app.all, app.get,app.post,app.use
  • Extend attributes in native REq, RES
  • Like Express, it is based on HTTP modules

Express using

const express = require('express'Const app = express () app.use()'/', (req, res, next) => {// Handle the public path in middleware next()})'/', (req, res)=> {
    res.end('ok')
})
app.listen(3000, () => {
    console.log('start')})Copy the code

The source code parsing

Routing system
// Main code //'application.js'
const http = require('http')
const Router = require('./router'/* create a router instance when initializing the application, and let the router handle all requestsfunction Application() {/ / when creating an initialize a router enclosing _router = new router ()} Application. The prototype. The get =function(path, ... handlers) { this._router.get(path, handlers) // }, Application.prototype.listen =function(... arg) { const server = http.createServer((req, res) => {function done() {// If the router cannot handle it, execute it directlydone
      res.end('not xx `Found')
    }
    this._router.handle(req, res, done) }) server.listen(... arg) } module.exports = Application //'layer.js'
function Layer(path, handle) {
  this.path = path
  this.handle = handle
}
module.exports = Layer

// 'route.js'
const Layer = require("./layer");

function RouteThis. stack = []} /* @route has a diapatch attribute, a call to the stack @ and a get method. */ route.prototype.dispatch =function(req, res, out) {
  let idx = 0
  const next = () => {
    
    if (idx >= this.stack.length) returnOut () const routeLayer = this.stack[idx++] // compare methodsif (routeLayer.method ===  req.method.toLowerCase()) {
      routeLayer.handle(req, res, next)
    } else {
      next()
    }
  }
  next()
}
Route.prototype.get = function(handles) {
  handles.forEach(handle => {
    const layer = new Layer(' ', handle)
    
    layer.method = 'get'
    this.stack.push(layer)
  });
}
module.exports = Route



// 'index.js'/* @router is a routing system, route is a routing system. @router is a routing system, route is a routing system. @router is a routing system, route is a routing system. This callback function is the diapatch of Route. When @calls the handle of the outer layer, route.dispatch will be executed. Dispatch will find the layer stored in route and match according to the method. */ const layer = require(const layer = require('./layer')
const Route = require('./route')
const url = require('url')
function Router() {
  this.stack = []
}
Router.prototype.route = function(path) {const route = new route () const layer = new layer (path, Route.dispatch. Bind (route)) // create layer layer.route = route // set route attribute for layer // add layer to routing stack this.stack.push(layer)returnRouter.prototype.get = {// router.prototype.get = {//function(path, handlers) {// When we call get we create a layer, Const route = this.route(path) const route = this.route(path) const route = this.route(path) const route = this.route(path) const route = this.route(path) const route = this.route(handlers)} Router.prototype.handle =function (req, res, done) {/* @ When a request comes in, we first get the path to find the outer layer, and then execute its handle. Then we ask the inner layer route to execute the inner layer handle @ */ according to methodlet { pathname } = url.parse(req.url)
  let idx = 0
  let next = () => {
    if (idx >= this.stack.length) return done() // If the route is not found, the route can be routed directlyletLayer = this.stack[idx++] // check whether the path on the layer is consistent with the current requested path. If so, call dispatchif(layer.path === pathName) {// The path matches and the corresponding dispatch on the layer needs to execute layer.handle(req, res, next) // pass in the method traversing the next layer in the routing system}else {
      next()
    }
  }
  next()
}
module.exports = Router

Copy the code
  • Express exports a function that creates an application and creates a routing system along with the application to make way for the system to handle subsequent requests
  • Each layer in the outer layer has a Route attribute. This attribute stores one inner layer and the corresponding relationship between method names and real callbacks sent by users. A layer stores a callback, and the outer layer stores the corresponding relationship between the route and Route. dispatch. When the route is matched to the outer city Laryer path, the route.dispatch execution is triggered, and then the inner layers stored in the route are executed in sequence
Lazy loading of routes
function Application() {/ / when creating an initialize a router} Application. The prototype. Lazy_router =function() {
  if(! {this._router) {this._router = new Router()}} methods.foreach (method => { Application.prototype[method] =function(path, ... handlers) { this.lazy_router() ... } }) Application.prototype.listen =function(... Arg) {const server = http.createserver ((req, res) => {this.lazy_router() // Implements lazy routing... } module.exports = ApplicationCopy the code
  • Instead of creating a routing system as soon as the application is created, users create a routing system when they have the express corresponding method or Listen
Other parts of the optimization

After we get the path and method of a request, we first go to the outer layer to match path, and then go to the inner layer to match methods. As a result, the inner layer cannot match the corresponding method, resulting in waste, so we add a method attribute to the route attribute of the outer layer. This attribute includes all existing methods in route. When matching to the outer layer, methods mapping is first used to find whether there is a route. If there is a route, route.dispatch is triggered

// index.js
Router.prototype.handle = function (req, res, done) {...letnext = () => { ... // Need to check whether the path on layer is consistent with the current requested path, if so, call dispatch methodif(layer.match(pathName)) {// The path matches and needs to be executed by the corresponding dispatch on the layerif(layer.route.methods[req.method.tolowerCase ()]) {layer.handle(req, res, next)}else {
        next()
      }
    } else {
      next()
    }
  }
  next()
}
module.exports = Router

// route.js

function Route() {... this.methods = {} } ... Prototype [method] = {Route. Prototype [method] = {Routefunction(handles) {
    handles.forEach(handle => {
      const layer = new Layer(' ', handle)
      this.methods[method] = true. }); } }) module.exports = RouteCopy the code
The middleware
The onion model
. app.get('/'.function(req, res, next) {
    console.log(1)
    setTimeout(() => {
        next();
        console.log('xxx')}, 6000); },function(req, res, next) {
    next();
    console.log(11)
}, function(req, res, next) {
    next();
    console.log(111);

})
app.get('/'.function(req, res, next) {
    console.log('2');
    res.end('end')
})
app.post('/'.function(req, res, next) {
  res.end('post ok')
})
app.listen(3001, () => {
    console.log('server start 3000'); }) // 1, 2, 111, 11 Onion model, in fact, the layers of depth from the outside, layers of wear out again, the execution order of middleware, strictly follow the onion model next first execution of a middleware is understandable, after the next code will return a middleware under the execution of the execution The output of the code: + 1 output and there is no doubt that + nextsetWait in TimeOut, enter the next middleware after the time, next middleware before output, continue to execute the next callback, output 2 + to the previous layer, output 111 + to the previous layer, output 11 + to the original layer, output XXXCopy the code
The realization of the use
What can middleware be used for
  • The middleware can decide whether to proceed down, intercepting the request for intermediate action
  • Permission to check
  • Extended attributes
usage
// the first argument matches the path starting with path, and the second argument is the callback + middleware mismatch method that matches only the path app.use('/', (req, res, next) => {
  // todo
})
Copy the code
implementation
  • Middleware and routing are placed in the same routing system. Its layer only has PATH (starting with PATH) and the callback function passed by the user. Different from routing, middleware has no route attribute
  • The middleware needs to call next internally to call the next step
  • The location of the middleware is in front of the route in the routing system
// application.js
Application.prototype.use = functionLazy_router () {this._router () {this._router () {this._router () {this._router () {this._router () {this._router (); arguments) } // index.js ... Router.prototype.use =function(path, ... handles) {if(typeof path == 'function') {// only one argument is passed, handles. Unshift (path) path ='/'} // create layer handles by traversing handles. ForEach (handle => {letLayer = new layer (path, handle) // For middleware, there is no route attribute. To differentiate, set route attribute to undeinfed. Layer.route = undefined this.stack.push(layer)})} router.prototype.handle =function (req, res, done) {
  let { pathname } = url.parse(req.url)
  let idx = 0
  let next = () => {
    ...
    if(layer.match(pathName)) {// If a path is matched, it may be middleware or routingif(! Layer. route) {layer.handle_request(req, res, next)}else{/ / routingif(layer.route.methods[req.method.tolowerCase ()]) {layer.handle(req, res, next)}else{next()}} // The path matches and needs to be executed by the corresponding dispatch on layer}else {
      next()
    }
  }
  next()
}

// layer.js

function Layer(path, handle) {
  this.path = path
  this.handle = handle
}
Layer.prototype.match = function(pathName) {// Routing and middleware have different matching rulesif (this.path === pathname) return true
  if(! This.route) {// Middlewareif (this.path === '/') return true
    return pathname.startsWith(this.path + '/')}return false

}
Layer.prototype.handle_request = function(req, res, next) {
  this.handle(req, res, next)

}
module.exports = Layer
Copy the code

Error handling of Express

  • In contrast to KOA listening for error times, Express uses middleware to handle errors
  • Error-handling middleware features four parameters (Err, REq, RES, next)
usage
const express = require('express');

const app = express();
app.use('/', (req, res, next) => {
  // todo
  console.log(1)
  next('Wrong')
})
app.use('/', (req, res, next) => {
  // todo
  console.log(2)
  next()
})
app.get('/'.function(req, res, next) {
    console.log(1)
    setTimeout(() => {
        next();
        console.log('xxx')}, 10000); },function(req, res, next) {
    next();
    console.log(11)
}, function(req, res, next) {
    next();
    console.log(111);

})
Copy the code

  • Express is based on asynchronous callbacks, so you cannot use tryCatch to catch exceptions. With Expres, if you pass parameters in next, it is considered an error and does not proceed
implementation
// route.js
...
Route.prototype.dispatch = function(req, res, out) {
  let idx = 0
  const next = (err) => {
    if(err) { returnOut (err)} // Out is the outer next, if there is an error in the inner layer, just jump outif (idx >= this.stack.length) returnOut () const routeLayer = this.stack[idx++] // compare methodsif (routeLayer.method ===  req.method.toLowerCase()) {
      routeLayer.handle(req, res, next)
    } else {
      next()
    }
  }
  next()
}
...


// index.js
Router.prototype.handle = function (req, res, done) {/* @ When a request comes in, we first get the path to find the outer layer, and then execute its handle. Then we ask the inner layer route to execute the inner layer handle @ */ according to methodlet { pathname } = url.parse(req.url)
  let idx = 0
  letNext = (err) => {// Perform uniform listening hereif (idx >= this.stack.length) return done() // If the route is not found, the route can be routed directlylet layer = this.stack[idx++]
    if(err) {// Error handling. The entry error can be either middleware or routingif(! Layer. route) {layer.handle_error(err, req, res, next)}else{// Routing continues to carry error messages matching middleware next(err)}}else {
      if(layer.match(pathName)) {// If a path is matched, it may be middleware or routingif(! Layer. route) {// Middleware to implement // eliminate errors middlewareif(layer.handle.length ! == 4) {layer.handle_request(req, res, next) // Common middleware}else{// error middleware, jump next()}}else{... }}else {
        next()
      }
    }
  }
  next()
}

// layer.js
...
Layer.prototype.handle_error = function(err, req, res, next) {// Find the layer where the error-handling middleware is located, if not proceed to next with the error messageif(this.handle.length === 4) {// Error-handling middleware lets Handle executereturnThis. Handle (err, req, res, next)} next(err) // Common middleware}... Test code server.js app.use('/', (req, res, next) => {
  // todo
  console.log(1)
  next()
})

app.use('/', (req, res, next) => {
  // todo
  console.log(2)
  next()
})
app.use('/', (req, res, next) => {// todo console.log(3) next()})'/'.function(req, res, next) {
    console.log(1)
    next()
}, function(req, res, next) {
    next();
    console.log(11)
}, function(req, res, next) {
    next('Wrong');
    console.log('Wrong');

})
app.get('/'.function(req, res, next) {
    console.log('2');
    res.end('end')
})
app.use((err,req,res,next)=>{ 
  next();
})
Copy the code

  • Next in the middleware is next from the outer layer, and routing is next from the inner layer

The realization of secondary routing

use
// server.js
const express = require('express');
const LoginRouter =require('./routes/loginRouter');

const app = express();

app.use('/login',LoginRouter);

app.listen(3000);

// loginRouter.js
const express = require('express');
letrouter = express.Router(); Router.get () router.get()'/add'.function (req,res) {
    res.end('/login-add')
})
router.get('/out'.function (req,res) {
    res.end('/login-out')
})

module.exports = router;
Copy the code
implementation
  • Express. Router is the Router defined in index
// router/index.js ... // Provide a Router class, either new or createApplication.Router = require('./router')...Copy the code
The Router is compatible with
  • The Router can be either new or executed
  • New returns a reference type. This refers to the reference type
function Router() {
  this.stack = []
  const router = (req, res, next) => {
    router.handle(req, res, next)
  }
  router.stack = []
  router.__proto__ = proto
  return router
}
Copy the code

After this modification, after passing new, this refers to the router function. There are no previous methods on this function, so all the logic written before is invalid. We need to put these attributes on the prototype chain

The secondary routing
  • When we call express.router (), we create a separate routing system, similar to the previous routing system structure type, that corresponds to the first layer of routing
  • When the request arrives, the first-level route will be matched first, and then the second-level routing system will be entered. The second-level route will be matched, and the dispatch will be called. If the request fails to match, the first-level routing system will be matched and the next one will be matched
  • When the middleware is used, when level-1 routes are matched, the level-1 routes are cut off and the remaining routes are entered into the level-2 routing system for matching
// index.js


proto.handle = function (req, res, done) {/* @ When a request comes in, we first get the path to find the outer layer, and then execute its handle. Then we ask the inner layer route to execute the inner layer handle @ */ according to methodlet { pathname } = url.parse(req.url)
  let idx = 0
  let removed = ' ';
  letNext = (err) => {// Perform uniform listening hereif (idx >= this.stack.length) return done() // If the route is not found, the route can be routed directlyletLayer = this.stack[idx++] // the path is replenished when the middleware comes outif(removed) { req.url = removed + pathname; // Add a path out to match other middleware removed =' ';
    }
    if(err) {// Error handling. Usually put at the end... }else {
      if (layer.match(pathname)) {
        if(! layer.route) {if(layer.handle.length ! = = 4) {if(layer.path ! = ='/'Removed = layer.path // Removed path req.url = pathName.slice (removed. Length); } layer.handle_request(req, res, next)else{// error middleware, jump next()}}else{... }else {
        next()
      }
    }
  }
  next()
}
Copy the code

koa

  • Small, es6 based, (Promise, async, await), focusing on the core use method
  • The middleware
  • The onion model
  • Source ideas
    • The constructor adds request, reponse, context
    • Use adds middleware to the array
    • While LISTEN, call handleRequest
    • In handleRequest, according to the REQ, the RES creates a context for the middleware to place the result on ctx.body after a single execution (returning a promise)
  • The middleware
  • Error handling, listening for onError
  • CTX has REQ, RES, Request, and Reponse

implementation

application
  • Create a KOA application and export a class with methods like use, Listen, and so on
  • Create a server using HTTP in the LISTEN method to process the request
    • The RES creates a context based on the requested REQ
    • The registered middleware is then executed one by one, and the middleware returns promises one by one, which are placed in ctx.body and returned to the user after the middleware execution is complete
  • Use places middleware one by one
// applicatin
const EventEmitter = require('events')
const http = require('http')
const context = require('./context.js')
const request = require('./request.js')
const response = require('./response.js')
const Stream = require('stream')

class Application extends EventEmitter {
  constructor() {super() // To implement a new context every time new, Context = object.create(context) this.response = object.create(response) this.request = Object.create(request) this.middlewares = [] } use(middleware) { this.middlewares.push(middleware) } generateContext(req, res) { .... } compose(ctx) { .... } handleRequest(req, res) {const CTX = this.generatecontext (req, generateContext) const CTX = this.generatecontext (req, Res) // Execute middleware this.pose (CTX).then(() => {// handle the returnletbody = ctx.body; // When the combined promise is complete, return with the final resultif(typeof body == 'string' || Buffer.isBuffer(body)){
          res.end(body);
      }else if(body instanceof Stream){
          res.setHeader('Content-Disposition',`attachement; filename=The ${encodeURIComponent (' 1111 ')}`)
          body.pipe(res);
      }else if(typeof body == 'object'){ res.end(JSON.stringify(body)); } }) } listen(... args) { const server = http.createServer(this.handleRequest.bind(this)) server.listen(... args) } } module.exports = ApplicationCopy the code
GenerateContext, creates the context according to req, RES
  • GenerateContext extends a request and reponse in the context, with the native RES and REQ on response and request
  • Copy it twice: the first is to make sure that each instance gets a new context, request, response, and the second is to get a new context, request, response, for each use
GenerateContext (req, res) // Ensures that a new context is created for each uselet context = Object.create(this.context);
    let request = Object.create(this.request);
    letresponse = Object.create(this.response); Context. request = request; // Context. request = request; context.response = response; Context.request.req = context.req = req; context.request.req = req; context.response.res = context.res = res;return context;
}
Copy the code
Compose (Middleware)
The asynchronous callback uses compose(CTX) of type next {const dispatch = I => {try {if(i >= this.middlewares.length) return Promise.reslove()
            const currentMiddle = this.middlewares[i]
            return Promise.reslove(currentMiddle(ctx, () => dispatch(i+1))
            
        } catch(e) {
            
        }
    }
    dispatch(0)
}
Copy the code

context

  • Definitions at Object initialization are appended after the Object is defined by the __defineGetter__, __defineSetter__ methods of Object
  • Query, go to ctx.requesrt.query, do a layer of proxy
  • CTX) body go to CTX). The response of the body
  • Setting ctx.body is equivalent to setting ctx.response.body
Const context = {} // proxy methodfunctionDefineGetter (target, key) {// Define a getter, context and this are not the same, this.__proto__.__proto__ = context context.__defineGetter__(key, () => {return this[target][key]
  })
}

function defineSetter(target, key) {
  context.__defineSetter__(key, (newValue) => {
    this[target][key] = newValue
  })
}
defineGetter('request'.'url')
defineGetter('request'.'path') 
defineGetter('request'.'query') // ctx.query = ctx.requesrt.query

defineGetter('response'.'body'); // ctx.body => ctx.response.body defineSetter('response'.'body'); // ctx.body => ctx.response.body module.exports = contextCopy the code

request

  • Use the property accessor to set up a layer of proxy,
const url = require('url') const request = {// Property accessor, similar to Object.definedProperty() geturl() {// This refers to ctx.requestreturn this.req.url
  },
  get path() {
    returnParse (this.req.url).pathname}, // Extend get to what attributes you wantquery() {
    return url.parse(this.req.url, true).query
  }
}
module.exports = request
Copy the code

response

const response = {
  _body:' ',
  get body() {return this._body;
  },
  set body(val){
      this._body = val;
  }
}
module.exports = response;
Copy the code

Body – the parse middleware

  • The body-parse middleware actually parses the incoming parameters on ctx.request.body. The incoming request is a readable stream, so it listens for data or end events
const querystring = require('querystring');
const uuid = require('uuid');
const path = require('path');
const fs = require('fs'); // Middleware functions can extend properties/methods module.exports =function(uploadDir) {
    return async (ctx, next) => {
        await new Promise((resolve,reject)=>{
            const arr = [];
            ctx.req.on('data'.function (chunk) {  
                arr.push(chunk);
            })
            ctx.req.on('end'.function () {
                if(ctx.get('content-type') = = ='application/x-www-form-urlencoded') {let result = Buffer.concat(arr).toString();
                    ctx.request.body = querystring.parse(result);
                }
                if(ctx.get('content-type').includes('multipart/form-data'){// Binary cannot be directly toString may be garbledlet result = Buffer.concat(arr); // buffer
                    let boundary = The '-'+ ctx.get('Content-Type').split('=') [1];let lines = result.split(boundary).slice(1,-1);
                    letobj = {}; Lines. forEach(line=>{let [head,body] = line.split('\r\n\r\n');
                        head = head.toString();
                        let key = head.match(/name="(. +?) "/) [1]if(! head.includes('filename')){
                            obj[key] = body.toString().slice(0,-2);
                        }else{// yes file file upload name needs to be randomlet content = line.slice(head.length + 4,-2);
                            letfilePath = path.join(uploadDir,uuid.v4()); obj[key] = { filePath, size:content.length } fs.writeFileSync(filePath,content); }}); ctx.request.body = obj; } resolve(); })}); await next(); }} buffer.prototype.split =function(sep) {// The delimiter may be Chinese, I want to convert it to buffer to countlet sepLen = Buffer.from(sep).length;
    let arr = [];
    let offset = 0;
    let currentIndex = 0;
    while((currentIndex = this.indexOf(sep,offset)) ! == -1){ arr.push(this.slice(offset,currentIndex)); offset = currentIndex + sepLen; } arr.push(this.slice(offset));return arr;
}
Copy the code

Koa project construction

Project directory

Divide the routing
const Router = require('koa-router')
const routerA = new Router({prefix: '/login'}) // Partition interfaces that start with loaginCopy the code
Koa-combine -routers Merge routes
const combineRouters = require('koa-combine-routers') combineRouters(A, B)// Merge A and B routes to return A middlewareCopy the code
Render template KOa-views
const views = require('koa-views'// Add a render method to CTX, based on the Promise app.use(views(__dirname +)'./views', {
    map: {
        html: 'ejs'// Use class Controller {async add(CTX, next) {await ctC.render ('a.html', { age: 11, name: 'xx'})}} // Template <! DOCTYPE html> <html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
</head>
<body>
    <%=name%> <%=age%>
</body>
</html>

Copy the code