01.node之学习之路
一、初识 Node.js
Node.js是一个基于 Chrome V8 引擎的 JavaScript 运行环境。- 官网Node.js (nodejs.org)
- Node.js 中文官网Node.js 中文网 (nodejs.cn)
- Node.js 的使用注意事项
- Node.js 中不能使用 BOM 和 Dom 的 API
- Node.js = ECMAScript + NodeAPI
- Node.js 中的顶级对象为
global,也可以使用globalThis访问顶级对象。
1.1 常见命令
| 说明 | 操作 |
|---|---|
| 切换盘符 | 盘符: |
| 切换工作目录 | cd |
| 查看目录文件 | dir |
| 查看文件夹下所有的文件内容(包括子文件里的内容) | dir/s |
1.2Buffer(缓冲区)
1.2.1 概念
- buffer 是类似于 Array 的对象,用于表示固定长度的字节序列
- Buffer 的本质是一段内存空间,专门用来处理二进制数据
1.2.2 特点
- Buffer 的大小固定,且不能调整
- Buffer 性能较好,可以直接对计算机内存进行操作
- 每个元素的大小为 1Byte
1.2.3 使用
1.2.3.1Buffer.alloc
// 创建了一个长度为 10 字节的 Buffer,相当于申请了 10 字节的内存空间,每个字节的值为 0
let buf_1 = Buffer.alloc(10); //=>结果为<Buffer 00 00 00 00 00 00 00 00 00 00>
1.2.3.2Buffer.allocUnsafe
// 创建了一个长度为 10 字节的 Buffer,buffer 中可能存在旧数据,可能会影响执行结果,所以叫 unsafe ,但是效率比 alloc 高
let buf_2 = Buffer.allocUnsafe(10);
1.2.3.3Buffer.from
// 通过字符串创建 Buffer
let buf_3 = Buffer.from("hello");
// 通过数组创建 Buffer
let buf_4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);
1.2.3.4buffer 与字符串的转换.toString
//buffer与字符串的转换
let buf = Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]);
console.log(buf.toString());
/**
* 输出结果:
* hello
*/
注意事项
- buffer 只能存储 8bit,如果超出,高位舍弃
- 在 utf-8 下,buffer 存储中文是时,一个汉字=8Byte
二、计算机基础
2.1 进程和线程
进程:
- 进程是程序的一次执行过程
线程:
- 线程是一个进程中执行的一个执行流
三、Node.js API
3.1 模块
3.1.1 fs(file system)
- fs 模块可以实现与硬盘的交互,列如文件的创建,删除,重命名,移动,还有文件内容的写入,读取,以及文件夹的相关操作
// 1. 引入fs模块
const fs = require("fs");
3.1.2 process
- process 可以获取内存的使用量情况
/**
* process 内置模块 用于获取内存使用量
* process.memoryUsage() 返回一个对象 包含内存使用量
* heapUsed: 用于获取当前进程的内存使用量
*/
const process = require("process");
console.log(process.memoryUsage()); //内存使用量
/**
* {
* rss: 30961664, // 常驻内存
* heapTotal: 6438912, // 堆内存总量
* heapUsed: 5873432, // 堆内存使用量
* external: 590279, // 外部内存使用量
* arrayBuffers: 148678 // ArrayBuffer使用量
* }
*/
3.1.3 path 模块
path 模块提供了 操作路径 的功能,我们将介绍如下几个较为常用的几个 API:
| API | 说明 |
|---|---|
| path.resolve(第一个为绝对路径,后面是相对路径) | 拼接规范的绝对路径 常用 |
| path.sep | 获取操作系统的路径分隔符 |
| path.parse | 解析路径并返回对象 |
| path.basename | 获取路径的基础名称 |
| path.dirname | 获取路径的目录名 |
| path.extname | 获得路径的扩展名 |
// 导入 fs 模块
const fs = require('fs')
// 导入 path 模块
const path = require('path')
// 写入文件
// fs.writeFileSync(__dirname + '/index.html', 'love')
console.log(__dirname + '/index.html') //=>D:\Desktop\Node\code\04-path/index.html
// resolve 解决问题 拼接绝对路径 (第一个为绝对路径,后面是相对路径)
console.log(path.resolve(__dirname, './index.html')) //=>D:\Desktop\Node\code\04-path\index.html
console.log(path.resolve(__dirname, 'index.html')) //=>D:\Desktop\Node\code\04-path\index.html
console.log(path.resolve(__dirname, '/index.html', './test')) //=>D:\index.html\test
// sep 获取路径分隔符
console.log(path.sep) //=> window \ linux /
// parse 方法 __filename '全局变量'
console.log(__filename) //=>文件的绝对路径 //=>D:\Desktop\Node\code\04-path\01-path.js
// 解析路径
let str = 'D:\\Desktop\\Node\\code\\04-path\\01-path.js'
console.log(path.parse(str))
// 获取路径基础名称
console.log(path.basename(pathname))
// 获取路径的目录名
console.log(path.dirname(pathname))
// 获取路径的拓展名
console.log(path.extname(pathname))
3.1.4 url 模块
| API | 语法 | 说明 |
|---|---|---|
| url.parse() | url.parse(urlString [,parseQueryString [,slashesDenoteHost]]) | 方法用于获取 URL 字符串,并对其进行解析,然后返回 URL 对象。 |
| url.format() | url.format(urlObject) | 方法将传入的 url 对象编程一个 url 字符串并返回。 |
| url.resolve() | url.resolve(from,to) | 解析相对于基础 URL 的目标 URL |
3.2 文件的写入
文件写入就是将 数据 保存到 文件 中,我们可以使用如下几个方法来实现该效果
| 方法 | 说明 |
|---|---|
| writeFile | 异步写入 |
| writeFileSync | 同步写入 |
| appendFile / appendFileSync | 追加写入 |
| createWriteStream | 流式写入 |
3.1.1 异步写入 writeFile
语法
fs.writeFile(file, data[, options], callback)- 参数说明:
- file 文件名
- data 待写入的数据
- options 选项设置
(可选) - callback 写入回调
- 返回值:undefined
/**
* 需求:新建一个文件,座右铭.txt, 内容为:得道者多助,失道者寡助
*/
// 1. 引入fs模块
const fs = require("fs");
// 2. 调用方法
// fs.writeFile(文件路径, 内容,可选项 回调函数)
fs.writeFile("./座右铭.txt", "得道者多助,失道者寡助", (err) => {
if (err) {
console.log(err);
return;
}
console.log("文件写入成功");
});
3.1.2 同步写入 writeFileSync
语法
fs.writeFileSync(file, data[, options])- 参数说明:
- file 文件名
- data 待写入的数据
- options 选项设置
(可选)
- 返回值:undefined
/**
* 需求:同步写入文件
*/
// 1. 引入fs模块
const fs = require("fs");
// 2. 调用方法
fs.writeFileSync("./座右铭.txt", "得道者多助,失道者寡助");
try {
fs.writeFileSync("./座右铭.txt", "三人行,必有我师焉。");
} catch (e) {
console.log(e);
}
注意
Node.js 中的磁盘操作是由其他 线程 完成的,结果的处理有两种模式:
- 同步处理 JavaScript 主线程
会等待其线程的执行结果,然后再继续执行主线程的代码,效率较低 - 异步处理 JavaScript 主线程
不会等待其线程的执行结果,直接执行后续的主线程代码,效率较好
3.1.3 文件追加 appendFile appendFileSync
语法
fs.appendFile(file, data[, options], callback)fs.appendFileSync(file, data[, options])- 参数说明:
- file 文件名
- data 待写入的数据
- options 选项设置
(可选) - callback 写入回调
- 返回值:undefined
// 异步追加写入文件
fs.appendFile("./座右铭.txt", "\r\n得道者多助,失道者寡助", (err) => {
if (err) {
console.log(err);
return;
}
console.log("追加成功");
});
// 同步追加写入文件
fs.appendFileSync("./座右铭.txt", "\r\n得道者多助,失道者寡助");
注意
还可以采用writeFile,只需要把 options 的 flag 设置为 a
flag:
- flag:'a'追加写入
- flag:'w'覆盖写入 默认 w
// 异步追加写入文件
//fs.writeFile('文件夹路径', '写入的内容', 可选参数, 回调函数)可选参数:flag:'a'追加写入 flag:'w'覆盖写入 默认w
fs.writeFile(
"./座右铭.txt",
"\r\n得道者多助,失道者寡助",
{ flag: "a" },
(err) => {
if (err) {
console.log(err);
return;
}
console.log("写入成功");
}
);
3.1.4 流式写入 createWriteStream
语法
fs.createWriteStream(path[, options])- 参数说明
path文件路径options选项配置(可选)
- 返回值:Object
const ws = fs.createWriteStream("./男儿志.txt");
// 写入内容
ws.write("男儿立志出乡关,\n");
ws.write("毕业无成誓不还。\n");
//关闭写入流,表明已没有数据要被写入可写流
ws.close();
注意
程序打开一个文件是需要消耗资源的,流式写入可以减少打开关闭文件的次数
流式写入方式适用于大文件写入或者频繁写入的场景,writeFile 适用于频率比较低的场景
3.1.5 写入文件的场景
- 当需要持久化保存数据的时候,应该想到文件写入
- 下载文件
- 安装软件
- 保存程序日志,如 Git
- 编辑器保存文件
- 视频录制
文件写入的规律
当需要持久化保存数据的时候,应该想到文件
3.2 文件的读取
文件的读取,顾名思义,就是用过程序从文件中取出其中的数据,我们可以用以下几种方式:
| 方法 | 说明 |
|---|---|
| readFile | 异步读取 |
| readFileSync | 同步读取 |
| createReadStream | 流式读取 |
3.2.1 异步读取 readFile
语法
fs.readFile(path[,options],callback)- 参数说明:
- path:文件路径
- options:选项配置
- callback:回调函数
- 返回值:undefined
fs.readFile("./男儿志.txt", (err, data) => {
if (err) {
console.log(err);
return;
} else {
console.log(data.toString());
}
});
3.2.2 同步读取 readFileSync
语法
fs.readFileSync(path[, options])- 参数说明:
- path:文件路径
- options:选项配置
- 返回值:String|Buffer
const res = fs.readFileSync("男儿志.txt", "utf-8").toString();
console.log(res);
3.2.3 流式读取 createReadStream
语法:
fs.createReadStream(path[, options])- 参数说明
- path 文件路径
- options 选项配置(
可选)
- 返回值:Object
// 创建流式读取
const rs = fs.createReadStream("./男儿志.txt");
// 监听读取的数据
rs.on("data", (chunk) => {
console.log(chunk); //一段为 65536字节 = 64kb
});
// 监听读取完成
rs.on("end", () => {
console.log("读取完成");
});
3.2.4 读取文件的应用场景
- 电脑开机
- 程序运行
- 编辑器打开文件
- 查看图片
- 播放视频
- 播放音乐
- Git 查看日志
- 上传文件
- 查看聊天记录
3.3 文件移动与重命名
在 Node.js 中,我们可以使用 rename 或 renameSync 来移动或重命名 文件或文件夹
语法
fs.rename(oldPath, newPath, callback)fs.renameSync(oldPath, newPath)- 参数说明:
- oldPath 文件当前的路径
- newPath 文件新的路径
- callback 操作后的回调
//重命名文件
fs.rename("./座右铭.txt", "./论语.txt", (err) => {
if (err) throw err;
console.log("重命名成功");
});
3.4 文件的删除
在 Node.js 中,我们可以使用 unlink 或 unlinkSync 来删除文件
语法
fs.unlink(path, callback)fs.unlinkSync(path)- 参数说明:
- path 文件路径
- callback 操作后的回调
语法
fs.rm(path[,options],callback)fs.rmSync(path[,options])- 参数说明
- path 文件路径
- options:可选项
- callback 操作后的回调
//删除文件
fs.unlink("./男儿志.txt", (err) => {
if (err) throw err;
console.log("文件已删除");
});
//删除文件夹 { recursive: true } 递归
fs.rm("./test", { recursive: true }, (err) => {
if (err) throw err;
console.log("文件已删除");
});
3.5 文件夹的操作
借助 Node.js 的能力,我们可以对文件夹进行 创建 、读取 、删除 等操作
| 方法 | 说明 |
|---|---|
| mkdir / mkdirSync | 创建文件夹 |
| readdir / readdirSync | 读取文件夹 |
| rmdir / rmdirSync | 删除文件夹 |
3.5.1 创建文件夹 mkdir / mkdirSync
在 Node.js 中,我们可以使用 mkdir 或 mkdirSync 来创建文件夹
语法
fs.mkdir(path[, options], callback)fs.mkdirSync(path[, options])- 参数说明:
- path 文件夹路径
- options 选项配置( 可选 )
- callback 操作后的回调
//创建目录
fs.mkdir("./lunyu", (err) => {
if (err) throw err;
console.log("创建成功");
});
//递归创建目录
fs.mkdir("./lunyu.1/2/3", { recursive: true }, (err) => {
if (err) throw err;
console.log("创建成功");
});
3.5.2 读取文件夹 readdir / readdirSync
在 Node.js 中,我们可以使用 readdir 或 readdirSync 来读取文件夹
语法
fs.readdir(path[, options], callback)fs.readdirSync(path[, options])- 参数说明:
- path 文件夹路径
- options 选项配置( 可选 )
- callback 操作后的回调
fs.readdir("./lunyu.1", (err, files) => {
// 读取目录
if (err) throw err;
console.log(files);
});
3.5.3 删除文件夹 rmdir / rmdirSync
在 Node.js 中,我们可以使用 rmdir 或 rmdirSync 来删除文件夹
语法
fs.rmdir(path[, options], callback)fs.rmdirSync(path[, options])- 参数说明:
- path 文件夹路径
- options 选项配置( 可选 )
- callback 操作后的回调
//单个删除
fs.rmdir('./lunyu'(err) => {
if (err) throw err;
console.log('文件夹删除成功');
})
//递归删除
fs.rmdir('./lunyu.1',{recursive:true},(err) => {
if (err) throw err;
console.log('文件夹删除成功');
})
建议使用 rm
(node:3384) [DEP0147] DeprecationWarning:在 node .js 的未来版本中,Rmdir (path, {recursive: true})将被删除。使用 fs.Rm (path, {recursive: true})代替
(node:3384) [DEP0147] DeprecationWarning: In future versions of Node.js, fs.rmdir(path, { recursive: true }) will be removed. Use fs.rm(path, { recursive: true }) instead
fs.rm("./lunyu.1", { recursive: true }, (err) => {
if (err) throw err;
console.log("文件夹删除成功");
});
3.6 查看资源的状态
在 Node.js 中,我们可以使用 stat 或 statSync 来查看资源的详细信息
语法
fs.stat(path[, options], callback)fs.statSync(path[, options])- 参数说明:
- path 文件夹路径
- options 选项配置( 可选 )
- callback 操作后的回调
- 返回值:Object
- size 文件体积
- birthtime 创建时间
- mtime 最后修改时间
- isFile 检测是否为文件
- isDirectory 检测是否为文件夹
- ...
fs.stat("./论语.txt", (err, stats) => {
if (err) {
console.log(err);
return;
}
console.log(stats);
console.log(stats.isFile()); // 是否是文件
console.log(stats.isDirectory()); // 是否是文件夹
});
/**
* Stats {
* dev: 2618169483,//设备id
* mode: 33206,//文件权限
* nlink: 1,//硬链接数
* uid: 0,//用户id
* gid: 0,//组id
* rdev: 0,//设备类型
* blksize: 4096,//块大小
* ino: 90634942500871400,//inode
* size: 68,//文件大小
* blocks: 0,///块数
* atimeMs: 1687997350641.621,//访问时间
* mtimeMs: 1687985159763.659,///修改时间
* ctimeMs: 1687996667712.6865,//创建时间
* birthtimeMs: 1687984071229.5378,//创建时间
* atime: 2023-06-29T00:09:10.642Z,//访问时间
* mtime: 2023-06-28T20:45:59.764Z,//修改时间
* ctime: 2023-06-28T23:57:47.713Z,//创建时间
* birthtime: 2023-06-28T20:27:51.230Z//创建时间
* }
*/
3.7 路径
3.7.1 相对路径
fs.writeFileSync("./test.txt", "hello world", "utf8");
fs.writeFileSync("test1.txt", "hello world", "utf8");
3.7.2 绝对路径
fs.writeFileSync("/test1.txt", "hello world", "utf8"); //绝对路径
fs.writeFileSync("F:/test2.txt", "hello world", "utf8"); //绝对路径
fs.writeFileSync(__dirname + "/test3.txt", "hello world", "utf8"); //__dirname当前文件所在的目录
注意
相对路径中所谓的 当前目录 ,指的是 命名行的工作目录 ,而并非是文件的所在目录
所以当命名行的工作目录与文件所在目录不一致时,会出现一些 Bug
__dirname与require类似,都是 Node.js 环境中的 '全局' 变量__dirname保存着 当前文件夹所在目录的绝对路径,可以使用__dirname与文件名拼接成绝对路径
3.8 经典案例
3.8.1 编写一个 JS 文件,实现复制文件的功能
/*
* 需求:
* 复制 资料文件夹下的 [笑看风云.mp4]
*/
// 导入 fs 模块
const fs = require("fs");
// 方式一 readFile
// 读取文件内容
let data = fs.readFileSync("./资料/笑看风云.mp4");
// 写入文件
fs.writeFileSync("./资料/笑看风云2.mp4", data);
// 方式二 流式操作
// 创建读取流对象
const rs = fs.createReadStream("./资料/笑看风云.mp4");
// 创建一个写入流对象
const ws = fs.createWriteStream("./资料/笑看风云3.mp4");
// 绑定data事件
// 理想状态下,读取 64k 就写入 64 k,这样消耗的内存最少,实际上读取的速度大于写入的速度
rs.on("data", (chunk) => {
ws.write(chunk);
});
// 绑定data事件 ==== rs.pipe(ws)
3.8.2 文件的批量重命名
// 1. 导入 fs 模块
const fs = require("fs");
// 读取 03-fs模块 文件夹
const files = fs.readdirSync("../03-fs模块");
// 遍历数组
files.forEach((item) => {
// 判断
let [num, name] = item.split("-");
if (num < 10) {
num = "0" + num;
}
// 创建新的文件名
let newName = num + "-" + name;
// 重命名
fs.renameSync(`../03-fs模块/${item}`, `../03-fs模块/${newName}`);
});
四、http 协议
- HTTP(hypertext transport protocol)协议;中文叫 超文本传输协议
- 互联网应用最广泛的协议之一
- 协议:双方必须共同遵从的一组约定
- 是一种基于 TCP/IP 的应用层通信协议
- 这个协议详细规定了 浏览器 和 万维网 服务器 之间互相通信的规则
- 协议中主要规定了两个方面的内容:
- 客户端:用来向服务器发送数据,可以被称之为 请求报文
- 服务端:向客户端返回数据,可以被称之为 响应报文
理解
报文:可以简单理解为就是一堆字符串
4.1 Fidder
Fidder 官网Fiddler
4.2 请求报文
//1. 请求行
GET https://www.baidu.com/ HTTP/1.1 // 请求方法 请求地址 协议版本
//2. 请求头
Host: www.baidu.com// 请求的服务器地址
Connection: keep-alive// 保持连接
Cache-Control: max-age=0// 缓存控制
Upgrade-Insecure-Requests: 1// 升级不安全的请求
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36// 用户代理
Accept: text/html,application/xhtml+xml,application/xml; // 接受的数据类型
Accept-Encoding: gzip, deflate, br// 接受的编码方式
Accept-Language: zh-CN,zh;q=0.9// 接受的语言
Cookie: BAI// cookie
// 3.空行
// 3. 请求体
// 空
4.2.1 http 的请求行
请求方法(get、post、put/patch、delete 等)
请求 URL(统一资源定位器)
例如:http://www.baidu.com:80/index.html?a=100&b=200#logo
- http: 协议 (https、ftp、ssh 等)
- www.baidu.com 域名
- 80 端口号
- /index.html 路径
- a=100&b=200 查询字符串
- #logo 哈希 (锚点链接)
HTTP 协议版本号
| 版本号 | 发布时间 |
|---|---|
| 1.0 | 1996 年 |
| 1.1 | 1999 年 |
| 2 | 2015 年 |
| 3 | 2018 年 |
面试题 说说 HTTP1.0/1.1/2.0 的区别?
HTTP1.0:
- 浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接
HTTP1.1:
- 引入了持久连接,即 TCP 连接默认不关闭,可以被多个请求复用
- 在同一个 TCP 连接里面,客户端可以同时发送多个请求
- 虽然允许复用 TCP 连接,但是同一个 TCP 连接里面,所有的数据通信是按次序进行的,服务器只有处理完一个请求,才会接着处理下一个请求。如果前面的处理特别慢,后面就会有许多请求排队等着
- 新增了一些请求方法
- 新增了一些请求头和响应头
HTTP2.0:
- 采用二进制格式而非文本格式
- 完全多路复用,而非有序并阻塞的、只需一个连接即可实现并行
- 使用报头压缩,降低开销
- 服务器推送
4.2.2 http 的请求头
Host: www.baidu.com// 请求的服务器地址
Connection: keep-alive// 保持连接
Cache-Control: max-age=0// 缓存控制
Upgrade-Insecure-Requests: 1// 升级不安全的请求
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36// 用户代理
Accept: text/html,application/xhtml+xml,application/xml; // 接受的数据类型
Accept-Encoding: gzip, deflate, br// 接受的编码方式
Accept-Language: zh-CN,zh;q=0.9// 接受的语言
Cookie: BAI// cookie
4.2.3 http 的请求体
4.2.3.1 表单数据格式
表单数据格式最为常见,其传输格式如下所示:
Content-Type: application/x-www-form-urlencoded 表单数据格式以 key=value 的形式提交,多个键值对之间使用 & 符号分割。例如:
name=张三&age=18&gender=男
注意
表单数据格式对文件上传支持不友好,因此在文件上传时,通常使用multipart/form-data:boundary=abcdef类型来提交数据。
# 请求头
Content-type: multipart/data; boundary="--abcdefg"
--abcdefg
Content-Disposition: form-data; name="x"
Content-type: text/plain; charset=ascii
It does NOT end with a linebreak # <=== 这里没有CRLF,隐式类型
--abcdefg
Content-Disposition: form-data; name="y"
Content-type: text/plain; charset=ascii
---abcefg---
4.2.3.2JSON 数据格式
JSON 是一种轻量级的数据交换格式,被广泛应用于前后端数据交互。JSON 格式请求体支持多级嵌套结构,以下是一个 JSON 格式数据的样例:
Content-Type: application/json
{
"name": "张三",
"age": 18,
"gender": "男",
"hobbies": [
"足球",
"篮球",
"羽毛球"
]
}
//JSON格式请求体通常会使用Content-Type: application/json头信息进行传输。
4.2.3.3 XML 数据格式
XML 是一种标记语言,可用于表示任何类型的数据。XML 格式的请求体与 JSON 类似,支持多级嵌套结构。以下是一个 XML 格式数据的样例:
Content-Type: application/xml
<person>
<name>张三</name>
<age>18</age>
<gender>男</gender>
<hobbies>
<hobby>足球</hobby>
<hobby>篮球</hobby>
<hobby>羽毛球</hobby>
</hobbies>
</person>
4.3 响应报文
//1. 请求行
HTTP/1.1 200 OK//http版本号 200状态码 OK状态描述
//2. 响应头
Content-Type: text/html;charset=utf-8//响应体类型
Content-Length: 11//响应体长度(字节)
Date: Tue, 10 Apr 2018 08:42:30 GMT//响应时间
Connection: keep-alive//连接状态
//3. 空行
//4. 响应体 响应体内容的类型是非常灵活的,常见的类型有 HTML、CSS、JS、图片、JSON
hello world
4.5 IP 地址
IP 地址本身就是一个 32Bit 的数字标识
IP 用来表示为网络中的设备,实现设备间的通信
//IP地址
10010110.00010000.00000000.00000000//二进制
450.16.0.0//十进制
4.6 端口
- 应用程序的数字标识
- 一台计算机有 65536 个端口(0~65535)
- 一个引用程序可以拥有一个或多个端口
- 作用:
- 实现不同主机应用程序之间的通信
4.7 创建 Http 服务
http.createServer里的回调函数的执行时机: 当接收到 HTTP 请求的时候,就会执行
//导入http模块
const http = require("http");
//创建服务器
const server = http.createServer((request, response) => {
//设置响应头
response.setHeader("content-type", "text/html;charset=utf-8");
//设置响应内容
response.end("hello world");
}); //=>返回的是一个服务器对象
//监听端口
server.listen(1234, (err) => {
if (err) {
console.log(err);
return;
}
console.log("服务器启动成功");
});
//=>在命令行中执行node node.js
//=>在浏览器中输入localhost:1234 127.000.001:1234
//=>在命令行中ctrl+c可以关闭服务器
注意事项
响应内容中文乱码的解决办法
// 设置响应头 response.setHeader('content-type','text/html;charset=utf-8');HTTP 协议默认端口是 80 。HTTPS 协议的默认端口是 443。
如果端口被其他程序占用,可以使用 资源监视器 找到占用端口的程序,然后使用 任务管理器 关闭对应的程序。
4.8 提取 http 请求报文
| 含义 | 语法 | 重点掌握 |
|---|---|---|
| 请求方法 | request.method | * |
| 请求版本 | request.httpVersion | |
| 请求路径 | request.url | * |
| URL 路径 | require('url').parse(request.url).pathname | * |
| URL 查询字符串 | require('url').parse(request.url, true).query | * |
| 请求头 | request.headers | * |
| 请求体 | request.on('data', function(chunk){}) request.on('end', function(){}) |
/**
* 操作步骤:
* 启动服务器 node node.js
* 在浏览器中输入localhost:1234 127.000.001:1234
* 命令行中查看输出信息
*/
//导入http模块
const http = require("http");
//创建服务器
const server = http.createServer((request, response) => {
//request:请求信息
console.log(request.method); //请求方法
console.log(request.url); //请求地址
console.log(request.httpVersion); //http版本号
console.log(request.headers); //请求头
//请求体
let data = "";
request.on("data", (chunk) => {
data += chunk;
});
request.on("end", () => {
console.log(data);
response.end("请求结束");
}); //请求结束
//response:响应信息
// 设置响应头
response.setHeader("content-type", "text/html;charset=utf-8");
// 设置响应内容
response.end("你好,Server");
}); //=>返回的是一个服务器对象
//监听端口
server.listen(1234, (err) => {
if (err) {
console.log(err);
return;
}
console.log("服务器启动成功");
});
//=>在命令行中执行node node.js
//=>在浏览器中输入localhost:1234 127.000.001:1234
//=>在命令行中ctrl+c可以关闭服务器
4.8.1 http 报文中 url 的路径 与 查询字符串
//导入http模块
const http = require("http");
//导入url模块
const url = require("url");
//创建服务器
const server = http.createServer((request, response) => {
let res = url.parse(request.url, true); //=>parse(data,true,true)将data转换为对象 第二个参数为true时,将query转换为对象 第三个参数为true时,将host转换为对象
console.log(res.query.key);
// 设置响应内容
response.end("你好,Server");
});
//监听端口
server.listen(1234, (err) => {
if (err) {
console.log(err);
return;
}
console.log("服务器启动成功");
});
node.js 官网建议使用url 网址 | Node.js v18.16.1 文档 (nodejs.cn)
//导入http模块
const http = require("http");
//创建服务器
const server = http.createServer((request, response) => {
/**
* 实例化URL对象
* 一个参数 url
* 二个参数 base url
* @param {string} url - 请求的url
* @param {string} [base] - 基础url
* @type {URL}
*/
// const url = new URL('http://127.0.0.1:1234/search?key=112233&aaabbb=123');
const url = new URL("/search?key=112233&aaabbb=123", "http://127.0.0.1:1234");
console.log(url);
console.log(url.pathname);
console.log(url.searchParams.get("key"));
console.log(url.searchParams.get("aaabbb"));
// 设置响应内容
response.end("你好,Server");
});
//监听端口
server.listen(1234, (err) => {
console.log("服务器启动成功");
});
4.9 设置 http 响应报文
//导入http模块
const http = require("http");
//创建服务器
const server = http.createServer((request, response) => {
//设置响应状态码
response.statusCode = 302;
//设置相应状态描述
response.statusMessage = "temporary redirect";
response.setHeader("content-type", "text/html;charset=utf-8"); //设置响应头
response.setHeader("array", ["a", "b", "c"]); //设置响应头
response.write("123"); //设置响应体
response.end("<h1>hello Response</h1>"); //设置响应体 并结束响应 有且只能有一次
});
//监听端口
server.listen(1234, (err) => {
console.log("服务器启动成功");
});
4.10 网页资源的基本加载过程
详见浏览器的工作原理
4.11 静态资源和动态资源
静态资源 是指 内容长时间不发生改变的资源 ,例如图片,视频,CSS 文件,JS 文件,HTML 文件,字体文件等
动态资源 是指 内容经常更新的资源 ,例如百度首页,网易首页,京东搜索列表页面等
4.11.1 搭建静态资源服务
/**
* 创建一个静态资源服务器,满足以下要求
* GET /table.html 响应table.html文件
* GET /table.js 响应table.js文件
* GET /table.css 响应table.css文件
*/
//导入http模块
const http = require("http");
//导入fs模块
const fs = require("fs");
//创建服务器
const server = http.createServer((request, response) => {
// 获取请求url的路径
let { pathname } = new URL(request.url, `http://${request.headers.host}`);
//拼接文件路劲
let filePath = `${__dirname}${pathname}`;
let file = fs.readFile(filePath, (err, data) => {
if (err) {
response.writeHead(404, { "Content-Type": "text/html;charset=utf-8" });
response.end("文件不存在");
return;
} else {
response.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
response.end(data);
}
});
});
//监听端口
server.listen(1234, (err) => {
console.log("服务器启动成功");
});
4.12 静态资源目录与网站根目录
HTTP 服务在哪个文件夹中寻找静态资源,那个文件夹就是 静态资源目录 ,也称之为 网站根目录
思考:vscode 中使用 live-server 访问 HTML 时, 它启动的服务中网站根目录是谁?该文件的所处的文件夹
4.13 网页中的 URL
网页中的 URL 主要分为两大类:相对路径 与 绝对路径
4.13.1 绝对路径
绝对路径可靠性强,而且相对容易理解,在项目中运用较多
| 形式 | 特点 |
|---|---|
| http://atguigu.com/web | 直接向目标资源发送请求,容易理解。网站的外链会用到此形式 |
| //atguigu.com/web | 与页面 URL 的协议拼接形成完整 URL 再发送请求。大型网站用的比较多 |
| /web | 与页面 URL 的协议、主机名、端口拼接形成完整 URL 再发送请求。中小型网站 |
4.13.2 相对路径
相对路径在发送请求时,需要与当前页面 URL 路径进行 计算 ,得到完整 URL 后,再发送请求,学习阶段用的较多
例如当前网页 url 为https://piniatop.gitee.io/pinia/clutter/node/node01.html
| 形式 | 最终的 URL |
|---|---|
| ./css/app.css | https://piniatop.gitee.io/pinia/clutter/node/css/app.css |
| js/app.js | https://piniatop.gitee.io/pinia/clutter/node/js/app.js |
| ../img/logo.png | https://piniatop.gitee.io/pinia/clutter/img/logo.png |
| ../../mp4/show.mp4 | https://piniatop.gitee.io/pinia/mp4/show.mp4 |
| ../../../index.html | https://piniatop.gitee.io/index.html |
4.13.3 网页中使用 URL 的场景小结
- a 标签 href
- link 标签 href
- script 标签 src
- img 标签 src
- video audio 标签 src
- form 中的 action
- AJAX 请求中的 URL
4.14 设置资源类型(mime类型)
媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型 )是一种标准,用来表示文档、文件或字节流的性质和格式。
mime 类型结构: [type]/[subType]
例如: text/html text/css image/jpeg image/png application/json
HTTP 服务可以设置响应头 Content-Type 来表明响应体的 MIME 类型,浏览器会根据该类型决定如何处理资源
下面是常见文件对应的 mime 类型
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'
注意
对于未知的资源类型,可以选择 application/octet-stream 类型,浏览器在遇到该类型的响应时,会对响应体内容进行独立存储,也就是我们常见的 下载 效果
代码
/**
* 创建一个静态资源服务器,满足以下要求
* GET /table.html 响应table.html文件
* GET /table.js 响应table.js文件
* GET /table.css 响应table.css文件
*/
//导入http模块
const http = require("http");
//导入fs模块
const fs = require("fs");
let path = require("path");
let mimes = {
html: "text/html",
css: "text/css",
js: "text/javascript",
png: "image/png",
jpg: "image/jpeg",
gif: "image/gif",
mp4: "video/mp4",
mp3: "audio/mpeg",
json: "application/json",
};
//创建服务器
const server = http.createServer((request, response) => {
if (request.method !== "GET") {
response.statusCode = 405;
response.end("<h1>405 Method Not Allowed</h1>");
return;
}
// 获取请求url的路径
let { pathname } = new URL(request.url, `http://${request.headers.host}`);
//拼接文件路劲
let filePath = `${__dirname}${pathname}`;
//获取文件后缀名
let extname = path.extname(filePath).slice(1);
//获取文件类型
let mimeType = mimes[extname];
//判断文件后缀名
if (mimeType) {
let file = fs.readFile(filePath, (err, data) => {
if (err) {
if (extname === "html") {
response.setHeader("Content-Type", `${mimeType};charset=utf-8`);
} else {
response.setHeader("Content-Type", `${mimeType}`);
}
switch (err.code) {
case "ENOENT":
response.statusCode = 404;
response.end("<h1>404 Not Found</h1>");
case "EPERM":
response.statusCode = 403;
response.end("<h1>403 Forbidden</h1>");
default:
response.statusCode = 500;
response.end("<h1>500 Internal Server Error</h1>");
}
response.end("文件不存在");
} else {
if (extname === "html") {
response.writeHead(404, {
"Content-Type": `${mimeType};charset=utf-8`,
});
} else {
response.writeHead(404, { "Content-Type": `${mimeType}` });
}
response.end(data);
}
});
} else {
response.writeHead(404, {
"Content-Type": "application/octet-stream;charset=utf-8",
}); //application/octet-stream 二进制流数据(常见的文件下载)
}
//判断请求路径
// if(pathname === '/'){
// let tablehtml = fs.createReadStream(`${__dirname}/table.html`);
// tablehtml.on('data',(chunk)=>{
// response.end(chunk);
// });
// }else if(pathname === '/table.js') {
// let tablejs = fs.createReadStream(`${__dirname}/table.js`);
// tablejs.on('data', (chunk) => {
// response.end(chunk);
// });
// }else if(pathname === '/table.css') {
// let tablecss = fs.createReadStream(`${__dirname}/table.css`);
// tablecss.on('data', (chunk) => {
// response.end(chunk);
// });
// }
});
//监听端口
server.listen(1234, (err) => {
console.log("服务器启动成功");
});
面试题 GET 和 POST 请求场景?区别
GET 和 POST 请求场景:
GET 请求的情况:
在地址栏直接输入 url 访问
点击 a 链接
link 标签引入 css
script 标签引入 js
img 标签引入图片
form 标签中的 method 为 get (不区分大小写)
ajax 中的 get 请求
POST 请求的情况:
form 标签中的 method 为 post(不区分大小写)
AJAX 的 post 请求
GET 和 POST 请求的区别
GET和POST是 HTTP 协议请求的两种方式。- 作用:
GET主要用来获取数据,POST主要用来提交数据 - 参数位置:
GET带参数请求是将参数放到 URL 之后,在地址栏中输入 url 访问网站就是 GET 请求,POST带参数请求是将参数放到请求体中 - 安全性:
POST请求相对GET安全一些,因为在浏览器中参数会暴露在地址栏 - 大小:
GET请求大小有限制,一般为 2K,而 POST 请求则没有
- 作用:
4.15 经典案例
4.15.1 http 请求
//导入http模块
const http = require("http");
//创建服务器
const server = http.createServer((request, response) => {
response.setHeader("content-type", "text/html;charset=utf-8"); //设置响应头
let { method } = request; // 请求方法
let { pathname } = new URL(request.url, "http://localhost:1234"); // 请求路径
if (pathname == "/register" && method == "GET") {
response.end("注册页面");
} else if (pathname == "/login" && method == "GET") {
response.end("登录页面");
} else {
response.end("<h1>Not Found</h1>");
}
});
//监听端口
server.listen(1234, (err) => {
console.log("服务器启动成功");
});
4.15.2 http 响应
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<link rel="stylesheet" href="./table.css" />
</head>
<body>
<table>
<th>111</th>
<th>222</th>
<th>333</th>
<tr>
<td>11</td>
<td>12</td>
<td>13</td>
</tr>
<tr>
<td>21</td>
<td>22</td>
<td>23</td>
</tr>
<tr>
<td>31</td>
<td>32</td>
<td>33</td>
</tr>
</table>
<script src="./table.js"></script>
</body>
</html>
table {
border: 1px solid #000;
border-collapse: collapse;
}
th,
td {
border: 1px solid #000;
padding: 10px;
width: 60px;
text-align: center;
height: 20px;
line-height: 30px;
}
tr:nth-child(2n) {
background: skyblue;
}
tr:nth-child(2n + 1) {
background: #fff;
}
th {
background: darkorange;
}
.active {
background: red;
}
let td = document.querySelectorAll("td");
td.forEach(function (item, index) {
item.onclick = (e) => {
td.forEach(function (item, index) {
item.classList.remove("active");
});
e.target.classList.add("active");
};
});
//导入http模块
const http = require("http");
//导入fs模块
const fs = require("fs");
//创建服务器
const server = http.createServer((request, response) => {
// 获取请求url的路径
let { pathname } = new URL(request.url, `http://${request.headers.host}`);
//判断请求路径
if (pathname === "/") {
let tablehtml = fs.createReadStream(`${__dirname}/table.html`);
tablehtml.on("data", (chunk) => {
response.end(chunk);
});
} else if (pathname === "/table.js") {
let tablejs = fs.createReadStream(`${__dirname}/table.js`);
tablejs.on("data", (chunk) => {
response.end(chunk);
});
} else if (pathname === "/table.css") {
let tablecss = fs.createReadStream(`${__dirname}/table.css`);
tablecss.on("data", (chunk) => {
response.end(chunk);
});
}
});
//监听端口
server.listen(1234, (err) => {
console.log("服务器启动成功");
});
五、Node.js 模块化
5.1 介绍
5.1.1 什么是模块化与模块 ?
将一个复杂的程序文件依据一定规则(规范)拆分成多个文件的过程称之为 模块化
其中拆分出的 每个文件就是一个模块,模块的内部数据是私有的,不过模块可以暴露内部数据以便其他模块使用
5.1.2 什么是模块化项目 ?
- 编码时是按照模块一个一个编码的, 整个项目就是一个模块化的项目
5.1.3 模块化好处
- 防止命名冲突
- 高复用性
- 高维护性
5.2 模块暴露数据
5.2.1 初体验
创建 demo.js
//声明函数
function demo() {
console.log(123);
}
//暴露数据
module.exports = tiemo;
module.exports = () => {
console.log(123);
};
创建 index.js
//导入模块
const tiemo = require("./demo.js");
//调用函数
tiemo(); //=> 123
5.2.2 暴露数据
5.2.2.1 模块暴露数据的方式有两种:
//module.exports = value
module.exports = {
demo1,
demo2,
};
//exports.name = value
exports.demo = demo;
注意
module.exports可以暴露 任意 数据- 不能使用
exports = value的形式暴露数据.- 模块内部 module 与 exports 的隐式关系
exports = module.exports = {} - require 返回的是目标模块中
module.exports的值
- 模块内部 module 与 exports 的隐式关系
exports = module.exports = {};
exports = "521";
//{}
5.2.3 导入模块
语法
const test = require("./demo.js");
注意
- 对于自己创建的模块,导入时路径建议写 相对路径,且不能省略
./和 `../`` - ``js
和json文件导入时可以不用写后缀,c/c++编写的node扩展文件也可以不写后缀,但是一般用不到,直接使用 **node** 的require()` 方法即可将 JSON 文件转换成 JS 对象 - 如果导入其他类型的文件,会以
js文件进行处理 - 如果导入的路径是个文件夹,则会 首先 检测该文件夹下
package.json文件中main属性对应的文件,- 如果存在则导入,反之如果文件不存在会报错。
- 如果 main 属性不存在,或者 package.json 不存在,则会尝试导入文件夹下的
index.js和index.json, - 如果还是没找到,就会报错
- 导入 node.js 内置模块时,直接 require 模块的名字即可,无需加
./和../
5.3 导入模块的基本流程
这里我们介绍一下 require 导入 自定义模块 的基本流程
- 将相对路径转为绝对路径,定位目标文件
- 缓存检测
- 读取目标文件代码
- 包裹为一个函数并执行(自执行函数)。通过
arguments.callee.toString()查看自执行函数 - 缓存模块的值
- 返回
module.exports的值
/**
* 伪代码
*/
function require(file) {
//1. 将相对路径转为绝对路径,定位目标文件
let absolutePath = path.resolve(__dirname, file);
//2. 缓存检测
if (caches[absolutePath]) {
return caches[absolutePath];
}
//3. 读取文件的代码
let code = fs.readFileSync(absolutePath).toString();
//4. 包裹为一个函数 然后执行
let module = {};
let exports = (module.exports = {});
//自执行函数
(function (exports, require, module, __filename, __dirname) {
const test = {
name: "尚硅谷",
};
module.exports = test;
//输出
console.log(arguments.callee.toString());
})(exports, require, module, __filename, __dirname);
//5. 缓存结果
caches[absolutePath] = module.exports;
//6. 返回 module.exports 的值
return module.exports;
}
5.4 CommonJS 规范
module.exports 、exports 以及 require 这些都是 CommonJS 模块化规范中的内容。
而 Node.js 是实现了 CommonJS 模块化规范,二者关系有点像 JavaScript 与 ECMAScript
六、包管理工具
6.1 介绍
6.1.1 包是什么
包:英文单词是 package ,代表了一组特定功能的源码集合。
6.1.2 包管理工具
管理包的应用软件,可以对包进行 下载安装 , 更新 , 删除, 上传等操作
借助包管理工具,可以快速开发项目,提升开发效率
包管理工具是一个通用的概念,很多编程语言都有包管理工具,所以 掌握好包管理工具非常重要
6.1.3 常用的包管理工具
详见:包管理器
6.1.4 package.json 内容示例:
{
"name": "text", #包的名字
"version": "1.0.0", #包的版本
"description": "", #包的描述
"main": "index.js", #包的入口文件
"scripts": { #脚本配置
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "", #作者
"license": "ISC" #开源证书
}
注意
- package name (
包名) 不能使用中文、大写,默认值是文件夹的名称,所以文件夹名称也不 能使用中文和大写 - version (
版本号)要求x.x.x的形式定义,x必须是数字,默认值是1.0.0 - ISC 证书与 MIT 证书功能上是相同的,关于开源证书扩展阅读 http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html
package.json可以手动创建与修改- 使用
npm init -y或者npm init --yes极速创建package.json
6.1.5 使用包
6.1.5.1 搜索包的方式有两种
- 命令行 『npm s/search 关键字』
- 网站搜索 mpm 网址
6.1.5.2 运行之后文件夹下会增加两个资源
node_modules 文件夹存放下载的包package-lock.json 包的锁文件,用来锁定包的版本
6.1.5.3 require 导入 npm 包基本流程
const demo = require("uniq");
- 在当前文件夹下 node_modules 中寻找同名的文件夹
- 在上级目录中下的 node_modules 中寻找同名的文件夹,直至找到磁盘根目录
6.2 生产环境与开发环境
开发环境 是程序员 专门用来写代码 的环境,一般是指程序员的电脑,开发环境的项目一般 只能程序员自己访问
生产环境 是项目 代码正式运行 的环境,一般是指正式的服务器电脑,生产环境的项目一般 每个客户都可以访问
6.2.1 生产依赖与开发依赖
我们可以在安装时设置选项来区分 依赖的类型,目前分为两类:
| 类型 | 命令 | 补充 |
|---|---|---|
| 生产依赖 | npm i -S uniq npm i --save uniq | -S 等效于 --save,-S 是默认选项 包信息保存在 package.json 中 dependencies 属性 |
| 开发依赖 | npm i -D less npm i --save-dev less | -D 等效于 --save-dev 包信息保存在 package.json 中 devDependencies 属性 |
6.3 npm 配置淘宝镜像
用 npm 也可以使用淘宝镜像,配置的方式有两种
- 直接配置
- 工具配置
6.3.1 直接配置
执行如下命令即可完成配置
npm config set registry https://registry.npmmirror.com/
yarn config set registry https://registry.npmmirror.com/
6.3.2 NRM 工具配置
使用 nrm 配置 npm 的镜像地址 npm registry manager
- 安装 nrm
npm i -g nrm
- 显示镜像源
nrm ls
- 修改镜像
nrm use taobao
- 检查是否配置成功(选做)
npm config list
检查 registry 地址是否为 https://registry.npmmirror.com/ , 如果 是 则表明成功
补充说明
建议使用第二种方式进行镜像配置,因为后续修改起来会比较方便
虽然 cnpm 可以提高速度,但是 npm 也可以通过淘宝镜像进行加速,所以 npm 的使用率还是高于 cnpm
6.3.3 NVM 工具配置
详见Node.js
nvm 全称 Node Version Manager 顾名思义它是用来管理 node 版本的工具,方便切换不同版本的 Node.js
6.3.3.1 使用
nvm 的使用非常的简单,跟 npm 的使用方法类似
6.3.3.2 下载安装
首先先下载 nvm,下载地址 https://github.com/coreybutler/nvm-windows/releases ,
选择 nvm-setup.exe 下载即可
常用命令
| 命令 | 说明 |
|---|---|
| nvm list available | 显示所有可以下载的 Node.js 版本 |
| nvm list | 显示已安装的版本 |
| nvm install 18.12.1 | 安装 18.12.1 版本的 Node.js |
| nvm install latest | 安装最新版的 Node.js |
| nvm uninstall 18.12.1 | 删除某个版本的 Node.js |
| nvm use 18.12.1 | 切换 18.12.1 的 Node.js |
6.4 发布包到 npm 上
6.4.1 创建与发布
我们可以将自己开发的工具包发布到 npm 服务上,方便自己和其他开发者使用,操作步骤如下:
- 创建文件夹,并创建文件 index.js, 在文件中声明函数,使用 module.exports 暴露
- npm 初始化工具包,package.json 填写包的信息 (包的名字是唯一的)
- 注册账号 https://www.npmjs.com/signup
- 激活账号 ( 一定要激活账号 )
- 修改为官方的官方镜像 (命令行中运行
nrm use npm) - 命令行下
npm login填写相关用户信息 - 命令行下
npm publish提交包 👌
6.4.2 更新包
后续可以对自己发布的包进行更新,操作步骤如下
- 更新包中的代码
- 测试代码是否可用
- 修改
package.json中的版本号 - 发布更新
npm publish
6.4.3 删除包
执行如下命令删除包
npm unpublish --force
删除包需要满足一定的条件, https://docs.npmjs.com/policies/unpublish
你是包的作者
发布小于 24 小时
大于 24 小时后,没有其他包依赖,并且每周小于 300 下载量,并且只有一个维护者
6.5 扩展内容
在很多语言中都有包管理工具,比如:
| 语言 | 包管理工具 |
|---|---|
| PHP | composer |
| Python | pip |
| Java | maven |
| Go | go mod |
| JavaScript | npm/yarn/cnpm/other |
| Ruby | rubyGems |
除了编程语言领域有包管理工具之外,操作系统层面也存在包管理工具,不过这个包指的是『软件包』
| 操作系统 | 包管理工具 | 网址 |
|---|---|---|
| Centos | yum | https://packages.debian.org/stable/ |
| Ubuntu | apt | https://packages.ubuntu.com/ |
| MacOS | homebrew | https://brew.sh/ |
| Windows | chocolatey | https://chocolatey.org/ |
七、Express.js
7.1Express 介绍
express 是一个基于 Node.js 平台的极简、灵活的 WEB 应用开发框架,官方网址: https://www.expressjs.com.cn/
简单来说,express 是一个封装好的工具包,封装了很多功能,便于我们开发 WEB 应用(HTTP 服务)
7.2 express 使用
7.2.1 初体验 express
- 安装 express
npm install express
yarn add express
- 创建 express.js
//导入express
const express = require("express");
//创建应用对象
const app = express();
//创建路由
app.get("/home", (request, response) => {
response.send("hello express");
});
//监听端口号
app.listen(3000, (err) => {
if (!err) console.log("服务器启动成功了");
else console.log(err);
});
- 配置 package.json
{
"name": "lunyu",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"express": "^4.18.2"
},
"scripts": {
"express": "node express.js"
}
}
- 启动
yarn express
7.2.2express 路由
7.2.2.1 什么是路由
官方定义: 路由确定了应用程序如何响应客户端对特定端点的请求
7.2.2.2 路由的使用
一个路由的组成有 请求方法, 路径 和 回调函数 组成。
语法
app.method(path, callback);
案例
//首页路由
app.get("/", (request, response) => {
response.send("/的路由");
});
app.post("/login", (request, response) => {
response.send("post请求");
});
//all表示所有的请求方式都可以
app.all("/text", (request, response) => {
response.send("all请求");
});
//404响应
app.all("*", (request, response) => {
response.send("404");
});
7.2.2.3 获取请求报文参数
app.get("/home", (request, response) => {
//请求
console.log(request.method); //请求方式
console.log(request.url); //请求地址
console.log(request.httpVersion); //请求协议版本
console.log(request.headers); //请求头
console.log(request.query); //查询字符串
console.log(request.path); //请求路径
console.log(request.params); //请求参数
console.log(request.body); //请求体
console.log(request.cookies); //请求cookie
console.log(request.ip); //请求ip
console.log(request.get("Host")); //获取指定请求头
});
7.2.2.4 获取路由参数
app.get("/:id.html", (request, response) => {
//获取参数
console.log(request.params);
response.send("商品详情页");
});
7.2.2.5 express 响应报文的设置
app.get("/response", (request, response) => {
response.status(500); //设置状态码
response.set("aaa", "bbb"); //设置响应头
response.send("hello express"); //响应内容
response.redirect("http://www.baidu.com"); //重定向
response.download(`${__dirname}/data.json`); //下载(绝对路径) __dirname:当前文件所在的目录 __filename:当前文件的目录
response.download(`${__dirname}/data.json`, "data.json"); //第二个参数是下载的文件名
response.json({ name: "123" }); //响应json数据
response.sendFile(`${__dirname}/data.json`); //响应文件(绝对路径)
response
.status(500)
.setHeader("Content-Type", "text/html;charset=utf-8")
.send("hello express"); //链式调用
});
7.2.3express 中间件
中间件(Middleware)本质是一个回调函数
中间件函数 可以像路由回调一样访问 请求对象(request) , 响应对象(response)
中间件的作用 就是 使用函数封装公共操作,简化代码
中间件的类型
- 全局中间件
- 路由中间件
7.2.3.1 定义全局中间件
- 每一个请求 到达服务端之后 都会执行全局中间件函数
//导入express
const express = require("express");
//创建应用对象
const app = express();
//导入fs模块
const fs = require("fs");
//导入path模块
const path = require("path");
/**
* 记录日志的中间件
* @param request //请求对象
* @param response //响应对象
* @param next //下一个中间件
*/
const recordMiddleWare = (request, response, next) => {
//获取请求的url和ip
let { url, ip } = request;
//将信息保存在文件中
fs.appendFileSync(
path.join(__dirname, "./access.log"),
`${url}--${ip}--${new Date().toLocaleString()}\n`
);
//放行
next();
};
app.use(recordMiddleWare);
//创建路由
app.get("/home", (request, response) => {
response.send("<h1>欢迎来到首页</h1>");
});
app.get("/admin", (request, response) => {
response.send("<h1>欢迎来到后台管理系统</h1>");
});
app.all("*", (request, response) => {
response.send("<h1>404 Not Found</h1>");
});
//监听端口号
app.listen(3000, (err) => {
if (!err) console.log("服务器启动成功了");
else console.log(err);
});
注意
中间件必须放在开始的位置
7.2.3.2 定义一个路由中间件
//导入express
const express = require("express");
//创建应用对象
const app = express();
/**
* 路由中间件
* 针对/admin的请求,要求url携带code=123的参数,才能访问,否则返回没有权限
*/
const rouleMiddleWare = (request, response, next) => {
//获取请求的url和ip
let {
query: { code },
} = request;
//将信息保存在文件中
if (code === "123") {
next(); //放行
} else {
response.send(`<h1>没有权限</h1>`);
}
};
//创建路由
app.get("/home", rouleMiddleWare, (request, response) => {
response.send("<h1>欢迎来到首页</h1>");
});
app.get("/admin", rouleMiddleWare, (request, response) => {
response.send("<h1>欢迎来到后台管理系统</h1>");
});
app.all("*", (request, response) => {
response.send("<h1>404 Not Found</h1>");
});
//监听端口号
app.listen(3000, (err) => {
if (!err) console.log("服务器启动成功了");
else console.log(err);
});
7.2.3.3 静态资源中间件
- 设置静态资源中间件
//设置静态资源目录
app.use(express.static(`${__dirname}/public`));
- 新建 public
注意
- index.html 文件为默认打开的资源 可以作为网站首页
- 如果静态资源与路由规则同时匹配,谁先匹配谁就响应
- 路由响应动态资源,静态资源中间件响应静态资源
7.2.3.4 获取请求体数据 body-parser
express 可以使用 body-parser 包处理请求体
- 安装
npm insatll body-parser
yarn add body-parser
- 使用
const bodyParser = require("body-parser");
//解析urlencoded格式的数据 推介使用路由中间件
const urlencodedParser = bodyParser.urlencoded({ extended: false }); //extended: false表示使用querystring来解析数据,这是官方推荐的
//创建路由
app.get("/login", (request, response) => {
request.readFile(`${__dirname}/login.html`, (err, data) => {
if (!err) response.end(data);
else console.log(err);
});
});
app.post("/login", urlencodedParser, (request, response) => {
console.log(request.body);
response.send("post请求");
});
7.2.4 防盗链中间件
- 顾名思义,防止外部网站盗用网站资源
//防盗链
app.use((request, response, next) => {
let referer = request.get("referer");
if (!referer) return next();
let nul = new URL(referer);
let host = nul.hostname;
if (host !== "localhost") return response.send("禁止盗链");
next();
});
7.2.5 express 的路由的模块化
//引入express模块
const express = require("express");
//创建路由对象
const router = express.Router();
//创建路由
router.get("/login", (request, response) => {
response.send("login请求");
});
//创建路由
router.get("/register", (request, response) => {
response.send("register请求");
});
//导出路由对象
module.exports = {
router,
};
7.2.6 EJS 模版引擎
- 模板引擎是分离 用户界面和业务数据 的一种技术
7.2.6.1 什么是模版引擎?
EJS 是一个高效的 Javascript 的模板引擎 官网: https://ejs.co/ 中文站: https://ejs.bootcss.com/
7.2.6.2Ejs 初体验
- 安装
npm i ejs --save
yarn add ejs --save
2.使用
//导入ejs
const ejs = require("ejs");
//导入fs模块
const fs = require("fs");
//定义一个变量
let ejs = "我是ejs";
//读取ejs文件
let html = fs.readFileSync("./views/index.html", "utf8").toString();
//使用ejs.render()方法渲染
let result = ejs.render(html, { ejs });
console.log(result);
7.2.6.3EJS 常用语法
执行 JS 代码
<% js语法 %>
输出转义的数据到模板上
<%= code %>
输出非转义的数据到模板上
<%- code %>
7.2.6.4 ejs 的条件渲染
//导入ejs
const ejs = require("ejs");
let islogin = false;
let result = ejs.render(
"<%if(islogin){%>" + "<h1>真</h1>" + "<%}else{%>" + "<h1>假</h1>" + "<%}%>",
{ islogin }
);
console.log(result);
7.2.6.5 express 中使用 ejs
- 使用 ejs
//导入ejs
const ejs = require("ejs");
//导入express
const express = require("express");
//创建应用对象
const app = express();
// 设置模版引擎
app.set("view engine", "ejs"); //pug,twing,ejs都是模板引擎 第一个参数是固定的 第二个参数是模板引擎的名字
// 设置模版目录
app.set("views", `${__dirname}/views`); //默认就是./views
//创建路由
app.get("/ejs", (request, response) => {
let isLogin = true;
response.render("index.ejs", { isLogin }); //render渲染
});
//监听端口号
app.listen(3000, (err) => {
if (!err) console.log("服务器启动成功了");
else console.log(err);
});
- 创建 views 文件夹,下面有 inde.ejs 文件
- index.ejs 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<header>
<% if(isLogin){ %>
<h1>登录</h1>
<% }else{ %>
<h1>注册</h1>
<% } %>
</header>
</body>
</html>
7.3 express 之 generator 工具
Express 应用程序生成器 - Express 中文文档 | Express 中文网 (expressjs.com.cn)
- 安装
npm install -g express-generator
yarn add express-generator -g
- 使用
express -h
- 创建
express -e <FloderName>
步骤
- 查看 package.json
{
"name": "express-generator",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "~2.6.1",
"express": "~4.16.1",
"http-errors": "~1.6.3",
"morgan": "~1.9.1"
}
}
- 启动
npm run start
- app.js
var createError = require("http-errors"); //错误处理
var express = require("express"); //express框架
var path = require("path"); //路径
var cookieParser = require("cookie-parser"); //解析cookie
var logger = require("morgan"); //日志
var indexRouter = require("./routes/index"); //一级路由
var usersRouter = require("./routes/users"); //二级路由
var app = express(); //创建一个express应用
//设置跨域访问
// view engine setup
app.set("views", path.join(__dirname, "views")); //设置模板文件夹
app.set("view engine", "ejs"); //设置模板引擎
//中间件
app.use(logger("dev")); //日志
app.use(express.json()); //解析json请求体
app.use(express.urlencoded({ extended: false })); //解析urlencoded请求体
app.use(cookieParser()); //解析cookie
app.use(express.static(path.join(__dirname, "public"))); //静态文件服务
app.use("/", indexRouter); //设置路由前缀
app.use("/users", usersRouter); //设置路由前缀 ,usersRouter是一个二级路由
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404)); //如果没有匹配的路由,就会抛出404错误,这里就建议使用404公益页面
});
//app.all('*', function(req, res, next) {});//设置404页面
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page
res.status(err.status || 500);
res.render("error");
});
module.exports = app;
7.4 经典案例
7.4.1 路由参数练习
路由参数练习,歌手信息
//导入express
const express = require("express");
//导入json数据
const { singers } = require("./data.json");
//创建应用对象
const app = express();
//创建路由
/**
* 根据路由参数获取歌手的信息
* singer/:id.html
* @param {string} id 歌手的id
*/
app.get("/singer/:id.html", (request, response) => {
//获取参数
let { id } = request.params;
//根据id获取歌手的信息
let singer_item = singers.find((item) => {
if (item.id === id) {
return true;
}
});
if (!singer_item) {
response.send("没有该歌手的信息");
return;
}
response.send(`
<h1>${singer_item.name}</h1>
<img src="${singer_item.pic}" alt="">
`);
});
//监听端口号
app.listen(3000, (err) => {
if (!err) console.log("服务器启动成功了");
else console.log(err);
});
7.4.2 文件上传报文
详情
npm install formidable@2.1.2
yarn add formidable@2.1.2
var express = require("express");
var router = express.Router();
const formidable = require("formidable"); //处理文件上传 版本2.1.2
/* GET home page. */
router.get("/", function (req, res, next) {
res.render("index", { title: "Express" });
});
//网页
router.get("/portrait", function (req, res, next) {
res.render("portrait.ejs", { title: "Express" });
});
//处理文件上传 请求
router.post("/portrait", (req, res, next) => {
const form = formidable({
multiples: true, //设置为true时,可以上传多个文件
uploadDir: `${__dirname}/../public/images`, //设置上传文件存放的文件夹
keepExtensions: true, //设置为true时,可以获取上传文件的后缀名
});
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
// console.log(fields);//fields是一个对象,里面存储了非文件的表单数据
// console.log(files);//files是一个对象,里面存储了上传的文件的信息
// res.json({fields, files});
let url = `/images/${files.portrait.newFilename}`; //使用数据库时把路径保存在数据库中
res.send(`<img src="${url}" />`);
});
});
module.exports = router;
:::
八、API 接口
8.1API 接口介绍
- 接口是 前后端通信的桥梁
- 简单理解:一个接口就是 服务中的一个路由规则 ,根据请求响应结果
- 接口的英文单词是 API (Application Program Interface),所以有时也称之为
API 接口 - 这里的接口指的是『数据接口』, 与编程语言(Java,Go 等)中的接口语法不同
- 接口的作用:实现前后端通信

