3. VCL - Varnish 配置语言

本节介绍如何使用 Varnish 配置语言 (VCL) 告诉 Varnish 如何处理 HTTP 流量。

Varnish 有一个很棒的配置系统。大多数其他系统都使用配置指令,基本上就是打开或关闭许多开关。而我们选择使用一种名为 VCL 的域专用语言来实现这一点。

每个入站请求都会流经 Varnish,您可以通过修改 VCL 代码来影响请求的处理方式。您可以将某些请求引导到特定的后端,可以更改请求和响应,或者让 Varnish 根据请求或响应的任意属性采取各种行动。这使得 Varnish 成为一个功能极其强大的 HTTP 处理器,而不仅仅是用于缓存。

Varnish 将 VCL 翻译成二进制代码,然后在请求到达时执行。VCL 对性能的影响可以忽略不计。

VCL 文件分为多个子程序。不同的子程序在不同的时间执行。其中一个在收到请求时执行,另一个在从后端服务器获取文件时执行。

如果您没有在子程序中调用某个操作,而子程序又到达终点,Varnish 就会执行一些内置的 VCL 代码。您会在 Varnish Cache 随附的 buildin.vcl 文件中看到注释掉的 VCL 代码。

3.1 VCL 语法

VCL 从 C 继承了很多东西,它读起来很像简单的 C 或 Perl。

块由大括号分隔,语句以分号结尾,注释可以根据您自己的喜好用 C、C++ 或 Perl 编写。

请注意,VCL 不包含任何循环或跳转语句。

本节概述了语法中更重要的部分。有关 VCL 语法的完整文档,请参阅 参考资料中的VCL 。

3.1.1 字符串

基本字符串用“…”括起来,并且不能包含换行符。

反斜杠并不特殊,因此例如在regsub()中,您不需要执行“计算反斜杠”波尔卡:

regsub("barf", "(b)(a)(r)(f)", "\4\3\2p") -> "frap"

长字符串包含在 {” … “} 或 “”” … “”” 中。它们可以包含任何字符,包括“、换行符和除 NUL (0x00) 字符之外的其他控制字符。如果您确实想要在字符串中包含 NUL 字符,可以使用 VMOD 来创建此类字符串。

3.1.2 访问控制列表 (ACL)

ACL 声明创建并初始化一个命名访问控制列表,该列表稍后可用于匹配客户端地址:

acl local {
  "localhost";         // myself
  "192.0.2.0"/24;      // and everyone on the local network
  ! "192.0.2.23";      // except for the dialin router
}

如果 ACL 条目指定了 Varnish 无法解析的主机名,它将匹配与之比较的任何地址。因此,如果它前面有一个否定标记,它将拒绝与之比较的任何地址,这可能不是您想要的。但是,如果该条目包含在括号中,则它将被忽略。

要将 IP 地址与 ACL 进行匹配,只需使用匹配运算符:

if (client.ip ~ local) {
  return (pipe);
}

在 7.0 之前的 Varnish 版本中,ACL 始终会在 VSL 日志中发出VCL_acl记录,从 7.0 及以后版本,必须通过指定+log标志显式启用此功能:

acl local +log {
  "localhost";         // myself
  "192.0.2.0"/24;      // and everyone on the local network
  ! "192.0.2.23";      // except for the dialin router
}

3.1.3 运算符

VCL 中提供了以下运算符。请参阅下面的示例,了解示例。

=

赋值运算符。

==

比较。

匹配。可以与正则表达式或 ACL 一起使用。

否定。

&&

逻辑

||

逻辑

3.1.4 内置子程序

Varnish 有许多内置子程序,每个事务流经 Varnish 时都会调用这些子程序。这些内置子程序都被命名为 vcl_*,并在 VCL 步骤中进行了说明。

内置子程序的处理以返回 () 结束(请参阅 VCL 操作)。

内置 VCL 还包含由内置子程序调用的自定义辅助子程序,这些子程序也以 vcl_ 作为前缀。

3.1.5 自定义子例程

您可以编写自己的子程序,但其名称不能以 vcl_ 开头。

子程序通常用于将代码分组,以便于阅读或重复使用:

sub pipe_if_local {
  if (client.ip ~ local) {
    return (pipe);
  }
}

要调用子程序,请使用调用关键字,后面跟上子程序的名称:

call pipe_if_local;

VCL 中的自定义子程序不接受参数,也不返回值。

