错误处理(异常处理)

  • 我们都知道,node应用程序是运行在一个单线程,单进程的环境当中的,这也就意味着只要成勋在运行过程中出现一个错误,整个服务器就会崩溃,查看下面的代码:
/* 使用node搭建一个简单的服务器 */
const http = require('http') // 引入http模块

// 这里我们定义一个接口返回一个丢出一个错误,当我们请求'/data'接口时,服务器就会报错
const server = http.createServer((req,res)=>{
if(req.url === '/data'){
throw new Error('丢出一个错误!!!')
}
res.end('服务器正常运行!')
})

server.listen(3000,()=>{
console.log('服务器已启动,3000端口正在监听...');
})

结果展示:

  1. 当我们调用'/list'接口时,数据成功返回,服务器也能正常运行

image

  1. 当我们调用'/data'接口时,数据不能正常返回且服务器也发生了报错,导致整个服务器崩溃

image

由此可见,要想搭建一个健壮的node服务器,就需要捕获该服务器可能发生的每一个异常错误,这一点在node开发中非常的重要,由此引出node中的异常捕获!

Node中的异常捕获以及处理

  • 首先我们可以将node中的代码非为两类,一类是同步代码,另一类则是异步代码,同步代码的异常处理还是非常简单的,我么直接将同步代码放进try..catch中即可,而异步代码就需要使用promise或者async/await

使用try...catch捕获同步代码的异常

const http = require('http') // 引入http模块

// 这里我们定义一个接口返回一个丢出一个错误,当我们请求'/data'接口时,服务器就会报错
const server = http.createServer((req,res)=>{
// 使用try-catch捕获同步代码的异常
try {
if(req.url === '/data'){
throw new Error('丢出一个错误!!!')
}
res.end('服务器正常运行!')
} catch (error) {
console.log('错误信息:',error);
res.end('请求失败!请稍后重试..')
}
})

server.listen(3000,()=>{
console.log('服务器已启动,3000端口正在监听...');
})

结果展示

image

由上图我们可以看出,即便第一个接口请求出错了,整个服务器也不会崩溃,其他接口也能正常响应!

使用promise以及async/await捕获异步代码的异常

  • promise
const http = require('http') // 引入http模块

// 这里我们定义一个接口返回一个丢出一个错误,当我们请求'/data'接口时,服务器就会报错
const server = http.createServer((req,res)=>{
// 使用promise来捕获异步代码的异常
new Promise(()=>{
if(req.url === '/data'){
throw new Error('丢出一个错误!!!')
}
res.end('服务器正常运行!')
}).catch((err)=>{
console.log('错误信息:',err);
res.end('请求失败,请稍后重试...')
})
})

server.listen(3000,()=>{
console.log('服务器已启动,3000端口正在监听...');
})
  • async/await
const http = require('http') // 引入http模块

// 这里我们定义一个接口返回一个丢出一个错误,当我们请求'/data'接口时,服务器就会报错
const server = http.createServer(async (req,res)=>{
// 使用async/await来捕获异步代码的异常
/*
async/await可以将异步代码转换为同步代码结合try-catch来使用
*/

/* 例子1 */
// await new Promise(()=>{
// if(req.url === '/data'){
// throw new Error('丢出一个错误!!!')
// }
// res.end('服务器正常运行!')
// }).catch((err)=>{
// console.log('错误信息:',err);
// res.end('请求失败,请稍后重试...')
// })

/* 例子2 */
try {
await new Promise(()=>{
if(req.url === '/data'){
throw new Error('丢出一个错误!!!')
}
res.end('服务器正常运行!')
})
} catch (error) {
console.log('错误信息:',error);
res.end('请求失败,请稍后重试...')
}
})

server.listen(3000,()=>{
console.log('服务器已启动,3000端口正在监听...');
})

结果均如上面的动图所示

全局异常捕获(process.on('uncaughtException'))

  • 这种方案可以监听全局的异常并处理,就可以避免了又一场未被捕获的情况出现导致整个应用程序崩溃,这种方案可以认为是万能的,但是实用性很差,因为如果一个错误没有被及时捕获,任由其冒泡并最终有process来处理,那么此时抛出的这个异常的上下文信息就已经丢失了,缺少诸如req,res等对象,我们就无法对客户端发出一个积极的响应,用户只能等待请求超时,这是一个非常不好的用户体验,因此这只能作为一个兜底的方案.
const http = require('http') // 引入http模块

// 引入全局异常捕获
process.on('uncaughtException',(error)=>{
console.log('全局process捕获的异常信息:',error);
})

