计算机网络

网络(三):应用层HTTP

2021-10-25  本文已影响0人  意一ineyee

目录

一、字符编码和各个码表
二、HTTP
 1、HTTP的数据格式
  1.1 HTTP请求的数据格式 = 请求行 + 请求头 + 请求体
  1.2 HTTP响应的数据格式 = 响应行 + 响应头 + 响应体
 2、请求行和响应行
  2.1 HTTP请求的方法
  2.2 HTTP请求的URI
  2.3 HTTP的版本
  2.4 HTTP响应的状态码
 3、请求头和响应头
  3.1 常见的请求头字段
  3.2 常见的响应头字段
 4、请求体和响应体
  4.1 请求体编码/响应体编码
  4.2 请求体格式/响应体格式
三、HTTP的安全问题

网络(一):基础知识
网络(二):传输层TCP、UDP
网络(三):应用层HTTP
网络(四):应用层HTTPS

一、字符编码和各个码表


首先了解一下字符编码和各个码表,因为HTTP里有URL编码和请求体编码/响应体编码这两个东西要用到编码的知识。

计算机起源于美国,它们只能存储二进制的0和1,也就是说只能存储数字,那它们是怎么存储英文的呢?

美国国家标准局推出了ASCII编码——用一个特定的数字来代表一个特定的字母,并且强制规定这个数字占一个字节。这样字母和数字一一对应的关系就形成了一张表——ASCII码表,考虑到实际使用,这张表里又塞进了阿拉伯数字和英文的标点符号,此外还塞进了一些特殊的控制字符,总之英文模式下键盘上能敲出来的东西都直接对应在这张表里了,总共也就128个。

计算机在存储英文时,会采用ASCII编码把一个一个的字符编码成对应的数字存储起来。而计算机在显示英文时,又会把一个字节一个字节的数字解码成字符显示出来,因为在ASCII编码和ASCII码表系统下,一个字符占一个字节。

后来世界各地都开始使用计算机,很多国家使用的不是英文,ASCII编码和ASCII码表根本不能满足他们的需求,比如我们中国就有6000多个常用的汉字,那计算机是怎么存储中文的呢?

国家标准局推出了GB2312编码——也是用一个特定的数字来代表一个特定的汉字,只不过强制规定这个数字占两个字节。这样汉字和数字一一对应的关系就形成了一张表——GB2312码表,对于ASCII码表里的字符,保持其原编码不变,只是将长度由原来的一个字节扩展为两个字节,GB2312码表可以说是ASCII码表的中文扩展集。

计算机在存储中文时,会采用GB2312编码把一个一个的汉字编码成对应的数字存储起来。而计算机在显示中文时,又会把两个字节两个字节的数字解码成汉字显示出来,因为在GB2312编码和GB2312码表系统下,一个字符占两个字节。

互联网发展之初很多国家都有一套自己的编码和码表,你不能识别我,我也不能识别你,你的电脑要想显示中文文件就得给电脑装一个“中文编码和码表系统”,你的电脑要还想显示俄语文件就还得给电脑装一个“俄语编码和码表系统”等等,一旦装错了字符系统,显示就会出现乱码,怎么解决这个问题呢?

国际标准化组织ISO推出了Unicode码表——一个包含了世界上所有国家字符的码表,同样地在这个码表里任何一个字符都唯一对应着一个数字,它是一个很庞大的集合,现在的规模可达100多万。

电脑上只要装一个Unicode字符系统,无论想显示那种语言的文件,只要约定好在存储文件的时候存储成特定编码方式的文件就好了,其它电脑就能正常解码并显示。

但需要注意的是,Unicode码表只强制规定了哪个数字代表哪个字符,却没有强制规定这个数字占几个字节(上面ASCII码表和GB2312码表不仅强制规定了哪个数字代表哪个字符,而且还强制规定了这个数字占一个字节或者两个字节),这是因为Unicode码表过于庞大,很多排在后面的字符需要用三个字节的数字才能代表,那如果Unicode码表强制规定这个数字占三个字节,这势必会造成存储空间的极大浪费,比如一个英文字母本来只需要一个字节就能存储得下了,现在却要在前面两个字节里全部填0,文本文件也因此大出两三倍。于是就衍生出了针对Unicode码表的众多编码方式:UTF-8编码、UTF-16编码(两个字节或四个字节)、UTF-32编码(四个字节)等来规定这个数字占几个字节。其中UTF-8编码是目前使用最广泛的,它最大的特点就是可变长编码,会根据不同的情况用不同字节的数字来代表字符,比如当遇到英文字母时就还是一个字节、和ASCII码表一模一样,当遇到中文汉字时就是三个字节等等。因此在UTF-8编码和Unicode码表系统下,一个字符占几个字节是不固定的,一个字母占一个字节,一个汉字占三个字节,但“具体占几个字节”这个信息也会被编码存储在这个数字里,以便后续解码时能知道到底应该把几个字节的数据连续在一起解码。

