03 请求响应原理及HTTP协议
1. 服务器端基础概念
1.1 网站的组成
网站应用程序主要分为两大部分:客户端和服务器端。
客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使
用HTML、CSS、JavaScript构建。
服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑。
1.2 Node网站服务器
能够提供网站访问服务的机器就是网站服务器,它能够接收客户端的请求,能够对请求做出响应。
1.3 IP地址
互联网中设备的唯一标识。
IP是Internet Protocol Address的简写,代表互联网协议地址.
1.4 域名
由于IP地址难于记忆,所以产生了域名的概念,所谓域名就是平时上网所使用的网址。
http://www.itheima.com => http://124.165.219.100/
虽然在地址栏中输入的是网址, 但是最终还是会将域名转换为ip才能访问到指定的网站服务器。
1.5 端口
端口是计算机与外界通讯交流的出口,用来区分服务器电脑中提供的不同的服务。
1.6 URL
统一资源定位符,又叫URL(Uniform Resource Locator),是专为标识Internet网上资源位置而设的一种编址方式,我们平时所说的网页地址指的即是URL。
url的组成
传输协议://服务器IP或域名:端口/资源所在位置标识
http://www.baidu.cn/news/20181018/09152238514.html
http:超文本传输协议,提供了一种发布和接收HTML页面的方法。
1.7 开发过程中客户端和服务器端说明
在开发阶段,客户端和服务器端使用同一台电脑,即开发人员电脑。
客户端 (浏览器)
服务器端(Node)
本机域名:localhost
本地IP :127.0.0.1
2.创建web服务器
浏览器输入localhost:3000 访问
// 引用系统模块
const http = require('http');
// 创建web服务器
const app = http.createServer();
// 当客户端发送请求的时候
app.on('request', (req, res) => {
// 响应
res.end('<h1>hi, user</h1>');
});
// 监听3000端口
app.listen(3000);
console.log('服务器已启动,监听3000端口,请访问 localhost:3000')
3.HTTP协议
3.1 HTTP协议概念
超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)规定了如何从网站服务器传输超文本到本地浏览器,它基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答的标准。
3.2 报文
在HTTP请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式。
3.3 请求报文
- 请求方式(request method)
GET 请求数据
POST 发送数据
app.on('request', (req, res) => {
req.headers // 获取请求报文
req.url // 获取请求地址
req.method // 获取请求方法
});
演示
// 创建网站服务器的模块
const http = require('http')
// app 对象就是网站服务对象
const app = http.createServer()
// 当客户端有请求来的时候
app.on('request', (req, res) => {
// 获取请求报文信息
// req.headers
console.log(req.headers);
console.log(req.headers['accept']);
// 获取请求地址
// req.url
// console.log(req.url);
if (req.url == '/index' || '/') {
res.end('index')
} else if (req.url == '/list') {
res.end('list')
} else {
res.end('not found')
}
// 获取请求方式
// req.method
if (req.method == "POST") {
res.end('post')
} else if (req.method == "GET") {
res.end('get')
}
})
// 监听端口
app.listen(8080)
console.log("网站服务器启动成功");
3.4 响应报文
1.HTTP状态码
200 请求成功
404 请求的资源没有被找到
500 服务器端错误
400 客户端请求有语法错误
2.内容类型
text/html
text/css
application/javascript
image/jpeg
application/json
app.on('request', (req, res) => {
// 设置响应报文
res.writeHead(200, { '
Content-Type': 'text/html;charset=utf8‘
});
});
4. HTTP请求与响应处理
4.1 请求参数
客户端向服务器端发送请求时,有时需要携带一些客户信息,客户信息需要通过请求参数的形式传递到服务器端,比如登录操作。
4.2 GET请求参数
-
参数被放在浏览器地址中 http://localhost:3000/?name=zhangsan&age=20
-
参数获取需要借助系统模块url,url模块用来处理url地址
// 创建网站服务器的模块
const http = require('http')// 处理url地址
const url = require('url')// app 对象就是网站服务对象
const app = http.createServer()// 当客户端有请求来的时候
app.on('request', (req, res) => {
// 设置相应报文
res.writeHead(200, {
// 返回类型
'content-type': 'text/html;charset=utf8' // 纯文本 设置中文编码
})// url解析 console.log('url ' + req.url); // 第一个参数解析URl,第二个参数查询参数转换为对象形式保存 let { query, pathname } = url.parse(req.url, true) console.log(query.name); console.log(query.age); // 获取请求地址 // req.url // console.log(req.url); if (pathname == '/index' || pathname == '/') { res.end('<h2>来到index</h2>') } else if (pathname == '/list') { res.end('list') } else { res.end('not found') } // 获取请求方式 // req.method if (req.method == "POST") { res.end('post') } else if (req.method == "GET") { res.end('get') }
})
// 监听端口
app.listen(8080)
console.log("网站服务器启动成功");
4.3 POST请求参数
-
参数被放置在请求体中进行传输
-
获取POST参数需要使用data事件和end事件
-
使用querystring系统模块将参数转换为对象格式
// 创建网站服务器的模块
const http = require('http')
// app 对象就是网站服务对象
const app = http.createServer()
// 处理请求参数模块
const querystring = require('querystring')
html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- method:指定当前表单提交方式 -->
<!-- action: 指定当前表单提交的地址 -->
<form action="http://localhost:8080" method="post">
<input type="text" name="username" id="">
<input type="password" name="password" id="">
<input type="submit" name="" id=""></form>
</body>
</html>
js文件
// 当客户端有请求来的时候
app.on('request', (req, res) => {
// post参数是通过事件的方式接收
// data 请求参数传递的时候触发data事件
// end 请求参数传递完成的时候触发end事件
let postParams = ''
// params:当前传递过来的参数
req.on('data', params => {
postParams += params
})
req.on('end', () => {
// 参数转换字符串对象
console.log(querystring.parse(postParams));
})
// 给客户端一个响应内容
res.end('ok')
})
// 监听端口
app.listen(8080)
console.log("网站服务器启动成功");
4.4 路由
http://localhost:3000/index
http://localhost:3000/login
路由是指客户端请求地址与服务器端程序代码的对应关系。简单的说,就是请求什么响应什么。
4.5 静态资源
服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,例如CSS、JavaScript、image文件。
4.6 动态资源
相同的请求地址不同的响应资源,这种资源就是动态资源。
// 1.创建网站服务器的模块
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
const mime = require('mime')
// 2.创建网站服务器
const app = http.createServer()
// 3.监听请求事件
app.on('request', (req, res) => {
// 获取用户请求路径
let pathname = url.parse(req.url).pathname
pathname = pathname == '/' ? '/default.html' : pathname
// 根据路径返回资源类型
let type = mime.getType(pathname)
// __dirname:获取当前文件所在路径
// 将用户请求路径转换为实际的服务器硬盘路径
resPath = path.join(__dirname, 'pulic', pathname)
// res.end(resPath)
console.log(resPath);
// 读取文件
fs.readFile(resPath, (error, result) => {
// 如果文件读取出错
if (error != null) {
res.writeHead(404, {
'content-type': 'text/html;charset=utf8'
})
res.end('文件读取失败');
return;
}
res.writeHead(200, {
'content-type': type
})
res.end(result);
})
})
// 监听端口
app.listen(8080)
console.log("网站服务器启动成功");\
5. Node.js异步编程
5.1 同步API, 异步API
同步API:只有当前API执行完成后,才能继续执行下一个API
console.log('before');
console.log('after');
异步API:当前API的执行不会阻塞后续代码的执行
// 路径拼接
const public = path.join(__dirname, 'public');
// 请求地址解析
const urlObj = url.parse(req.url);
// 读取文件
fs.readFile('./demo.txt', 'utf8', (err, result) => {
console.log(result);
});
5.2 同步API, 异步API的区别( 获取返回值 )
同步API可以从返回值中拿到API执行的结果, 但是异步API是不可以的
function getMsg() {
setTimeout(function() {
return {
msg: 'hello'
}
}, 2000)
}
// 执行这个的时候 没过两秒拿不到返回值
const msg = getMsg()
console.log(msg);
5.3 回调函数
怎么才能拿到异步API的返回值,自己定义函数让别人去调用。
function getMsg(callback) {
setTimeout(function() {
callback({
msg: 'hello'
})
}, 2000)
}
getMsg(function(data) {
console.log(data);
})
5.4 5.5 同步API, 异步API的区别(代码执行顺序)
- 同步API从上到下依次执行,前面代码会阻塞后面代码的执行
演示
for (var i = 0; i < 100000; i++) {
console.log(i);
}
console.log('for循环后面的代码');
- 异步API不会等待API执行完成后再向下执行代码
演示
console.log('代码开始执行');
setTimeout(() => { console.log('2秒后执行的代码')}, 2000);
setTimeout(() => { console.log('"0秒"后执行的代码')}, 0);
console.log('代码结束执行');
先执行同步,后执行异步(先执行即将开始的)
5.6 Node.js中的异步API
1
fs.readFile('./demo.txt', (err, result) => {});
2
var server = http.createServer();
server.on('request', (req, res) => {});
如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,这个问题要怎么解决呢?
fs.readFile('./demo.txt', (err, result) => {});
console.log('文件读取结果');
需求:依次读取A文件、B文件、C文件
const fs = require('fs')
// 回调地狱
fs.readFile('./1.txt', 'utf8', (err, res1) => {
console.log(res1);
fs.readFile('./2.txt', 'utf8', (err, res2) => {
console.log(res2);
fs.readFile('./3.txt', 'utf8', (err, res3) => {
console.log(res3);
})
})
})
5.8 Promise
Promise出现的目的是解决Node.js异步编程中回调地狱的问题。
十分臃肿
const fs = require('fs')
// 回调地狱
// fs.readFile('./1.txt', 'utf8', (err, res1) => {
// console.log(res1);
// fs.readFile('./2.txt', 'utf8', (err, res2) => {
// console.log(res2);
// fs.readFile('./3.txt', 'utf8', (err, res3) => {
// console.log(res3);
// })
// })
// })
function p1() {
return new Promise((reslove, reject) => {
fs.readFile('./1.txt', 'utf8', (err, res) => {
reslove(res)
})
})
}
function p2() {
return new Promise((reslove, reject) => {
fs.readFile('./2.txt', 'utf8', (err, res) => {
reslove(res)
})
})
}
function p3() {
return new Promise((reslove, reject) => {
fs.readFile('./3.txt', 'utf8', (err, res) => {
reslove(res)
})
})
}
p1().then((r1) => {
console.log(r1);
return p2()
}).then((r2) => {
console.log(r2);
return p3()
}).then((r3) => {
console.log(r3);
})
5.9 异步函数
异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。
const fn = async () => {};
async function fn () {}
async关键字
1. 普通函数定义前加async关键字 普通函数变成异步函数
2. 异步函数默认返回promise对象
3. 在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法
4. 在异步函数内部使用throw关键字抛出程序异常
5. 调用异步函数再链式调用then方法获取异步函数执行结果
6. 调用异步函数再链式调用catch方法获取异步函数执行的错误信息
await关键字
1. await关键字只能出现在异步函数中
2. await promise await后面只能写promise对象 写其他类型的API是不不可以的
3. await关键字可是暂停异步函数向下执行 直到promise返回结果
异步函数
// 1.在普通函数定义的前面加上async关键字
// 2.异步函数默认的返回值是promise对象
// 3.在异步函数内部使用throw关键字进行错误抛出
// async function fn() {
// throw '发生了一些错误' // 抛出错误异常
// return 123
// // await关键字
// // 1.它只能出现在异步函数中
// // 2.await promise 它可以暂停异步函数的执行 等待promise对象返回结果再向下执行
// }
// fn().then(function(data) {
// console.log(data);
// }).catch(function(err) {
// console.log(err);
// })
异步函数的基本用法
async function p1() {
return 'p1'
}
async function p2() {
return 'p2'
}
async function p3() {
return 'p3'
}
async function run() {
let r1 = await p1() // p1函数执行有返回结果再向下执行
let r2 = await p2()
let r3 = await p3()
console.log(r1);
console.log(r2);
console.log(r3);
}
run()
重写读取文件异步任务
const fs = require('fs')
const promisify = require('util').promisify
// fs.readFile函数不能返回 promise 对象 所以要给他包装个promisify,能让异步API返回promise 对象
const readFile = promisify(fs.readFile)
async function run() {
let r1 = await readFile('./1.txt', 'utf8')
let r2 = await readFile('./2.txt', 'utf8')
let r3 = await readFile('./3.txt', 'utf8')
console.log(r1);
console.log(r2);
console.log(r3);
}
run()