十一、网络
对于前端而言,网络部分代码不多,以概念为主
对概念的理解程度,决定了是否能够看懂接口文档,同时也决定了是否能更好的掌控网络相关代码
(一)客户端和服务器
1.通信
- 在网络的世界里,两个应用程序之间会经常发生通信
- 在大部分情况下,通信总是由一方发出一个消息开始,而另一方回复一个消息结束
- 发出消息的一方称之为 客户端 Client ,发出消息的过程称之为 请求 Request
- 回复消息的一方称之为 服务器 Server,回复消息的过程称之为 响应 Response
- 一次完整的交互,总是从请求开始,响应结束
2.特别注意
- 不管是客户端,还是服务器,都是一个 应用程序,而不是一台计算机
- 客户端和服务器可以分布在不同的计算机上,也可以在同一台计算机上,并不需要特殊看待
- 比如 live server 插件,就是一个服务器,运行在本地的计算机上
- 大部分后端开发的就是服务器程序,前端的 Node 技术也能开发服务器程序
3.C/S & B/S
- 客户端和服务器的这种交互模式称之为 经典 C/S 结构
- 在这种结构中,如果客户端是浏览器,则我们称之为 B/S 结构
- 服务器程序往往是为互联网产品提供服务,因此又称之为 Web 服务器
(二)URL
- 要完成一次请求和响应,首先需要让客户端找到服务器,还要在服务器上找到想要的资源
- 在现实生活中,如果要找一个人,可以通过一个地址来找到他
- 在互联网中,可以通过 URL 地址 找到想要的资源
- URL, Uniform Resource Locator,统一资源定位符
- 是一个字符串,用于表达互联网中某个资源的位置
1.示例
- 百度首页的 url 地址
- https://www.baidu.com/
- 某篇新闻页面的 url 地址
- https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_9610068257663826418%22%7D&n_type=-1&p_from=-1
- 某知名 css 的 url 地址
- https://meyerweb.com/eric/tools/css/reset/reset.css
- 某知名 js 的 url 地址
- https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js
- 某张图片的 url 地址
- https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic_source%2F84%2F87%2F80%2F848780a296b66b382018fa7f675ecd06.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652600873&t=803c81d81387ec5f9fd1d92ba9d7665a
2.特点
- URL 地址可以很长,也可以很短
- 通过 URL 地址可以找到互联网上的资源
- 可以是页面、图片、视频、音频、css 代码、js 代码、可供下载的文件,或者其他任何东西
- URL 中不能出现非 ASCII 字符
- 只能出现数字、英文字母、英文标点符号
- 非 ASCII 字符会自动编码
3.组成
- 一个完整的 URL 地址由多个部分组成
协议 + 主机 + 端口 + 路径 + 参数 + hash
1)协议 Protocal / Schema
- 表示客户端希望用什么方式和服务器沟通
- 一般为 http 或 https
- 如果在浏览器的地址栏省略了协议,浏览器会自动补全
- 可以在 Chrome 浏览器的地址栏点击右键,显示完整的地址
- https 协议比 http 协议更安全
- 往往出现在线上
- 本地的服务器通常不会是 https
2)主机 Host
- 表示客户端希望在哪台计算机上寻找资源
- 有两种写法:IP 地址和域名
- IP 地址
- IP 地址是一个网络中计算机的唯一编号
- 通常,一个 IP 对应一台计算机
- 域名
- 域名类似 IP 地址的别名,把不容易记忆的数字变为容易记忆的单词
- 当使用域名访问时,会自动转换为 IP 地址
记住
- 特殊 IP 地址:127.0.0.1 表示本机 IP
- 特殊域名:localhost 表示的 IP 地址是 127.0.0.1
3)端口 Port
- 表示客户端希望在哪个应用程序中寻找资源
- 每个服务器程序,都会监听一个或多个端口
- 只有找到对应的端口,才能找到这个服务器程序
- 端口号是可选的,若不填写:
- 如果使用的是 HTTP 协议,默认端口号为 80
- 如果使用的是 HTTPS 协议,默认端口号为 443
4)路径 Path
- 服务器上往往有许许多多的资源,每个资源都有自己的访问路径
- 路径是可选的,若不填写,则路径为
/
5)参数 Query / Param
- 某些资源可以根据需要呈现不同的内容
- 比如一篇新闻列表的页面,可以指定它呈现第几页的新闻
- 而 “第几页” 就属于一些额外信息,这些额外信息可以通过参数传递
- 比如,访问一个新闻列表的页面,同时希望它展示第 5 页,每页展示 10 条新闻
- 可能会得到的 url 地址:
http://duyiedu.com/news?page=1&limit=10
page=1&limit=10
就是参数部分,这部分可以包含多个参数- 不同的参数之间使用
&
分割
- 可能会得到的 url 地址:
- 参数是可选的
6)hash
- 在网络通信中,hash 没有什么用
- 往往作为浏览器的 锚链接 出现
(三)PostMan
(四)常见错误
1.跨域错误
- 这个错误通常发生在 AJAX 请求的时候,是一个跨域错误
1)跨域
- 浏览器为了安全,制定了一个规则
- 即 页面的源和请求目标的源应该保持一致
- 如果不一致,就产生了跨源或者叫跨域
相关信息
源 = 协议 + 主机 + 端口
页面源 | 目标源 | 是否跨域 |
---|---|---|
https://baidu.com/news.html | http://103.231.13.42/1.jpg | 是 |
https://www.baidu.com/news.html | http://baidu.com:8080/1.jpg | 是 |
https://baidu.com/news.html | https://baidu.com/1.jpg | 否 |
2)同源策略
- 浏览器对跨域行为作出的不同限制,统称为同源策略
- 浏览器对 img、link、script 的限制比较宽松,一般允许跨域
- 浏览器对 AJAX 比较严格,一般不允许跨域
- 如果在 AJAX 中出现跨域请求,就会报出以上错误
- 但如果服务器明确告知浏览器允许跨域,则浏览器会允许 AJAX 跨域请求
注意
http://127.0.0.1
和 http://localhost
不满足同源策略,会跨域
2.404 错误
- 浏览器请求某个资源,但服务器响应了一个 404 状态码,就会在控制台中报出这个错误
- 可以在浏览器的网络调试中进一步观察到这个错误
- 404 错误是一种非常常见的错误
- 表示服务器告诉客户端:你要的资源并不存在
- 要解决这个错误,首先要检查请求的 url 地址是否正确
- 如果 url 地址正确,则可能是服务器的问题,需要联系后端开发人员或者将问题上报
3.favicon
- 报错内容
- 加载资源失败:服务器响应了 404 状态码
- 请求地址
:5500/favicon.ico
- 原因
- 很多浏览器在解析页面后,如果发现页面中并没有使用
link
元素加载站点图标 - 会尝试请求以下地址来获取图标:
站点协议://站点主机:站点端口/favicon.ico
- 如果这个地址无法获得图标,就会报出相应错误
- 很多浏览器在解析页面后,如果发现页面中并没有使用
- 该错误会在下一次刷新后消失,是因为再次刷新后,浏览器记忆了之前无法获取图标的情况,就不再发出请求了(缓存)
4.其他问题
网络断开,检查你的网络连接,或者检查你是否在调试工具中进行了网络断开调试
访问的域名不存在,无法连接到服务器
(五)HTTP
- 通过 url 地址,能够在茫茫互联网中准确的找到想要的服务
- 但光找到服务还是不够,双方需要 用同一种语言 来对话,否则都听不懂对方在说什么
- 这个语言就是协议,而互联网中最常见的协议就是 HTTP 协议
- https 是在 http 协议基础上发展起来的,增加了 安全性 ,其他和 http 协议完全一致
- http 是基于 请求-响应 的方式完成通信的
- 每一次通信都是由客户端向服务器发出请求,传递一些消息过去,然后经过服务器程序处理后,响应给客户端一些消息
1.HTTP 协议规定
1)每次请求-响应都是 独立 的
- 相互之间互不干扰
- 这种模式的协议我们称之为 无状态协议/断开式连接
- http 的无状态会带来一些问题
2)每次请求-响应传递的消息都是 纯文本(字符串)
- 文本格式必须按照 http 协议规定的格式书写
2.请求的消息格式
- 请求消息格式由三部分组成
- 请求行:高度概括了客户端想要干什么
- 请求头:描述了请求的一些额外信息
- 请求体:包含了要给服务器传递的正文数据
- 请求体是可以省略的
3.请求行
- 请求行是整个 http 报文的第一行字符串
- 包含三个部分
- 请求方法
- 路径 + 参数 + hash
- 协议和版本
- 请求方法是一个单词,表达了客户端的动作
在 http 协议中,并没有规定只能使用 POST 和 GET 两种动作
甚至没有规定每种动作会带来怎样的变化
而在实际的应用中,逐渐有了一些约定俗成的规范
- GET(获取资源)
- POST(提交消息)
- PUT(修改数据)
- DELETE(删除数据)
- 其中,GET 和 POST 最为常见
注意
- GET 和 DELETE 请求不能有请求体
- POST 和 PUT 请求可以有请求体
- 浏览器遵循了上面的规范,这带来了 GET 和 POST 的诸多区别
- 比如,由于 GET 请求没有请求体,所以要传递数据只能把数据放到 url 的参数中
- 在浏览器中,获取数据一般使用的都是 GET 请求
- 在地址栏输入地址并按下回车
- 点击了某个 a 元素
- 获取图片、音频、视频
- 获取 css、js、字体等文件
- 事实上,浏览器自动发出的请求基本都是 GET 请求
- POST 请求需要开发者手动处理
- 比如在 form 表单中设置 method 为 POST
<form action="https://www.baidu.com" method="post"></form>
4.请求头 header
- 请求头是一系列的键值对,里面包含了诸多和业务无关的信息
- 浏览器每次请求服务器都会自动附带很多的请求头,其实这些请求头大部分服务器是不需要的
1)Host
- URL 地址中的主机
2)User-Agent
- 客户端的信息描述
3)Content-Type
- 请求体的消息是什么格式
- 如果没有请求体,这个字段无意义
- MIME,Multipurpose Internet Mail Extensions
- 标准格式的字符串
- 用于表达内容格式
值 | 含义 |
---|---|
application/x-www-form-urlencoded | 表示请求体的数据格式和 url 地址中参数的格式一样loginId=admin&loginPwd=123123 |
application/json | 表示请求体的数据是 json 格式{"loginId": "admin", "loginPwd": "123123"} |
multipart/form-data | 一种特殊的请求体格式 上传文件一般选择该格式 |
5.请求体 body
- 包含业务数据的字符串
- 理论上,请求体可以是任意格式的字符串
- 但习惯上,服务器普遍能识别以下格式
值 | 字符串 |
---|---|
application/x-www-form-urlencoded | 属性名=属性值&属性名=属性值... |
application/json | {"属性名":"属性值", "属性名":"属性值"} |
multipart/form-data | 使用某个随机字符串作为属性之间的分隔符 通常用于文件上传 |
- 由于请求体格式的多样性,服务器在分析请求体时可能无法知晓具体的格式,从而不知道如何解析请求体
- 因此,服务器往往要求在请求头中附带一个属性
Content-Type
来描述请求体使用的格式
Content-Type: application/x-www-form-urlencoded
Content-Type: application/json
Content-Type: multipart/form-data
6.响应的消息格式
- 服务器(通常由后端开发)收到请求的消息后,会运行后端代码对请求进行处理,处理完成后,会给予响应
- 服务器的响应格式包含三个部分
7.响应行
- 响应行是整个响应字符串的第一行
- 包含两个部分
- 协议版本
- 状态码、状态消息
1)协议版本
- 表示服务器打算和客户端用什么协议通信
2)状态码、状态消息
- 表示服务器对当前请求的表态
- 通常,状态码和状态消息是一一对应的
- 比如状态码 200 的消息就是 OK
- 不同的请求可能会得到不同的状态码
- 至于到底会得到哪个状态码,由后端程序决定
- 状态码分为五类
分类 | 分类描述 |
---|---|
1** | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作以完成请求 |
4** | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
通常认为
0~399 之间的状态码都是正常的,其他是不正常的
- 200 OK:一切正常
啥事没有
- 301 Moved Permanently:资源已被永久重定向
你的请求我收到了,但是呢,你要的东西不在这个地址了,我已经永远地把它移动到了一个新的地址,麻烦你去请求新的地址,地址我放到了响应头的 Location 中了
浏览器重定向后会缓存,下一次请求直接请求本次重定向后的地址(Location 中的值)
- 302 Found:资源已被临时重定向
你的请求我收到了,但是呢,你要的东西不在这个地址了,我临时地把它移动到了一个新的地址,麻烦你去请求新的地址,地址我放到了请求头的 Location 中了
浏览器不会缓存,下一次请求还是请求原地址,再根据状态码决定是否重定向
- 304 Not Modified:文档内容未被修改
你的请求我收到了,你要的东西跟之前是一样的,没有任何的变化,所以我就不给你结果了,你自己就用以前的吧。啥?你没有缓存以前的内容,关我啥事
- 400 Bad Request:语义有误,当前请求无法被服务器理解
你给我发的是个啥啊,我听都听不懂
- 403 Forbidden:服务器拒绝执行
你的请求我已收到,但是我就是不给你东西
- 404 Not Found:资源不存在
你的请求我已收到,但我没有你要的东西
- 500 Internal Server Error:服务器内部错误
你的请求我已收到,但这道题我不会,解不出来,先睡了
8.响应头 header
- 和请求头一样,响应头也是由很多个键值对组成的
- 具体有哪些键值对,完全取决于服务器程序
- 最重要的键值对是
Content-Type
- 有多种取值,表示响应体的数据类型
- 在 B/S 模式中,浏览器会自动根据响应头中
Content-Type
的取值,决定如何处理响应体
值 | 处理 |
---|---|
text/plain | 普通的纯文本 浏览器通常会将响应体原封不动的显示到页面上 |
text/html | html 文档 浏览器通常会将响应体作为页面进行渲染 |
text/javascript 或application/javascript | js 代码 浏览器通常会使用 JS 执行引擎将它解析执行 |
text/css | css 代码 浏览器会将它视为样式 |
image/jpeg | 图片资源 浏览器会将它视为 jpg 图片 |
attachment | 附件 浏览器看到这个类型,通常会触发下载功能 |
其他 MIME 类型 |
9.响应体 body
- 响应的主体内容
(六)关于 Apifox 的使用
- Apifox 是国内推出的一款类似于 postman 的接口测试工具
1.安装 Apifox
2.加入团队
3.测试接口
1)第一种:服务器是由渡一直接部署到了云服务器上面
- 直接发送请求就可以拿到数据
- 比如【网络课程接口文档】,能够直接在 Apifox 发送请求拿到数据
- 发送请求时直接输入地址即可
2)第二种:没有办法直接通过 Apifox 发请求拿到数据的
- 比如【博客】【个人空间】【织信人事管理系统】【Coder Station】
- 这些项目的服务器在对应的课件中是能够拿到的
- 需要在本地启动对应项目的服务器
- 针对这种类型的接口,如果没有启动本地的服务器
- 但是又想要快速的看一下响应对应有哪些字段
- 可以通过 mock 的形式
- 点击【快捷请求】后,对应的效果如下:
(七)浏览器页面处理流程
- 访问网址后显示的页面源代码和 postman 直接请求网址返回的数据是一致的
- 之所以能看到样式是因为浏览器进行了一系列的处理
- 因此浏览器又叫用户代理(User Agent)
1.服务器程序提供的服务
1)静态资源服务
- HTML 文档
- CSS 代码
- JS 代码
- 图片
- 字体文件
- 视频
- 音频
2)数据服务
- 接口
2.当在浏览器地址栏中输入一个 url 地址,并按下回车后,会发生什么?
3.相对路径 & 绝对路径
1)相对路径
- 相对的是 URL 地址(当前页面)
./
表示的是当前目录下(可省略)- 在加载
http://oss.duyiedu.com/test/index.html
时需要请求./css/index.css
- 实际上请求 CSS 文件的绝对地址是:
http://oss.duyiedu.com/test/css/index.css
- 实际上请求 CSS 文件的绝对地址是:
- 在加载
../
表示的是当前目录的上一级目录下- 在加载
http://oss.duyiedu.com/test/index.html
时需要请求../css/index.css
- 实际上请求 CSS 文件的绝对地址是:
http://oss.duyiedu.com/css/index.css
- 实际上请求 CSS 文件的绝对地址是:
- 在加载
2)绝对路径
- 完整的 URL 地址(与页面路径无关)
- 可省略协议
//oss.duyiedu.com/test/index.html
- 可省略协议、主机、端口号
/css/index.css
表示http://oss.duyiedu.com/css/index.css
相关信息
前端开发常用相对路径
(八)AJAX
- AJAX 就是浏览器赋予 JS 的一套 API
- 通过这套 API 能够使 JS 具备网络通信的能力
1.历史
1)诞生
- 浏览器本身就具备网络通信的能力,但在早期,浏览器并没有把这个能力开放给 JS
- 最早是微软在 IE 浏览器中把这一能力向 JS 开放,让 JS 可以在代码中实现发送请求
- 这项技术在 2005 年被正式命名为 AJAX,Asynchronous Javascript And XML
2)成形
- IE 使用了一套 API 来完成请求的发送
- 这套 API 主要依靠一个构造函数完成
- 该构造函数的名称为
XMLHttpRequest
- 简称为
XHR
,所以这套 API 又称之为XHR API
- 简称为
3)进阶
- 由于
XHR API
有着诸多缺陷,在 HTML5 和 ES6 发布之后,产生了一套更完善的 API 来发送请求 - 这套 API 主要使用的是一个叫做
fetch
的函数- 因此这套 API 又称之为
Fetch API
- 因此这套 API 又称之为
相关信息
无论是 XHR 还是 Fetch,都是实现 ajax 的技术手段,只是 API 不同
2.XHR API
1)xhr.readyState
- 一个数字
- 用于判断请求到了哪个阶段
值 | 含义 |
---|---|
0 | 刚刚创建好了请求对象 但还未配置请求(未调用 open 方法) |
1 | open 方法已被调用 |
2 | send 方法已被调用 |
3 | 正在接收服务器的响应消息体 |
4 | 服务器响应的所有内容均已接收完毕 |
2)xhr.responseText
- 获取服务器响应的消息体文本
- 需要转换为 JSON 格式
JSON.parse(xhr.responseText)
// 创建发送请求的对象
var xhr = new XMLHttpRequest();
// 当请求状态发生改变时运行的函数,会运行多次直到readyState === 4
xhr.onreadystatechange = function () {
// 获取响应头Content-Type
xhr.getResponseHeader("Content-Type");
};
// 配置请求
xhr.open("请求方法", "url地址");
// 设置请求头
xhr.setRequestHeader("Content-Type", "application/json");
// 构建请求体,发送到服务器,如果没有请求体,传递null
xhr.send("请求体内容");
3.Fetch API
- fetch 会返回一个 Promise
- 该 Promise 会在接收完响应头后变为 fulfilled
const resp = await fetch("url地址", {
// 请求配置对象,可省略,省略则所有配置为默认值
// 默认为GET
method: "请求方法",
// 请求头配置
headers: {
"Content-Type": "application/json",
a: "abc",
},
// 请求体
body: "请求体内容",
});
// 获取响应头对象
resp.headers;
// 获取响应状态码,例如200
resp.status;
// 获取响应状态码文本,例如OK
resp.statusText;
// 用json的格式解析即将到来的响应体,返回Promise,解析完成后得到一个对象
resp.json();
// 用纯文本的格式解析即将到来的响应体,返回Promise,解析完成后得到一个字符串
resp.text();
fetch("xxx")
.then((resp) => {
console.log("这里可以拿到响应头");
return resp.json();
})
.then((resp) => {
console.log("这里才能拿到响应体");
});
特别注意
无论使用哪一种 API,AJAX 始终都是异步的
在初学的时候,可以把网络传输的时间想象的夸张一点
比如每一次请求和响应都要经过一年才能完成
这样有助于理解网络是异步这一点
(九)案例实操:聊天机器人
1.效果访问地址
2.test.http
1)接口
- POST
- http://study.duyiedu.com/api/user/login
- HTTP/1.1
2)请求头
- content-type: application/json
{
"loginId": "Sutee",
"loginPwd": "sutee"
}
3.表单验证
1)表单校验规则类
/**
* 表单项验证类
* 用户登录和注册页面的表单验证
*/
class FieldValidator {
/**
* 构造器
* @param {String} inputId 当前校验的表单项的id
* @param {Function} validator 验证规则函数,当需要校验时调用
*/
constructor(inputId, validator) {
this.input = $(`#${inputId}`);
this.validator = validator;
this.p = this.input.nextElementSibling;
this.input.onblur = () => {
this.validate();
};
}
/**
* 校验函数
* @returns 校验结果,成功返回true,失败返回false
*/
async validate() {
const err = await this.validator(this.input.value);
this.p.innerHTML = err || "";
return !err;
}
/**
* 静态方法,校验所有验证对象
* @param {FieldValidator[]} validators 验证对象数组
* @returns 所有验证对象的校验函数都返回true时返回true,有一个false则为false
*/
static async validateAll(...validators) {
const promises = validators.map((v) => v.validate());
const res = await Promise.all(promises);
return res.every((r) => r);
}
}
2)表单校验对象
const loginIdValidator = new FieldValidator("txtLoginId", async (val) => {
// 是否为空
if (!val) {
return "请输入账号";
}
});
const loginPwdValidator = new FieldValidator("txtLoginPwd", async (val) => {
// 是否为空
if (!val) {
return "请输入密码";
}
});
const signInForm = $(".user-form");
signInForm.onsubmit = async (e) => {
e.preventDefault();
const result = await FieldValidator.validateAll(loginIdValidator, loginPwdValidator);
// 校验失败
if (!result) return;
// 登录
const formData = new FormData(signInForm);
const userInfo = Object.fromEntries(formData.entries());
const resp = await API.signIn(userInfo);
if (resp.code === 0) {
alert("登录成功!欢迎您!");
location.href = "index.html";
} else {
loginIdValidator.p.innerText = "登录失败,请检查账号或密码" + resp.msg;
loginPwdValidator.input.value = "";
}
};
4.首页身份验证
- 访问首页时必须确保当前用户已登录
1)错误方式一:登录时保存全局变量
- 登录后跳转首页将刷新页面
- Ajax 不能跨 JS 发送请求
- 如果将已登录的状态保存为全局变量,跳转首页后获取不到保存的变量
2)错误方式二:判断 localStorage
- 可以初步实现进入首页时校验用户身份
- 但是 localStorage 的值可以人为修改
3)正确方式:调用接口
- 直接使用 localStorage 保存的 token 调用获取用户信息的接口
- 无论是否有 token、token 是否过期都可以根据接口返回的数据对象判断
const init = async () => {
// 如果当前未登录,则返回登录页面
const res = await API.getUser();
const user = res.data;
if (!user) {
alert(res.msg);
location.href = "login.html";
return;
}
};
5.渲染用户信息
- 永远不要信任用户的输入信息
- 如果账号和昵称携带了 HTML 标签
- 如:
<script>alert(1)</script>
- 此时使用
innerHTML
会使用户输入的“代码”被页面执行
- 如:
const setUserInfo = (user) => {
if (user) {
doms.aside.nickname.innerText = user.nickname;
doms.aside.loginId.innerText = user.loginId;
}
};
6.时间格式化
1690614843741
=>2023-07-29 15:14:03
// 格式化时间前置0
const padStartZero = (time) => time.toString().padStart(2, "0");
// 格式化日期
const formateDate = (timestamp) => {
const date = new Date(timestamp);
return `${date.getFullYear()}-${padStartZero(date.getMonth() + 1)}-${date.getDate()} ${padStartZero(date.getHours())}:${padStartZero(
date.getMinutes(),
)}:${padStartZero(date.getSeconds())}`;
};