如上例所示,return ()(请参阅 VCL 操作)从顶层内置子程序(请参阅 VCL 步骤)开始返回值,该子程序可能经过多个步骤,最终调用了自定义子程序。

无操作的 return 在调用子程序的调用语句后恢复执行。

3.2 内置 VCL

每当加载一个 VCL 程序时,内置的 VCL 都会附加到程序中。vcl 内置子程序(VCL 步骤)有一个特殊属性,它们可以多次出现,其结果是所有内置子程序的串联。

例如,我们来看看下面的代码段:

sub vcl_recv {
    # loaded code for vcl_recv
}

提供给编译器的有效 VCL 如下所示:

sub vcl_recv {
    # loaded code for vcl_recv
    # built-in code for vcl_recv
}

这就是如何保证所Varnish处理状态至少有一个.return (<action>)

一般建议不要从已加载的代码中一成不变地返回,而是让 Varnish 执行内置代码,因为内置代码基本上为 HTTP 缓存提供了合理的默认行为。

3.2.1 内置子程序分割

然而,内置的 VCL 规则并不总能在状态结束时生效,因此一些子程序(如 vcl_recv)会被拆分成多个调用其他子程序的程序。
按照惯例,这些辅助子程序以其操作的变量命名,如 req 或 beresp,这样就可以规避默认行为。
例如,内置 VCL 中的 vcl_recv 可以防止在客户端有 cookie 时进行缓存。如果您可以相信您的后端会始终指定响应是否可缓存,而不管请求是否包含 cookie,您就可以这样做:

sub vcl_req_cookie {
    return;
}

这样,内置 vcl_recv 的所有其他默认行为都会执行,只有 cookie 处理会受到影响。

另一个例子是内置 vcl_backend_response 如何将负 TTL 视为不缓存信号。这是一种将响应标记为不可缓存的历史机制,但前提是内置vcl_backend_response 不能通过return () 来规避。

不过,在后端可能是另一台 Varnish 服务器的多层架构中,你可能希望缓存过期的响应,以允许交付受保护对象,并在下一次获取时重新验证。这可以通过以下代码段来实现:

sub vcl_beresp_stale {
    if (beresp.ttl + beresp.grace > 0s) {
        return;
    }
}

这种粒度以及内置子程序拆分的总体目标是,在不放弃整个逻辑的情况下,规避默认规则的某个特定方面。

3.2.2 内置 VCL 参考

Varnish 安装时可能会提供 builtin.vcl 文件的副本,但 __varnishd __是确定附加到任何加载的 VCL 的代码的参考。

VCL 编译分两次进行:

第一次只编译内置的 VCL、

第二步是编译加载的 VCL 和内置 VCL 的连接。

内置 VCL 中的任何 VCL 子程序都可以扩展,在这种情况下,加载的 VCL 代码将在内置代码之前执行。

3.3 请求和响应 VCL 对象

在 VCL 中,有几个重要的对象需要注意。可以使用 VCL 访问和操作这些对象。

req

请求对象。当 Varnish 收到请求后,就会创建并填充 req 对象。vcl_recv 中的大部分工作都是在 req 对象上或通过 req 对象完成的。

bereq

后端请求对象。Varnish 在将其发送至后端之前会构建该对象。它基于 req 对象。

beresp

后台响应对象。它包含来自后端的对象的标头。如果要修改来自服务器的响应,请在 vcl_backend_response 中修改此对象。

resp

HTTP 响应在交付给客户端之前的状态。它通常在 vcl_deliver 中修改。

obj

缓存中存储的对象。只读。

3.4 后端服务器

Varnish 有一个 "后端 "或 "源 "服务器的概念。后端服务器是提供 Varnish 将加速的内容的服务器。

我们的第一项任务是告诉 Varnish 在哪里可以找到它的后端服务器。启动您最喜欢的文本编辑器,打开相关的 VCL 文件。

在顶部的某个地方,会有一个看起来有点像这样的部分:

# backend default {
#     .host = "127.0.0.1";
#     .port = "8080";
# }

我们去掉这个文本节中的注释标记,使它看起来像…:

backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

现在,这段配置在 Varnish 中定义了一个名为 default 的后端。当 Varnish 需要从这个后端获取内容时,它会连接到 localhost (127.0.0.1) 上的 8080 端口。

Varnish 可以定义多个后端,你甚至可以将多个后端连接成后端集群,以达到负载平衡的目的。

3.4.1 “无”后端

也可以使用以下语法将后端声明为 “无”::

backend default none;

