What is the koa

Summary:

  • KoaIt’s a new onewebFramework, committed to bewebThe application andAPIA smaller, more expressive, and more robust cornerstone of development.
  • koaExpressThe next generation based onNode.jswebFramework.
  • koa2Fully using thePromiseAnd comply withasyncTo implement asynchrony.

Features:

  • Light weight, no binding
  • Middleware architecture
  • elegantAPIdesign
  • Enhanced error handling

Installation:

npm i koa -S
Copy the code

Simple use of KOA

/ / the introduction of koa
const Koa = require("koa")

// Create a KOA instance
const app = new Koa()

// Calculate the request time
app.use(async (ctx, next) => {
  const start = Date.now()
  await next()
  const end = Date.now()
  console.log(` 🚀 🚀 ~ requests${ctx.url}Time consumingThe ${parseInt(end - start)}ms`);// 🚀🚀~ request / 1014ms
})

/ / request
app.use(async (ctx, next) => {
  // Simulate some time-consuming operations
  await imitateDelay(1000);
  ctx.body = {
    name: 'warbler'}})// Start the service to listen on port 3000
app.listen(3000.() = > {
  console.log('🚀 🚀 ~ 3000:'.3000);
})
Copy the code

Hand write a simple version of KOA

An entry level HTTP service based on NodeJS, similar to the following code.

const http = require("http")
const fs = require("fs")
const server = http.createServer((req, res) = > {
  res.writeHead(200)
  res.end("hello, koa!")
})
server.listen(3000.() = > {
  console.log('🚀 🚀 to sever the at 3000');
})
Copy the code

Koa’s goal is to implement the callback part of the business logic in a more simplified, streamlined, and modular way.

Let’s implement MyKoa from scratch.

The first step implements the use and LISTEN methods

First we implement koA’s use and Listen methods, so we use MyKoa like this.

/ / introduce MyKoa
const MyKoa = require('./mykoa')

// Create MyKoa instance
const app = new MyKoa()

// Call the use method
app.use((req, res) = > {
  res.writeHead(200)
  res.end("hello, MyKoa!")})// Call the listen method
app.listen(3000.() = > {
  console.log('🚀 🚀 to sever the at 3000');
})
Copy the code

MyKoa source code is as follows.

// Introduce the native HTTP module
const http = require("http")

// Declare class MyKoa
class MyKoa {
  // Implement the listen method
  listen(. args) {
    // Call native HTTP.createserver to create the service
    const server = http.createServer((req, res) = > {
      // Call callback to implement the business code
      this.callback(req, res)
    })
    // Call native server.listen to listen on the portserver.listen(... args) }// Implement the use method
  use(callback) {
    // The real business logic code is stored in this.callback using the use method.
    this.callback = callback
  }
}

module.exports = MyKoa
Copy the code

So far, MyKoa is just a sham, and a context and middleware mechanism need to be introduced to make it happen.

Step 2 Build the context

In order to simplify the API, KOA introduces the concept of context, encapsulates and mounts the original request object REq and response object RES into the context, and sets getters and setters on the context to simplify operations.

A little chestnut that describes getters and setters, we can actually access Person.info.name by accessing Person.name.

const person = {
  info: {
    name: A Warbler's Tail
  },
  get name() {
    return this.info.name
  },
  set name(val) {
    this.info.name = val
  }
}
console.log('🚀 🚀 ~ person. Name:', person.name);// 🚀🚀~person.name: a warbler
person.name = "warbler"
console.log('🚀 🚀 ~ person. Name:', person.name);/ / 🚀 🚀 ~ person. Name: warbler
Copy the code

Let’s briefly implement MyKoa’s context.

// request.js
module.exports = {
  get url() {
    return this.req.url;
  },

  get method() {
    return this.req.method.toLowerCase()
  }
};
Copy the code
// response.js
module.exports = {
  get body() {
    return this._body;
  },
  set body(val) {
    this._body = val; }};Copy the code
// context.js
module.exports = {
  get url() {
    return this.request.url;
  },
  get body() {
    return this.response.body;
  },
  set body(val) {
    this.response.body = val;
  },
  get method() {
    return this.request.method
  }
};
Copy the code
// myKoa.js
const http = require("http")
const context = require("./context");
const request = require("./request");
const response = require("./response");
class MyKoa {
  listen(. args) {
    const server = http.createServer((req, res) = > {
      // Create context
      let ctx = this.createContext(req, res);
      this.callback(ctx)
      / / responseres.end(ctx.body); }) server.listen(... args) }use(callback) {
    this.callback = callback
  }
  // Build the context to mount res and req to CTX and save both ctx.req and ctx.request.req
  createContext(req, res) {
    const ctx = Object.create(context);
    ctx.request = Object.create(request);
    ctx.response = Object.create(response);
    ctx.req = ctx.request.req = req;
    ctx.res = ctx.response.res = res;
    returnctx; }}module.exports = MyKoa
Copy the code

Then we can use the context just like koA. When we access ctx.url, we are actually accessing ctx.request.req.url. Similarly, when we access ctx.body, we are accessing ctx.response.body.

// index.js
const MyKoa = require('./myKoa')

const app = new MyKoa()

app.use(ctx= > {
  ctx.body = 'welcome to MyKoa'
})

app.listen(3000.() = > {
  console.log('🚀🚀~ sever at 3000 ~~');
})
Copy the code

Step 3 middleware mechanism

Let’s take a look at function composition. It is a combination of a group of functions that need to be executed sequentially into a single function. The arguments of the outer function are actually the return values of the inner function.

// Compute x + y
const add = (x, y) = > x + y
// Compute z squared
const square = z= > z * z
// Calculate m - 1
const cutOne = (m) = > m - 1

// function combination z = x + y, then z squared
const fn = (x, y) = > square(add(x, y))

console.log(fn(1.2)); / / = > 9
Copy the code

If we simply encapsulate it, we get the same result.

// Function combination encapsulates two function combinations
const compose = (fn1, fn2) = > (. args) = >fn2(fn1(... args))const fn2 = compose(add, square)

console.log(fn2(1.2)); / / = > 9
Copy the code

But it only works with a combination of two functions, so let’s wrap it up a little bit, so it’s possible to combine any number of functions.

// Function combination encapsulates multiple function combinations
const composeMore = (. [first, ...other]) = > (. args) = > {
  letret = first(... args) other.forEach((fn) = > {
    ret = fn(ret)
  })
  return ret
}

const fn3 = composeMore(add, square, cutOne, cutOne, cutOne)

console.log(fn3(1.2)); / / = > 6
Copy the code

Koa middleware mechanism is the concept of functional composition concept Compose, the onion circle model can vividly represent this mechanism, is the essence and difficulty in the source code.

The Onion circle model is not quite the same as a normal combination of functions, where you execute one function and then the next. In the Onion circle model, half of a function is executed, the next function is executed, and so on, until the last function is executed, and the other half is returned.

// Function combination
function compose(middleWares) {
  return function() {
    return dispatch(0)
    function dispatch(i) {
      let fn = middleWares[i]
      // To support asynchronous methods, return promise.resolve ()
      if(! fn) {return Promise.resolve()
      }
      return Promise.resolve(
        fn(function next() {
          return dispatch(i + 1)}))}}async function fn1(next) {
  console.log("fn1 begin");
  await next();
  console.log("fn1 end");
}
async function fn2(next) {
  console.log("fn2 begin");
  await delay();
  await next();
  console.log("fn2 end");
}
function fn3(next) {
  console.log("fn3");
}

// Emulate asynchronous methods
function delay() {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      resolve();
    }, 2000);
  });
}

