应用层——HTTP

万维网的工作过程

每个万维网网点都有一个服务器进程,它不断地监听 TCP 的端口 80,以便发现是否有浏览器向它发出连接建立请求。

一旦监听到连接建立请求并建立了 TCP 连接之后,浏览器就向万维网服务器发出浏览某个页面的请求,服务器接着就返回所请求的页面作为响应。

最后,TCP 连接就被释放了。

在浏览器和服务器之间的请求和响应的交互,必须按照规定的格式和遵循一定的规则。这些格式和规则就是超文本传送协议 HTTP。

HTTP 规定在 HTTP 客户与 HTTP 服务器之间的每次交互,都由一个 ASCII 码串构成的请求和一个类似的通用互联网扩充,即“类 MIME(MIME-like)”的响应组成。

HTTP 报文通常都使用 TCP 连接传送。

用户浏览页面的两种方法:

  1. 在浏览器的地址窗口中键入所要找的页面的 URL。
  2. 在某一个页面中用鼠标点击一个可选部分,这时浏览器会自动在互联网上找到所要链接的页面。

HTTP 的主要特点

HTTP 使用了面向连接的 TCP 作为运输层协议,保证了数据的可靠传输。

HTTP 协议本身也是无连接的,虽然它使用了面向连接的 TCP 向上提供的服务。

HTTP 是面向事务的客户服务器协议。

HTTP 1.0 协议是无状态的。

请求一个万维网文档所需的时间

用户在点击鼠标链接某个万维网文档时,HTTP 协议首先要和服务器建立 TCP 链接,这需要使用三报文握手。当建立 TCP 连接的三报文握手的前两部分完成后(即经过了一个 RTT 的时间后),万维网客户就把 HTTP 请求报文,作为建立 TCP 连接的三报文握手中的第三个报文的数据发送给万维网服务器。服务器收到 HTTP 请求报文后,就把所请求的文档作为响应报文返回给客户。

从图中可以看出,请求一个万维网文档所需的时间是该文档的传输时间加上两倍往返时间 RTT(一个 RTT 用于 TCP 连接,一个用于请求和接收万维网文档)。

HTTP/1.0 主要的缺点就是每请求一个文档就要有两倍 RTT 的开销。另一种开销就是万维网客户和服务器每建立一次新的 TCP 连接都要分配缓存和变量。

HTTP/1.1 较好的解决了这个问题,它使用了持续连接。

持续连接

持续连接就是万维网服务器在发送响应后仍然在一段时间内保持这条连接,使同一个客户(浏览器)和该服务器可以继续在这条连接上传送后续的 HTTP 请求报文和响应报文。

这并不局限于传送同一个页面上链接的文档,而是只要这些文档都在同一个服务器上就行。

持续连接的两种工作方式:

  • 非流水线方式:客户在收到前一个响应后才能发出下一个请求。这比非持续连接的两倍 RTT 的开销节省了建立 TCP 连接所需的一个 RTT 时间。但服务器在发送完一个对象后,其 TCP 连接就处于空闲状态,浪费了服务器资源。
  • 流水线方式:客户在收到 HTTP 的响应报文之前就能够接着发送新的请求报文。一个接一个的请求报文到达服务器后,服务器就可连续发回响应报文。使用流水线方式时,客户访问所有的对象只需花费一个 RTT时间,使 TCP 连接中的空闲时间减少,提高了下载文档效率。

代理服务器

代理服务器又称为万维网高速缓存,它代表浏览器发出 HTTP 请求。

万维网高速缓存把最近的一些请求和响应暂存在本地磁盘中。

当与暂时存放的请求相同的新请求到达时,万维网高速缓存就把暂存的响应发送出去,而不需要按 URL 的地址再去互联网访问该资源。

代理服务器可在客户端或服务器工作,也可在中间系统工作。

使用高速缓存可减少访问互联网服务器的时延。

没有使用高速缓存的情况

使用高速缓存的情况

在使用代理服务器的情况下,由于有相当大一部分通信量局限在校园网内,因此,专线链路上的通信量大大减少,因而减小了访问互联网的时延。

HTTP 的报文结构

HTTP 有两类报文:

  • 请求报文——从客户向服务器发送请求报文。
  • 响应报文——从服务器到客户的回答。

由于 HTTP 是面向正文的,因此在报文中的每一个字段都是一些 ASCII 码串,因而每个字段的长度都是不确定的。

请求报文

报文由三个部分组成,即开始行、首部行和实体主体。

在请求报文中开始行叫请求行,在响应报文中开始行叫状态行。

首部行用来说明浏览器、服务器或报文主体的一些信息。首部行可以有好几行,也可以不使用。

HTTP 请求报文的一些方法:

方法 意义
OPTION 请求一些选项的信息
GET 请求读取由 URL所标志的信息
HEAD 请求读取由 URL所标志的信息的首部
POST 给服务器添加信息
PUT 在指明的 URL下存储一个文档
DELETE 删除指明的 URL所标志的资源
TRACE 用来进行环回测试的请求报文
CONNECT 用于代理服务器

响应报文

状态行包括三项内容,即 HTTP 的版本,状态码,以及解释状态码的简单短语。

HTTP 常见状态码

含义 常见状态码
1xx 提示信息,如请求收到了或正在进行处理
2xx 成功,报文已经收到并被正确处理 200、204、206
3xx 重定向,资源位置发生变动,需要客户端重新发送请求 301、302、304
4xx 客户端错误,请求报文有误,服务器无法处理 400、403、404
5xx 服务器错误,服务器正在处理请求时内部发生了错误 500, 502、503

1xx类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。

2xx类状态码表示服务器成功处理了客户端的请求。

  • 200 OK是最常见的成功状态码,表示一切正常。如果是非HEAD请求,服务器返回的响应头都会有body数据。
  • 204 No Content也是常见的成功状态码,与200 OK基本相同,但响应头没有body数据。
  • 206 Partial Content是应用于 HTTP 分块下载或断点续传,表示响应返回的body数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态。

3xx类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。

  • 301 Moved Permanently表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。
  • 302 Found表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。
    301 和 302 都会在响应头里使用字段Location,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。
  • 304 Not Modified不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,也就是告诉客户端可以继续使用缓存资源,用于缓存控制。

4xx类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。

  • 400 Bad Request表示客户端请求的报文有错误,但只是个笼统的错误。
  • 403 Forbidden表示服务器禁止访问资源,并不是客户端的请求出错。
  • 404 Not Found表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。

5xx类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。

  • 500 Internal Server Error与 400 类型,是个笼统通用的错误码,服务器发生了什么错误,我们并不知道。
  • 501 Not Implemented表示客户端请求的功能还不支持,类似“即将开业,敬请期待”的意思。
  • 502 Bad Gateway通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。
  • 503 Service Unavailable表示服务器当前很忙,暂时无法响应客户端,类似“网络服务正忙,请稍后重试”的意思。

GET 与 POST

GET 和 POST 区别

根据 RFC 规范,GET 的语义是从服务器获取指定的资源,这个资源可以是静态的文本、页面、图片视频等。GET 请求的参数位置一般是写在 URL 中,URL 规定只能支持 ASCII,所以 GET 请求的参数只允许 ASCII 字符 ,而且浏览器会对 URL 的长度有限制(HTTP 协议本身对 URL 长度并没有做任何规定)。

根据 RFC 规范,POST 的语义是根据请求负荷(报文body)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST 请求携带数据的位置一般是写在报文body中,body中的数据可以是任意格式的数据,只要客户端与服务端协商好即可,而且浏览器不会对body大小做限制。

GET 和 POST 方法都是安全和幂等的吗?

安全和幂等的概念:

  • 在 HTTP 协议里,所谓的安全是指请求方法不会「破坏」服务器上的资源。
  • 所谓的幂等,意思是多次执行相同的操作,结果都是「相同」的。

如果从 RFC 规范定义的语义来看:

  • GET 方法就是安全且幂等的,因为它是只读操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。所以,可以对 GET 请求的数据做缓存,这个缓存可以做到浏览器本身上(彻底避免浏览器发请求),也可以做到代理上(如 nginx),而且在浏览器中 GET 请求可以保存为书签。
  • POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。所以,浏览器一般不会缓存 POST 请求,也不能把 POST 请求保存为书签。

做个简要的小结。

  • GET 的语义是请求获取指定的资源。GET 方法是安全、幂等、可被缓存的。
  • POST 的语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST 不安全,不幂等,(大部分实现)不可缓存。

注意, 上面是从 RFC 规范定义的语义来分析的。

但是实际过程中,开发者不一定会按照 RFC 规范定义的语义来实现 GET 和 POST 方法。比如:

  • 可以用 GET 方法实现新增或删除数据的请求,这样实现的 GET 方法自然就不是安全和幂等。
  • 可以用 POST 方法实现查询数据的请求,这样实现的 POST 方法自然就是安全和幂等。

如果「安全」放入概念是指信息是否会被泄漏的话,虽然 POST 用 body 传输数据,而 GET 用 URL 传输,这样数据会在浏览器地址拦容易看到,但是并不能说 GET 不如 POST 安全的。

因为 HTTP 传输的内容都是明文的,虽然在浏览器地址拦看不到 POST 提交的 body 数据,但是只要抓个包就都能看到了。

所以,要避免传输过程中数据被窃取,就要使用 HTTPS 协议,这样所有 HTTP 的数据都会被加密传输。

GET 请求可以带 body 吗?

RFC 规范并没有规定 GET 请求不能带body的。理论上,任何请求都可以带body的。只是因为 RFC 规范定义的 GET 请求是获取资源,所以根据这个语义不需要用到body

另外,URL 中的查询参数也不是 GET 所独有的,POST 请求的 URL 中也可以有参数的。

HTTP 常见字段

Host 字段

客户端发送请求时,用来指定服务器的域名。

1
Host: www.A.com

有了Host字段,就可以将请求发往同一台服务器上的不同网站。

Content-Length 字段

服务器在返回数据时,会有Content-Length字段,表明本次回应的数据长度。

1
Content-Length: 1000

如上面则是告诉浏览器,本次服务器回应的数据长度是 1000 个字节,后面的字节就属于下一个回应了。

HTTP 是基于 TCP 传输协议进行通信的,而使用了 TCP 传输协议,就会存在一个“粘包”的问题,HTTP 协议通过设置回车符、换行符作为 HTTPheader的边界,通过Content-Length字段作为 HTTPbody的边界,这两个方式都是为了解决“粘包”的问题。

Connection 字段

Connection字段最常用于客户端要求服务器使用「HTTP 长连接」机制,以便其他请求复用。

HTTP 长连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。

HTTP/1.1 版本的默认连接都是长连接,但为了兼容老版本的 HTTP,需要指定Connection首部字段的值为Keep-Alive

1
Connection: Keep-Alive

开启了 HTTPKeep-Alive机制后, 连接就不会中断,而是保持连接。当客户端发送另一个请求时,它会使用同一个连接,一直持续到客户端或服务器端提出断开连接。

Content-Type 字段

Content-Type字段用于服务器回应时,告诉客户端,本次数据是什么格式。

1
Content-Type: text/html; Charset=utf-8

上面的类型表明,发送的是网页,而且编码是 UTF-8。

客户端请求的时候,可以使用Accept字段声明自己可以接受哪些数据格式。