8.1.1 接口的开发与调用
大多数接口都是由 后端工程师 开发的, 开发语言不限
一般情况下接口都是由 前端工程师 调用的,但有时 后端工程师也会调用接口 ,比如短信接口,支付接口 等
8.1.2 接口的组成
一个接口一般由如下几个部分组成
请求方法
接口地址(URL)
请求参数
响应结果
一个接口示例 https://www.free-api.com/doc/325
体验一下: https://api.asilu.com/idcard/?id=371522199111299668
8.2 RESTful API
RESTful API 是一种特殊风格的接口,主要特点有如下几个:
- URL 中的路径表示 资源,路径中不能有 动词,例如 create , delete , update 等这些都不能有
- 操作资源要与 HTTP 请求方法 对应
- 操作结果要与 HTTP 响应状态码 对应
规则示例:
| 操作 | 请求类型 | URL | 返回 |
|---|---|---|---|
| 新增歌曲 | POST | /song | 返回新生成的歌曲信息 |
| 删除歌曲 | DELETE | /song/10 | 返回一个空文档 |
| 修改歌曲 | PUT | /song/10 | 返回更新后的歌曲信息 |
| 修改歌曲 | PATCH | /song/10 | 返回更新后的歌曲信息 |
| 获取所有歌曲 | GET | /song | 返回歌曲列表数组 |
| 获取单个歌曲 | GET | /song/10 | 返回单个歌曲信息 |
扩展阅读: https://www.ruanyifeng.com/blog/2014/05/restful_api.html
8.3 json-server
json-server 本身是一个 JS 编写的工具包,可以快速搭建 RESTful API 服务
官方地址: https://github.com/typicode/json-server
详情见:03.json-server
8.4 API 接口测试工具
- apipost https://www.apipost.cn/ (中文)
- apifox https://www.apifox.cn/ (中文)
- postman https://www.postman.com/ (英文)
九、会话控制
9.1 介绍
- 所谓会话控制就是 对会话进行控制。
- HTTP 是一种无状态的协议,它没有办法区分多次的请求是否来自于同一个客户端,无法区分用户。
- 而产品中又大量存在的这样的需求,所以我们需要通过 会话控制 来解决该问题。
- 常见的会话控制技术有三种:
- cookie
- session
- token
9.2cookie
cookie 是 HTTP 服务器发送到用户浏览器并保存在本地的一小块数据
cookie 是保存在浏览器端的一小块数据
cookie 是按照域名划分保存的
简单示例:
| 域名 | cookie |
|---|---|
| www.baidu.com | a=100; b=200 |
| www.bilibili.com | xid=1020abce121; hm=112411213 |
| jd.com | x=100; ocw=12414cce |
9.2.1cookie 的特点
浏览器向服务器发送请求时,会自动将 当前域名下 可用的 cookie 设置在请求头中,然后传递给服务器
这个请求头的名字也叫 cookie ,所以将 cookie 理解为一个 HTTP 的请求头也是可以的
9.2.2cookie 的运行流程
- 填写账号和密码校验身份,校验通过后下发 cookie