const middleWares = [fn1, fn2, fn3];
const finalFn = compose(middleWares);
finalFn();
Copy the code

The result of the final execution is

fn1 begin
fn2 begin 
/ / ms after 2000
fn3
fn2 end
fn1 end
Copy the code

Next applied in MyKoa, modify the code as follows.

// myKoa.js
class MyKoa {
  constructor() {
    // Store middleware
    this.middleWares = []
  }

  listen(. args) {
    // Create a native service
    const server = http.createServer(async (req, res) => {
      // Create context
      let ctx = this.createContext(req, res);
      // Form onion rings
      const fn = this.compose(this.middleWares)
      await fn(ctx)
      / / responseres.end(ctx.body); }) server.listen(... args) }use(middleWare) {
    // Save the middleware functions
    this.middleWares.push(middleWare)
  }

  // Build the context to mount res and req to CTX and save both ctx.req and ctx.request.req
  createContext(req, res) {
    const ctx = Object.create(context);
    ctx.request = Object.create(request);
    ctx.response = Object.create(response);
    ctx.req = ctx.request.req = req;
    ctx.res = ctx.response.res = res;
    return ctx;
  }

  // synthesize the function
  compose(middleWares) {
    return function(ctx) {
      return dispatch(0)
      function dispatch(i) {
        let fn = middleWares[i]
        if(! fn) {return Promise.resolve()
        }
        return Promise.resolve(
          // Pass the context CTX here
          fn(ctx, function next() {
            return dispatch(i + 1)})}}}}Copy the code
// index.js
const MyKoa = require('./myKoa')

// Create MyKoa instance
const app = new MyKoa()

// Simulate asynchronous operations
const delay = () = > new Promise(resolve= > setTimeout(() = > resolve(), 2000));

app.use(async (ctx, next) => {
  ctx.body = "1";
  await next();
  ctx.body += "5";
});
app.use(async (ctx, next) => {
  ctx.body += "2";
  await delay();
  await next();
  ctx.body += "4";
});
app.use(async (ctx, next) => {
  ctx.body += "3";
});

app.listen(3000.() = > {
  console.log('🚀🚀~ sever at 3000 ~~');
})
Copy the code

Step 4: Common middleware

koaMiddleware specification

  • Is aasyncfunction
  • receivectxnextTwo parameters
  • The task needs to be performednext
const middleWare = async (ctx, next) => {
  // Go to the middleware, left of the onion ring
  next() // Enter other middleware
  // Go to the middleware again, right of the onion ring
};
Copy the code

Common tasks of middleware:

