目录

网络基础 | 应用层-http/https

HTTP

HTTP 请求返回的过程

  • 使用了域名的话,会先将域名发送给 DNS 服务器,让它解析成 IP 地址。

  • HTTP 基于 TCP 协议,所以先建立 TCP 连接。

    目前使用的 HTTP 协议大部分都是 1.1。在 1.1 的协议里面,默认是开启了 keep-alive 的,这样建立的 TCP 连接,就可以在多次请求中复用。

  • 建立连接之后,浏览器就要发送 HTTP 的请求了。请求的格式见「HTTP 报文格式」。

  • 接下来,会把 HTTP 请求报文通过 socket 这些传递给传输层。

  • 由于 TCP 是面向流的,所以到了 TCP 层之后,会把 HTTP 请求报文当成二进制,然后封装成一个个报文段发送给服务器,相当于一个 HTTP 请求可能会变成多个 TCP 报文段。

    在发送给每个 TCP 报文段的时候,都需要对方有一个回应 ACK,来保证报文可靠地到达了对方。如果没有回应,那么 TCP 这一层会进行重新传输,直到可以到达。同一个包有可能被传了好多次,但是 HTTP 这一层不需要知道这一点。TCP 层发送每一个报文的时候,都需要加上自己的地址(即源地址)和它想要去的地方(即目标地址),将这两个信息放到 IP 头里面,交给 IP 层进行传输。

  • IP 层需要查看目标地址和自己是否是在同一个局域网。如果是,就发送 ARP 协议来请求这个目标地址对应的 MAC 地址,然后将源 MAC 和目标 MAC 放入 MAC 头,发送出去即可;如果不在同一个局域网,就需要发送到网关,还要需要发送 ARP 协议,来获取网关的 MAC 地址,然后将源 MAC 和网关 MAC 放入 MAC 头,发送出去。网关收到包发现 MAC 符合,取出目标 IP 地址,根据路由协议找到下一跳的路由器,获取下一跳路由器的 MAC 地址,将包发给下一跳路由器。

    这样路由器一跳一跳终于到达目标的局域网。这个时候,最后一跳的路由器能够发现,目标地址就在自己的某一个出口的局域网上。于是,在这个局域网上发送 ARP,获得这个目标地址的 MAC 地址,将包发出去。

  • 目标的机器发现 MAC 地址符合,就将包收起来;发现 IP 地址符合,根据 IP 头中协议项,知道自己上一层是 TCP 协议,于是解析 TCP 的头,里面有序列号,需要看一看这个序列包是不是我要的,如果是就放入缓存中然后返回一个 ACK,如果不是就丢弃。

    TCP 头里面还有端口号,HTTP 的服务器正在监听这个端口号。于是,目标机器自然知道是 HTTP 服务器这个进程想要这个包,于是将包发给 HTTP 服务器。HTTP 服务器的进程收到所有包之后,发现这个请求是要访问一个网页。

    于是构造好返回的 HTTP 报文,然后将这个报文发送出去。接下去的流程还是跟上述是一样的。

  • 当浏览器拿到了 HTTP 的报文。发现返回“200”,一切正常。对于浏览器的网页请求来说的话,就从正文中将 HTML 拿出来。HTML 是一个标准的网页格式。浏览器只要根据这个格式,展示出一个绚丽多彩的网页。

HTTP1.1 报文格式

请求格式

请求格式以 HTTP 1.1 来进行阐述。

https://img.dawnguo.cn/NetworkingProtocol/Basic/85ebb0396cbaa45ce00b505229e523c1.jpeg

HTTP 的报文大概分为三大部分,第一部分是请求行,第二部分是首部,第三部分是实体。