none后端很特殊:

  • 所有声明的后端none比较相等:
backend a none;
backend b none;

sub vcl_recv {
    set req.backend_hint = a;
    if (req.backend_hint == b) {
        return (synth(200, "this is true"));
    }
}
  • 当在布尔上下文中使用时,后端none的计算结果为:false
backend nil none;

sub vcl_recv {
    set req.backend_hint = nil;
    if (! req.backend_hint) {
        return (synth(200, "We get here"));
    }
}
  • 当监控发现没有健康的后端时,他们通常会返回后 none

3.4.2 多个后端

在某些情况下,您可能需要 Varnish 来缓存来自多个服务器的内容。您可能希望 Varnish 将所有 URL 映射到一台主机,也可能不这样做。有很多选择。

比方说,我们需要在 PHP 网站中引入 Java 应用程序。假设我们的 Java 应用程序要处理以 /java/ 开头的 URL。

我们设法让它在端口 8000 上运行。现在,让我们来看看 default.vcl:

backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

我们添加一个新的后端:

backend java {
    .host = "127.0.0.1";
    .port = "8000";
}

现在我们需要告诉 Varnish 将差异 URL 发送到哪里。让我们看看vcl_recv:

sub vcl_recv {
    if (req.url ~ "^/java/") {
        set req.backend_hint = java;
    } else {
        set req.backend_hint = default;
    }
}

其实很简单。让我们停下来想一想。正如你所看到的,你可以根据任意数据定义如何选择后端。你想把移动设备发送到不同的后端?没问题。if (req.http.User-agent ~ /mobile/) … 就可以了。

如果没有明确的后端选择,Varnish 将继续使用默认后端。如果没有名为默认的后端,vcl 中找到的第一个后端将被用作默认后端。

3.4.3 Varnish 中的后端和虚拟主机

Varnish 完全支持虚拟主机。不过,由于虚拟主机从未明确声明过,因此它们的工作方式可能有点违反直觉。你可以在 vcl_recv 中设置传入 HTTP 请求的路由。如果希望根据虚拟主机进行路由,只需检查 req.http.host。

可以这样设置

sub vcl_recv {
    if (req.http.host ~ "foo.com") {
        set req.backend_hint = foo;
    } elsif (req.http.host ~ "bar.com") {
        set req.backend_hint = bar;
    }
}

请注意,第一个正则表达式将匹配“foo.com”、“www.foo.com”、“zoop.foo.com”以及以“foo.com”结尾的任何其他主机。在此示例中,这是有意为之,但您可能希望它更紧凑一些,也许改为依赖运算==符,如下所示:

sub vcl_recv {
    if (req.http.host == "foo.com" || req.http.host == "www.foo.com") {
        set req.backend_hint = foo;
    }
}

3.4.4 通过代理连接

从此版本开始,Varnish 可以使用 PROXY2 协议通过代理连接到实际目的地。可能还会添加其他协议。

目前,该功能的典型用例是通过 TLS onloader 进行 TLS 加密连接。加载器需要支持动态连接,其目标地址信息取自 __PROXY2 __前言。例如,在使用 2.2 版或更高版本的 haproxy 时,可将此片段作为配置开加载器的基础:

# to review and adjust:
# - maxconn
# - bind ... mode ...
# - ca-file ...
#
listen sslon
       mode    tcp
       maxconn 1000
       bind    /path/to/sslon accept-proxy mode 777
       stick-table type ip size 100
       stick   on dst
       server  s00 0.0.0.0:0 ssl ca-file /etc/ssl/certs/ca-bundle.crt alpn http/1.1 sni fc_pp_authority
       server  s01 0.0.0.0:0 ssl ca-file /etc/ssl/certs/ca-bundle.crt alpn http/1.1 sni fc_pp_authority
       server  s02 0.0.0.0:0 ssl ca-file /etc/ssl/certs/ca-bundle.crt alpn http/1.1 sni fc_pp_authority
       # ...
       # A higher number of servers improves TLS session caching

然后,在同一服务器/命名空间上运行的 Varnish 可以使用 带有该功能的加载器.via(请参阅Attribute .via):

backend sslon {
  .path = "/path/to/sslon";
}

backend destination {
  .host = "my.https.service";
  .port = "443";
  .via = sslon;
}

如果该属性不同于 ,.authority则可用于指定连接的SNI.host

3.4.5 Directors

您还可以将多个后端分组为一组后端。这些群组被称为 directors。这样可以提高性能和弹性。

您可以定义多个后端,并将它们组合到一个 Director 中。这需要加载一个 VMOD、一个 Varnish 模块,然后在 vcl_init 中调用某些操作:

import directors;    # load the directors

backend server1 {
    .host = "192.168.0.10";
}
backend server2 {
    .host = "192.168.0.11";
}

sub vcl_init {
    new bar = directors.round_robin();
    bar.add_backend(server1);
    bar.add_backend(server2);
}

sub vcl_recv {
    # send all traffic to the bar director:
    set req.backend_hint = bar.backend();
}

该 Director 是轮循 Director。这意味着该 Director 将以轮循方式分配收到的请求。还有一种随机 Director,它以随机方式分配请求,你猜对了,就是随机方式。如果这些还不够,你还可以编写自己的 Director(请参阅编写 Director)。

但如果其中一台服务器宕机了怎么办?Varnish 能把所有请求导向健康的服务器吗?当然可以。这就是健康检查发挥作用的地方。

3.4.6 健康检查

让我们建立一个包含两个后端和健康检查的 Director。首先让我们定义后端:

backend server1 {
    .host = "server1.example.com";
    .probe = {
        .url = "/";
        .timeout = 1s;
        .interval = 5s;
        .window = 5;
        .threshold = 3;
    }
}

backend server2 {
    .host = "server2.example.com";
    .probe = {
        .url = "/";
        .timeout = 1s;
        .interval = 5s;
        .window = 5;
        .threshold = 3;
    }
}

这里的新功能是探针。在本示例中,Varnish 将每 5 秒钟检查一次每个后端是否健康,1 秒钟后计时结束。如果过去 5 次中有 3 次成功,则认为后端是健康的,否则将标记为 “生病”。

更多信息请参阅 __VCL __文档中的 "后端健康探测器 "部分。

现在我们定义 “director”:

import directors;

sub vcl_init {
    new vdir = directors.round_robin();
    vdir.add_backend(server1);
    vdir.add_backend(server2);
}

您可以将此 vdir director 用作请求的后台提示(backend_hint),就像使用简单的后台一样。Varnish 不会向被标记为不健康的主机发送流量。

如果所有后端都宕机了,Varnish 也可以提供过时的内容。有关如何启用此功能的更多信息,请参阅 Grace mode 和 keep

请注意,Varnish 会为所有加载的 VCL 保持运行健康探测器。Varnish 会合并看似相同的探针,因此如果要加载大量 VCL,请注意不要更改探针配置。卸载 VCL 会丢弃探针。有关如何操作的详细信息,请参阅 ref:reference-vcl-director。

3.4.7 分层

默认情况下,大多数控制器的.backend()方法都会返回对控制器本身的引用。这允许分层,如下例所示:

import directors;

sub vcl_init {
    new dc1 = directors.round_robin();
    dc1.add_backend(server1A);
    dc1.add_backend(server1B);

    new dc2 = directors.round_robin();
    dc2.add_backend(server2A);
    dc2.add_backend(server2B);

    new dcprio = directors.fallback();
    dcprio.add_backend(dc1);
    dcprio.add_backend(dc2);
}

初始化后,如果服务器 1A 或服务器 1B 都健康,dcprio.backend() 将解析到服务器 1A 或服务器 1B;如果只有一个健康,则解析到其中一个。只有当两个服务器都有问题时,才会从 dc2 返回一个健康的服务器(如果有的话)。

3.4.8 Director 决议

实际解析发生在 vcl_backend_fetch {} 或 vcl_pipe {} 返回后准备后端连接时。

在某些情况下(如服务器分片),VCL 中已经需要解析结果。在这种情况下,可以使用 .resolve() 方法,如本例所示:

set req.backend_hint = dcprio.backend().resolve();

将此语句与前面的示例代码一起使用时,req.backend_hint 将被设置为服务器*后端之一,如果它们都有问题,则设置为无后端。

.resolve() 适用于任何 BACKEND 类型的对象。

3.4.9 连接池

打开后端连接总是要付出代价的:根据连接类型和后端基础设施的不同,打开一个新连接的开销从本地 Unix 域套接字(参见后端定义 .path 属性)的相当低到通过可能的多跳和长网络路径建立可能的多个 TCP 和/或 TLS 连接的巨大开销不等。无论开销有多大,它总是存在的。

