iKcamp|基于Koa2搭建Node.js实战(含视频)? 错误处理

沪江CCtalk视频地址:https://www.cctalk.com/v/15114923887518

处理错误请求

爱能遮掩一切过错。

当我们在访问一个站点的时候,如果访问的地址不存在(404),或服务器内部发生了错误(500),站点会展示出某个特定的页面,比如:

那么如何在 Koa 中实现这种功能呢?其实,一个简单的中间件即可实现,我们把它称为 http-error。实现过程并不复杂,拆分为三步来看:

  • 第一步:确认需求
  • 第二步:整理思路
  • 第三步:代码实现

确认需求

打造一个事物前,需要先确认它具有什么特性,这就是需求。

在这里,稍微整理下即可得到几个基本需求:

  • 在页面请求出现 400500 类错误码的时候,引导用户至错误页面;
  • 提供默认错误页面;
  • 允许使用者自定义错误页面。

整理思路

现在,从一个请求进入 Koa 开始说起:

  1. 一个请求访问 Koa,出现了错误;
  2. 该错误会被 http-error 中间件捕捉到;
  3. 错误会被中间件的错误处理逻辑捕捉到,并进行处理;
  4. 错误处理逻辑根据错误码状态,调用渲染页面逻辑;
  5. 渲染页面逻辑渲染出对应的错误页面。

可以看到,关键点就是捕捉错误,以及实现错误处理逻辑和渲染页面逻辑。

代码实现

建立文件

基于教程目录结构,我们创建 middleware/mi-http-error/index.js 文件,存放中间件的逻辑代码。初始目录结构如下:

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

注意: 目录结构不存在,需要自己创建。

捕捉错误

该中间件第一项需要实现的功能是捕捉到所有的 http 错误。根据中间件的洋葱模型,需要做几件事:

1. 引入中间件

修改 middleware/index.js,引入 mi-http-error 中间件,并将它放到洋葱模型的最外层

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. 捕获中间件异常情况

修改 mi-http-error/index.js,在中间件内部对内层的其它中间件进行错误监听,并对捕获 catch 到的错误进行处理

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) {
      /*此处进行错误处理,下面会讲解具体实现*/
    }
  }
}

上面的准备工作做完,下面实现两个关键逻辑。

错误处理逻辑

错误处理逻辑其实很简单,就是对错误码进行判断,并指定要渲染的文件名。这段代码运行在错误 catch 中。

修改 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‘
        }
      }
    }
  }
}

也就是说,对于不同的情况,会展示不同的错误页面:

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

这几个页面文件我们会在后面创建,接下来我们开始讲述下页面渲染的问题。

渲染页面逻辑

首先我们创建默认的错误页面模板文件 mi-http-error/error.html,这里采用 nunjucks 语法。

<!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>

因为牵涉到文件路径的解析,我们需要引入 path 模块。另外,还需要引入 nunjucks 工具来解析模板。pathnode 模块,我们只需从 npm 上安装nunjucks 即可。

安装 nunjucks 模块来解析模板文件:

npm i nunjucks -S

修改 mi-http-error/index.js,引入 pathnunjucks 模块:

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

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

为了支持自定义错误文件目录,原来调用中间件的代码需要修改一下。我们给中间件传入一个配置对象,该对象中有一个字段 errorPageFolder,表示自定义错误文件目录。

修改 middleware/index.js

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

注意: 代码中,我们指定了 /errorPage 为默认的模板文件目录。

修改 mi-http-error/index.js,处理接收到的参数:

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
    }
  }
}

路径和参数准备好之后,我们需要做的事情就剩返回渲染的页面了。