二、HTTP


HTTP(Hyper Text Transfer Protocol),超文本传输协议,是互联网中应用最广泛的应用层协议之一。设计HTTP最初的目的就是用来传输HTML文本的、就是为了能让用户浏览网页(比如浏览器发送一个HTTP请求,服务器就返回一串HTML文本,然后浏览器把HTML文本解析、渲染成一个网页展示给用户),只不过后来HTTP能传输的数据类型非常广泛了(比如还能传输图片、音频、视频等),不仅仅是HTML文本。

HTML(Hyper Text Markup Language),超文本标记语言,用来编写网页。所谓超文本是指HTML文本能表达超出文本之外的内容(比如<a href="test.html">点击跳转到测试页面</a>这个HTML文本,它除了能表达“点击跳转到测试页面”这个普通字符串文本之外,它还表达了这是个超链接、可以点击跳转到测试页面);所谓标记语言是指HTML文本是由一个一个的标签(比如<head><body>等)组成的。

1、HTTP的数据格式

我本地服务器上有一串HTML文本如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
    <form action="/hw/login" method="post">
        <div>Username:<input name="username"></div>
        <div>Password:<input name="password"></div>
        <button type="submit">Login</button>
    </form>
</body>
</html>

可以通过URLhttp://localhost:8080/hw/html/login.html请求到,浏览器加载出来的效果如下:

现在我们就在浏览器里请求一下这个URL,并用Wireshark抓一下数据看看。

选中HTTP请求或者HTTP响应,右键选择追踪流,再选择追踪HTTP流,就能看到这一次HTTP请求和HTTP响应的数据了。

双击HTTP请求和HTTP响应,更是可以看到这一次HTTP请求和HTTP响应的二进制流(任何数据最终都是转换成二进制的0和1进行存储和传输的)。

1.1 HTTP请求的数据格式 = 请求行 + 请求头 + 请求体

我们拿上面例子中HTTP请求的数据来套一下看看对不对:

补充:回车换行

回车符(return):它的本意是回到当前行的开头。
换行符(newline):它的本意是下移一行。

Windows系统保留了最原始的做法,每行的结尾都会有一个回车符 + 一个换行符,文本编辑器只要见到这两个字符就会先回到当前行的开头、然后再下移一行显示,也就是我们口头上常说的换行。但是为了节省内存,Mac系统设计一个回车符的功能就相当于Windows系统一个回车符 + 一个换行符的功能,所以Mac系统每行的结尾只有一个回车符,文本编辑器只要见到回车符就换行;同理Unix系统每行的结尾只有一个换行符,文本编辑器只要见到换行符就换行。为了兼容各个系统,HTTP要求使用最原始的做法一个回车符 + 一个换行符来代表换行。

1.2 HTTP响应的数据格式 = 响应行 + 响应头 + 响应体

同样我们拿上面例子中HTTP响应的数据来套一下看看对不对:

2、请求行和响应行

2.1 HTTP请求的方法

HTTP请求的方法一共有九个:

2.2 HTTP请求的URI
2.2.1 URI和URL

URI(Uniform Resource Identifier),统一资源标识符,用于唯一标识一个资源。比如/hw/html/login.html可以唯一标识我本地服务器上的一个HTML资源,它就是一个URI,但是别人也可能使用/hw/html/login.html来唯一标识他们服务器上的一个HTML资源。

URL(Uniform Resource Locator),统一资源定位符,用于唯一定位一个资源。比如http://localhost:8080/hw/html/login.html可以唯一定位我本地服务器上的一个HTML资源,它就是一个URL,我们可以通过URL直接找到某个资源。