- 有了 cookie 之后,后续向服务器发送请求时,就会自动携带 cookie

9.2.3 浏览器操作 cookie
浏览器操作 cookie 的操作,使用相对较少,大家了解即可
- 禁用所有 cookie
- 删除 cookie
- 查看 cookie
9.2.4 express 中的 cookie 的代码操作
- 安装 cookie-parser
npm i cookie-parser
yarn add cookie-parser
- 使用
//导入express
const express = require('express');
//导入cookie-parser
const cookieParser = require('cookie-parser');
//创建应用对象
const app = express();
//使用cookieParser中间件
app.use(cookieParser());
//导入创建路由规则
//设置cookie
app.get('/set-cookie', (request, response) => {
// 不带时效性 会在浏览器关闭的时候,销毁
// response.cookie('username','wangwu');
response.cookie('username', 'zhangsan', {maxAge: 1000 * 60 * 60 * 24 * 7});//设置cookie, 7天后过期
//响应体
response.send('cookie设置成功');
})
//获取cookie
app.get('/get-cookie', (request, response) => {
//读取 cookie
console.log(request.cookies);
//响应体
response.send('Cookie的读取');
});
//删除cookie
app.get('/del-cookie', (request, response) => {
//删除cookie
response.clearCookie('username');
//响应体
response.send('Cookie的删除');
})
//监听端口号
app.listen(3000, (err) => {
if (!err) console.log('服务器启动成功了');
else console.log(err);
})
9.3 session
- session 是保存在 服务器端的一块儿数据,保存当前访问用户的相关信息
- 作用:实现会话控制,可以识别用户的身份,快速获取当前用户的相关信息
9.3.1 session 运行流程
- 填写账号和密码校验身份,校验通过后创建
session 信息,然后将session_id的值通过响应头返回给浏览器