// 这里我们定义一个接口返回一个丢出一个错误,当我们请求'/data'接口时,服务器就会报错
const server = http.createServer(async (req,res)=>{
if(req.url === '/data'){
throw new Error('丢出一个错误!!!')
}
res.end('服务器正常运行!')
})

server.listen(3000,()=>{
console.log('服务器已启动,3000端口正在监听...');
})

以上提到的均为node原生的异常捕获以及处理, 使用框架则另需额外学习(框架应该有自带的错误捕获)

Crypto(加密模块)

  • 官方文档

  • crypto模块的目的是为了提供通用的加密和哈希算法。用纯JavaScript代码实现这些功能不是不可能, 但速度会非常慢。NodejsC/C++实现这些算法后,通过cypto这个模块暴露为JavaScript接口,这样用 起来方便,运行速度也快。

  • 加密模块的使用场景也非常的多,例如我们搭建后台将用户数据加密保存到数据库中,这样的话即便别人拿到了你的数据库,也是获取到一堆没有用的加密信息

MD5加密算法

  • 这种算法是不可逆的,也就是说你使用它进行一些数据的加密后获得到的加密数据是无法转回来来的数据,这种一般用于用户的隐私信息保存,如密码,使用场景如下:你可以使用MD5算法将用户的密码经过加密后保存到数据库中,最后用户的每一次登录提交过来的密码再进行一次MD5的数据加密,如果两者一致证明登录校验成功.
// MD5是一种常用的哈希算法,用于给任意数据一个“签名”。 
const crypto = require('crypto') // 引入加密模块

const hash = crypto.createHash('md5') // 创建hash加密算法

hash.update('Hello World!') // 加密数据
hash.update('lam') // 加密数据(可以执行多次)

console.log(
'加密后的数据输出:(16进制或者base64):',
// hash.digest('hex'),// 16进制 ed076287532e86365e841e92bfc50d8c
hash.digest('base64'),// base64 7Qdih1MuhjZehB6Sv8UNjA==
);

SHA1密钥算法

  • 上面提到的md5这种加密算法还不算非常的安全,因为它是随机的,意味着你也不知道你传进去的数据会生成什么,这也就导致了别人一样可以通过的方式来获取你的原始数据(彩虹表了解一下),下面介绍这种SHA1算法它可以让你自定义设置一个密码,去生成不一样的加密数据,这就可以让用户有多种选择,不一样的数据类型我可以设置不一样的密钥去加密
// SHA1是一种常用的Hmac算法,用于给任意数据一个“签名”。 
/*
只要密钥发生了变化,那么同样的输入数据也会得到不同的签名,
因此,可以把Hmac理解为用随机数“增强”的哈希算法。
*/
const crypto = require('crypto') // 引入加密模块

const hash = crypto.createHmac('sha1','secret-key') // 创建hash加密算法,传入自定义的密钥

hash.update('Hello World!') // 加密数据
hash.update('lam') // 加密数据(可以执行多次)

console.log(
'加密后的数据输出:(16进制或者base64):',
hash.digest('hex'),// 16进制 3cd7576deea14c8dd9ef379389bc44d54e260972
// hash.digest('base64'),// base64 PNdXbe6hTI3Z7zeTibxE1U4mCXI=
);

对称加密算法

  • 对称加密算法下面讲讲AES``,AES是一种常用的对称加密算法,加解密都用同一个密钥。crypto模块提供了AES支持,但是需要自己 封装好函数
const crypto = require("crypto")

// 加密方法
/*
key: 加密密钥
iv: 数据偏移量
data: 要加密的数据
*/
function encrypt(key,iv,data){
let dep = crypto.createCipheriv("aes-128-cbc",key,iv)

// 固定写法(binary为二进制) 以dep.final()结尾
return dep.update(data,'binary','hex') + dep.final("hex")
}

// 解密方法
/*
key: 加密密钥
iv: 数据偏移量
crypted: 要解密的数据
*/
function decrypt(key,iv,crypted){
crypted = Buffer.from(crypted,"hex").toString("binary")

let dep = crypto.createDecipheriv("aes-128-cbc",key,iv)
// 固定写法(binary为二进制) 以dep.final()结尾
return dep.update(crypted,'binary','utf8')+dep.final("utf8")
}

// 设置加密密钥以及数据偏移量以及加密数据
//16*8 = 128
let key="abcdef1234567890"
let iv="tbcdey1234567890"

let data = "lam"

// 加密数据
let cryted = encrypt(key,iv,data)
console.log("加密结果-",cryted) // 加密结果- 7c6b70287c32e8c55c80e49f231a782a

// 解密数据
let decrypted = decrypt(key,iv,cryted)
console.log("解密结果-",decrypted) // 解密结果- lam