因此,由于重复使用现有连接通常可减少开销和延迟,Varnish 默认会将后端连接集中起来:每当后端任务完成时,使用过的连接不会被关闭,而是会被添加到连接池中,以供以后重用。为了避免连接被重复使用,可以在__ vcl_backend_fetch__ 中添加 Connection: close http 标头。

虽然后端是按 VCL 定义的,但连接池可跨 VCL 甚至跨后端使用:默认情况下,池连接的标识符由后端定义的 .host/.port 或 .path 属性构建(VMOD 可使用自定义标识符)。因此,只要两个后端共享相同的地址信息,无论它们在哪个 VCL 中定义,它们的连接都来自一个共同的池。

如果后端没有主动关闭,Varnish 会保持池中连接的开放状态,直到 backend_idle_timeout 到期。

3.5 散列

在内部,当 Varnish 在缓存中存储内容时,会使用一个哈希键来索引,以便再次找到该对象。在默认设置中,这个键是根据 URL、Host: 头信息计算的,如果没有,则根据服务器的 IP 地址计算:

sub vcl_hash {
    hash_data(req.url);
    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }
    return (lookup);
}

如您所见,它首先对 req.url 进行散列,然后对 req.http.host 进行散列(如果存在的话)。值得注意的是,Varnish 在散列之前不会小写主机名或 URL,因此理论上 "Varnish.org/"和 "varnish.org/"会导致不同的缓存条目。不过,浏览器倾向于小写主机名。

您可以更改哈希值的内容。这样,您就可以让 Varnish 根据任意标准向不同的客户端提供不同的内容。

比方说,您想根据用户的 IP 地址为其提供不同语言的网页。您需要一些 Vmod 来获取国家代码,然后将其放入哈希值中。它可能是这样的

在 vcl_recv:

set req.http.X-Country-Code = geoip.lookup(client.ip);

然后添加 vcl_hash:

sub vcl_hash {
    hash_data(req.http.X-Country-Code);
}

由于没有 return(lookup),内置 VCL 会像往常一样将 URL、Host: 或服务器 IP# 添加到哈希值中。

如果vcl_hash返回了,即:

sub vcl_hash {
    hash_data(req.http.X-Country-Code);
    return(lookup);
}

那么只有国家代码是重要的,Varnish 会返回看似随机的对象,忽略 URL(但它们总是有正确的 X-国家代码)。

3.6 宽容模式和保持

有时,您希望 Varnish 提供有些陈旧的内容,而不是等待来自后台的新鲜对象。例如,如果您运行的是一个新闻网站,提供几秒钟前的主页面并不是问题,因为这样可以加快网站的加载速度。

在 Varnish 中,这可以通过使用宽容模式来实现。一个关联的想法是保持,这里也有解释。

3.6.1 宽容模式

当多个客户端请求访问同一个页面时,Varnish 会向后台发送一个请求,并在从后台获取一个副本时搁置其他请求。在某些产品中,这被称为 “请求聚合”,Varnish 会自动这样做。

如果您每秒提供数以千计的命中率,等待请求的队列可能会变得很大。存在两个潜在的问题 - 一个是“惊群问题”(thundering herd problem)- 突然释放上千个线程来提供内容可能会使负载激增。其次 - 没有人喜欢等待。

将对象的 Grace 设置为正值告诉 Varnish,它应该在 TTL 过期后为客户端提供该对象一段时间,同时 Varnish 会获取该对象的新版本。默认值由运行时参数控制default_grace

3.6.2 保持

设置对象的 keep 会告诉 Varnish,它应该在缓存中保留某个对象一段时间。设置 keep 的原因是使用该对象来构建有条件的 GET 后端请求(使用 If-Modified-Since: 和/或 Ìf-None-Match: 头信息),允许后端回复 304 Not Modified(未修改),这对后端来说可能更有效率,还能节省重新传输未修改正文的时间。

这些值是相加的,因此如果 grace 为 10 秒,keep 为 1 分钟,那么对象将在 TTL 过期后在缓存中存活 70 秒。

3.6.3 设置宽容和保持

我们可以使用 VCL 使 Varnish 将所有对象保留超过其 TTL 10 分钟,并有 2 分钟的宽限期:

sub vcl_backend_response {
     set beresp.grace = 2m;
     set beresp.keep = 8m;
}

3.6.4 宽容和保持的效果

对于大多数用户来说,设置默认的宽限期和/或为每个对象设置一个合适的宽限期就足够了。默认的 VCL 会做正确的事,并按上述方式行事。不过,如果您想自定义 Varnish 的行为方式,就应该了解一些工作细节。

sub vcl_recvreturn (lookup) 结束时(这是默认行为),Varnish 将在缓存中查找匹配对象。然后,如果只找到一个 TTL 已耗尽的对象,Varnish 会考虑以下情况:

  • 该对象是否已经在后台请求?

  • 该对象是否在宽限期内?

然后,Varnish 使用以下规则做出反应:

  • 如果宽限期已过并且没有正在进行的后端请求,则立即调用sub vcl_miss ,并且该对象将用作 304 候选者。

  • 如果宽限期已过并且有正在进行的后端请求,则该请求将等待,直到后端请求完成。

  • 如果没有对该对象的后端请求,则会安排一个。

  • 假设对象将被传递,则立即调用sub vcl_hit

请注意,后端获取是异步发生的,当新对象进入其中时,它将替换我们已经获得的对象。

如果您没有定义自己的,则使用默认的。它看起来像这样:sub vcl_hit

sub vcl_hit {
     return (deliver);
}

请注意,(在 sub vcl_hit 中)obj.ttl + obj.grace > 0s 条件将始终为真。在早期版本(6.0.0 及更早版本)中,情况并非如此,有必要在内置 VCL 中进行测试,以确保 “keep objects”(缓存中 TTL 和 grace 均已耗尽的对象)不会传送到客户端。

在当前版本中,当只有 "保留对象 "可用时,将调用sub vcl_miss ,并开始获取新对象。

3.6.5 行为不当的服务器

Varnish 的一个主要功能是保护您免受网络和应用服务器故障的影响。

如果启用了 "健康检查 "功能,就可以检查后端是否生病,并在出现问题时修改行为。具体方法如下:

sub vcl_backend_response {
     set beresp.grace = 24h;
     // no keep - the grace should be enough for 304 candidates
}

sub vcl_recv {
     if (std.healthy(req.backend_hint)) {
          // change the behavior for healthy backends: Cap grace to 10s
          set req.grace = 10s;
     }
}

在上面的示例中,设置了特殊变量 req.grace。其效果是,当后端处于健康状态时,grace大于10秒的对象将具有10秒的有效宽限期。当后端出现问题时,将触发默认的VCL,并使用较长的宽限期。

此外,您可能希望在后端获取返回 5xx 错误时停止插入缓存:

sub vcl_backend_response {
     if (beresp.status >= 500 && bereq.is_bgfetch) {
          return (abandon);
     }
}

3.6.6 总结

宽容模式允许 Varnish 向客户端提供稍微陈旧的内容,同时从后端获取新版本。结果是以更低的成本实现更快的加载时间。

可以通过设置 req.grace 来限制查找过程中的宽限期,然后在宽限期到来时改变行为。这样做通常是为了根据后台的健康状况改变有效的宽限期。

3.7 单独的 VCL 文件

在同一个 Varnish 中拥有多个不同的虚拟主机是一个非常典型的用例,从 Varnish 5.0 开始,可以为单独的虚拟主机或任何其他不同的请求子集提供单独的 VCL 文件。

假设我们要处理varnish.org一个 VCL 文件和varnish-cache.org另一个 VCL 文件。

首先加载两个 VCL 文件:

vcl.load vo_1 /somewhere/vo.vcl
vcl.load vc_1 /somewhere/vc.vcl

这些是 100% 正常的 VCL 文件,就像您在 Varnish 实例上仅运行该单个域时的样子一样。

接下来我们需要将 VCL 标签指向它们:

vcl.label l_vo vo_1
vcl.label l_vc vc_1

接下来,我们编写顶级 VCL 程序,该程序根据请求中的 Host: 标头分支到其他两个程序:

import std;

# We have to have a backend, even if we do not use it
backend default { .host = "127.0.0.1"; }

sub vcl_recv {
    # Normalize host header
    set req.http.host = std.tolower(req.http.host);

    if (req.http.host ~ "\.?varnish\.org$") {
        return (vcl(l_vo));
    }
    if (req.http.host ~ "\.?varnish-cache\.org$") {
        return (vcl(l_vc));
    }
    return (synth(302, "http://varnish-cache.org"));
}

sub vcl_synth {
    if (resp.status == 301 || resp.status == 302) {
        set resp.http.location = resp.reason;
        set resp.reason = "Moved";
        return (deliver);
    }
}

最后,我们加载顶层 VCL 并将其设为活动 VCL:

vcl.load top_1 /somewhere/top.vcl
vcl.use top_1