因为URL能唯一定位一个资源,所以它肯定也能唯一标识一个资源,因此所有的URL都是URI,但并非所有的URI都是URL,即URL是URI的子集。URI和URL的主要区别就是URL是全网唯一的,我们可以通过URL直接找到某个资源,而非URL的URI不是全网唯一的,它必须得配合协议、IP地址、端口号才能找到某个资源。

2.2.2 URL编码

国际标准化组织ISO强制规定URL里只能出现:

其它任何字符都必须经过编码后才能出现在URL里,比如中文,又比如/?之外的保留字符:#[]@!$&'()*+,;=

我们客户端开发人员可以采用任意编码方式对URL编码,比如ASCII编码、GB2312编码等,但是如果决定采用这些编码方式,就必须先跟服务端开发人员约定好,以便他们能够正确对URL解码。

但是实际开发中我们很少写对URL编码的代码,这是因为实际的URL(比如http://localhost:8080/hw/html/login)都被服务端开发人员规范成了合法字符;而GET请求传给服务器的一堆入参(比如username=123&password=4 5%6)是放在URL里,虽说有可能出现非法字符,但是它们也都被网络三方库内部默认采用UTF-8编码给编码掉了;而POST请求传给服务器的一堆入参是放在请求体里,谈不到URL编码了。

那虽然我们客户端这边的网络三方库内部针对URL默认采用UTF-8编码,但服务端开发人员那边知道吗?他们接收到URL后万一用别的方式解码呢?实际开发中我们也很少跟服务端开发人员约定说“我们都采用UTF-8编码”啊,这是因为服务端那边的网络三方库内部也默认采用UTF-8编码来对URL解码。也就是说,现在客户端和服务端的网络三方库内部针对URL默认都采用UTF-8编码,大家都默认了......如果真得决定采用别的编码方式,那就去约定吧。

采用UTF-8编码对URL编码的格式为:一个%,后面加上该字符在Unicode码表里对应的十六进制数字。比如我们用AFNetworking或者Dio发起一个GET请求http://localhost:8080/hw/login?username=123&password=4 5%6,其实这个GET请求真正上是http://localhost:8080/hw/login?username=123&password=4%205%256,空格" "会被编码成%20%会被编码成%25

AFNetworking内部默认采用UTF-8编码对GET请求的参数编码(即对URL编码) Dio内部默认采用UTF-8编码对GET请求的参数编码(即对URL编码)
2.3 HTTP的版本

1、只支持GET请求
2、只支持传输HTML文本

1、新增支持POST请求等
2、新增支持传输图片、音频、视频等
3、浏览器每发送一个HTTP请求,都要与服务器建立TCP连接,数据传输完成后立即断开TCP连接

比如我们在浏览器里访问一下http://baidu.com,浏览器就会与服务器建立TCP连接,服务器会返回一串HTML文本,浏览器收到后就会断开TCP连接。

但是当浏览器解析HTML文本的时候,会发现HTML文本里引用了很多其它的资源:CSS、JS、图片等,此时浏览器就会针对每一个资源都发起一个HTTP请求去服务器获取,而每发起一个HTTP请求都意味着一次建立TCP连接和一次断开TCP连接。

采用长连接(Connection: keep-alive),多个HTTP请求共用同一个TCP连接

比如我们在浏览器里访问一下http://baidu.com,浏览器就会与服务器建立TCP连接,服务器会返回一串HTML文本,但是浏览器收到后不会断开TCP连接。

当浏览器解析HTML文本的时候,会发现HTML文本里引用了很多其它的资源:CSS、JS、图片等,此时浏览器就会针对每一个资源都发起一个HTTP请求去服务器获取,但是这些HTTP请求都共用的是刚才创建的那个TCP连接。那这个TCP连接什么时候断开?其实这个长连接有个超时时间,等传输完数据,然后再过超时时间这么长时间,这个TCP连接就会断开。

HTTP/2主要是优化了数据传输的速度,对比HTTP/1.1和HTTP/2的速度

HTTP/3进一步优化了数据传输的速度,传输层不再使用TCP协议,而是改用基于UDP协议的QUIC协议,QUIC协议会保证可靠传输。

2.4 HTTP响应的状态码

HTTP响应的状态码一共有五类:

常见HTTP响应的状态码:

3、请求头和响应头

3.1 常见的请求头字段

实际开发中我们很少写设置请求头的代码,这是因为很多事情都被网络三方库内部帮我们给做了,比如设置Host、User-Agent、Connection、Content-Type、Content-Length等,我们只需要调用API发起网络请求就可以了。当然有些情况下我们也可能需求去写设置请求头的代码,比如设置Accept、Accept-Charset,或者断点续传设置Range,或者设置缓存Cookie、Cache-Control等。此外除了标准的请求头,我们还可以自定义请求头字段塞到请求头里。

3.2 常见的响应头字段
HTTP缓存的使用流程

4、请求体和响应体

4.1 请求体编码/响应体编码

国际标准化组织ISO强制规定Body里也只能出现跟URL要求一样的合法字符,非法字符都必须编码后才能出现在Body里。Body编码就不多说了,实际开发中我们很少去写Body编码的代码,这是因为现在客户端和服务端的网络三方库针对Body默认也都采用UTF-8编码,大家都默认了......如果真得决定采用别的编码方式,那就去约定吧。

4.2 请求体格式/响应体格式

实际开发中当我们使用POST请求的时候,要考虑一个问题,那就是”要采用什么格式把请求体发送给服务端“,常用的有三种格式:表单提交(请求头里对应的应该写Content-Type: application/x-www-form-urlencoded,AFNetworking默认)、JSON提交(请求头里对应的应该写Content-Type: application/json,Dio默认)和multipart提交(请求头里对应的应该写Content-Type: multipart/form-data; boundary=...,上传文件时必须采用这种格式),好在网络三方库已经帮我们把设置请求头、组装请求体格式这些非常非常繁琐的事情给做了,我们需要做的就是跟后台约定好要采用什么格式上传、然后调用网络三方库相应的API、把要传给服务端的入参以Map的形式扔给网络三方库就好了,比如当我们调用表单提交的API时,三方库就会在请求头里设置Content-Type: application/x-www-form-urlencoded,然后把入参组装成表单提交特有的格式,其它两种也是同理。不过可能你会发现实际开发中我们即便是在使用POST请求,但也很少去跟服务端开发人员对接到底要使用哪种格式,这是因为大家已经默认提交普通数据时采用表单提交,上传文件时采用multipart提交,响应体采用JSON提交返回,而一旦服务端真有哪个接口要求采用JSON提交、可是双方又没有提前对接、客户端继续采用了默认的表单提交,那这个POST请求就会报错,因为客户端发给服务器的数据他们解析不了,大家就一块儿去调试吧,最终会发现原来是提交格式不对。

而使用GET请求的时候,就不需要考虑什么表单提交、JSON提交和multipart提交这些东西,因为它们三个是用来控制请求体的格式的,GET请求根本就没有请求体啊。也许你会考虑一下GET请求的URL是否编码了,但实际上也不用过虑,因为网络三方库肯定会帮我们做URL编码。

下面就举例子看一下采用表单提交、JSON提交和multipart提交对请求体格式的影响究竟是什么,响应体的格式也是同理的,就不贴AFNetworking和Dio的源码了,以后专门分析源码的时候再说。

&分割请求参数,用=来分割请求参数名和请求参数值。

AFNetworking默认采用表单提交 Wireshark抓包AFNetworking Dio采用表单提交 Wireshark抓包Dio

{key1: value1, key2: value2}这样key-value键值对的形式。

AFNetworking采用JSON提交 Wireshark抓包AFNetworking Dio默认采用JSON提交 Wireshark抓包Dio

--boundary
附加信息 参数名1

参数值1
--boundary
附加信息 参数名2

参数值2
--boundary--

例子:

AFNetworking采用multipart提交 Wireshark抓包AFNetworking Dio采用multipart提交 Wireshark抓包Dio

三、HTTP的安全问题


encrypt:加密
decrypt:解密
plaintext:明文
ciphertext:密文

HTTP默认是采用明文传输数据的,所以会有很大的安全隐患。常见的提高安全性的办法就是对传输的数据进行加密,然后再进行传输。常见的加密方式有

参考

1、字符编码:字符编码笔记:ASCII,Unicode 和 UTF-8
2、各个码表的演变:Unicode 和 UTF-8 有什么区别?
3、URL编码:URL编码
4、URL里的保留字符:保留字符
5、关于URL的长度限制:关于 HTTP GET/POST 请求参数长度最大值的一个理解误区
6、头部字段:HTTP请求头与响应头
7、Content-Type:Content-Type 详解

上一篇 下一篇

猜你喜欢

热点阅读