- 有了 cookie,下次发送请求时会自动携带 cookie,服务器通过
cookie中的session_id的值确定用 户的身份

9.3.2 express 中的 session 的代码操作
- 安装
npm i express-session
yarn add express-session
npm i connect-mongo
yarn add connect-mongo
- 使用
//导入express
const express = require("express");
//导入session中间件
const session = require("express-session");
//导入connect-mongo模块
const MongoStore = require("connect-mongo");
//创建应用对象
const app = express();
//使用session中间件
app.use(
session({
name: "sessionID", //设置cookie的name,默认值是:connect.sid
secret: "atguigu", //参与加密的字符串(又称签名) 这个属性值为必须指定的属性 加盐
saveUninitialized: false, //是否在存储内容之前创建会话 默认值是true 建议设置成false 是否为每次请求都设置一个cookie用来存储session的id
resave: true, //是否在每次请求时,强制重新保存session,即使他们没有变化
store: MongoStore.create({
mongoUrl: "mongodb://127.0.0.1:27017/bilibili",
}), //设置存储session的实例
cookie: {
httpOnly: true, // 开启后前端无法通过 JS 操作cookie
maxAge: 1000 * 60 * 60 * 24, // 设置cookie的过期时间 一天
},
})
);
//创建路由规则
//session的设置
app.get("/setsession", (request, response) => {
if (request.query.username === "admin" && request.query.password === "123") {
//登录成功
request.session.username = request.query.username;
response.send("登录成功");
} else {
//登录失败
response.send("登录失败");
}
});
//获取session
app.get("/getsession", (request, response) => {
if (request.session.username) {
response.send(`欢迎${request.session.username}回来`);
} else {
response.send("请先登录");
}
});
//删除session
app.get("/delsession", (request, response) => {
//删除session
request.session.destroy(() => {
response.send("删除session成功");
});
});
//监听端口号
app.listen(3000, (err) => {
if (!err) console.log("服务器启动成功了");
else console.log(err);
});
9.4 session 和 cookie 的区别
- 存在的位置
cookie:浏览器端
session:服务端
- 安全性
cookie 是以明文的方式存放在客户端的,安全性相对较低
session 存放于服务器中,所以安全性
相对较好
网络传输量
cookie 设置内容过多会增大报文体积, 会影响传输效率
session 数据存储在服务器,只是通过 cookie 传递 id,所以不影响传输效率
存储限制
浏览器限制单个 cookie 保存的数据不能超过
4K,且单个域名下的存储数量也有限制(165)session 数据存储在服务器中,所以没有这些限制
9.5 token
token 是服务端生成并返回给 HTTP 客户端的一串加密字符串, token 中保存着用户信息
token 不属于 http 标准,完全由前后端协商而定,但 cookie 属于 http 标准
作用:实现会话控制,可以识别用户的身份,主要用于移动端 APP
9.5.1express 中的 token 的工作流程
填写账号和密码校验身份,校验通过后响应 token,token 一般是在响应体中返回给客户端的