如果要更新分离的 VCL 之一,请加载新的 VCL 并更改标签以指向它:

vcl.load vo_2 /somewhere/vo.vcl
vcl.label l_vo vo_2

如果您想更改顶级 VCL,请像往常一样进行操作:

vcl.load top_2 /somewhere/top.vcl
vcl.use top_2

细节,细节,细节:

  • 所有请求始终从活动 VCL 开始 - 来自以下位置的请求vcl.use

  • 只有 VCL 标签可以在return(vcl(name)). 如果没有这个限制,每次单独的 VCL 之一发生更改时,都必须重新加载顶级 VCL。

  • 您只能从活动 VCL 切换 VCL。如果您从单独的 VCL 之一尝试,您将得到 503

  • vcl.discard如果任何 VCL 包含,则无法删除 VCL 标签(带有)return(vcl(name_of_that_label))

  • 您无法删除带有标签的 VCL。

  • 此代码在测试用例 c00077 中进行了测试

  • 这是一个非常新的功能,可能会改变

  • 我们非常希望得到您的反馈

3.8 使用内联 C 来扩展 Varnish

您可以使用内联 C来扩展 Varnish。请注意,这样可能会严重搞乱 Varnish。C 代码在 Varnish Cache 进程中运行,因此如果您的代码生成段错误,则缓存将崩溃。

内联 C 语言最早的用途之一是向系统日志记录日志:

# The include statements must be outside the subroutines.
C{
        #include <syslog.h>
}C

sub vcl_something {
        C{
                syslog(LOG_INFO, "Something happened at VCL line XX.");
        }C
}

要使用内联 C,您需要使用参数启用它vcc_allow_inline_c

3.9 VCL 示例

这些是展示 VCL 语言的一些功能的简短示例集合。

3.9.1 在 VCL 中操作请求标头

比方说,我们要删除网络服务器 /images 目录中所有对象的 cookie:

sub vcl_recv {
  if (req.url ~ "^/images") {
    unset req.http.cookie;
  }
}

现在,当请求被处理到后端服务器时,将没有 cookie 头信息。最有趣的一行是 if 语句。它匹配请求对象中的 URL,并将其与正则表达式进行匹配。请注意匹配操作符。如果匹配成功,请求的 Cookie: 标头将被取消设置(删除)。

3.9.2 改变后端响应

在这里,如果来自后台的对象符合特定条件,我们将覆盖该对象的 TTL:

sub vcl_backend_response {
   if (bereq.url ~ "\.(png|gif|jpg)$") {
     unset beresp.http.set-cookie;
     set beresp.ttl = 1h;
  }
}

我们还删除了所有 Set-Cookie 标头,以避免创建 “命中未命中”对象。请参阅 VCL 操作

3.9.3 ACLs

您可以使用 acl 关键字创建已命名的访问控制列表。您可以使用匹配操作员将客户端的 IP 地址与 ACL 匹配:

# Who is allowed to purge....
acl local {
    "localhost";
    "192.168.1.0"/24; /* and everyone on the local network */
    ! "192.168.1.23"; /* except for the dialin router */
}

sub vcl_recv {
  if (req.method == "PURGE") {
    if (client.ip ~ local) {
       return(purge);
    } else {
       return(synth(403, "Access denied."));
    }
  }
}

3.9.4 添加 WebSocket 支持

WebSockets 是一种通过 HTTP 创建基于流的双向通道的技术。

要通过 Varnish 运行 WebSocket,您需要通过管道传输请求并复制 Upgrade 和 Connection 标头,如下所示:

sub vcl_recv {
    if (req.http.upgrade ~ "(?i)websocket") {
        return (pipe);
    }
}

sub vcl_pipe {
    if (req.http.upgrade) {
        set bereq.http.upgrade = req.http.upgrade;
        set bereq.http.connection = req.http.connection;
    }
}

3.10 设备检测

设备检测是根据请求中提供的 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/

3.10.1 在同一 URL 上提供不同的内容

其中涉及的技巧有

1.检测客户端(非常简单,只需包含 devicedetect.vcl 并调用即可)。

2.想办法向后台发出客户端类的信号。这包括设置标头、更改标头,甚至更改后端请求 URL。

  1. 修改来自后端的任何响应,添加缺失的 "Vary "头信息,以便 Varnish 在内部处理此问题。

  2. 修改发送到客户端的输出,以免我们无法控制的缓存提供错误的内容。

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

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

