iKcamp|Building Node.js combat based on Koa2 (including video) ☞ Error handling

Hujiang CCtalk video address: https://www.cctalk.com/v/15114923887518

Handling Bad Requests

Love can cover up all wrongs.

When we visit a site, if the visited address does not exist (404), or an error occurs inside the server (500), the site will display a specific page, such as:

So how Koato implement this functionality in ? In fact, a simple middleware can be achieved, we call it http-error. The implementation process is not complicated and can be divided into three steps:

  • Step 1: Confirm your needs
  • Step 2: Organize your thoughts
  • Step 3: Code Implementation

<br/>

Confirm needs

Before building a thing, you need to confirm what characteristics it has, which is the requirement.

<br/>

Here, after a little sorting out, you can get a few basic requirements:

  • When the page request appears 400and 500the error code is like, guide the user to the error page;
  • Provide a default error page;
  • Allow users to customize error pages.

<br/>

Organize ideas

Now, let's Koastart :

  1. A request for access Koa, an error occurred;
  2. The error will be caught by the http-errormiddleware ;
  3. Errors will be caught by the error handling logic of the middleware and processed;
  4. The error handling logic calls the rendering page logic according to the error code status;
  5. The rendering page logic renders the corresponding error page.

As you can see, the key point is to catch errors and implement error handling logic and page rendering logic.

<br/>

Code

build file

Based on the tutorial directory structure, we create a middleware/mi-http-error/index.jsfile to store the logic code of the middleware. The initial directory structure is as follows:

middleware/
├─ mi-http-error/
│  └── index.js
└─ index.js

Note: The directory structure does not exist, you need to create it yourself.

<br/>

catch errors

The first function that the middleware needs to implement is to catch all httperrors . According to the onion model of middleware, several things need to be done:

1. Introduce middleware

Modify middleware/index.js, introduce mi-http-errormiddleware , and put it in the outermost layer of the onion model

const path = require('path')
const ip = require("ip")
const bodyParser = require('koa-bodyparser')
const nunjucks = require('koa-nunjucks-2')
const staticFiles = require('koa-static')
const miSend = require('./mi-send')
const miLog = require('./mi-log')

// 引入请求错误中间件
const miHttpError = require('./mi-http-error')
module.exports = (app) => {
  // 应用请求错误中间件
  app.use(miHttpError())
  app.use(miLog(app.env, {
    env: app.env,
    projectName: 'koa2-tutorial',
    appLogLevel: 'debug',
    dir: 'logs',
    serverIp: ip.address()
  }));
  app.use(staticFiles(path.resolve(__dirname, "../public")))
  app.use(nunjucks({
    ext: 'html',
    path: path.join(__dirname, '../views'),
    nunjucksConfig: {
      trimBlocks: true
    }
  }));
  app.use(bodyParser())
  app.use(miSend())
}

2. Catch middleware exceptions

Modify mi-http-error/index.js, monitor the errors of other inner middleware inside the middleware, and process catchthe errors

module.exports = () => {
  return async (ctx, next) => {
    try {
       await next();
       /**
        * 如果没有更改过 response 的 status,则 koa 默认的 status 是 404 
        */
       if (ctx.response.status === 404 && !ctx.response.body) ctx.throw(404);
    } catch (e) {
      /*此处进行错误处理,下面会讲解具体实现*/
    }
  }
}

<br/>

After the above preparations are completed, two key logics are implemented below.

<br/>

error handling logic

The error handling logic is actually very simple, that is, to judge the error code and specify the file name to be rendered. This code runs in catcherror .

Edit mi-http-error/index.js:

module.exports = () => {
  let fileName = 'other'
  return async (ctx, next) => {
    try {
       await next();
       /**
        * 如果没有更改过 response 的 status,则 koa 默认的 status 是 404 
        */
       if (ctx.response.status === 404 && !ctx.response.body) ctx.throw(404);
    } catch (e) {
      let status = parseInt(e.status)
      // 默认错误信息为 error 对象上携带的 message
      const message = e.message

      // 对 status 进行处理,指定错误页面文件名 
      if(status >= 400){
        switch(status){
          case 400:
          case 404:
          case 500:
            fileName = status;
            break;
          // 其它错误 指定渲染 other 文件
          default:
            fileName = 'other'
        }
      }
    }
  }
}

That is, for different situations, different error pages will be displayed:

├─ 400.html
├─ 404.html
├─ 500.html
├─ other.html

We will create these page files later, and then we will start to talk about the problem of page rendering.

<br/>

Rendering page logic

