十一、网络

郁子大约 21 分钟约 6182 字笔记渡一教育语言基础袁进HTTPAJAX

对于前端而言,网络部分代码不多,以概念为主


对概念的理解程度,决定了是否能够看懂接口文档,同时也决定了是否能更好的掌控网络相关代码

(一)客户端和服务器

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 就是参数部分,这部分可以包含多个参数
    • 不同的参数之间使用 & 分割
  • 参数是可选的

6)hash

  • 在网络通信中,hash 没有什么用
  • 往往作为浏览器的 锚链接 出现

(三)PostMan

(四)常见错误

1.跨域错误

  • 这个错误通常发生在 AJAX 请求的时候,是一个跨域错误

1)跨域

  • 浏览器为了安全,制定了一个规则
    • 页面的源和请求目标的源应该保持一致
    • 如果不一致,就产生了跨源或者叫跨域

相关信息

源 = 协议 + 主机 + 端口

页面源目标源是否跨域
https://baidu.com/news.htmlhttp://103.231.13.42/1.jpg
https://www.baidu.com/news.htmlhttp://baidu.com:8080/1.jpg
https://baidu.com/news.htmlhttps://baidu.com/1.jpg

2)同源策略

  • 浏览器对跨域行为作出的不同限制,统称为同源策略
    • 浏览器对 img、link、script 的限制比较宽松,一般允许跨域
    • 浏览器对 AJAX 比较严格,一般不允许跨域
  • 如果在 AJAX 中出现跨域请求,就会报出以上错误
  • 但如果服务器明确告知浏览器允许跨域,则浏览器会允许 AJAX 跨域请求

注意

http://127.0.0.1http://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/htmlhtml 文档
浏览器通常会将响应体作为页面进行渲染
text/javascript
application/javascript
js 代码
浏览器通常会使用 JS 执行引擎将它解析执行
text/csscss 代码
浏览器会将它视为样式
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 地址,并按下回车后,会发生什么?

试试这个地址:oss.duyiedu.com/test/index.html

3.相对路径 & 绝对路径

1)相对路径

  • 相对的是 URL 地址(当前页面)
  • ./ 表示的是当前目录下(可省略)
    • 在加载 http://oss.duyiedu.com/test/index.html 时需要请求 ./css/index.css
      • 实际上请求 CSS 文件的绝对地址是:http://oss.duyiedu.com/test/css/index.css
  • ../ 表示的是当前目录的上一级目录下
    • 在加载 http://oss.duyiedu.com/test/index.html 时需要请求 ../css/index.css
      • 实际上请求 CSS 文件的绝对地址是:http://oss.duyiedu.com/css/index.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

相关信息

无论是 XHR 还是 Fetch,都是实现 ajax 的技术手段,只是 API 不同

2.XHR API

1)xhr.readyState

  • 一个数字
  • 用于判断请求到了哪个阶段
含义
0刚刚创建好了请求对象
但还未配置请求(未调用 open 方法)
1open 方法已被调用
2send 方法已被调用
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())}`;
};
上次编辑于: