/ Computer Science

HTTP协议之304状态码

在写上一篇博客【HTTP协议之简单总结】的时候,遇到一个问题——那就是我在访问自己的博客的时候,抓到的响应包的状态码为304,并且没有返回正文。

那么这个304是什么呢?我们来详细了解一下吧!

在百度百科中,我们可以看到304状态码的描述:

未按预期修改文档。客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。

简而言之,就是网页已缓存到本地,并且服务端对网页并没有进行更新,因此直接调用本地缓存页面就可以访问啦!在这种情况下有两种请求报文首部来判断,一种是通过ETag,另一种是通过Last-Modified。

通过ETag首部来判断

来详细看一下数据包:
在抓包之前,我已经访问过该网站,并且网站未进行更新,然后抓一下GET包:

GET / HTTP/1.1
Host: lonelyme.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
DNT: 1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
If-None-Match: W/"4ecc-cv4Z2GaFyIukbfDtnSLoA1JH7Sg"
Connection: close

在请求头里,我们看到一个If-None-Match字段,这是HTTP 1.1推出的验证浏览器缓存是否过时的首部字段,和响应头里的ETag字段相对应。我们先把响应包也抓一下,然后再分析:

HTTP/1.1 304 Not Modified
Server: nginx/1.10.3 (Ubuntu)
Date: Wed, 02 Aug 2017 17:04:58 GMT
Connection: close
X-Powered-By: Express
Cache-Control: public, max-age=0
ETag: W/"4ecc-cv4Z2GaFyIukbfDtnSLoA1JH7Sg"

现在我们可以很明显地看到,If-None-Match的值和ETag的值是完全相同的,服务器依据这个值来判断客户端对网页是否进行了缓存。如果GET请求中携带的If-None-Match值与服务器上的ETag值相同,则服务器返回304状态码,通知浏览器从缓存里载入页面。

现在我们在服务器更新一下网站,然后再来抓包:

GET / HTTP/1.1
Host: lonelyme.cn
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
DNT: 1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
If-None-Match: W/"4ecc-cv4Z2GaFyIukbfDtnSLoA1JH7Sg"

Connection: close

新的GET包,可以看出,If-None-Match值和上面还是一样的。再来看一下Response包:

HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Wed, 02 Aug 2017 17:06:00 GMT
Content-Type: text/html; charset=utf-8
Connection: close
X-Powered-By: Express
Cache-Control: public, max-age=0
ETag: W/"553b-ANbGiJ+JMBSGjBJPJGAw4Wp5P8I"
Vary: Accept-Encoding
Content-Length: 21819

<!DOCTYPE html>
<html>

……

这里由于If-None-MatchETag对应不上,所以服务器直接返回200与新的页面。

服务器会通过某种算法,给资源计算出一个唯一标识符(比如md5标识),然后在把资源响应给浏览器的时候,在实体首部加上“ETag:唯一标识符”一起返回给浏览器。
浏览器会保留该ETag字段,并在下次请求时,作为If-None-Match字段的值带给服务器,服务器只要比较浏览器传来的ETag字段与自己服务器上该资源的ETag字段是否一致,就能判断资源相对客户端而言是否修改过了。
如果服务器发现ETag不能匹配,那么直接返回常规的GET 200数据包把新的资源(也包括新的ETag)发给浏览器;如果ETag是一致的,则直接返回304来通知浏览器使用本地缓存。

浏览器在请求报文中,有两个首部字段可以带上ETag值:
If-None-Match:"ExampleValue"
告诉服务器如果ETag没有匹配上需要重发数据,否则直接返回304和Response报头即可。当前各浏览器均使用该请求首部来向服务器传递保存的ETag值。
If-Match:ETag-Value
告诉服务器如果没有匹配到ETag或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed)状态码给客户端,否则服务器直接忽略该字段。If-Match的一个应用场景是,客户端走PUT方法向服务器请求上传/更替资源,这时可以通过If-Match来传递资源的ETag。

通过Last-Modified判断

服务器在将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified:GMT”的形式加在实体首部上一起返回给客户端。
客户端会为资源标记上该信息,下次再请求时,会把该信息附带在请求报文里一起发送给服务器检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明资源没有被修改,直接返回304即可。
具体字段的实现如下:
If-Modified-Since:Thu, 3 August 2017 13:06:01 GMT
告诉服务器如果最后修改时间没有匹配上需要重发数据,否则直接返回304和Response报头即可。当前各浏览器均使用该请求首部来向服务器传递保存的Last-Modified值。
If-Unmodified-Since:Last-Modified-value
告诉服务器,若Last-Modified没有匹配上(资源被更新),则返回412(Precondition Failed)状态码给客户端。

当遇到以下情况时,If-Unmodified-Since字段会被忽略:

  • Last-Modified值对上了(未被修改);
  • 服务端需返回2XX和412以外的状态码;
  • 传来的指定日期不合法

由于Last-Modified可能存在不准确的情况,故ETag相对是一个更好的选择。

HTTP协议之304状态码
Share this

Subscribe to Zed's Blog