基本情况是,Varnish 在后端请求中添加 "X-UA-Device "HTTP 头,而后端在响应的 "Vary "头中提到内容依赖于此头。

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

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

3.10.2 移动客户端的不同后端

如果您有一个为移动客户端提供页面的不同后台,或在 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);
    }
}

3.10.3 重定向移动客户端

如果您想重定向手机客户端,可以使用以下代码段。

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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ava实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现
### 回答1: DevExpress VCL是一种用于开发Windows桌面应用程序的组件库。它提供了丰富的可视化控件和工具,可以帮助开发人员快速构建现代化和功能强大的应用程序界面。 DevExpress VCL的v21版本是该组件库的最新版本。它引入了许多新功能和改进,以提高开发效率和用户体验。一些新特性包括: 1. 新的控件-新增了一些新的控件,如Ribbon Control和Navigation Pane Control,以实现更现代化的界面设计和导航体验。 2. 数据可视化-新增了一些用于数据可视化的控件,如Chart Control和Gauge Control,可以帮助开发人员以直观的方式显示和分析数据。 3. 界面优化-优化了界面渲染和性能,以提供更流畅的用户体验。同时,还增加了一些界面动画效果,提高了应用程序的交互性。 4. 主题支持-提供了多个内置的主题,使开发人员可以轻松地改变应用程序的外观和样式,以满足不同用户的需求。 5. 设计时支持-改进了设计时体验,使开发人员可以更轻松地使用和配置控件。还提供了一些辅助工具,如布局管理器和界面设计器,以帮助开发人员更方便地设计和排列界面元素。 综上所述,DevExpress VCL v21是一个功能强大的组件库,可以大大简化Windows桌面应用程序的开发过程,并提供了丰富的控件和功能来改善用户体验。无论是新项目还是现有项目的升级,开发人员都可以从这个版本中受益。 ### 回答2: devexpress.vcl.v21是DevExpress公司开发的一个VCL组件库,用于在Delphi和C++Builder等开发环境中创建基于Windows的桌面应用程序。 devexpress.vcl.v21提供了丰富的组件和控件,包括各种界面元素如按钮、文本框、列表框、树形控件等,以及数据展示和操作相关的组件如表格控件、数据输入控件等。这些组件具有强大的功能和灵活的配置选项,可以帮助开发人员快速构建功能丰富、界面美观的桌面应用程序。 devexpress.vcl.v21还提供了一系列增强的功能和工具,如数据绑定、数据筛选、样式管理等,使开发人员可以轻松处理数据和用户界面之间的交互,并实现高度定制化的应用程序。 此外,devexpress.vcl.v21还支持跨平台开发,可以在不同版本的Delphi和C++Builder上使用,同时也支持多种版本的Windows操作系统。 总而言之,devexpress.vcl.v21是一个功能强大、易用性高的VCL组件库,它能够帮助开发人员加快开发速度,提高应用程序的质量和用户体验。无论是初学者还是经验丰富的开发人员,都能够从中受益,并得到更好的开发效果。 ### 回答3: DevExpress是一个软件开发工具公司,提供各种面向开发人员的工具和解决方案。DevExpress.VCL.V21是该公司针对Visual Component Library(VCL)框架开发的一个版本。 VCL是一个用于Delphi和C++ Builder的图形用户界面(GUI)开发框架。该框架提供了一组可用于构建Windows应用程序的组件和控件。DevExpress.VCL.V21是DevExpress为VCL框架提供的最新版本,其中包含了许多新的组件、控件和功能。 使用DevExpress.VCL.V21,开发人员可以轻松地创建功能强大、专业级别的Windows应用程序。该版本提供了各种各样的控件,如按钮、文本框、列表框、表格等,这些控件具有丰富的外观和交互特性。另外,DevExpress.VCL.V21还提供了一些专门用于数据处理、图表绘制、报表生成等的组件,以满足不同开发需求。 除了组件和控件,DevExpress.VCL.V21还提供了一些工具和功能,以提高开发效率。例如,它提供了强大的设计时和运行时界面编辑器,开发人员可以通过可视化方式设计和布局界面。此外,该版本还提供了一些辅助工具,如代码生成器、查找替换工具等,以简化开发流程。 总之,DevExpress.VCL.V21是一个功能强大的软件开发工具,适用于使用VCL框架开发Windows应用程序的开发人员。它提供了丰富的控件和组件,以及一系列的辅助工具和功能,可帮助开发人员快速构建专业水平的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值