修改 mi-http-error/index.js,对捕捉到的不同错误返回相应的视图页面:

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}`)
      }
    }
  }
}

上面所做的是使用渲染引擎对模板文件进行渲染,并将生成的内容放到 HttpResponse 中,展示在用户面前。感兴趣的同学可以去中间件源码中查看 error.html 查看模板内容(其实是从 koa-error 那里拿来稍作修改的)。

在代码的最后,我们还有一个异常的抛出 ctx.throw(),也就是说,中间件处理时候也会存在异常,所以我们需要在最外层做一个错误监听处理。

修改 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)
      }
    }
  })
}

下面,我们增加对应的错误渲染页面:

创建 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>

创建 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>

创建 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>

创建 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>

errorPage 中的页面展示内容,可以根据自己的项目信息修改,以上仅供参考。

至此,我们基本完成了用来处理『请求错误』的中间件。而这个中间件并不是固定的形态,大家在真实项目中,还需要多考虑自己的业务场景和需求,打造出适合自己项目的中间件。

下一节中,我们将学习下规范与部署——制定合适的团队规范,提升开发效率。

上一篇:iKcamp新课程推出啦~~~~~iKcamp|基于Koa2搭建Node.js实战(含视频)? 处理静态资源

推荐: 翻译项目Master的自述:

1. 干货|人人都是翻译项目的Master

2. iKcamp出品微信小程序教学共5章16小节汇总(含视频)

原文地址:https://www.cnblogs.com/ikcamp/p/8404355.html

时间: 2024-10-13 08:54:21

iKcamp|基于Koa2搭建Node.js实战(含视频)? 错误处理的相关文章

iKcamp|基于Koa2搭建Node.js实战(含视频)? 代码分层

视频地址:https://www.cctalk.com/v/15114923889408 文章 在前面几节中,我们已经实现了项目中的几个常见操作:启动服务器.路由中间件.Get 和 Post 形式的请求处理等.现在你已经迈出了走向成功的第一步. 目前,整个示例中所有的代码都写在 app.js 中.然而在业务代码持续增大,场景更加复杂的情况下,这种做法无论是对后期维护还是对患有强迫症的同学来说都不是好事.所以我们现在要做的就是:『分梨』. 分离 router 路由部分的代码可以分离成一个独立的文件

iKcamp|基于Koa2搭建Node.js实战(含视频)? 视图Nunjucks

视频地址:https://www.cctalk.com/v/15114923888328 视图 Nunjucks 彩虹是上帝和人类立的约,上帝不会再用洪水灭人. 客户端和服务端之间相互通信,传递的数据最终都会展示在视图中,这时候就需要用到『模板引擎』. 什么是模板引擎? 模板引擎是为了使用户界面与业务数据分离而产生的,可以生成特定格式的文档.例如,用于网站的模板引擎会生成一个标准的 HTML 文档. 市面上常见的模板引擎很多,例如:Smarty.Jade.Ejs.Nunjucks 等,可以根据个

iKcamp|基于Koa2搭建Node.js实战(含视频)? 记录日志

沪江CCtalk视频地址:https://www.cctalk.com/v/15114923883523 log 日志中间件 最困难的事情就是认识自己. 在一个真实的项目中,开发只是整个投入的一小部分,版本迭代和后期维护占了极其重要的部分.项目上线运转起来之后,我们如何知道项目运转的状态呢?如何发现线上存在的问题,如何及时进行补救呢?记录日志就是解决困扰的关键方案.正如我们每天写日记一样,不仅能够记录项目每天都做了什么,便于日后回顾,也可以将做错的事情记录下来,进行自我反省.完善的日志记录不仅能

用node.js 搭建的博客程序心得(node.js实战读书笔记1)

学习node已经有一段时间了,之前把了不起的node.js看完了,基本算了解了一些node的基本的用法还有一些概念了,然后就开始看第二本node.js实战,第一章就是搭建一个博客程序.但是不得不吐槽一下node,发展得太块了,很多库已经和之前的用法不一样了,就要一直去百度google来查询最新的用法,其实我觉得这样并不见得是一件好事,因为不稳定,所以就不好学习,就要一直保持对于node的关注.不废话了,这篇文章就大概说一些在这章里面所学习到的一些东西,经验总结吧 1.express - 基于 N

SSH2+LigerUI+JBPM5+Node.js实战开发视频教程

基于SSH2+LigerUI+JBPM5+Node.js技术实现大型J2EE金融行业财务预算系统 (第三季适合二年以上的开发者)课程分类:Java框架适合人群:中级课时数量:51课时用到技术:SSH2.LigerUI.JBPM5.Node.js技术涉及项目:大型J2EE金融行业财务预算系统咨询qq:1840215592课程项目所采用的技术架构为:struts2+spring+hibernate+LigerUI+jbpm5+mysql+自定义表单+node.js+webservice+思维导图+j

腾讯云:搭建 Node.js 环境

搭建 Node.js 环境 安装 Node.js 环境 任务时间:5min ~ 10min Node.js 是运行在服务端的 JavaScript, 是基于 Chrome JavaScript V8 引擎建立的平台. 下载并安装 Node.js 下载最新的稳定版 v6.10.3 到本地 wget https://nodejs.org/dist/v6.10.3/node-v6.10.3-linux-x64.tar.xz 下载完成后, 将其解压 tar xvJf node-v6.10.3-linux

快速搭建 Node.js 开发环境以及加速 npm

在公交车上刷微博,还是有很多同学在咨询: 如何快速搭建 node 开发环境 npm 超慢 github 无法打开的问题 于是我觉得应该写一篇文章解答所有这些起步问题,让新同学也能顺顺利利入门. 快速搭建 Node.js 开发环境 如果你想长期做 node 开发, 或者想快速更新 node 版本, 或者想快速切换 node 版本, 那么在非 Windows(如 osx, linux) 环境下, 请使用 nvm 来安装你的 node 开发环境, 保持系统的干净. 如果你使用 Windows 做开发,

基于hapi的Node.js后端开发

基于hapi的Node.js后端开发 1.背景今年下半年公司线上程序频繁出现问题.不是内存跑满,就是CPU跑满就是程序自己挂掉了.严重影响了现有的小程序业务.目前线上主要架构是dubbo-x搭建的分布式.之前主要用来为app做数据服务.解决思路1:优化现有架构,找到问题(奈何本人虽然写了不少java代码.但是对java基本还是处于一窍不通的状态.)解决思路2:业务分离,为小程序从新搭建一套服务器系统.与原有的互不影响.这样既解决了小程序服务稳定性问题,也流出了时间来处理"思路1",不会

《Node.js实战》博客实例 express4.x

大致看了一阵子nodejs的书,对语法有初步的了解.但是还是写不出个实例来.最近学长给我的这本书感觉挺入门的<Node.js实战>电子工业出版社.毕竟图书馆借的,出版时间已经是两年前了.很多代码都更新了.特别是express 4.x相对与express 3.x有很多的修改.比如把中间件独立出来.好处是有.这样express的更新就不用受这些中间件的影响 坏处也许主要是对我这种新手吧.大部分教材都是express 3.x甚至是 2.x 的.这样学习起来就有困难.也许不会有人看到这个.即使解决不了