设备检测
设备检测就是基于请求中的User-agent用户代理字符串来找出相应的内容返回来给客户端。
举例说,对于小屏幕的手机客户端或者在复杂的网络环境下,减小发送文件的数量,或者给客户端提供它能够解析的视频流。
在这种情况下有一些典型的处理策略:
- 重定向到另外一个地址
- 对于特殊的客户端使用不同的后端服务器
- 改变后端请求为了让后端发送定制的内容
为了让这种策略更加容易理解,我们在这里,假设 req.http.X-UA-Device 头存在并且对于每一类客户端都是唯一的。
可以简单的这样设置:
sub vcl_recv {
if (req.http.User-Agent ~ "(?i)iphone" {
set req.http.X-UA-Device = "mobile-iphone";
}
}
有一些不同的商业或者免费的产品来做分组和更加详细的客户端识别。对于基本的和以社区为基础的正则表达式集合,可以查看https://github.com/varnishcache/varnish-devicedetect/。
同一URL返回不同内容
这里涉及的对策如下:
- 检测客户端(很简单,只要引入 devicedetect.vcl 并调用它即可)
- 根据客户端的类型找出怎样发送请求到后端。这包含了些例子,设置header,改变header,甚至改变发送到后端的url。
- 修改后端响应,增加miss标志'Vary'头。这样varnish就会对其进行内部处理。
- 修改发送到客户端的输出,这样任何缓存失控,我们都不返回错误的内容。
这一切我们都需要做,同时我们还要确保每个设备类型每个url都只会获取到同一个缓存对象。
例1:发送http头道后端
最基本的情况是Varnish增加X-UA-Device头在发送到后端的请求上,然后后端在响应header中添加Vary头,响应的内容依赖发送到后端的header。
从Varnish角度看一切开箱即用。
VCL:
sub vcl_recv {
# call some detection engine that set req.http.X-UA-Device
}
# req.http.X-UA-Device is copied by Varnish into bereq.http.X-UA-Device
# so, this is a bit counterintuitive. The backend creates content based on
# the normalized User-Agent, but we use Vary on X-UA-Device so Varnish will
# use the same cached object for all U-As that map to the same X-UA-Device.
#
# If the backend does not mention in Vary that it has crafted special
# content based on the User-Agent (==X-UA-Device), add it.
# If your backend does set Vary: User-Agent, you may have to remove that here.
sub vcl_backend_response {
if (bereq.http.X-UA-Device) {
if (!beresp.http.Vary) { # no Vary at all
set beresp.http.Vary = "X-UA-Device";
} elseif (beresp.http.Vary !~ "X-UA-Device") { # add to existing Vary
set beresp.http.Vary = beresp.http.Vary + ", X-UA-Device";
}
}
# comment this out if you don't want the client to know your
# classification
set beresp.http.X-UA-Device = bereq.http.X-UA-Device;
}
# to keep any caches in the wild from serving wrong content to client #2
# behind them, we need to transform the Vary on the way out.
sub vcl_deliver {
if ((req.http.X-UA-Device) && (resp.http.Vary)) {
set resp.http.Vary = regsub(resp.http.Vary, "X-UA-Device", "User-Agent");
}
}
例2:规范User-Agent字符串
另一种处理设备类型的方式是重写或者规范User-Agent头,然后发送给后端。
例如:
User-Agent: Mozilla/5.0 (Linux; U; Android 2.2; nb-no; HTC Desire Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1
变为:
User-Agent: mobile-android
这种方法适用于你的后端服务器处理不需要原始的头信息。一种可能性就是可以用于CGI脚本,只需设置下预定义的头信息在可以用的脚本中。
VCL:
sub vcl_recv {
# call some detection engine that set req.http.X-UA-Device
}
# override the header before it is sent to the backend
sub vcl_miss { if (req.http.X-UA-Device) { set req.http.User-Agent = req.http.X-UA-Device; } }
sub vcl_pass { if (req.http.X-UA-Device) { set req.http.User-Agent = req.http.X-UA-Device; } }
# standard Vary handling code from previous examples.
sub vcl_backend_response {
if (bereq.http.X-UA-Device) {
if (!beresp.http.Vary) { # no Vary at all
set beresp.http.Vary = "X-UA-Device";
} elseif (beresp.http.Vary !~ "X-UA-Device") { # add to existing Vary
set beresp.http.Vary = beresp.http.Vary + ", X-UA-Device";
}
}
set beresp.http.X-UA-Device = bereq.http.X-UA-Device;
}
sub vcl_deliver {
if ((req.http.X-UA-Device) && (resp.http.Vary)) {
set resp.http.Vary = regsub(resp.http.Vary, "X-UA-Device", "User-Agent");
}
}
例3:添加设备类型作为一个get参数
如果所有方法都不行,那么你可以将设备类型作为get参数放在query串中。
http://example.com/article/1234.html --> http://example.com/article/1234.html?devicetype=mobile-iphone
客户端本身并不会看到自己的类型,只有后端请求改变了。
VCL:
sub vcl_recv {
# call some detection engine that set req.http.X-UA-Device
}
sub append_ua {
if ((req.http.X-UA-Device) && (req.method == "GET")) {
# if there are existing GET arguments;
if (req.url ~ "\?") {
set req.http.X-get-devicetype = "&devicetype=" + req.http.X-UA-Device;
} else {
set req.http.X-get-devicetype = "?devicetype=" + req.http.X-UA-Device;
}
set req.url = req.url + req.http.X-get-devicetype;
unset req.http.X-get-devicetype;
}
}
# do this after vcl_hash, so all Vary-ants can be purged in one go. (avoid ban()ing)
sub vcl_miss { call append_ua; }
sub vcl_pass { call append_ua; }
# Handle redirects, otherwise standard Vary handling code from previous
# examples.
sub vcl_backend_response {
if (bereq.http.X-UA-Device) {
if (!beresp.http.Vary) { # no Vary at all
set beresp.http.Vary = "X-UA-Device";
} elseif (beresp.http.Vary !~ "X-UA-Device") { # add to existing Vary
set beresp.http.Vary = beresp.http.Vary + ", X-UA-Device";
}
# if the backend returns a redirect (think missing trailing slash),
# we will potentially show the extra address to the client. we
# don't want that. if the backend reorders the get parameters, you
# may need to be smarter here. (? and & ordering)
if (beresp.status == 301 || beresp.status == 302 || beresp.status == 303) {
set beresp.http.location = regsub(beresp.http.location, "[?&]devicetype=.*$", "");
}
}
set beresp.http.X-UA-Device = bereq.http.X-UA-Device;
}
sub vcl_deliver {
if ((req.http.X-UA-Device) && (resp.http.Vary)) {
set resp.http.Vary = regsub(resp.http.Vary, "X-UA-Device", "User-Agent");
}
}
对于手机客户端使用不同的后端服务器
如果你对手机客户端页面使用不同的后端服务器,或者在VCL中有任何特殊需求,你可以使用'X-UA-Device'头就像这样:
backend mobile {
.host = "10.0.0.1";
.port = "80";
}
sub vcl_recv {
# call some detection engine
if (req.http.X-UA-Device ~ "^mobile" || req.http.X-UA-device ~ "^tablet") {
set req.backend_hint = mobile;
}
}
sub vcl_hash {
if (req.http.X-UA-Device) {
hash_data(req.http.X-UA-Device);
}
}
跳转到手机页面
如果你想跳转到手机客户端页面,那么你可以使用下面的代码片段:
VCL:
sub vcl_recv {
# call some detection engine
if (req.http.X-UA-Device ~ "^mobile" || req.http.X-UA-device ~ "^tablet") {
return(synth(750, "Moved Temporarily"));
}
}
sub vcl_synth {
if (obj.status == 750) {
set obj.http.Location = "http://m.example.com" + req.url;
set obj.status = 302;
return(deliver);
}
}