Device detection
设备检测基于请求中的User-agent找出哪种内容返回给客户端。
使用Device detection情况的一个例子,发送给小屏幕的手机客户端或者复杂的网络环境,减少发送文件的数量,或者提供给不能解码的客户端一个video解码器。
有些使用这种方案的典型场景:
- 重写url
- 使用不同的后端给客户端
- 改变后端请求为了让后端发送裁剪的内容
可能为了便于理解,下文中假定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/varnish/varnish-devicedetect/。
Serve the different content on the same URL
这个戏法表明:
- 验证客户端(相当简单,只需要包含devicedetect.vcl,然后调用它)
- 根据客户端的类型找出怎样发送请求到后端。这包含了些例子,设置header,改变header,甚至改变发送到后端的url。
- 修改后端响应,增加miss标志‘Vary‘头。
- 修改发送到客户端的输出,这样如果任何cache不受控,可以不提供错误的内容给客户端。
所有这些都需要确定我们每种设备只会获得一个缓存的对象。
Example 1: Send HTTP header to backend
基本情况是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。 } # req.http.X-UA-Device is copied by Varnish into bereq.http.X-UA-Device # req.http.X-UA-Device 被复制到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. # # 因此这是有点反直觉的。后端创建响应内容基于正常的User-Agent,但是在X-UA-Device使用 # Vary,varnish将使用相同的已缓存的对象,对所有的U-As映射到相同的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. # 如果基于User-Agent的后端响应不带有Vary,在header种增加它。 # If your backend does set Vary: User-Agent, you may have to remove that here. # 如果后端设置了Vary:User-Agent,你可以在此删除它,或者设置成其他的。 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"); # 在resp.http.Vary的值中将X-UA-Device替换为User-Agent。 } }
Example 2: Normalize the User-Agent string
标准化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
如果你在后端上不需要源http header的任何信息可以这样设置。一种可能性就是使用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 # 发送到后端之前重写header。 sub vcl_miss { if (req.http.X-UA-Device) { set bereq.http.User-Agent = req.http.X-UA-Device; } } sub vcl_pass { if (req.http.X-UA-Device) { set bereq.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"); } }
Example 3: Add the device class as a GET query parameter
以GET参数作为设备类型
上面两种方式都不行的话你可使用GET参数来增加设备类型。
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; # 重写url unset req.http.X-get-devicetype; # 移除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"); } }
Different backend for mobile clients
对手机客户端使用不同的后端。
如果对于手机客户端你有不同的后端来提供页面,或者在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); } }
Redirecting mobile clients
如果想重定向手机客户端可以使用下面的代码片段:
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); } }
自己的例子
sub vcl_recv { if (req.http.host == "www.example.com" && req.http.User-Agent ~ "(?i)MIDP|WAP|UP.Browser|Smartphone|Obigo|Mobile|AU.Browser|wxd.Mms|WxdB.Browser|CLDC|UP.Link|KM.Browser|UCWEB|SEMC\-Browser|Mini|Symbian|Palm|Nokia|Panasonic|MOT|SonyEricsson|NEC|Alcatel|Ericsson|BENQ|BenQ|Amoisonic|Amoi|Capitel|PHILIPS|SAMSUNG|Lenovo|Mitsu|Motorola|SHARP|WAPPER|LG|EG900|CECT|Compal|kejian|Bird|BIRD|G900/V1.0|Arima|CTL|TDG|Daxian|DAXIAN|DBTEL|Eastcom|EASTCOM|PANTECH|Dopod|Haier|HAIER|KONKA|KEJIAN|LENOVO|Soutec|SOUTEC|SAGEM|SEC|SED|EMOL|INNO55|ZTE|iPhone|Android|Windows CE|Wget|Java|Opera") { set req.backend_hint = mobile; return(pass); } }