请求行

  • 方法

    • GET,就是去服务器获取一些资源。对于访问网页来讲,要获取的资源往往是一个页面。其实也有很多其他的格式,比如说返回一个 JSON 字符串,到底要返回什么,是由服务器端的实现决定的。

      例如,在云计算中,如果我们的服务器端要提供一个基于 HTTP 协议的 API,获取所有云主机的列表,这就会使用 GET 方法得到,返回的可能是一个 JSON 字符串。字符串里面是一个列表,列表里面是一项的云主机的信息。

    • POST,需要主动告诉服务端一些信息,而非获取。而需要告诉服务端的信息,都会放在正文里面。正文可以有各种各样的格式。常见的格式也是 JSON。

      再如,在云计算里,如果我们的服务器端,要提供一个基于 HTTP 协议的创建云主机的 API,也会用到 POST 方法。这个时候往往需要将“我要创建多大的云主机?多少 CPU 多少内存?多大硬盘?”这些信息放在 JSON 字符串里面,通过 POST 的方法告诉服务器端。

    • PUT,就是向指定资源位置上传最新内容。但是,HTTP 的服务器往往是不允许上传文件的,所以 PUT 和 POST 就都变成了要传给服务器东西的方法。在实际使用过程中,这两者还会有稍许的区别。POST 往往是用来创建一个资源的,而 PUT 往往是用来修改一个资源的。

      例如,云主机已经创建好了,我想对这个云主机打一个标签,说明这个云主机是生产环境的,另外一个云主机是测试环境的。往往就是用 PUT 方法来修改标签。

    • DELETE。这个顾名思义就是用来删除资源的。

      例如,我们要删除一个云主机,就会调用 DELETE 方法。

  • URL 就是 http://multiparam.com

  • 版本为 HTTP 的版本,比如版本 1.1;

首部字段

首部是 key value,通过冒号分隔。这里面,往往保存了一些非常重要的字段。

  • Accept-Charset,表示客户端可以接受的字符集。防止传过来的是另外的字符集,从而导致出现乱码。
  • Content-Type 是指正文的格式。例如,我们进行 POST 的请求,如果正文是 JSON,那么我们就应该将这个值设置为 JSON。
  • Cache-control 是用来控制从缓存中获取资源的情况。当客户端发送的请求中包含 max-age 时,如果缓存层中资源的缓存时间数值比指定时间的数值小,那么客户端可以接受缓存的资源;当指定 max-age 值为 0,那么缓存层通常需要将请求转发给应用集群。
  • If-Modified-Since 也是一个关于缓存的。也就是说,如果服务器的资源在某个时间之后更新了,那么客户端就应该下载最新的资源;如果没有更新,服务端会返回“304 Not Modified”的响应,那客户端就不用下载了,也会节省带宽。

浏览一个商品的详情,里面有这个商品的价格、库存、展示图片、使用手册等等。商品的展示图片会保持较长时间不变,而库存会根据用户购买的情况经常改变。如果图片非常大,而库存数非常小,如果我们每次要更新数据的时候都要刷新整个页面,对于服务器的压力就会很大。对于这种高并发场景下的系统,在真正的业务逻辑之前,都需要有个接入层,将这些静态资源的请求拦在最外面。

如下所示,对于静态资源,Nginx 有 Vanish 缓存层。当缓存没有过期的时候,返回的是 Vanish 中的资源。当缓存过期的时候,才会访问真正的 Tomcat 应用集群。

https://img.dawnguo.cn/NetworkingProtocol/Basic/caec3ba1086557cbf694c621e7e01e1d.jpeg

返回格式

返回格式也以 HTTP 1.1 来进行阐述。

https://img.dawnguo.cn/NetworkingProtocol/Basic/6bc37ddcb4e7a61ca3275790820f2263.jpeg

状态行

  • 状态码会反映 HTTP 请求的结果,比如 200、404。

首部

  • Retry-After 表示,告诉客户端应该在多长时间以后再次尝试一下。当出现“503 错误”的时候,表示“服务暂时不再和这个值配合使用”。
  • Content-Type,表示返回的是 HTML,还是 JSON。

HTTP 2.0

HTTP 1.1 在应用层以纯文本的形式进行通信。每次通信都要带完整的 HTTP 的头,而且不考虑 pipeline 模式的话,每次的过程总是像上面描述的那样一去一回。这样在实时性、并发性上都存在问题。

为了解决这些问题,HTTP 2.0 会对 HTTP 的头进行一定的压缩,将原来每次都要携带的大量 key value 在两端建立一个索引表,对相同的头只发送索引表中的索引。

另外,HTTP 2.0 协议将一个 TCP 的连接中,切分成多个 stream(逻辑概念),每个 stream 都有自己的 ID,而且 stream 可以是客户端发往服务端,也可以是服务端发往客户端。它其实只是一个虚拟的通道。stream 是有优先级的。