  • Request to intercept
  • routing
  • The log
  • Static file service

Routing middleware

Routing is an implementation of the policy pattern, eliminating a lot of if… The else.

// router.js
class Router {
  constructor() {
    / / strategy library
    this.stack = []
  }

  /** * Register the policy to the policy library *@param {*} Path Request path *@param {*} Method Request method *@param {*} MiddleWare */
  register(path, method, middleWare) {
    let route = { path, method, middleWare }
    this.stack.push(route)
  }

  // Register the GET request
  get(path, middleWare) {
    this.register(path, 'get', middleWare)
  }

  // Register a POST request
  post(path, middleWare) {
    this.register(path, 'post', middleWare)
  }

  // Routing middleware
  routes() {
    let _stack = this.stack
    // Returns a piece of middleware
    return async function(ctx, next) {
      // Get the url in the context
      let currentPath = ctx.url
      // Declare a policy
      let route
      // Find the corresponding policy based on method in the context
      for (let i = 0; i < _stack.length; i++) {
        const item = _stack[i];
        if (currentPath === item.path && item.method === ctx.method) {
          route = item.middleWare
          break}}// If the retrieved policy is a function, execute the function
      if (typeof route === 'function') {
        route(ctx, next)
        return
      }
      // Go to the next middleware
      await next()
    }
  }
}

module.exports = Router
Copy the code
// index.js
const MyKoa = require('./myKoa')
const Router = require('./router')

const app = new MyKoa()
const router = new Router();
router.get('/index'.async ctx => {
  ctx.body = 'index page';
});
router.get('/post'.async ctx => { ctx.body = 'post page'; });
router.get('/list'.async ctx => { ctx.body = 'list page'; });
router.post('/index'.async ctx => { ctx.body = 'post page'; });


Router.routes ()
app.use(router.routes());

app.listen(3000.() = > {
  console.log('🚀🚀~ sever at 3000 ~~');
})
Copy the code

Static file service middleware

Handle requests for static files.

  • Set the absolute resource directory address. The default value isstatic
  • Get file or directory information
  • Static file reading
  • return
const fs = require("fs");
const path = require("path");
module.exports = (dirPath = "./public") = > {
  return async (ctx, next) => {
    if (ctx.url.indexOf("/public") = = =0) {
      // public starts to read files
      const url = path.resolve(__dirname, dirPath);
      const fileBaseName = path.basename(url);
      const filepath = url + ctx.url.replace("/public"."");
      console.log(filepath);
      // console.log(ctx.url,url, filepath, fileBaseName)
      try {
        stats = fs.statSync(filepath);
        if (stats.isDirectory()) {
          const dir = fs.readdirSync(filepath);
          // const
          const ret = ['<div style="padding-left:20px">'];
          dir.forEach(filename= > {
            console.log(filename);
            // Simply assume that the format without the decimal point is the folder, the actual use of statSync
            if (filename.indexOf(".") > -1) {
              ret.push(
                `<p><a style="color:black" href="${ctx.url }/${filename}">${filename}</a></p>`
              );
            } else {
              / / file
              ret.push(
                `<p><a href="${ctx.url}/${filename}">${filename}</a></p>`); }}); ret.push("</div>");
          ctx.body = ret.join("");
        } else {
          console.log("File");
          constcontent = fs.readFileSync(filepath); ctx.body = content; }}catch (e) {
        // An error was reported that the file does not exist
        ctx.body = "404, not found"; }}else {
      // Otherwise not static resources, go directly to the next middleware
      awaitnext(); }}; };Copy the code

Request interception middleware

Request interception is widely used: login status verification, CORS header setting, blacklist, etc.

This implementation of a blacklist of existing IP will be denied access to the function.

module.exports = async function(ctx, next) {
  const { res, req } = ctx;
  const blackList = ['127.0.0.1'];
  const ip = getClientIP(req);

  if (blackList.includes(ip)) {// The blacklist will be rejected
    ctx.body = "not allowed";
  } else {
    awaitnext(); }};function getClientIP(req) {
  return (
    req.headers["x-forwarded-for"] | |// Check whether there is a reverse proxy IP address
    req.connection.remoteAddress || // Determine the remote IP address of the connection
    req.socket.remoteAddress || // Determine the IP address of the socket at the back end
    req.connection.socket.remoteAddress
  );
}
Copy the code

BodyParser middleware

const middleWare = async (ctx, next) => {
  console.log('🚀 🚀 ~ body - parser');
  const req = ctx.request.req
  let reqData = [];
  let size = 0;
  await new Promise((resolve, reject) = > {
    req.on('data'.data= > {
      console.log('🚀 🚀 ~ the req on', data);
      reqData.push(data);
      size += data.length
    })
    req.on('end'.() = > {
      console.log('🚀 🚀 ~ end');
      const data = Buffer.concat(reqData, size)
      console.log('🚀 🚀 ~ data:', size, data.toString()); ctx.request.body = data.toString() resolve(); })})await next()
};
Copy the code

The resources

Open the full stack architect course