发送请求时,需要手动将 token 添加在请求报文中(cookie 是自动携带的),一般是放在请求头中

9.5.2 token 的特点
- 服务端压力更小
- 数据存储在客户端
- 相对更安全
- 数据加密
- 可以避免 CSRF(跨站请求伪造)
- 扩展性更强
- 服务间可以共享
- 增加服务节点更简单
9.5.3 JWT
JWT(JSON Web Token )是目前最流行的跨域认证解决方案,可用于基于
token的身份验证JWT 使 token 的生成与校验更规范
9.5.3.1 我们可以使用 jsonwebtoken 包 来操作 token
- 安装
yarn add jsonwebtoken
- 使用
//导入 jsonwebtokan
const jwt = require('jsonwebtoken');
//创建 token
// jwt.sign(数据, 加密字符串, 配置对象)
let token = jwt.sign({
username: 'zhangsan'
}, 'melikepinia', {
expiresIn: 60 *60 *24*7 //单位是 s
})
//解析 token
// jwt.verify(token,加密字符串,回调函数)
jwt.verify(token, 'melikepinia', (err, data) => {
if(err){
console.log('校验失败~~');
return
}
console.log(data);// { username: '张三', iat: (创建时间), exp:(过期时间)}
})
扩展阅读: https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
9.6 CSRF 跨站请求伪造
- 对一些特定的操作尽量不要使用 get 请求,避免危险行为发生。
- 因为 link,script,img 标点发送的就是 get 请求
9.7 本地域名
- 所谓本地域名就是 只能在本机使用的域名,一般在开发阶段使用
9.7.1 操作流程
- 编辑文件
C:\Windows\System32\drivers\etc\hosts
127.0.0.1 www.pinia.top
如果修改失败,可以修改该文件的权限

