设备检测

设备检测是根据请求中提供的User-Agent字符串确定向客户端提供哪种内容。

例如,这种情况的用例是向小屏幕和高延迟网络上的移动客户端发送缩小大小的文件,或者提供客户端理解的流视频编解码器。

有几种典型的策略可用于此类情况:1)重定向到另一个URL。2)特殊客户端使用不同的后台。3)修改后台请求,使后台发送定制内容。

也许为了使这些策略更容易理解,在这种情况下,我们假设 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上提供不同的内容

涉及的技巧包括:1.检测客户端(非常简单,只需包括 devicedetect.vcl 然后叫它)。2.弄清楚如何向后端发送客户端类信号。例如,这包括设置报头、更改报头甚至更改后端请求URL。3.修改来自后端的任何响应,以添加缺少的‘Variable’标头,以便Varnish对此进行内部处理。4.修改发送到客户端的输出,以便我们控制之外的任何缓存不会提供错误的内容。

所有这些都需要在确保每个设备类的每个URL只获得一个缓存对象的同时完成。

示例1:向后端发送HTTP报头

基本情况是,Varnish在后端请求上添加了‘X-UA-Device’HTTP头,并且后端在响应‘Variable’头中提到内容依赖于这个头。

从瓦尼什的角度来看,一切都是开箱即用的。

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: 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参数。

客户端本身看不到此分类,只有后端请求被更改。

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);
    }
}