写在前面:Vary 是一个HTTP响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复(response)还是向源服务器请求一个新的回复。它被服务器用来表明在 content negotiation algorithm(内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息(headers)。

描述问题

今天碰到一个问题,需要根据不同的UA跳转不同的页面,实现手机访问网站跳转到Wap页面,PC端访问跳转到PC端的页面。根据之前的了解,在Nginx做了UA区分,实现了根据UA跳转页面。

结果,解决一个问题,同时引入一个更大问题。

CND中缓存的文档出现了错位,因为请求地址是相同的,结果有时候存储的是PC页面,有时候存储的是WAP页面。

分析问题

CDN缓存了错误的数据,是因为相同地址、不同UA,Nginx返回的内容是不一样的。

在CDN缓存过期时,UA是手机,Nginx就会返回Wap页面,缓存的就是Wap页面。

在CDN缓存过期时,UA是PC,Nginx就会返回PC页面,缓存的就是PC页面。

解决问题

针对这种架构,有两种解决方法,一种是,在PC页面中增加UA校验,如果是手机,就跳转页面,代码如下:

var originHref = window.location.href;
var afterSchema = originHref.indexOf("://") + 3;
var domainEnd = originHref.indexOf("/", afterSchema);
var domain = originHref.substring(afterSchema, domainEnd);
if (domain !== 'm.howardliu.cn') {
    if (navigator) {
        var ua = navigator.userAgent.toLowerCase();
        if (/mobile|android|iphone|ipad|phone/i.test(ua)) {
            window.location.href = originHref.substring(0, afterSchema) + 'm.howardliu.cn' + originHref.substring(domainEnd);
        }
    }
}

这种方式可以解决问题,但是不够优雅,因为需要浏览器先加载PC的页面,然后再进行检查,也就是会加载两个页面。如果网速比较慢时,就会在PC站驻留比较长的时间,然后才跳转到wap站,用户体验差。

那就得只能在CDN进行处理,CDN服务商提供的解决方案是,在响应头中增加vary: Accept-Encoding, User-Agent,具体配置是在nginx中配置:

add_header Vary "Accept-Encoding, User-Agent";

果然解决了问题。

学非探其花,要深拔其根。http响应头中的Vary是什么作用呢?为什么配置了它之后,CDN就能够区分不同的响应信息了?

RFC 7231中的7.1.4. Vary给出了解释:

The “Vary” header field in a response describes what parts of a request message, aside from the method, Host header field, and request target, might influence the origin server’s process for selecting and representing this response. The value consists of either a single asterisk (“*”) or a list of header field names (case-insensitive).

Vary = “*” / 1#field-name

A Vary field value of “” signals that anything about the request might play a role in selecting the response representation, possibly including elements outside the message syntax (e.g., the client’s network address). A recipient will not be able to determine whether this response is appropriate for a later request without forwarding the request to the origin server. A proxy MUST NOT generate a Vary field with a “” value.

A Vary field value consisting of a comma-separated list of names indicates that the named request header fields, known as the selecting header fields, might have a role in selecting the representation. The potential selecting header fields are not limited to those defined by this specification.

For example, a response that contains

Vary: accept-encoding, accept-language

indicates that the origin server might have used the request’s Accept-Encoding and Accept-Language fields (or lack thereof) as determining factors while choosing the content for this response.

An origin server might send Vary with a list of fields for two purposes:

  1. To inform cache recipients that they MUST NOT use this response to satisfy a later request unless the later request has the same values for the listed fields as the original request (Section 4.1 of [RFC7234]). In other words, Vary expands the cache key required to match a new request to the stored cache entry.
  2. To inform user agent recipients that this response is subject to content negotiation (Section 5.3) and that a different representation might be sent in a subsequent request if additional parameters are provided in the listed header fields (proactive negotiation).

An origin server SHOULD send a Vary header field when its algorithm for selecting a representation varies based on aspects of the request message other than the method and request target, unless the variance cannot be crossed or the origin server has been deliberately configured to prevent cache transparency. For example, there is no need to send the Authorization field name in Vary because reuse across users is constrained by the field definition (Section 4.2 of [RFC7235]). Likewise, an origin server might use Cache-Control directives (Section 5.2 of [RFC7234]) to supplant Vary if it considers the variance less significant than the performance cost of Vary’s impact on caching.

简单来说,Vary是http的一种约定,当客户端与服务端之间,针对相同的URL请求,服务端存在不同内容的响应,且响应内容是根据请求头的不同,返回不同时,就需要Vary指定需要区分的请求头了。

比如,上面提到的需要根据UA来区分,那响应信息里面就需要包括:Vary: User-Agent。如果需要根据Accept-EncodingAccept-Language进行区分,响应头就需要包含Vary: Accept-Encoding, Accept-Language。这样做,其实是为了客户端能够很好的对结果缓存。

总结一下:

  1. Vary是服务端添加在响应头的信息
  2. Vary的内容来源于请求头
  3. 实现完整协议的客户端(包括浏览器和缓存服务器)缓存数据时,会将Vary一起缓存。