目录
客户端和服务器
客户端和服务器是什么
- 在网络中,两个应用程序之间会发生通信,在绝大多数的情况下,这个通信总是由一发发出一个消息开始,另一方回复一个消息结束
- 发出消息的一方为
客户端(Client)
,发出消息的过程称之为请求(Request)
- 回复消息的一方为
服务器(Server)
,回复消息的过程称之为响应(Response)
- 因此在一次通信中,谁发出消息谁就是客户端,反之则是服务器,当然,大部分情况下,发出消息的一方都是
浏览器
、
扩展
- 在我们软件开发中,不管是客户端还是服务器都只是一个
应用程序
,也包括数据库,数据库本质也只是一个持久化存储的软件
, - 客户端和服务器可以在不同的计算机上,也可以在同一台计算机上
- 客户端和服务端的交互模式中我们称之为
C/S
结构,但是如果在这个交互中,客户端是浏览器
,我们称之为B/S
结构 - 服务器往往是在互联网的产品提供服务,所以我们一般把服务器称之为
web服务器
URL
什么是 URL
-
了解了客户端和服务器之后,我们可以来思考一件事情,这个客户端和服务器是在交互,但是客户端怎么就可以找到服务器呢?
-
在生活中,我们网购下单的时候就相当于一次请求,快递发到家里就是一次响应,那么快递是通过什么找到你家的,是不是就是我们填写的地址啊,比如:xx省xx市xx区xx小区xx单元xx号
-
那么在客户端和服务器的一次通信当中呢,也存在着这样的一个地址,就是
URL 地址
,全称是(Uniform Resource Locator)
,也叫作全球资源定位器
-
URL 是一种标准格式的字符串,通过这个字符串就可以找到资源在互联网上的地址,比如:音乐、图片、文件、视频等等
-
一个完整的 URL 地址组成部分如下:
URL = 协议 + 主机 + 端口 + 路径 + 参数 + hash
-
示例:
http://www.coderjc.com:8080/api/books?page=0&offset=8#main - http【协议】 - www.coderjc.com:8080【主机】 - /api/books【路径】 - page=0&offset=8【参数】 - main【hash】
协议
- 表示客户端和服务器之间使用什么语言沟通,就比如间谍和上线交流时的密语,传递什么信息都按照我们所规定的密语表示即可
主机
- 主机表示客户端会在那一台计算机上寻找对应的资源,一般有两种表现方式,
IP地址
和域名
- IP地址:IP 地址是计算机在网络中唯一编号,类似我们的身份证号码
- 域名:将难以记忆的IP地址数字转为了更容易记忆的英文单词,在访问这个域名的时候会自动被转为IP地址
端口
- 表示客户端会在这台计算机上的那个应用程序中查找资源,类似于一家酒店就是主机,而在酒店中的各个房间中,存放这你需要获取的资源,来到酒店中,根据对应的门牌号找到对应的房间
- 端口号是可选的,如果不选择,在 http 协议默认为 80,https 协议下默认端口号为 443
路径
- 而在一个房间中,往往可能不止存放这一样资源,可能有许多的资源,那么这时候你可能就需要一个路径指引你,比如告诉你在某个柜子下的第几层第几个抽屉里面
- 路径也是可以选择的,如果不填写默认为 /
参数
- 找到对应位置之后,你又会面临一项选择,是全部拿走呢还是拿走一部分,还是拿走指定的一个呢?这个参数就是决定这件事的
hash
- 在网络的通信中,hash 并不会起到什么作用,常见于描点链接
HTTP
- 前面我们提到过,协议就是一种规则,在规则内进行通信,而 http 就是一种协议,这里可以提一点,https 对比 http 是增加了安全性,其余的地方和 http 别无二致
- http 是基于
请求 - 响应
的方式完成通信,每一次通信都是由客户端向服务器发出请求,然后服务器处理请求并响应给客户端请求的结果 - 每次 请求-响应,也就是一次完整的通信都是
独立的
,相互之间互不干扰,因此我们也称之为无状态协议
,这种情况我们就可以举例说明一下,比如,你现在需要进去一个地方,但是要进去这个地方你每次都需要获取一张令牌,且一次只能携带一张,那么第一次你拿着令牌进去了,令牌收走后,你出来之后发现你掉了东西在里面,你就和守卫说,我刚刚进去的,我掉了东西在里面,我需要回去拿,那不好意思,只认令牌不认人,你在想进来就必须再一次获取令牌才能进去,这样的通信方式即为无状态
,每一次通信都互不干扰 - 当然这种方式肯定是存在了弊端,后面我们在拿说一下具体是什么弊端,http 又对他做了什么解决的方案
- 每次通信传递的消息都是
纯文本信息(字符串)
,而文本格式必须参照 http 协议的规范
请求消息格式
- 请求的消息格式大体可以分为三部分:
- 请求行:表名本次的请求的来意
- 请求头:描述了请求的额外信息
- 请求体:包含本次请求携带的数据(请求体是可以省略的,因为有时候请求并需要携带额外的参数,当然这个并不包含路径?后的参数)
请求行
-
是整个 http 报文的第一行字符串,它包含了三个部分:协议和协议的版本 + 请求方法(GET、POST、DELETE…) + 路径(/api/info) + 参数
-
在这里我们需要来仔细说一下
请求方法
,首先说 GET 请求,相信大家都不陌生吧,一般我们获取数据就会使用 GET 方法,提交数据就会使用 POST 方法,修改数据就是 PATCH 方法,删除数据就是 DELETE 方法 -
但是在 http 协议中,其实并没有规定每个请求方式必须应该怎么做,也就表示你可以用 GET 方法去提交数据,也可以使用 POST 方法获取数据,都是可以的
-
只不过在实际开发中,我们有一些约定俗成的规范:
-
GET(获取数据) POST(提交数据) DELETE(删除数据) PATCH(修改数据)
-
GET 和 DELETE 方法
不会携带请求体
,而 POST 和 PATCH 方法可以有请求体
-
也正是因为这些约定俗成的规范,从而
导致了 GET 和 POST 方法的一系列的区别
:- GET 方法没有请求体,所以会把参数卸载 URL 地址的参数中,这种
携带参数
的区别 - 对参数的数据类型,GET 只接受
ASCII
字符,而 POST 没有限制 - GET 比 POST 更不安全,因为参数
直接暴露在 URL 上
,所以不能用来传递敏感信息 - GET 请求参数会被完整保留在
浏览器历史记录里
,而 POST 中的参数不会被保留
- GET 请求在 URL 中传送的参数是
有长度限制的
,而 POST 没有 - GET 在浏览器回退时是无害的,而 POST 会再次提交请求
- 等等…
- GET 方法没有请求体,所以会把参数卸载 URL 地址的参数中,这种
-
请求头
-
请求头就是一系列的
键值对
,里面包含很多与本次业务无关的信息,浏览器在每次请求服务器时都会携带很多无关的信息,这个我们不用关心。我们只需要关心其中几个请求头即可: -
Host
: url 地址中的主机(IP地址
) -
User-Agent
:客户端的信息描述(比如是win10,或者mac,或者安卓等...
) -
Content-type
:本次请求体
的是什么格式的消息,当然如果没有请求体这个字段就没有什么意义,一般它会有常见的几种值-
application/x-www-form-urlencoded(
早期比较流行,现在已经使用的很少了
)表示请求体的数据格式和 URL 地址中的参数格式一样,如:name=zs&age=18,生Form表单,如果不设置 enctype 属性,默认为 application/x-www-form-urlencoded 方式提交数据。比如我们给一个 input 的 name 属性设置为 username,输入值 zhangsan ,最后请求提交时 username 就是键,zhangsan 就是值
-
application/json(
现在比较常见的传递文本数据的方式
)表示请求体的格式是 JSON 格式,如:{ name: “zs”, age: 18 }
-
multipart/form-data(
现在比较常见的文件上传消息格式
)一般表示文件上传,当然也是可以携带文本信息的,不过一般用于文件上传格式
-
binary
顾名思义,是一种二进制的数据格式
-
application/octet-stream
二进制数据流格式,告知浏览器这是一个字节流,浏览器处理字节流的默认方式就是下载
-
请求体
-
包含本次请求提交的字符串信息,理论上,这个请求体是可以任意形式的字符串,不过一般情况下,我们都是使用上述提前的前三种类型的字符串,也正是由于请求体的格式的类型如此之多,服务器可能无法正确识别本次消息传递的消息格式,因此我们请求时往往会附带一个 Conten-type 属性,表示本次请求体的消息格式是什么
-
我们可以来看一下图解实例,如图:
响应消息格式
- 响应的消息与请求的相信格式相差不多,也分为三部分组成
响应行
-
包含两个部分:
-
协议版本:表示服务器和客户端之间通信的协议版本
-
状态码、状态消息:表示服务器对当前请求的一个结果状态,是失败还是成功还是未找到等等
-
这里的话我们提一下状态码,详细的状态码代表的意思大家可以在 KOA 文档中找到:https://github.com/guo-yu/koa-guide
-
一般分为以下五类:
状态码分类 | 分类描述 |
---|---|
1** | 服务器收到信息,需要客户端继续执行 |
2** | 成功,操作被成功的处理 |
3** | 重定向 |
4** | 客户端错误,常见的有 404 |
5** | 服务器错误 |
-
200
:请求一切正常 -
301
:你此次请求的资源地址不存在了,并且一般会把新地址放入请求头中的 Location 中,资源被搬迁到另外一个地址了,也就是迁户口 -
302
:你此次请求的资源临时不在了,并且一般会把新地址放入请求头中的 Location 中,以后可能还会回来的,即短暂的离家 -
304
: 文档内容没有变化,表示你上次请求的结果与本次是一样的,不在返回结果了 -
400
:当前请求无法被服务器处理,就比如一个外国人找上门来,但是和你噼里啪啦的说了一大堆,完全听不懂 -
403
:服务器拒绝执行本次请求,流浪汉向你发出了救济请求,你给了一个白眼走了 -
404
:资源不存在了,也就是人家寻亲寻到了你家,但是他的亲戚也是之前这个地方的主人已经搬走了,不在了 -
500
:服务器内部错误,事不关己高高挂起,此时你应该去找后端打嘴炮 -
Tips:不过在浏览器中,302 304 一般会自动帮助我们跳转到新地址
-
当然现在也有一种常见的做法就是,无论什么都返回 200,错误放在响应体当中
响应头
- 和请求头一样,响应头也是多个
键值对
组成的,具体由那些,取决于服务器的设置,当然最值得我们注意的依然是Content-type
,它的类型表示了响应体的数据类型 - 在 B/S 模式中,浏览器会自动根据 Content-type 的值来决定如何处理响应体
text/plain
:普通的纯文本text/html
:html 文档,浏览器会自动当做页面渲染text/javascript
:JavaScript 文件,浏览器会自动使用 JS 引擎解析text/css
:css 文件image/jpeg
:表示是一个图片,图片类型是 jpeg,也可以是 png、jpg等等attachment
:浏览器发现此类型会自动触发下载功能- 等等…
响应体
- 携带了本次响应的响应结果,通常我们获取的数据就是在这里面
AJAX
什么是 AJAX
AJAX是指在 web 程序中异步向服务器发送请求
- AJAX 是浏览器提供给 JavaScript 的一套 api ,JavaScript 就可以通过这些 api 来发送网络请求
- 大家也注意到了是吧,我说的是浏览器提供给 JavaScript,也就是 JavaScript 本身不具备发送网络请求通信的能力,是调用浏览器提供的 api 才可以完成的
- 这开发的 api 也拥有一个专有的技术名词,即 AJAX
- 在实现 AJAX 之初,我们使用的是
XHR(XMLHttpRequest)
,XMLHttpRequest 是一个构造函数,不过随着时间的推移,大家发现了 xhr 的一些缺陷,于是诞生了另外一个构造函数 fetch
- 但是不管是 xhr 还是 fetch 都只是实现 AJAX 的方法。代表的并不是 AJAX 本身
xhr
-
xhr 本质只是一套 api 的使用方法,本身就不复杂,所以大家也不要对这个东西有恐惧感,我们就简单的使用一下 xhr
// 1、创建 xhr 实例对象 const xhr = new XMLHttpRequest() // 2、当请求状态发生改变时会运行的函数 xhr.onreadystatechange = function () { console.log('触发~') } // 3、配置请求 xhr.open('GET', 'http://localhost:5000/api/moment/infos?offset=0&size=5') // 4、设置请求头-在这里我们是 get 请求,无需配置 // xhr.setRequestHeader('Content-type', 'application/json') // 5、配置请求体,发送到服务器,如果没有请求体,传递一个 null 即可 xhr.send(null)
-
在上面可与看到,使用非常简单,我们看一下请求是否成功呢?如图:
-
除了第二步,其余大家可能都好理解,就是字面意思的 api,第二步是什么意思呢?这个请求字体发生改变指的有啥什么呢
- 1:open 方法被调用
- 2:send 方法被调用
- 3:正在接收服务器的响应消息体
- 4:服务器的所有内容均已接收完毕
-
现在问题又来了,那我怎么知道他因为什么被调用或触发的呢?上面的 1 2 3 4 数字就表明了它是在什么阶段触发的,怎么获得这个数字呢?在 xhr 中有一个 api 是
xhr.readyState
,通过这个我们可以知道数字状态,如图:
-
不过这些我们一般不是很关心,它什么时候调用对我来说不重要,重要的是不是我需要获取响应的结果啊,在上面我们通过浏览器的预览看到了成功返回的结果,如果想在代码中获取它,我们需要一个 api,
xhr.responseText
,代码如下:xhr.onreadystatechange = function () { // 获取状态 // console.log(xhr.readyState) // 获取响应的响应体文本 if (xhr.readyState === 4) { console.log(xhr.responseText) // 不过字符串一般不利于我们查看,所以会转为 json console.log(JSON.parse(xhr.responseText)) } }
-
如果你需要获取某个响应头,比如获取 Content-Type ,可以利用
xhr.getResponseHeader('Content-Type')
,如下:xhr.onreadystatechange = function () { if (xhr.readyState === 4) { // 获取某个响应头信息 console.log(xhr.getResponseHeader('Content-Type')) } }
-
api 的基本介绍就到这里了,xhr 只是属于一个 api 的调用,并不涉及很多复杂的东西,还有更多的使用方式大家自行查阅文档即可
fetch
-
在这里我们也简单演练一下 fetch,fetch 是返回一个 promise 对象,也就意味着我们可直接使用 async 和 await 来进行优化
-
fetch 也比 xhr 会简单很多,为什么这么说呢,上面我们发送一个请求看起来是不是很繁琐,我们使用 fetch 来改造一下看看,如下:
fetch('http://localhost:5000/api/moment/infos?offset=0&size=5')
-
我们看一下结果,如图:
-
怎么样,这个代码的对比量是不是一目了然啊,不过也正常,毕竟后浪推前浪嘛,fetch 默认是 get 请求,所以如果是 get 请求可以不需要填写
-
那如何获取返回的响应结果呢?前面我们提到,它返回的是一个 promise,那么就可以通过 then 方法来获取,如下:
fetch('http://localhost:5000/api/moment/infos?offset=0&size=5').then(res => { console.log(res) })
-
那是不是真都可以获取到我们需要的响应体呢?如图:
-
好像并没有发现响应体啊,这是为什么呢?因为当服务器收到请求之后,返回一个响应头,当收到这个响应头的时候,promise 就完成了,此时调用 then 方法获取的是一个响应对象,并不包含响应体,如果我们需要获取响应体,可以使用
json
或text
两个 api,从字面意思就不难看出,两者的区别无非就是格式上的区别,一个 json 格式,一个纯文本格式 -
在这里,通过json 获取响应体时,它返回的也是一个 promise 对象,因此可以再次使用 then 方法获取最终的数据,如下:
fetch('http://localhost:5000/api/moment/infos?offset=0&size=5').then(res => { res.json().then(res => { console.log(res) }) })
-
输出结果如下:
-
json 和 text 使用没有区别,返回结果格式不同而已
-
了解了基本的使用之后,那我们在来看一下 fetch 比较完整的写法,如下:
const user = { username: 'coder', password: '123456' } const url = 'http://127.0.0.1:5000/api/users/register' fetch(url, { method: 'POST', // 请求方法 body: JSON.stringify(user), // 请求体 headers: { 'Content-Type': 'application/json' // 请求头 } }) .then(res => res.json()) .then(res => { console.log(res) })
-
来看一下响应结果,如图:
结语
相信看完前面的介绍,在看后面 xhr 和 fetch 你都会对这些 api 有着更清晰的认知,不过伴随通信的出现,往往会带来另外一个问题,那就是跨域。关于跨域,有兴趣的可以查看我的另一篇博客:跨域及其解决方案-CORS-JSONP-代理,