1 服务器端基础概念
1.1 服务器端的基本组成
- 网站应用程序主要分为两大部分:客户端和服务器端
- 客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序,使用html,css,js构造
- 服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑
1.2 Node网站服务器
- 能够提供网站访问服务的机器就是网站服务器.能够接收客户端的请求并及时响应
1.3 IP地址
互联网中设备的唯一表示,
IP是Intenet Protocal Adress,代表互联网协议地址
1.4 域名
由于IP地址难于记忆,所以产生了域名的概念,所谓域名就是平时上网所使用的网址。
虽然在地址栏中输入的是网址, 但是最终还是会将域名转换为ip才能访问到指定的网站服务器。
1.5 端口
端口是计算机与外界通讯交流的出口,用来区分服务器电脑中提供的不同的服务。
1.6 URL
统一资源定位符,又叫URL(Uniform Resource Locator),是专为标识Internet网上资源位置而设的一种编址方式,我们平时所说的网页地址指的即是URL。
URL组成:
- 传输协议://服务器IP或域名:端口/资源所在位置标识
- http://www.itcast.cn/news/20181018/09152238514.html
- http:超文本传输协议,提供了一种发布和接收HTML页面的方法。
注意端口号是可以省略的,浏览器会默认添加
1.7 开发过程中客户端和服务器端说明
在开发阶段,客户端和服务器端使用同一台电脑,即开发人员电脑。
本机域名:localhost
本地IP :127.0.0.1
2 创建web服务器
- 创建web服务器的流程
- 应用http模块
- 创建网站服务器对象
- 事件启动,on()来接收和响应请求
- 监听端口确定响应客户端哪个服务
- 启动服务器端服务
- 创建web服务器的流程
服务器启动:node/nodemon app.js
客户端请求:http://localhost:3000/
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 用于创建网站服务器的模块
const http = require('http');
// 用于处理url地址
// app对象就是网站服务器对象
const app = http.createServer();
// 当客户端有请求来的时候
app.on('request', (req, res) => {
/*
on():
- 参数1:表示请求表示
- 参数2:响应函数,包含两个参数,
- req:(request)表示发送的请求,是请求对象
- res:(response)表示请求响应,是响应对象,通过该参数对象调用方法来对客户端请求作出响应
*/
res.end('<h1>hello </h1>');
})
// 监听端口
app.listen(3000);
console.log("网站服务器开启了");3 HTTP协议
3.1 HTTP协议概念
超文本传输协议(HyperText Transfer Protocal,HTTP)规定了如何从网站服务器传输超文本到本地浏览器,基于客户端架构工作,是客户端和服务器端请求和响应的标准
3.2 报文
在HTTP请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式。
3.3 请求报文
- 请求方式(Request Method)
- GET 请求数据
- POST 发送数据
- 注意:
- 浏览器发送的请求是GET请求,
- form表单提交的请求可以是post可以是get,但是post请求更为安全
1.1. HTTP状态码
- 200 请求成功
- 404 请求的资源没有被找到
- 500 服务器端错误
- 400 客户端请求有语法错误
内容类型
- text/plain:默认文本类型
- text/html
- text/css
- application/javascript
- image/jpeg
- application/json
解决编码问题(通过设置报文头的方式)
- ‘Content-Type’: ‘text/plain;charset=utf-8’
4 HTTP请求与响应处理
4.1 请求参数
- 客户端向服务器端发送请求时,有时需要携带一些客户信息,客户信息需要通过请求参数的形式传递到服务器端,比如登录操作
4.2 GET请求参数
- 参数被放置在浏览器地址栏中,例如:http://localhost:3000/?name=zhangsan&age=20
- 参数获取需要借助系统模块url,url模块用来处理url地址
- 获取url通过req.url
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// 用于处理url地址
const url = require('url');
//处理响应请求
app.on('request', (req, res) => {
// 获取请求地址req.url
console.log(req.url);
// 通过系统模块url获取GET参数
/*
url.parse(要解析的url地址,true/false是否解析为对象(对象中的对象))
- 通过该方法将字符串类型转化为对象类型
*/
console.log(url.parse(req.url, true));
// 将查询参数解析成对象形式,通过对象解构的方式
let { query, pathname } = url.parse(req.url, true);
<!-- 对获取的参数进行获取 -->
console.log(query.name);
console.log(query.age);
// 返回指定的页面
if (pathname == '/index' || pathname == '/') {
res.end("welcom index首页");
} else if (pathname == '/list') {
res.end("welcome list");
} else {
res.end("not found");
}
})4.3 POST请求参数
- 参数被放置在请求体中进行传输(报文头)
- 获取POST参数需要使用data事件和end事件
- 使用querystring系统模块将参数转换为对象格式
1
querystring.parse(postData)
- end事件触发服务器端输出的结果显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32// 用于创建网站服务器的模块
const http = require('http');
// app对象就是网站服务器对象
const app = http.createServer();
// 获取处理请求参数模块
const querystring = require('querystring');
// 当客户端有请求来的时候
app.on('request', (req, res) => {
/*
post是通过事件的方式接受的,事件有两个
- data 当请求参数传递的时候发出data事件
- end 当请求参数传递完成的时候触发end事件
*/
// 声明变量保存参数值
let postData = '';
// 为请求对象绑定事件
req.on('data', paramas => {
// 这里通过paramas接受post传过来的字符串参数
// 数据在接受参数的是时候并不是一次性接受完毕的.所以传过来的参数要经过多次接受,这里声明变量来存储多次接受的字符串参数
postData += paramas;
console.log(paramas);
});
req.on('end', () => {
// querystring.parse(postData)将传过来的参数字符串转换为对象的形式
console.log(querystring.parse(postData));
})
// 服务器端必须要有响应,不然end点击事件一直在等待,直到有响应才会触发end事件
res.end("ok");
})
// 监听端口y
app.listen(3000);
console.log("网站服务器开启了");
- end事件触发服务器端输出的结果显示
- POST参数在浏览器中的form data
4.4 路由
http://localhost:3000/index
http://localhost:3000/login - 路由是指客户端请求地址与服务器端程序代码的对应关系。简单的说,就是请求什么响应什么。
- 如图
- 核心代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 1.引入系统模块http
// 2.创建网站服务器
// 3.为网站服务器对象添加请求事件
// 4.实现路由功能
// 1.获取客户端的请求方式
// 2.获取客户端的请求地址
const http = require('http');
const app = http.createServer();
const url = require('url');
app.on('request', (req, res) => {
// 获取请求方式
const method = req.method.toLowerCase();//转化为小写
// 获取客户端的请求地址
const pathname = url.parse(req.url).pathname;
// 添加报文头设置编码,通过响应对象添加设置
res.writeHead(200, {
'content-type': 'text/html;charset=utf8',
})
**核心代码**
if (method == 'get') {
if (pathname == '/' || pathname == '/inde') {
res.end("欢迎来到首页");
} else if (pathname == '/list') {
res.end("欢迎来到列表页");
} else {
res.end('您访问的页面不存在');
}
} else if (method == 'post') {
}
});
// 设置监听
app.listen(3000);
console.log("服务器启动成功");4.5 静态资源
- 服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,例如CSS、JavaScript、image文件。比如一张固定的图片:http://www.itcast.cn/images/logo.png,是不需要进行处理的
- 静态资源访问的代码
- 目录结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43// 用于创建网站服务器的模块
const http = require('http');
// 用于处理url地址
const url = require('url');
// app对象就是网站服务器对象
const app = http.createServer();
const path = require('path');//引入路径模块
const fs = require('fs');//引入文件模块
const mime = require('mime');//获取多种文件类型
app.on('request', (req, res) => {
// 访问静态页面
// 获取客户端地址
let pathname = url.parse(req.url).pathname;
// 解决文件默认打开的页面为default页面
pathname = pathname == '/' ? '/default.html' : pathname;
// 获取当前文件所在的地址
let realpath = path.join(__dirname, 'public' + pathname);
// 获取文件请求的文本类型
let type = mime.getType(realpath);
// 读取静态资源文件
fs.readFile(realpath, (error, result) => {
if (error != null) {
res.writeHead(404, {
'content-type': 'text/html;charset=utf8',
});
res.end("文件读取失败");
return;//直接返回不在执行下面的语句
}
// html页面中想服务器请求多个文件时文件类型不同,需要写多个报文头时通过插件模块mime安装
res.writeHead(200, {
'content-type': type
})
res.end(result);//读取成功则返回当前静态页面
})
})
// 监听端口
app.listen(3000);
console.log("网站服务器开启了");
- 目录结构
- html页面中想服务器请求多个文件时文件类型不同,需要写多个报文头时通过插件模块mime安装;通过mime.getType()方法获取文件类型
1
2
3
4
5
6
7安装mime插件模块
const mime = require('mime');//获取多种文件类型
let type = mime.getType(realpath);
<!-- 设置报文头文本类型为多类型 -->
res.writeHead(200, {
'content-type': type
}) - 相同的请求地址不同的响应资源,这种资源就是动态资源。(后面参数的不同)
1
2http://www.itcast.cn/article?id=1
http://www.itcast.cn/article?id=25 Nodejs异步编程
5.1 同步API和异步API
- 同步API:只有当前api执行之后,才能继续执行下一个API
- 异步API:当前API的执行不会影响后续的API
1
2
3
4
5
6console.log('before');
// 定时器是异步api
setInterval(function () {
console.log("last");
}, 2000);
console.log('after');5.2 同步API和异步API的区别
1.获取返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14同步API:
// 同步
function sum (n1, n2) {
return n1 + n2;
}
const result = sum (10, 20);
异步API
// 异步
function getMsg () {
setTimeout(function () {
return { msg: 'Hello Node.js' }
}, 2000);
}
const msg = getMsg ();//没有返回值 - 同步API可以通过返回值拿到API执行的结果,异步API不可以通过返回值拿到
异步API要拿到API执行的结果借助回调函数callback
- 通过传参的形式拿到异步API执行的结果
1
2
3
4
5
6
7
8
9
10
11
12function getMsg(callback) {
setTimeout(function () {
// 通过回调函数参数的形式拿到返回值
callback({
msg: 'hello node.js'
});
}, 2000)
}
//这里的data用于接收会回调函数参数{msg:'hello node.js'}中的信息
getMsg(function (data) {
console.log(data);
}); - 输出结果
2.代码执行顺序
- 同步API从上到下依次执行,前面代码会阻塞后面代码的执行
1
2
3
4
5for (var i = 0; i < 10000; i++) {
console.log(i);
}
console.log("代码执行完毕");
// 按照顺序进行执行,前面的代码没有执行完毕会阻塞后面代码的执行 - 异步API不会等到API执行完成之后在向下执行代码,不会阻塞代码的执行代码执行结果
1
2
3
4
5
6
7
8console.log("代码开始执行");
setTimeout(() => {
console.log("定时器1开始执行")
}, 2000);
setTimeout(() => {
console.log("定时器2开始执行");
}, 0);
console.log("代码执行结束");
异步API执行顺序分析
- 先执行完同步代码,在执行异步代码
- 同步代码在同步代码执行区执行,异步代码在异步代码执行区执行
- 执行到异步代码部分会把异步代码放在异步代码执行区,将回调函数放在回调函数队列中等待执行
- 当同步代码执行之后会把回调函数队列中的代码放在同步代码执行区进行执行
5.3 node.js中的异步API(回调地狱问题)
- nodejs中的异步API:readFile(),server.on(),都需要调用回调函数解决API返回值的问题
- 回调地狱问题的出现:对于异步API中需要按照顺序依次执行1,2,3三个文件的代码如下
1
2
3
4
5
6
7
8
9
10
11
12const fs = require('fs');
fs.readFile('./1.txt', 'utf8', (err, result1) => {
console.log(result1);
fs.readFile('./2.txt', 'utf8', (err, result2) => {
console.log(result2);
fs.readFile('./3.txt', 'utf8', (err, result3) => {
console.log(result3);
})
})
})
注意这种循环嵌套的方式执行异步API也称为回调地狱,实际开发中不建议使用,
- 执行结果
5.4 解决回调地狱方案-Promise
- Promise出现的目的是解决Node.js异步编程中回调地狱的问题。
- 原理:将异步API的执行和结果分离
- promise()是一个构造函数,传入的参数是一个匿名函数,匿名函数中含有两个参数,分别用于接收返回值
- resolve:是API执行成功调用的函数
- reject:是API执行失败调用的函数
- 返回值的获取通过调用promise中的then()和catch()方法
- then(匿名函数):匿名函数中的参数用于接收resolve函数中的参数值
- catch(匿名函数):匿名函数中的参数用于接收reject函数中的参数
- 上述的方法可以通过链式编程的方式直接使用
1
promise.then((result)=>{}).catch((err)=>{})
- promise对象支持链式编程
- 使用方法代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// promise是es6中新增的语法,通过promise可以将异步API的执行和结果分离
const fs = require('fs');
/*
promise()是一个构造函数,传入的参数是一个匿名函数,匿名函数中含有两个参数
- resolve:是API执行成功调用的函数
- reject:是API执行失败调用的函数
*/
let promise = new Promise((resolve, reject
) => {
fs.readFile('./1.txt', 'utf8', (err, result) => {
if (err != null) {
reject(err);//将错误信息作为参数传递给reject函数
}
resolve(result);//执行正确将结果作为参数传递给resolve函数
})
});
// 通过promise调用方法返回异步API的返回值
/*
promise.then(匿名函数)方法将执行正确的返回值作为参数传递给then的匿名函数中的参数进行接收
promise.catch(匿名函数)方法将执行错误的返回值作为参数传递给catch的匿名函数中的参数进行接收
*/
promise.then((result) => {
console.log(result);//
})
.catch((err) => {
console.log(err);
}) - 执行结果展示
5.5 回调地狱问题代码重写
- 问题:使1,2,3三个文件一次执行
- 解决:
- 需要执行几个文件就要创建几个promise对象,
- 为了方便调用需要将每一个promise对象封装在函数中
- 通过promise的then方法解决了顺序执行的嵌套问题
- then链式编程方法执行中返回的是哪个promise对象就执行哪个promise的then方法,(无return是是自身)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38// 回调地狱问题代码解决
// 需要执行几个文件就要创建几个promise对象,
// 为了方便调用需要将每一个promise对象封装在函数中
const fs = require('fs');
// 暂时先不考虑返回错误的情况
function p1() {
return new Promise((resolve, reject) => {
fs.readFile('./1.txt', 'utf8', (err, result) => {
resolve(result);
})
})
}
function p2() {
return new Promise((resolve, reject) => {
fs.readFile('./2.txt', 'utf8', (err, result) => {
resolve(result);
})
})
}
function p3() {
return new Promise((resolve, reject) => {
fs.readFile('./3.txt', 'utf8', (err, result) => {
resolve(result);
})
})
}
p1().then((result) => {
console.log(result);
return p2();
})//返回的对象是谁就执行谁的then()方法
.then((result) => {
console.log(result);
return p3();
})
.then((result) => {
console.log(result);
})
- 执行结果
- 注意promise虽然解决了回调地狱问题但是比较繁琐,需要在函数外面封装promise,在封装函数,而且在链式编程的调用过程中显得繁琐,
- 解决: nodejs中提供了异步函数代码进行封装
5.6 异步函数
- 异步函数是异步编程语法的终极解决方案,让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套的情况,时代吗更加清晰明了
1. async关键字
- 普通函数定义前加async关键字,普通函数变为异步函数
- 异步函数默认返回promise对象
- 在异步函数内部使用return关键字进行结果返回,结果会被包裹在promise对象中,return关键字代替了resolve方法
- 在异步函数内部使用throw关键字抛出程序异常
- 异步函数在链式调用then方法获取异步函数的执行结果
- 异步函数在链式调用catch方法获取异步函数执行的错误信息
1
2
3
4
5
6
7
8
9
10
11
12// 异步函数声明
async function fun() {
throw "程序执行错误";//throw关键字代替reject()方法,执行到这里将不再执行
return 123;//return关键字代替resolve()返回正确情况下的返回值
}
console.log(fun());//Promise { 123 }返回promise对象
fun().then(function (data) {
// console.log(data);//返回123的值
})
.catch(function (err) {
console.log(err);//返回程序执行错误
}) - 执行结果
2. await关键字
- await关键字只能出现在异步函数中
- await promise对象:await后面只能写promise对象,写其他类型的API是不允许的
- await关键字只是暂停异步函数向下执行,直到promise返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 回调地狱问题的异步函数解决
async function p1() {
return 'p1';
}
async function p2() {
return 'p2';
}
async function p3() {
return 'p3';
}
async function run() {//将异步代码写成同步形式
let r1 = await p1();//直到返回结果才执行下一个代码
let r2 = await p2();
let r3 = await p3();
console.log(r1);
console.log(r2);
console.log(r3);
}
run();3. nodejs在异步函数中的应用(改造回调地狱promisify)
- 系统模块util下promisify():改造现有异步api 让其返回promise对象 从而支持异步函数语法
- 使用方式
1
2const promisify = require('util').promisify;//返回这个对象
const readFile = promisify(fs.readFile);//通过对象调用方法,返回redFile对象并将其返回值设置为promise对象
1 | const fs = require('fs'); |
总结
解决异步API执行的方式有三种
- 回调函数的方式
- promise的方法
- 异步函数的方式
注意node中的大多数API都是异步API(数据库中的操作,文件操作等)
本文链接: https://sparkparis.github.io/2020/04/05/Node-%E7%AC%94%E8%AE%B02/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!