First we create the default error page template file mi-http-error/error.html, here using the nunjuckssyntax .

<!DOCTYPE html>
<html>
<head>
  <title>Error - {{ status }}</title>
</head>
<body>
  <div id="error">
    <h1>Error - {{ status }}</h1>
    <p>Looks like something broke!</p>
    {% if (env === 'development') %}
    <h2>Message:</h2>
    <pre>
      <code>
        {{ error }}
      </code>
    </pre>
    <h2>Stack:</h2>
    <pre>
      <code>
        {{ stack }}
      </code>
    </pre> 
    {% endif %}
  </div>
</body>
</html>

<br/>

Because it involves parsing the file path, we need to import the pathmodule . In addition, nunjuckstools to parse templates. pathis a nodemodule , we just need npmto install it from nunjucks.

<br/>

Install the nunjucksmodule to parse template files:

npm i nunjucks -S

<br/>

Modify mi-http-error/index.js, import pathand nunjucksmodules:

// 引入 path nunjucks 模块 
const Path = require('path') 
const nunjucks = require('nunjucks')

module.exports = () => {
  // 此处代码省略,与之前一样
}

<br/>

In order to support the custom error file directory, the code that originally called the middleware needs to be modified. We pass in a configuration object to the middleware, which has a field errorPageFolderrepresenting the custom error file directory.

Edit middleware/index.js:

// app.use(miHttpError())
app.use(miHttpError({
  errorPageFolder: path.resolve(__dirname, '../errorPage')
}))

Note: In the code, we specified /errorPageas the default template file directory.

<br/>

Modify mi-http-error/index.js, process the received parameters:

const Path = require('path') 
const nunjucks = require('nunjucks')

module.exports = (opts = {}) => {
  // 400.html 404.html other.html 的存放位置
  const folder = opts.errorPageFolder
  // 指定默认模板文件
  const templatePath = Path.resolve(__dirname, './error.html') 

  let fileName = 'other'
  return async (ctx, next) => {
    try {
       await next()
       if (ctx.response.status === 404 && !ctx.response.body) ctx.throw(404);
    } catch (e) {
      let status = parseInt(e.status)
      const message = e.message
      if(status >= 400){
        switch(status){
          case 400:
          case 404:
          case 500:
            fileName = status;
            break;
          default:
            fileName = 'other'
        }
      }else{// 其它情况,统一返回为 500
        status = 500
        fileName = status
      }
      // 确定最终的 filePath 路径
      const filePath = folder ? Path.join(folder, `${fileName}.html`) : templatePath
    }
  }
}

<br/>

With the path and parameters ready, all we need to do is return to the rendered page.

<br/>

Modify mi-http-error/index.js, return the corresponding view page for different captured errors:

const Path = require('path') 
const nunjucks = require('nunjucks')
module.exports = (opts = {}) => {
  // 增加环境变量,用来传入到视图中,方便调试
  const env = opts.env || process.env.NODE_ENV || 'development'  

  const folder = opts.errorPageFolder
  const templatePath = Path.resolve(__dirname, './error.html')
  let fileName = 'other'
  return async (ctx, next) => {
    try {
       await next()
       if (ctx.response.status === 404 && !ctx.response.body) ctx.throw(404);
    } catch (e) {
      let status = parseInt(e.status)
      const message = e.message
      if(status >= 400){
        switch(status){
          case 400:
          case 404:
          case 500:
            fileName = status;
            break;
          default:
            fileName = 'other'
        }
      }else{
        status = 500
        fileName = status
      }
      const filePath = folder ? Path.join(folder, `${fileName}.html`) : templatePath
      
      // 渲染对应错误类型的视图,并传入参数对象
      try{
        // 指定视图目录
        nunjucks.configure( folder ? folder : __dirname )
        const data = await nunjucks.render(filePath, {
          env: env, // 指定当前环境参数
          status: e.status || e.message, // 如果错误信息中没有 status,就显示为 message
          error: e.message, // 错误信息
          stack: e.stack // 错误的堆栈信息
        })
        // 赋值给响应体
        ctx.status = status
        ctx.body = data
      }catch(e){
        // 如果中间件存在错误异常,直接抛出信息,由其他中间件处理
        ctx.throw(500, `错误页渲染失败:${e.message}`)
      }
    }
  }
}

What the above does is use the rendering engine to render the template file, and put the generated Httpcontent Responsein the , and display it in front of the user. Interested students can go to the middleware source code to error.htmlview template content (in fact, it is koa-errorslightly modified from there).

<br/>

