请求响应原理及HTTP协议
1. 服务器端基础概念
1.1 网站的组成
网站应用程序
主要分为两大部分:客户端
和服务器端
。
客户端
:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使用HTML、CSS、JavaScript构建。
服务器端
:在服务器中运行的部分,负责存储数据和处理应用逻辑。
请求
:客户端向服务器端发送一个指令request,告诉服务器端我要获取数据
响应
:服务器端接收到指令,对客户端进行回应response,将请求的数据发送给客户端
前后端交互
:前端页面通过工具调用后端接口,拿到数据库中的数据,然后在做前端页面的渲染。
什么是前端? 什么是后端? 什么是数据库?
1.2 Node网站服务器
能够提供网站访问服务的机器就是网站服务器
,它能够接收客户端的请求
,能够对请求做出响应
。
1.3 IP地址
IP地址
是互联网中设备的唯一标识(biaoshi)。
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.itcast.cn/news/20181018/09152238514.html
http
:超文本传输协议,提供了一种发布和接收HTML页面的方法。
1.7 开发过程中客户端和服务器端说明
在开发阶段,客户端和服务器端使用同一台电脑,即开发人员电脑。
2. 创建web服务器
2.1 什么是 Web 服务器?
Web服务器
一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,Web服务器的基本功能就是提供Web信息浏览服务。它只需支持HTTP协议、HTML文档格式及URL,与客户端的网络浏览器配合。大多数 web 服务器都支持服务端的脚本语言(php、python、ruby)等,并通过脚本语言从数据库获取数据,将结果返回给客户端浏览器。
目前最主流的三个Web服务器是Apache、Nginx、IIS。
下面为Web 应用架构
2.2 使用 Node 创建 Web 服务器
`Node 创建 Web 服务器``
1.引入 required 模块:我们可以使用 require 指令来载入 Node.js 模块。
2.创建web服务器:服务器可以监听客户端的请求,类似于 Apache 、Nginx 等 HTTP 服务器。
3.接收请求与响应请求: 客户端使用浏览器或终端发送 HTTP 请求,服务器接收请求后返回响应数据。
Node中的http
模块中主要用于搭建 HTTP 服务端和客户端,使用 HTTP 服务器或客户端功能必须调用 http 模块。
http.Server是一个基于事件的http服务器,http.request则是一个http客户端工具,用于向http服务器发起请求。而下面的createServer方法中的参数函数中的两个参数req和res则是分别代表了请求对象和响应对象。
request事件:当客户端请求到来时,该事件被触发,提供两个参数req和res,表示请求和响应信息,是最常用的事件
on(event, listener)为指定事件添加一个监听器,接受一个event和一个回调函数。
//app.js
//1.引用创建服务器http模块,将实例化的 HTTP 赋值给变量 http
const http =require('http');
//2.创建网站服务器
//app对象就是网站服务器对象
const app =http.createServer();
//对request设置监听器
app.on('request',(req,res)=>{
//响应 res.end
res.end('<h2>hello user</h2>');
});
//监听3000端口
app.listen(3000);
console.log('网站服务器启动成功');
输入localhost:监听端口 访问内容
Node 应用程序是如何工作的?
在 Node 应用程序中,执行异步操作的函数将回调函数作为最后一个参数, 回调函数将接收错误对象作为第一个参数。
创建一个 input.txt ,文件内容如下:
加油啊
//main.js
var fs = require("fs");
fs.readFile('input.txt', function (err, data) {
if (err){
console.log(err.stack);
return;
}
console.log(data.toString());
});
console.log("程序执行完毕");
以上程序中 fs.readFile() 是异步函数用于读取文件。 如果在读取文件过程中发生错误,错误 err 对象就会输出错误信息。
如果没发生错误,readFile 跳过 err 对象的输出,文件内容就通过回调函数输出。
3. HTTP协议
3.1 HTTP协议的概念
超文本传输协议
(英文:HyperText Transfer Protocol,缩写:HTTP
)规定了如何从网站服务器传输超文本到本地浏览器,它基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答的标准。
3.2 报文
在HTTP请求和响应的过程中传递的数据块就叫报文
,包括要传送的数据和一些附加信息,并且要遵守规定好的格式。
3.3 请求报文
1. 请求方式 (Request Method)
●GET:从服务器上获取数据
●POST:向服务器传送数据
浏览器用GET请求
来获取一个html页面/图片/css/js等资源;用POST请求
来提交一个表单,并得到一个结果的网页。
2. 请求地址 (Request URL)
app.on('request', (req, res) => {
req.headers // 获取请求报文
req.url // 获取请求地址
req.method // 获取请求方法
});
示例
//app.js
//1.引入创建服务器http模块
const http = require('http');
//2.创建网站服务器
//app对象就是网站服务器对象
const app = http.createServer();
//当客户端请求来的时候
app.on('request',(req,res)=>{
//获取请求方式
//req.method
// console.log(req.method);
if(req.method=='GET'){
res.end('get')
}else if(req.method=='POST'){
res.end('post')
}
//获取请求地址
// req.url
// console.log(req.url);
if(req.url=='/index' ||req.url=='/'){
res.end('welcome to homepage')
}else if (req.url=='/list'){
res.end('welcome to listpage')
}else {
res.end('not found')
}
//获取请求报文
// req.headers
console.log(req.headers)
//单独获取请求报文中的accpet内容
//console.log(req.headers['accept']);
//响应
res.end('<h2>hello user</h2>');
});
//监听3000端口
app.listen(3000);
console.log('网站服务器启动成功');
//form.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 method="post" action='http://localhost:3000'>
<!-- <input type="text" name='username'>
<input type="password" name='password'> -->
<input type="submit" name=''>
</form>
</body>
</html>
获取请求方式
获取请求地址
获取请求报文
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, {//200为状态码
//内容类型
'Content-Type': 'text/html;charset=utf8‘//utf8为编码类型
});
});
4. HTTP请求与响应处理
4.1 请求参数
客户端向服务器端发送请求时,有时需要携带一些客户信息,客户信息需要通过请求参数
的形式传递到服务器端,比如登录操作。
4.2 GET请求参数
获取get请求内容
:由于GET请求直接被嵌入在路径中,URL是完整的请求路径,包括了?后面的部分,因此你可以手动解析后面的内容作为GET请求的参数。node.js 中 url 模块中的 parse 函数提供了这个功能
●参数被放置在浏览器地址栏中,例如:http://localhost:3000/?name=zhangsan&age=20
●参数获取需要借助系统模块url,url模块用来处理url地址
,url.parse 方法
来解析 url中的参数
//app.js
//引入创建网站服务器的模块
const http = require('http');
// 引入处理url地址模块
const url = require('url');
//创建网站服务器
const app = http.createServer();
// 当客户端有请求来的时候
app.on('request', (req, res) => {
// 获取请求方式
// req.method
// console.log(req.method);
// 获取请求地址
// req.url
// console.log(req.url);
// 获取请求报文信息
// req.headers
// console.log(req.headers['accept']);
res.writeHead(200, {
'content-type': 'text/html;charset=utf8'
})
console.log(req.url);
//url.parse(req.url, true); url.parse 方法来解析 URL 中的参数
// 1) 第一个参数为要解析的url地址
// 2) 第二个参数true代表将参数解析为对象格式
//pathname存储客户端的地址
let { query, pathname } = url.parse(req.url, true);
console.log(query.name)//zhangsan
console.log(query.age)//20
if (pathname == '/index' || pathname == '/') {
res.end('<h2>欢迎来到首页</h2>');
}else if (pathname == '/list') {
res.end('welcome to listpage');
}else {
res.end('not found');
}
if (req.method == 'POST') {
res.end('post')
} else if (req.method == 'GET') {
res.end('get')
}
// res.end('<h2>hello user</h2>');
});
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');
4.3 POST请求参数
●参数被放置在请求体中进行传输
●获取POST参数
需要使用data事件
和end事件
●使用querystring系统模块
将参数
转换为对象格式
//post.js
//1.引入系统模块
//用于创建网站服务器的模块
const http = require('http');
//2.创建web服务器
const app = http.createServer()
//处理请求参数模块querystring用于将HTTP参数转换为对象格式
const querystring = require('querystring')
//3.当客户端发送请求的时候
app.on('request', (req, res) => {
//post参数是通过事件的方式接受的
//data 当请求参数传递的时候触发data事件
//end 当参数传递完成的时候触发end事件
let postParams = '';
req.on('data', params => {
postParams += params;
})
req.on('end', () => {
//querystring中的parse方法把字符串参数处理成对象格式
console.log(querystring.parse(postParams));
})
res.end('ok')
});
//4.监听3000端口
app.listen(3000);
console.log('网站服务器启动成功');
4.4 路由
路由
是指客户端请求地址
与服务器端程序代码
的对应关系。简单的说,就是请求什么响应什么
路由决定了由谁(指定脚本)去响应客户端请求
告诉你去哪
,对于前端
,主要是导向告诉浏览器应该去哪,对于后端
,可以理解为一个子服务,一个路由就是一个小的服务(server/app),处理一个接口
//app.js
//1.引入创建网站服务器模块http
const http =require('http');
const url =require('url')
//2.创建网站服务器
const app =http.createServer();
//3.为网站服务器对象添加请求事件
app.on('request',(req,res)=>{
//4.实现路由功能
//1)获取客户端的请求方式
//2)获取客户端的请求地址
//获取请求方式
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 =='/index'){
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
4.6 动态资源
相同的请求地址不同的响应资源,这种资源就是动态资源。
http://www.itcast.cn/article?id=1
http://www.itcast.cn/article?id=2
//app.js
const http = require('http');
const url = require('url')
const path = require('path')
const fs = require('fs')
const mime = require('mime') //mime模块可以分析返回资源的类型
const app = http.createServer();
app.on('request', (req, res) => {
//获取用户的请求路径
let pathname = url.parse(req.url).pathname;
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=utf-8'
});
res.end('文件读取失败');
return;
}
res.writeHead(200, {
'content-type': type
})
res.end(result)
});
});
app.listen(3000);
console.log('服务器启动成功')
4.7 客户端请求途径
1.GET方式
●浏览器地址栏
●link标签的href属性
●script标签的src属性
●img标签的src属性
●Form表单提交
2.POST方式
●Form表单提交
5. Node.js异步编程
Node.js 异步编程的直接体现就是回调,回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。
5.1 同步API, 异步API
同步API
:只有当前API执行完成后,才能继续执行下一个API
//先打印before 在打印after
console.log('before');
console.log('after');
异步API
:当前API的执行不会阻塞后续代码的执行
//先打印before 在打印after 2秒后打印last
console.log('before');
setTimeout(function () {
console.log('last');
}, 2000)
console.log('after');
5.2 同步API, 异步API的区别( 获取返回值 )
同步API可以从返回值中拿到API执行的结果, 但是异步API是不可以的
// 同步
function sum (n1, n2) {
return n1 + n2;
}
const result = sum (10, 20);//30
//异步API不可以从返回值中拿到API执行的结果
function getMsg() {
setTimeout(function () {
return {
msg: 'hello node.js'
}
}, 2000)
//没有指定return内容 默认return undefined
}
const msg = getMsg();
console.log(msg);//undefined
//异步api 我跳过了定时函数,先执行了return undefined 两秒后才拿到return {msg: 'hello node.js'},此时控制台已经拿到了undefined
5.3 回调函数
回调函数
是一个函数,将在另一个函数完成执行后立即执行。回调函数是一个作为参数
传递给另一个JavaScript函数的函数。该回调函数在传递给它的函数内部执行。回调函数一般作为函数的最后一个参数出现
// getData函数定义
function getData (callback) {}//callback为形参
// getData函数调用
getData (() => {});//() => {}这个回调函数为callback对应的实参
function getData(callback) {//callback为形参
callback(123); //callback函数被调用了 123 function(n){}这个回调函数在getData函数内部执行
//使用callback调用下面的回调函数,n为形参,123为实参
// 改写成
//function (n) {
// console.log('callback函数被调用了');
// console.log(n);
// }
// callback(123); callback函数被调用了 123
}
getData(function (n) {//function(n){}这个回调函数为实参
console.log('callback函数被调用了');
console.log(n);
})
使用回调函数获取异步API执行结果
function getMsg(callback) {
setTimeout(function () {
callback({
msg: 'hello node.js'
})
}, 2000)
}
const msg = getMsg(function (data) {//{ msg: 'hello node.js'}作为实参传给了形参data
console.log(data); //{msg: 'hello node.js'}
});
5.4 同步API, 异步API的区别(代码执行顺序)
同步API
从上到下依次执行,前面代码会阻塞后面代码的执行
//同步API从上到下依次执行,前面代码会阻塞后面代码的执行,for循环执行完之后在执行log打印
for (var i = 0; i < 1000; i++) {
console.log(i);
}
console.log('for循环后面的代码');
异步API
不会等待API执行完成后再向下执行代码
console.log('代码开始执行');
setTimeout(() => { console.log('2秒后执行的代码')}, 2000);
setTimeout(() => { console.log('"0秒"后执行的代码')}, 0);
console.log('代码结束执行');
代码执行顺序分析
:代码从上到下依次执行,遇到同步API放到同步代码执行区执行,遇到异步API放到异步代码执行区,当同步代码执行区执行完毕,然后执行异步代码,当异步代码区执行完毕,系统会到回调函数队列中找异步API对应的回调函数,然后把回调函数放到同步代码执行区执行
5.5 Node.js中的异步API
如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,这个问题要怎么解决呢?
依次读取 1 2 3.txt文件 方案一:嵌套
//依次读取 1 2 3.txt文件(分别写入的1 2 3),采用嵌套的方式 不可维护 回调地狱
const 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);
})
})
})
//结果依次输出 1 2 3
5.6 Promise
Promise
出现的目的是解决Node.js异步编程中回调地狱的问题。
const fs = require('fs');
let promise = new Promise((resolve, reject) => {
fs.readFile('./1.txt', 'utf8', (err, result) => {
if (err != null) { //如果文件读取失败,要把文件失败的信息传递到promise外面
reject(err)
} else {
resolve(result)//如果文件读取成功,要把文件成功的信息传递到promise外面
}
});
})
promise.then((result) => {//then中传递匿名函数,形参result是接受上面resolve中的result
console.log(result);
})
.catch((err) => { //catch中传递匿名函数,形参err是接受上面reject中的err
console.log(err);
})
5.7 异步函数
异步函数
是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。
const fn = async () => {};
async function fn () {}
依次读取 1 2 3.txt文件 方案二:异步函数
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((r1) => {
console.log(r1);
return p2();
})
.then((r2) => {
console.log(r2);
return p3();
})
.then((r3) => {
console.log(r3);
})
async关键字
1.普通函数定义前加async关键字 普通函数变成异步函数
2.异步函数默认返回promise对象
3.在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法
4.在异步函数内部使用throw关键字抛出程序异常
5.调用异步函数再链式调用then方法获取异步函数执行结果
6.调用异步函数再链式调用catch方法获取异步函数执行的错误信息
await关键字
1.await关键字只能出现在异步函数中
2.await后面只能写promise对象 写其他类型的API是不可以的
3.await关键字可是暂停异步函数向下执行 直到promise返回结果
同步函数
:具有顺序性,当调用函数未返回值前不能执行后续行为;
异步函数
:无顺序性,调用的函数会立即返回,不影响执行后续的行为,其中调用函数的真实执行情况是在另一个线程中发生;
//1.在普通函数定义的前面加上async关键字,普通函数变成异步函数
//2.异步函数默认的返回值是promise对象
//3.在异步函数内部使用throw关键字进行错误的抛出
//4.await关键字
//1)它只能出现在异步函数中
//2)await+promise 它可以暂停异步函数的执行 等待promise对象返回结果在向下执行
// async function fn() {
//return new Promise 省略这一步
// throw '发生了一些错误' //throw关键字: 抛出异常,throw一旦执行,后面的代码就不会执行
// return 123;
// }
// console.log(fn()); //Promise{123}
// fn().then(function (data) {
// console.log(data); //123
// }).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函数没有结果,可以暂停异步函数的执行 等待promise对象返回结果在向下执行
let r2 = await p2()
let r3 = await p3()
console.log(r1);
console.log(r2);
console.log(r3);
}
run()
依次读取 1 2 3.txt文件 方案三:await+promise
const fs = require('fs');
//promisify 改造现有异步函数api,让其返回promise对象,从而支持异步函数语法
const promisify = require('util').promisify; //获取该方法
//调用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();