HTTP 2.0 还可以将所有的传输信息分割为更小的消息和帧,并对它们采用二进制格式编码,同时使用 stream 来传输信息。常见的帧有 Header 帧,用于传输 Header 内容,并且会开启一个新的 stream。再就是 Data 帧,用来传输正文实体。多个 Data 帧属于同一个 stream。

通过这两种机制,HTTP 2.0 的客户端可以将多个请求分到不同的 stream 中(比如每个请求一个 stream),将请求内容拆成帧,进行二进制传输。这些帧可以打散乱序发送。然后,根据每个帧首部的流标识符重新组装,并且可以根据优先级,决定优先处理哪个 stream 的数据。

个人理解 stream 其实就是 HTTP 将多个 HTTP 请求报文进行打包统一使用一个 TCP 连接发送。

下面举个例子。假设我们的一个页面要发送三个独立的请求,一个获取 css,一个获取 js,一个获取图片 jpg。如果使用 HTTP 1.1 就是串行的,也就是说先发送 css 的请求,然后发送 js 的请求,之后再发送图片的请求。但是,如果使用 HTTP 2.0,就可以在一个 TCP 连接里,客户端和服务端都可以同时发送多个请求或回应,而且不用按照顺序一对一对应。

HTTP 2.0 其实是将三个请求变成三个 stream,将数据分成帧,乱序发送到一个 TCP 连接中。

https://img.dawnguo.cn/NetworkingProtocol/Basic/3da001fac5701949b94e51caaee887d3.jpeg

HTTP 2.0 成功解决了 HTTP 1.1 的队首阻塞问题。同时,也不需要通过 HTTP 1.x 的 pipeline 机制用多条 TCP 连接来实现并行请求与响应;减少了 TCP 连接数对服务器性能的影响,同时将页面的多个数据 css、js、 jpg 等通过一个数据链接进行传输,能够加快页面组件的传输速度。

http1.0的队首阻塞:对于同一个tcp连接,所有的http1.0请求放入队列中,只有前一个请求的响应收到了,然后才能发送下一个请求。可见,http1.0的队首组塞发生在客户端。

http1.1的队首阻塞:采用 pipeline 模式之后,对于同一个tcp连接,http1.1允许一次发送多个http1.1请求,也就是说,不必等前一个响应收到,就可以发送下一个请求,这样就解决了http1.0的客户端的队首阻塞。但是,http1.1规定,服务器端的响应的发送要根据请求被接收的顺序排队,也就是说,先接收到的请求的响应也要先发送。这样造成的问题是,如果最先收到的请求的处理时间长的话,响应生成也慢,就会阻塞已经生成了的响应的发送。也会造成队首阻塞。

没有采用 pipeline 模式的话,同一个tcp连接在同一时间只能处理一个http请求(与http1.0一样),并没有解决http1.0的客户端客户端阻塞问题。

Http2 解决的方式:http2 无论在客户端还是在服务器端都不需要排队,前面提到在同一个tcp连接上,有多个stream,由各个stream发送和接收http请求,各个steam相互独立,互不阻塞。只要tcp没有人在用那么就可以发送已经生成的requst或者reponse的数据,在两端都不用等,从而彻底解决了http协议层面的队首阻塞问题。

存在问题

HTTP 2.0 虽然大大增加了并发性,但还是有问题的。因为 HTTP 2.0 也是基于 TCP 协议的,TCP 协议在处理包时是有严格顺序的。当其中一个数据包遇到问题,TCP 连接需要等待这个包完成重传之后才能继续进行。虽然 HTTP 2.0 通过多个 stream,使得逻辑上一个 TCP 连接可以并行地进行多路数据的传输。但是,即使传输的数据之间并没有实际上的前后关系,它们还是得遵循 TCP 的严格顺序。比如,前面 stream 2 的帧没有收到,后面 stream 1 的帧跟前面的帧实际是没有先后关系,但是因为 TCP 的严格顺序也会因此受到阻塞(这个阻塞更多是指在 TCP 层,而非 HTTP 层)。

HTTPS

加密手段

加密分为两种方式一种是对称加密,一种是非对称加密。

在对称加密算法中,加密和解密使用的密钥是相同的。也就是说,加密和解密使用的是同一个密钥。因此,对称加密算法要保证安全性的话,密钥要做好保密。只能让使用的人知道,不能对外公开。对称加密中最大的问题是如何传输密钥。