At the end of the code, we also have an exception thrown ctx.throw(), that is to say, there will be exceptions when the middleware is processing, so we need to do an error monitoring process in the outermost layer.

Edit middleware/index.js:

const path = require('path')
const ip = require("ip")
const bodyParser = require('koa-bodyparser')
const nunjucks = require('koa-nunjucks-2')
const staticFiles = require('koa-static')

const miSend = require('./mi-send')
const miLog = require('./mi-log')
const miHttpError = require('./mi-http-error')
module.exports = (app) => {
  app.use(miHttpError({
    errorPageFolder: path.resolve(__dirname, '../errorPage')
  }))

  app.use(miLog(app.env, {
    env: app.env,
    projectName: 'koa2-tutorial',
    appLogLevel: 'debug',
    dir: 'logs',
    serverIp: ip.address()
  }));

  app.use(staticFiles(path.resolve(__dirname, "../public")))

  app.use(nunjucks({
    ext: 'html',
    path: path.join(__dirname, '../views'),
    nunjucksConfig: {
      trimBlocks: true
    }
  }));

  app.use(bodyParser())
  app.use(miSend())

  // 增加错误的监听处理
  app.on("error", (err, ctx) => {
    if (ctx && !ctx.headerSent && ctx.status < 500) {
      ctx.status = 500
    }
    if (ctx && ctx.log && ctx.log.error) {
      if (!ctx.state.logged) {
        ctx.log.error(err.stack)
      }
    }
  }) 
}

<br/>

Next, we add the corresponding error rendering page:

Create errorPage/400.html:

<!DOCTYPE html>
<html>
<head>
  <title>400</title>
</head>
<body>
  <div id="error">
    <h1>Error - {{ status }}</h1>
    <p>错误码 400 的描述信息</p>
    {% if (env === 'development') %}
    <h2>Message:</h2>
    <pre>
      <code>
        {{ error }}
      </code>
    </pre>
    <h2>Stack:</h2>
    <pre>
      <code>
        {{ stack }}
      </code>
    </pre> 
    {% endif %}
  </div>
</body>
</html>

<br/>

Create errorPage/404.html:

<!DOCTYPE html>
<html>
<head>
  <title>404</title>
</head>
<body>
  <div id="error">
    <h1>Error - {{ status }}</h1>
    <p>错误码 404 的描述信息</p>
    {% if (env === 'development') %}
    <h2>Message:</h2>
    <pre>
      <code>
        {{ error }}
      </code>
    </pre>
    <h2>Stack:</h2>
    <pre>
      <code>
        {{ stack }}
      </code>
    </pre> 
    {% endif %}
  </div>
</body>
</html>

<br/>

Create errorPage/500.html:

<!DOCTYPE html>
<html>
<head>
  <title>500</title>
</head>
<body>
  <div id="error">
    <h1>Error - {{ status }}</h1>
    <p>错误码 500 的描述信息</p>
    {% if (env === 'development') %}
    <h2>Message:</h2>
    <pre>
      <code>
        {{ error }}
      </code>
    </pre>
    <h2>Stack:</h2>
    <pre>
      <code>
        {{ stack }}
      </code>
    </pre> 
    {% endif %}
  </div>
</body>
</html>

<br/>

Create errorPage/other.html:

<!DOCTYPE html>
<html>
<head>
  <title>未知异常</title>
</head>
<body>
  <div id="error">
    <h1>Error - {{ status }}</h1>
    <p>未知异常</p>
    {% if (env === 'development') %}
    <h2>Message:</h2>
    <pre>
      <code>
        {{ error }}
      </code>
    </pre>
    <h2>Stack:</h2>
    <pre>
      <code>
        {{ stack }}
      </code>
    </pre> 
    {% endif %}
  </div>
</body>
</html>

<br/>

errorPageThe content displayed on the page can be modified according to your own project information. The above is for reference only.

<br/>

So far, we have basically completed the middleware used to handle "request errors". And this middleware is not a fixed form. In real projects, everyone needs to consider their own business scenarios and needs, and create middleware suitable for their own projects.

In the next section, we'll learn about specifications and deployment - developing appropriate team specifications to improve development efficiency.

Previous: iKcamp new course is launched~~~~~iKcamp|Building Node.js based on Koa2 (video included) ☞ Processing static resources

Recommendation: The self-report of the translation project master:

1. Dry goods | Everyone is a master of translation projects

2. The teaching of WeChat Mini Program produced by iKcamp consists of 5 chapters and 16 subsections (including video)

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325130657&siteId=291194637