1
Accept: */*

上面代码中,客户端声明自己可以接受任何格式的数据。

Content-Encoding 字段

Content-Encoding字段说明数据的压缩方法。表示服务器返回的数据使用了什么压缩格式。

1
Content-Encoding: gzip

上面表示服务器返回的数据采用了gzip方式压缩,告知客户端需要用此方式解压。

客户端在请求时,用Accept-Encoding字段说明自己可以接受哪些压缩方法。

1
Accept-Encoding: gzip, deflate

HTTP 缓存技术

对于一些具有重复性的 HTTP 请求,比如每次请求得到的数据都一样的,我们可以把这对「请求-响应」的数据都缓存在本地,那么下次就直接读取本地的数据,不必在通过网络获取服务器的响应了,这样的话 HTTP/1.1 的性能肯定肉眼可见的提升。

所以,避免发送 HTTP 请求的方法就是通过缓存技术,HTTP 设计者早在之前就考虑到了这点,因此 HTTP 协议的头部有不少是针对缓存的字段。

HTTP 缓存有两种实现方式,分别是强制缓存和协商缓存。

强制缓存

强缓存指的是只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存,决定是否使用缓存的主动性在于浏览器这边。

如下图中,返回的是 200 状态码,但在size项中标识的是from disk cache,就是使用了强制缓存。

强缓存是利用下面这两个 HTTP 响应头部(Response Header)字段实现的,它们都用来表示资源在客户端缓存的有效期:

  • Cache-Control,是一个相对时间;
  • Expires,是一个绝对时间;

如果 HTTP 响应头部同时有Cache-ControlExpires字段的话,Cache-Control的优先级高于Expires

Cache-control选项更多一些,设置更加精细,所以建议使用Cache-Control来实现强缓存。具体的实现流程如下:

  • 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在Response头部加上Cache-ControlCache-Control中设置了过期时间大小;
  • 浏览器再次请求访问服务器中的该资源时,会先通过请求资源的时间与Cache-Control中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;
  • 服务器再次收到请求后,会再次更新Response头部的Cache-Control

协商缓存

当我们在浏览器使用开发者工具的时候,你可能会看到过某些请求的响应码是 304,这个是告诉浏览器可以使用本地缓存的资源,通常这种通过服务端告知客户端是否可以使用缓存的方式被称为协商缓存。

上图就是一个协商缓存的过程,所以协商缓存就是与服务端协商之后,通过协商结果来判断是否使用本地缓存。

协商缓存可以基于两种头部来实现。

第一种:请求头部中的If-Modified-Since字段与响应头部中的Last-Modified字段实现,这两个字段的意思是:

  • 响应头部中的Last-Modified:标示这个响应资源的最后修改时间;
  • 请求头部中的If-Modified-Since:当资源过期了,发现响应头中具有Last-Modified声明,则再次发起请求的时候带上Last-Modified的时间,服务器收到请求后发现有If-Modified-Since则与被请求资源的最后修改时间进行对比(Last-Modified),如果最后修改时间较新(大),说明资源又被改过,则返回最新资源,HTTP 200 OK;如果最后修改时间较旧(小),说明资源无新修改,响应 HTTP 304 走缓存。

第二种:请求头部中的 If-None-Match 字段与响应头部中的 ETag 字段,这两个字段的意思是:

  • 响应头部中Etag:唯一标识响应资源;
  • 请求头部中的If-None-Match:当资源过期时,浏览器发现响应头里有Etag,则再次向服务器发起请求时,会将请求头If-None-Match值设置为Etag的值。服务器收到请求后进行比对,如果资源没有变化返回 304,如果资源变化了返回 200。

第一种实现方式是基于时间实现的,第二种实现方式是基于一个唯一标识实现的,相对来说后者可以更加准确地判断文件内容是否被修改,避免由于时间篡改导致的不可靠问题。

如果在第一次请求资源的时候,服务端返回的 HTTP 响应头部同时有EtagLast-Modified字段,那么客户端再下一次请求的时候,如果带上了ETagLast-Modified字段信息给服务端,这时Etag的优先级更高,也就是服务端先会判断Etag是否变化了,如果Etag有变化就不用在判断Last-Modified了,如果Etag没有变化,然后再看Last-Modified

为什么ETag的优先级更高?

这是因为ETag主要能解决Last-Modified几个比较难以解决的问题:

  • 在没有修改文件内容情况下文件的最后修改时间可能也会改变,这会导致客户端认为这文件被改动了,从而重新请求;
  • 可能有些文件是在秒级以内修改的,If-Modified-Since能检查到的粒度是秒级的,使用Etag就能够保证这种需求下客户端在 1 秒内能刷新多次;
  • 有些服务器不能精确获取文件的最后修改时间。

注意,协商缓存这两个字段都需要配合强制缓存中Cache-Control字段来使用,只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求。

下图是强制缓存和协商缓存的工作流程:

当使用ETag字段实现的协商缓存的过程:

  • 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在Response头部加上ETag唯一标识,这个唯一标识的值是根据当前请求的资源生成的;
  • 当浏览器再次请求访问服务器中的该资源时,首先会先检查强制缓存是否过期:
  • 如果没有过期,则直接使用本地缓存;
  • 如果缓存过期了,会在Request头部加上If-None-Match字段,该字段的值就是ETag唯一标识;
  • 服务器再次收到请求后,会根据请求中的If-None-Match值与当前请求的资源生成的唯一标识进行比较:
  • 如果值相等,则返回304 Not Modified,不会返回资源;
  • 如果不相等,则返回 200 状态码和返回资源,并在Response头部加上新的ETag唯一标识;
  • 如果浏览器收到 304 的请求响应状态码,则会从本地缓存中加载资源,否则更新资源。
打赏
  • Copyrights © 2017-2023 WSQ
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信