10. crypto模块
crypto模块的目的是为了提供通用的加密和哈希算法。用纯JavaScript代码实现这些功能不是不可能,但速度会非常慢。Nodejs用C/C++实现这些算法后,通过crypto这个模块暴露为JavaScript接口,这样用起来方便,运行速度也快。
MD5是一种常用的哈希算法,用于给任意数据一个“签名”。这个签名通常用一个十六进制的字符串表示:
const crypto = require('crypto'); const hash = crypto.createHash('md5'); //可以用update方法把一段字符给加密 hash.update("hello csdn") //digest()该函数可以传入两个参数:hex(十六进制)或 base64 ,表示以什么形式输出 console.log(hash.digest("base64"))
update()方法默认字符串编码为UTF-8,也可以传入Buffer。
如果要计算SHA1,只需要把'md5'改成'sha1',就可以得到SHA1的结果1f32b9c9932c02227819a4151feed43e131aca40。
Hmac算法也是一种哈希算法,它可以利用MD5或SHA1等哈希算法。不同的是,Hmac还需要一个密钥:
const crypto = require('crypto'); //密钥 let secretKey="32332fd22fd" const hmac = crypto.createHmac('sha256', secretKey); hmac.update('Hello, nodejs!'); console.log(hmac.digest('hex'));
只要密钥发生了变化,那么同样的输入数据也会得到不同的签名,因此,可以把Hmac理解为用随机数“增强”的哈希算法。
AES是一种常用的对称加密算法,加解密都用同一个密钥。crypto模块提供了AES支持,但是需要自己封装好函数,便于使用:
const crypto = require("crypto"); //加密的方法 //key,iv必须是16个字节 function encrypt (key, iv, data) { //加密和解密都要用同一种算法,这里是aes-128-cbc let decipher = crypto.createCipheriv('aes-128-cbc', key, iv); return decipher.update(data, 'binary', 'hex') + decipher.final('hex'); } //解密的方法 function decrypt (key, iv, crypted) { crypted = Buffer.from(crypted, 'hex').toString('binary'); let decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); return decipher.update(crypted, 'binary', 'utf8') + decipher.final('utf8'); } //测试加密和解密 let key = "abcdef1234567890" let iv = "djfiuchfbd887766" let data = "hello world" //加密 let encry = encrypt(key,iv,data) console.log(encry) //解密 let decry = decrypt(key,iv,encry) console.log(decry)
可以看出,加密后的字符串通过解密又得到了原始内容。
11. 路由小练习
路由就是服务器对前端访问传来的不同路径的处理的一种机制就叫路由。我们这一节就慢慢打造一个路由,来检验我们上面知识的学习成果。注意,这里只是模拟写,有一些细节问题勿喷。
我们项目下新建一个static文件夹,里面提前创建好home.html 、login.html 、404.html 。然后创建一个server.js创建服务器。
先来写一个最笨的路由版本:
server.js代码:
const http=require("http") const fs=require("fs") http.createServer((req, res) => { let myURL = new URL(req.url,"http://127.0.0.1"); switch (myURL.pathname) { case "/login": res.writeHead(200,{ "Content-Type":"text/html;charset=utf-8", }); res.write(fs.readFileSync("./static/login.html","utf8")); break case "/home": res.writeHead(200,{ "Content-Type":"text/html;charset=utf-8", }); res.write(fs.readFileSync("./static/home.html","utf8")); break default : res.writeHead(404,{ "Content-Type":"text/html;charset=utf-8", }); res.write(fs.readFileSync("./static/404.html","utf8")); break } res.end() }).listen(3000,()=>{ console.log("server start") })
我们发现上面的写法是不是很麻烦很冗杂啊?我们其实可以把switch单独拿到一个router.js文件中,用module.exports导出。
server.js:
const http=require("http") const router =require("./router") http.createServer((req, res) => { let myURL = new URL(req.url,"http://127.0.0.1"); router(res,myURL.pathname); res.end() }).listen(3000,()=>{ console.log("server start") })
router.js:
const fs=require("fs") function router(res,pathname){ switch (pathname) { case "/login": res.writeHead(200,{ "Content-Type":"text/html;charset=utf-8", }); res.write(fs.readFileSync("./static/login.html","utf8")); break case "/home": res.writeHead(200,{ "Content-Type":"text/html;charset=utf-8", }); res.write(fs.readFileSync("./static/home.html","utf8")); break default : res.writeHead(404,{ "Content-Type":"text/html;charset=utf-8", }); res.write(fs.readFileSync("./static/404.html","utf8")); break } } module.exports = router;
虽然小小地改造了一下,但我还是对改造不怎么满意,我们发现switch写得还是太复杂了。
我们再代码进行改造:
router.js:
const fs = require("fs") const path = require("path") //把重复的方法封装成一个函数 function render(res, path) { res.writeHead(200, { "Content-Type": "text/html;charset=utf8" }) res.write(fs.readFileSync(path, "utf8")) res.end() } const router = { "/login": (req, res) => { render(res, "./static/login.html") }, "/home": (req, res) => { render(res, "./static/home.html") }, "/404": (req, res) => { res.writeHead(404, { "Content-Type": "text/html;charset=utf8" }) res.write(fs.readFileSync("./static/404.html", "utf8")) } } module.exports = router;
server.js:
const http=require("http") const router =require("./router") http.createServer((req, res) => { let myURL = new URL(req.url,"http://127.0.0.1"); try { router[myURL.pathname](req,res); }catch (e){ router["/404"](req,res); } }).listen(3000,()=>{ console.log("server start") })
12. path路径模块
path 模块提供了许多非常实用的函数来访问文件系统并与文件系统进行交互,是非常重要的一个模块。
我们无需安装,它作为 Node.js 核心的组成部分,可以通过简单地引用来使用它:
const path = require('path')
该模块提供了 path.sep(作为路径段分隔符,在 Windows 上是 \,在 Linux/macOS 上是 /)和 path.delimiter(作为路径定界符,在 Windows 上是 ;,在 Linux/macOS 上是 :)。
path模块有如下常用的方法:
(1) path.basename()
用于返回路径的最后一部分。 第二个参数可以过滤掉文件的扩展名:
const path = require('path') console.log(path.basename('/test/something'));//something console.log(path.basename('/test/something.txt')); //something.txt console.log(path.basename('/test/something.txt', '.txt'));//something
(2) path.dirname()
用于返回路径的目录部分。
const path = require('path') console.log(path.dirname('/test/something'));// test console.log(path.dirname('/test/something/file.txt')); // test/something
(3) path.extname()
返回路径的扩展名部分。
const path = require('path') console.log(path.extname('/test/something')) // '' console.log(path.extname('/test/something/file.txt')) // .txt
(4) path.isAbsolute()
判断是否为绝对路径,如果是绝对路径,则返回 true。
const path = require('path') console.log(path.isAbsolute('/test/something')) // true console.log(path.isAbsolute('./test/something')) // false
(6) path.join()
连接路径的两个或多个部分
const path = require('path') const name = 'haiexijun' console.log(path.join('/', 'users', name, 'notes.txt')) // \users\haiexijun\notes.txt
(7) path.normalize()
当包含类似 .、.. 或双斜杠等相对的说明符时,则尝试计算实际的路径:
const path = require('path') console.log(path.normalize('/users/haiexijun/..//test.txt')) // \users\test.txt
(8) path.parse()
解析对象的路径为组成其的片段:
const path = require('path') console.log(path.parse('/users/test.txt')) // \users\test.txt
root
: 根路径。
dir
: 从根路径开始的文件夹路径。
base
: 文件名 + 扩展名
name
: 文件名
ext
: 文件扩展名
(9) path.relative()
接受 2 个路径作为参数。 基于当前工作目录,返回从第一个路径到第二个路径的相对路径。
const path = require('path') console.log(path.relative('/Users/haiexijun', '/Users/haiexijun/test.txt')) // test.txt console.log(path.relative('/Users/haiexijun', '/Users/haiexijun/something/test.txt')) // something\test.txt
(10) path.resolve()
可以使用 path.resolve() 获得相对路径的绝对路径计算。
const path = require('path') console.log(path.resolve('test.txt')) // D:\JavaWebProject\ts-practice\test.txt
通过指定第二个参数,resolve 会使用第一个参数作为第二个参数的基准:
const path = require('path') console.log(path.resolve('tmp', 'test.txt')) // D:\JavaWebProject\ts-practice\tmp\test.txt
如果第一个参数以斜杠开头,则表示它是绝对路径
const path = require('path') console.log(path.resolve('/tmp', 'test.txt')) // D:\tmp\test.txt
(11) __dirname和__filename 内置常量
内置的意思就是不需要额外去定义它,它的作用就是表示当前所在的地址
__dirname:用来动态获取当前文件模块所属目录的绝对路径
__filename:用来动态获取当前文件的绝对路径
const path = require('path') console.log(__dirname) // D:\JavaWebProject\ts-practice console.log(__filename) // D:\JavaWebProject\ts-practice\server.js
13. os操作系统模块
该模块提供了许多函数,可用于从底层的操作系统和程序运行所在的计算机上检索信息并与其进行交互。
const os = require('os')
os 提供的主要方法:
(1) os.arch()
返回标识底层架构的字符串,例如 arm、x64、arm64。
(2) os.cpus()
返回关于系统上可用的 CPU 的信息。
(3) os.freemem()
返回代表系统中可用内存的字节数。
(4) os.homedir()
返回到当前用户的主目录的路径。
(5) os.hostname()
返回主机名。
(6) os.loadavg()
返回操作系统对平均负载的计算。
(7) os.networkInterfaces()
返回系统上可用的网络接口的详细信息。
(8) os.platform()
返回为 Node.js 编译的平台有:darwin、freebsd、linux、openbsd、win32
(9) os.release()
返回标识操作系统版本号的字符串。
(10) os.tmpdir()
返回指定的临时文件夹的路径。
(11) os.totalmem()
返回表示系统中可用的总内存的字节数。
(12) os.type()
标识操作系统:在macOS 上为Darwin,在Windows 上为 Windows_NT。
(13) os.uptime()
返回自上次重新启动以来计算机持续运行的秒数。
(14) os.userInfo()
返回包含当前 username、uid、gid、shell 和 homedir 的对象。
14. Buffer二进制缓冲区
Buffer 对象用于表示固定长度的字节序列,可以将 buffer 视为存储二进制数据的数组,每个整数代表一个数据字节。
Buffer 被引入用以帮助开发者处理二进制数据,在此生态系统中传统上只处理字符串而不是二进制数据。
Buffer 与流紧密相连。 当流处理器接收数据的速度快于其消化的速度时,则会将数据放入 buffer 中。
一个简单的场景是:当观看 哔哩哔哩 的 视频时,白线超过了观看点:即下载数据的速度比查看数据的速度快,且浏览器会对数据进行缓冲。
使用Buffer不用引入模块,直接使用就可以了。使用 Buffer.from()、Buffer.alloc() 和Buffer.allocUnsafe()方法可以创建 buffer。
let str = "hello" let buf = Buffer.from(str); console.log(buf);
我们上面不是说,Buffer 是以二进制数据存储的吗?这里为什么不是二进制呢?但其实计算机里是以二进制数据存储,但显示时会以十六进制去显示,这是因为二进制太长了。
我们要创建指定字节的空的Buffer , 可以用Buffer.alloc() 和Buffer.allocUnsafe()
let buf1 = Buffer.alloc(1024) let buf2 = Buffer.allocUnsafe(1024)
虽然 alloc 和 allocUnsafe 均分配指定大小的 Buffer(以字节为单位),但是 alloc 创建的 Buffer 会被使用零进行初始化,而 allocUnsafe 创建的 Buffer 不会被初始化。 这意味着,尽管 allocUnsafe 比 alloc 要快得多,但是分配的内存片段可能包含可能敏感的旧数据。
当 Buffer 内存被读取时,如果内存中存在较旧的数据,则可以被访问或泄漏。 这就是使 allocUnsafe 不安全的原因,在使用它时必须格外小心。
let buf = Buffer.alloc(10) buf[0] = 88 //可以存16进制 buf[1] = 0xaa;//可以存8进制 //可以使用 toString() 方法打印 buffer 的全部内容: console.log(buf.toString()) //使用 length 属性获取 buffer 的长度 console.log(buf.length)
要注意Buffer的长度大小一旦确定,就不可以修改了,Buffer其实是对底层内存的直接操作。
当然,还有非常多的方法,这里就不一 一例举了。大家看到这里知道Buffer的作用就好了。