在非对称加密算法中,加密使用的密钥和解密使用的密钥是不相同的。一把是作为公开的公钥,另一把是作为谁都不能给的私钥。公钥加密的信息,只有私钥才能解密。私钥加密的信息,只有公钥才能解密。非对称加密中最大的问题是私钥加密的信息,公钥都可以解开。这样,黑客拿到了公钥之后也可以对私钥加密的信息进行解密。

因为对称加密算法相比非对称加密算法来说,效率要高得多,性能也好,所以交互的场景下多用对称加密。

数字证书

在采用非对称加密算法的时候,如何将公钥给对方呢?一种是放在一个公网的地址上,让对方下载;另一种就是在建立连接的时候,传给对方。这两种方法有相同的问题,你怎么鉴别别人给你的公钥是对的。而不是有人冒充外卖网站,发给你一个它的公钥。

这个时候需要权威部门 CA( Certificate Authority),CA 会颁发证书(Certificate)。这个证书里面包含了,

  • 公钥;
  • 证书的所有者;
  • 证书的发布机构和证书的有效期;
  • 域名(证书里带域名的作用是,如果浏览器联的是外卖网站的域名,被黑客拦截发回了黑客的 ca 证书,校验证书的时候会发现域名不对从而证书认证失败);

而 CA 的证书的获取过程如下所示,

  • 首先生成需要生成证书请求,也就是第一条命中的 cliu8sitecertificate.req。

  • 之后 CA 会使用自己的私钥对这个请求进行签名,也就是第二条命令所示,最终 cliu8sitecertificate.pem 就是签过名的证书。

    这里,CA 用自己的私钥给外卖网站的公钥签名,就相当于给外卖网站背书,形成了外卖网站的证书。

  • 最后可以使用第三条命令查看证书的内容。其中,Issuer 也即证书是谁颁发的;Subject 就是证书颁发给谁;Validity 是证书期限;Public-key 是公钥内容;Signature Algorithm 是签名算法。

1
2
3
4
5
openssl req -key cliu8siteprivate.key -new -out cliu8sitecertificate.req

openssl x509 -req -in cliu8sitecertificate.req -CA cacertificate.pem -CAkey caprivate.key -out cliu8sitecertificate.pem

openssl x509 -in cliu8sitecertificate.pem -noout -text 

之后,当我们向网站访问的时候,网站不会返回一个公钥,而是经过 CA 签名的证书。你只要得到这个发布机构 CA 的公钥,去解密外卖网站证书的签名,如果解密成功了,Hash 也对的上,就说明这个外卖网站的公钥没有啥问题,因为它经过了 CA 的认证。

但是,这个时候,我们会发现还需要发布机构的 CA 的公钥。而获取这个公钥,我们可以采用类似的方式。也就是要想知道某个 CA 的证书是否可靠,要看 CA 的上级证书的公钥,能不能解开这个 CA 的签名。这样,层层上去,直到全球皆知的几个著名大 CA,称为 root CA,做最后的背书。通过这种层层授信背书的方式,从而保证了非对称加密模式的正常运转(就是公钥发布的安全性)。

除此之外,还有一种证书,称为 Self-Signed Certificate,就是自己给自己签名。两者的区别是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用 CA 机构申请的证书则不会弹出提示页面。

各大 CA 机构的公钥是默认安装在操作系统里的。网银盾,淘宝客户端插件等相当于客户端内置 CA 公钥。

HTTPS 的工作模式

HTTPS 采用的方式是先用公钥私钥传输对称加密的密钥,之后双方大数据量的通信都是通过对称加密进行。用户在浏览器里输入一个 https 的网址之后,会连接到服务端的 443 端口,整体的过程如下所示:

  • TCP 三次同步握手建立连接。
  • 之后,客户端和服务端沟通密钥,包括验证服务器数字证书和协商对称加密算法的密钥。
  • 最后数据以加密的方式传输,用协商的对称加密算法和密钥加密,保证数据机密性;用协商的hash算法进行数据完整性保护,保证数据不被篡改。