9.7.2 原理
在地址栏输入
域名之后,浏览器会先进行 DNS(Domain Name System)查询,获取该域名对应的 IP 地 址 请求会发送到 DNS 服务器,可以根据域名返回 IP 地址可以通过
ipconfig /all查看本机的 DNS 服务器hosts文件也可以设置域名与 IP 的映射关系,在发送请求前,可以通过该文件获取域名的 IP 地址
十、HTTPS 配置 SSL 证书
- https 本意是 http+SSL(Secure Sockets Layer 安全套接层)
- https 可以加密 HTTP 报文,所以大家可以理解为是安全的 HTTP
- 工具官网:Certbot (eff.org)
- 操作流程
- 下载工具
https://dl.eff.org/certbot-beta-installer-win_amd64.exe - 安装工具
- 管理员运行命令
certbot certonly --standalone,输入域名,下载证书 - 代码配置如下
- 下载工具
const fs = require('fs')
const https = require('https')
https.createServer({
//证书文件
key:fs.readFileSync('/etc/letsencrypt/path/to/privkey.pem'),
cert:fs.readFileSync('/etx/letsencrypt/path/to/cert.pem'),
ca:fs.readFileSync('/etc/letsencrypt/path/to/chain.pem'),
}app).listen(443,()=>{
console.log('Listening...')
})
10.1 证书更新
- 证书有效期为三个月,一个月内为一般更新,俩个月左右需要强制更新
certbot renew
certbot --force-renewal