整体沟通密钥的过程如下所示:

  • 客户端会发送 Client Hello 消息到服务器,用明文传输 TLS 版本信息、加密套件候选列表、压缩算法候选列表等信息。另外,还会有一个随机数,在协商对称密钥的时候使用。

  • 服务端返回 Server Hello 消息,告诉客户端,服务器选择使用的协议版本、加密套件、压缩算法等,还有一个随机数,用于后续的密钥协商。

    这一步其实相当于双方都知道了这些信息。

  • 服务端会返回一个服务器端的证书,之后发送 “Server Hello Done”。

  • 这个时候,客户端会使用自己有的 CA 公钥去解密服务端返回的证书。如果成功,则说明外卖网站是可信的。这个过程中,你可能会不断往上追溯 CA、CA 的 CA、CA 的 CA 的 CA,反正直到一个授信的 CA,就可以了。

    这个过程是由客户端的 TLS 来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等。如果发现异常,则会弹出一个警告,提示证书存在问题。

  • 证书验证完毕之后,觉得这个服务端可信,于是客户端计算产生随机数字 Pre-master,发送 Client Key Exchange,用证书中的公钥加密,再发送给服务器,服务器可以通过私钥解密出来。

    到目前为止,无论是客户端还是服务器,都有了三个随机数,分别是:自己的、对端的,以及刚生成的 Pre-Master 随机数。通过这三个随机数,可以在客户端和服务器产生相同的对称密钥。

  • 有了对称密钥,客户端就会发送:“Change Cipher Spec,表示都采用协商的通信密钥和加密算法进行加密通信了。

  • 然后发送一个 Encrypted Handshake Message,将已经商定好的参数等,采用协商密钥进行加密,发送给服务器用于数据与握手验证。

  • 同样,服务器也可以发送 Change Cipher Spec,表示以后都采用协商的通信密钥和加密算法进行加密通信了。并且也发送 Encrypted Handshake Message 的消息试试。当双方握手结束之后,就可以通过对称密钥进行加密传输了。

https://img.dawnguo.cn/NetworkingProtocol/Basic/df1685dd308cef1db97e91493f911ab4.jpg

这个过程除了加密解密之外,其他的过程和 HTTP 是一样的。上面的过程只包含了 HTTPS 的单向认证,也即客户端验证服务端的证书,是大部分的场景,也可以在更加严格安全要求的情况下,启用双向认证,双方互相验证证书。

重放和篡改

有了加密解密之后,黑客截获了包也打开不了,但是这个时候它可以将这个包发送 N 次。这个时候服务端通过 Nonce 和 Timestamp 来保证。

Nonce 是由服务器生成的一个随机数,在客户端第一次请求页面时将其发回客户端;客户端拿到这个 Nonce,将其与用户密码串联在一起并进行非可逆加密(MD5、SHA1 等等),然后将这个加密后的字符串和用户名、Nonce、加密算法名称一起发回服务器;服务器使用接收到的用户名到数据库搜索密码,然后跟客户端使用同样的算法对其进行加密,接着将其与客户端提交上来的加密字符串进行比较,如果两个字符串一致就表示用户身份有效。这样就解决了用户密码明文被窃取的问题,攻击者就算知道了算法名和 nonce 也无法解密出密码。

每个 nonce 只能供一个用户使用一次,这样就可以防止攻击者使用重放攻击,因为该 Http 报文已经无效。可选的实现方式是把每一次请求的 Nonce 保存到数据库,客户端再一次提交请求时将请求头中得Nonce 与数据库中得数据作比较,如果已存在该 Nonce,则证明该请求有可能是恶意的。然而这种解决方案也有个问题,很有可能在两次正常的资源请求中,产生的随机数是一样的,这样就造成正常的请求也被当成了攻击,随着数据库中保存的随机数不断增多,这个问题就会变得很明显。所以,还需要加上另外一个参数 Timestamp(时间戳)。

Timestamp 是根据服务器当前时间生成的一个字符串,与 nonce 放在一起,可以表示服务器在某个时间点生成的随机数。这样就算生成的随机数相同,但因为它们生成的时间点不一样,所以也算有效的随机数。

如果有人想篡改 Timestamp 和 Nonce,还有非可逆加密保证不可篡改性,如果改了使用非可逆算法一算,发现对不上了,可以丢弃了。

和 HTTP 的区别

  • HTTP 明文传输,数据都是未加密的,安全性较差。HTTP 协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。

    HTTPS(Hypertext Transfer Protocol Secure:超文本传输安全协议),数据传输过程是加密的,安全性较好,HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。

  • http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。

  • 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。

  • HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。

  • HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,HTTPS 比 HTTP 要更耗费服务器资源。

参考链接

https://blog.csdn.net/guolin_blog/article/details/104546558