VCL语言 #

一、VCL概述 #

1.1 什么是VCL #

Varnish Configuration Language (VCL) 是Varnish的核心配置语言,用于定义请求处理流程、缓存策略和响应处理逻辑。

1.2 VCL特点 #

特点 说明
声明式 描述"做什么"而非"怎么做"
状态机 请求在不同状态间流转
可编程 支持条件判断、正则匹配
可扩展 支持VMOD模块扩展
热重载 配置可动态加载

1.3 VCL版本 #

vcl
vcl 4.1;
版本 Varnish版本 特性
4.0 4.x 基础VCL语法
4.1 6.x+ 新变量、增强特性

二、基础语法 #

2.1 注释 #

vcl
# 单行注释

// 单行注释(C风格)

/*
 * 多行注释
 * 跨越多行
 */

2.2 字符串 #

vcl
# 双引号字符串
set req.http.X-Value = "hello world";

# 包含引号
set req.http.X-Value = "say \"hello\"";

# 拼接字符串
set req.http.X-Value = "prefix" + req.url + "suffix";

# 多行字符串
set resp.http.X-Body = {"This is a
multi-line
string"};

2.3 数字 #

vcl
# 整数
set beresp.ttl = 3600;

# 时间单位
set beresp.ttl = 1h;    # 1小时
set beresp.ttl = 30m;   # 30分钟
set beresp.ttl = 60s;   # 60秒
set beresp.ttl = 1d;    # 1天

2.4 正则表达式 #

vcl
# 匹配操作符 ~
if (req.url ~ "\.(css|js)$") {
    return (hash);
}

# 不匹配操作符 !~
if (req.url !~ "^/api/") {
    return (pass);
}

# 捕获组
if (req.url ~ "^/image/(.*)\.jpg$") {
    # \1 引用第一个捕获组
}

2.5 运算符 #

vcl
# 比较运算符
==  !=  <  >  <=  >=

# 逻辑运算符
&&  ||  !

# 字符串匹配
~   !~

# 赋值
=

2.6 条件语句 #

vcl
# if语句
if (req.method == "GET") {
    return (hash);
}

# if-else语句
if (req.method == "GET") {
    return (hash);
} else {
    return (pass);
}

# if-elseif-else语句
if (req.method == "GET") {
    return (hash);
} elseif (req.method == "POST") {
    return (pass);
} else {
    return (synth(405, "Method Not Allowed"));
}

# 多条件判断
if (req.url ~ "^/api/" && req.method == "POST") {
    return (pass);
}

三、数据类型 #

3.1 基本类型 #

类型 说明 示例
STRING 字符串 “hello”
INT 整数 3600
DURATION 时间间隔 1h, 30m
IP IP地址 “192.168.1.1”
BOOL 布尔值 true, false
REGEX 正则表达式 “.css$”
HEADER HTTP头 req.http.Host
BODY HTTP体 仅用于synthetic

3.2 类型转换 #

vcl
# 字符串转整数
set req.http.X-Int = std.integer(req.http.X-Str, 0);

# 整数转字符串
set req.http.X-Str = std.integer2string(123);

# 时间转换
set req.http.X-Time = std.time2integer(now);

四、变量系统 #

4.1 变量分类 #

类别 前缀 可用范围
客户端 client.* vcl_recv, vcl_hash, vcl_hit, vcl_miss, vcl_deliver
请求 req.* vcl_recv, vcl_hash, vcl_hit, vcl_miss, vcl_deliver
后端请求 bereq.* vcl_backend_fetch, vcl_backend_response
后端响应 beresp.* vcl_backend_response
响应 resp.* vcl_deliver, vcl_synth
对象 obj.* vcl_hit, vcl_deliver
存储 storage.* 全局

4.2 客户端变量 #

vcl
# client.ip - 客户端IP地址
if (client.ip ~ admin_acl) {
    # 管理员访问
}

# client.identity - 客户端标识
set client.identity = req.http.X-User-ID;

4.3 请求变量 #

vcl
# req.url - 请求URL
if (req.url ~ "^/api/") {
    return (pass);
}

# req.method - 请求方法
if (req.method == "GET") {
    return (hash);
}

# req.http.* - 请求头
set req.http.X-Forwarded-For = client.ip;
unset req.http.Cookie;

# req.proto - HTTP协议版本
if (req.proto ~ "HTTP/1.0") {
    # HTTP/1.0请求
}

# req.restarts - 重启次数
if (req.restarts > 0) {
    # 已重启的请求
}

# req.xid - 请求唯一ID
set resp.http.X-Request-ID = req.xid;

4.4 后端请求变量 #

vcl
# bereq.url - 后端请求URL
set bereq.url = regsub(bereq.url, "^/api", "");

# bereq.method - 后端请求方法
set bereq.method = "GET";

# bereq.http.* - 后端请求头
set bereq.http.X-Forwarded-For = client.ip;
unset bereq.http.Cookie;

# bereq.connect_timeout - 连接超时
set bereq.connect_timeout = 5s;

# bereq.first_byte_timeout - 首字节超时
set bereq.first_byte_timeout = 30s;

# bereq.between_bytes_timeout - 字节间超时
set bereq.between_bytes_timeout = 5s;

4.5 后端响应变量 #

vcl
# beresp.status - 响应状态码
if (beresp.status == 200) {
    set beresp.ttl = 1h;
}

# beresp.http.* - 响应头
unset beresp.http.Set-Cookie;

# beresp.ttl - 缓存TTL
set beresp.ttl = 1h;

# beresp.grace - 宽限时间
set beresp.grace = 1h;

# beresp.keep - 保持时间
set beresp.keep = 10m;

# beresp.uncacheable - 是否不可缓存
if (beresp.http.Set-Cookie) {
    set beresp.uncacheable = true;
}

# beresp.backend.name - 后端名称
set resp.http.X-Backend = beresp.backend.name;

4.6 响应变量 #

vcl
# resp.status - 响应状态码
set resp.status = 200;

# resp.http.* - 响应头
set resp.http.X-Cache = "HIT";

# resp.proto - HTTP协议版本

4.7 对象变量 #

vcl
# obj.status - 对象状态码
if (obj.status == 200) {
    # 成功响应
}

# obj.ttl - 对象TTL
if (obj.ttl > 0s) {
    return (deliver);
}

# obj.hits - 命中次数
set resp.http.X-Hits = obj.hits;

# obj.grace - 宽限时间
if (obj.ttl + obj.grace > 0s) {
    return (deliver);
}

五、子程序详解 #

5.1 子程序概览 #

text
请求流程:

Client Request
      │
      ▼
┌─────────────┐
│  vcl_recv   │ ◄─── 接收请求
└──────┬──────┘
       │
       ├─── hash ──► vcl_hash ──► lookup ──┬──► hit ──► vcl_hit
       │                                    │
       ├─── pass ──► vcl_pass               │
       │                                    │
       └─── pipe ──► vcl_pipe               └──► miss ──► vcl_miss
                                                      │
                                                      ▼
                                             ┌─────────────────┐
                                             │vcl_backend_fetch│
                                             └────────┬────────┘
                                                      │
                                                      ▼
                                                   Backend
                                                      │
                                                      ▼
                                             ┌──────────────────┐
                                             │vcl_backend_response│
                                             └────────┬─────────┘
                                                      │
                                                      ▼
                                             ┌─────────────┐
                                             │ vcl_deliver │ ◄─── 返回响应
                                             └─────────────┘

5.2 vcl_init #

VCL加载时执行,用于初始化:

vcl
import directors;

sub vcl_init {
    # 初始化负载均衡器
    new cluster = directors.round_robin();
    cluster.add_backend(server1);
    cluster.add_backend(server2);
    
    # 初始化其他VMOD
}

5.3 vcl_recv #

接收客户端请求时执行:

vcl
sub vcl_recv {
    # 1. 方法检查
    if (req.method != "GET" && 
        req.method != "HEAD" &&
        req.method != "PUT" &&
        req.method != "POST" &&
        req.method != "TRACE" &&
        req.method != "OPTIONS" &&
        req.method != "DELETE") {
        return (pipe);
    }
    
    # 2. 非GET/HEAD不缓存
    if (req.method != "GET" && req.method != "HEAD") {
        return (pass);
    }
    
    # 3. 处理PURGE
    if (req.method == "PURGE") {
        if (!client.ip ~ purge_acl) {
            return (synth(405, "Not allowed."));
        }
        return (purge);
    }
    
    # 4. 移除不必要的头
    unset req.http.Cookie;
    unset req.http.X-Forwarded-For;
    set req.http.X-Forwarded-For = client.ip;
    
    # 5. 静态资源
    if (req.url ~ "\.(css|js|png|gif|jpg|jpeg|ico|svg|woff|woff2)$") {
        return (hash);
    }
    
    # 6. API请求
    if (req.url ~ "^/api/") {
        return (pass);
    }
    
    return (hash);
}

返回值:

返回值 说明
hash 计算缓存键,查找缓存
pass 跳过缓存,直接请求后端
pipe 管道模式,直接转发
purge 清除缓存
synth 返回合成响应
vcl 切换到其他VCL

5.4 vcl_hash #

计算缓存键时执行:

vcl
sub vcl_hash {
    # 默认:URL + Host
    hash_data(req.url);
    if (req.http.Host) {
        hash_data(req.http.Host);
    }
    
    # 基于Cookie区分
    if (req.http.Cookie ~ "session_id") {
        hash_data(regsub(req.http.Cookie, ".*session_id=([^;]+).*", "\1"));
    }
    
    # 基于设备类型区分
    if (req.http.User-Agent ~ "Mobile") {
        hash_data("mobile");
    }
    
    return (lookup);
}

5.5 vcl_hit #

缓存命中时执行:

vcl
sub vcl_hit {
    # 检查对象是否过期
    if (obj.ttl >= 0s) {
        # 未过期,正常返回
        return (deliver);
    }
    
    # 已过期,检查宽限时间
    if (obj.ttl + obj.grace > 0s) {
        # 在宽限期内,返回过期对象并后台更新
        return (deliver);
    }
    
    # 完全过期,重新获取
    return (restart);
}

返回值:

返回值 说明
deliver 返回缓存对象
restart 重新处理请求
pass 跳过缓存

5.6 vcl_miss #

缓存未命中时执行:

vcl
sub vcl_miss {
    # 可以修改请求
    set bereq.http.X-Cache-Status = "MISS";
    
    return (fetch);
}

返回值:

返回值 说明
fetch 从后端获取
pass 跳过缓存

5.7 vcl_pass #

跳过缓存时执行:

vcl
sub vcl_pass {
    # 可以在此添加逻辑
    set bereq.http.X-Pass = "true";
}

sub vcl_backend_response {
    if (bereq.http.X-Pass) {
        set beresp.uncacheable = true;
        set beresp.ttl = 0s;
    }
}

5.8 vcl_backend_fetch #

向后端发送请求前执行:

vcl
sub vcl_backend_fetch {
    # 修改后端请求
    set bereq.http.Host = "backend.example.com";
    set bereq.http.X-Real-IP = client.ip;
    
    # 移除不必要的头
    unset bereq.http.Cookie;
    
    # 设置超时
    set bereq.first_byte_timeout = 30s;
}

5.9 vcl_backend_response #

收到后端响应时执行:

vcl
sub vcl_backend_response {
    # 设置默认TTL
    if (beresp.ttl <= 0s) {
        set beresp.ttl = 5m;
    }
    
    # 静态资源长缓存
    if (bereq.url ~ "\.(css|js|png|gif|jpg|jpeg|ico|svg|woff|woff2)$") {
        set beresp.ttl = 7d;
        unset beresp.http.Set-Cookie;
    }
    
    # HTML短缓存
    if (beresp.http.Content-Type ~ "text/html") {
        set beresp.ttl = 5m;
    }
    
    # 不缓存错误响应
    if (beresp.status >= 500) {
        set beresp.ttl = 0s;
        set beresp.uncacheable = true;
    }
    
    # 不缓存带Set-Cookie的响应
    if (beresp.http.Set-Cookie) {
        set beresp.ttl = 0s;
        set beresp.uncacheable = true;
    }
    
    # 设置宽限时间
    set beresp.grace = 1h;
    
    # 启用ESI
    if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
        unset beresp.http.Surrogate-Control;
        set beresp.do_esi = true;
    }
}

返回值:

返回值 说明
deliver 缓存并返回
retry 重试后端请求
abandon 放弃请求

5.10 vcl_deliver #

返回响应给客户端时执行:

vcl
sub vcl_deliver {
    # 添加缓存状态头
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
        set resp.http.X-Cache-Hits = obj.hits;
    } else {
        set resp.http.X-Cache = "MISS";
    }
    
    # 添加请求ID
    set resp.http.X-Request-ID = req.xid;
    
    # 移除敏感头
    unset resp.http.X-Powered-By;
    unset resp.http.Server;
    unset resp.http.Via;
    
    # 添加安全头
    set resp.http.X-Content-Type-Options = "nosniff";
    set resp.http.X-Frame-Options = "SAMEORIGIN";
    set resp.http.X-XSS-Protection = "1; mode=block";
}

5.11 vcl_synth #

返回合成响应时执行:

vcl
sub vcl_synth {
    # 自定义错误页面
    set resp.http.Content-Type = "text/html; charset=utf-8";
    
    if (resp.status == 403) {
        synthetic({"<!DOCTYPE html>
<html>
<head><title>403 Forbidden</title></head>
<body>
<h1>403 Forbidden</h1>
<p>Access denied.</p>
</body>
</html>"});
    } elseif (resp.status == 404) {
        synthetic({"<!DOCTYPE html>
<html>
<head><title>404 Not Found</title></head>
<body>
<h1>404 Not Found</h1>
<p>The requested page was not found.</p>
</body>
</html>"});
    } else {
        synthetic({"<!DOCTYPE html>
<html>
<head><title>"} + resp.status + " " + resp.reason + {"</title></head>
<body>
<h1>"} + resp.status + " " + resp.reason + {"</h1>
<p>"} + resp.reason + {"</p>
</body>
</html>"});
    }
    
    return (deliver);
}

5.12 vcl_backend_error #

后端错误时执行:

vcl
sub vcl_backend_error {
    set beresp.http.Content-Type = "text/html; charset=utf-8";
    synthetic({"<!DOCTYPE html>
<html>
<head><title>Backend Error</title></head>
<body>
<h1>503 Service Unavailable</h1>
<p>The backend server is temporarily unavailable.</p>
<p>Please try again later.</p>
</body>
</html>"});
    return (deliver);
}

5.13 vcl_fini #

VCL卸载时执行:

vcl
sub vcl_fini {
    # 清理资源
    return (ok);
}

六、内置函数 #

6.1 字符串函数 #

vcl
# regsub - 正则替换(首次)
set req.url = regsub(req.url, "^/old/", "/new/");

# regsuball - 正则替换(全部)
set req.url = regsuball(req.url, "/+", "/");

# ban - 添加BAN规则
ban("req.url ~ ^/images/");

# synthetic - 合成响应体
synthetic("Error message");

6.2 时间函数 #

vcl
# now - 当前时间
set beresp.http.Date = now;

# 时间计算
set beresp.ttl = 1h;
set beresp.grace = 30m;

6.3 其他函数 #

vcl
# return - 返回状态
return (hash);
return (pass);
return (deliver);

# call - 调用子程序
call custom_subroutine;

七、std模块函数 #

7.1 导入模块 #

vcl
import std;

7.2 常用函数 #

vcl
# std.log - 记录日志
std.log("Custom log message");

# std.syslog - 发送syslog
std.syslog(6, "Info message");

# std.timestamp - 记录时间戳
std.timestamp("custom_point");

# std.integer - 字符串转整数
set req.http.X-Int = std.integer(req.http.X-Str, 0);

# std.real - 字符串转浮点数
set req.http.X-Float = std.real(req.http.X-Str, 0.0);

# std.duration - 字符串转时间
set beresp.ttl = std.duration(req.http.X-TTL, 60s);

# std.set_ip_tos - 设置IP TOS
std.set_ip_tos(0x10);

# std.healthy - 检查后端健康
if (std.healthy(backend)) {
    # 后端健康
}

# std.port - 获取端口
set req.http.X-Port = std.port(server.ip);

# std.rollback - 回滚请求
std.rollback(bereq);

# std.collect - 收集头
std.collect(req.http.Cookie);

# std.toupper - 转大写
set req.http.X-Upper = std.toupper(req.http.X-Value);

# std.tolower - 转小写
set req.http.X-Lower = std.tolower(req.http.X-Value);

八、自定义子程序 #

8.1 定义子程序 #

vcl
sub remove_cookies {
    unset req.http.Cookie;
}

sub add_security_headers {
    set resp.http.X-Content-Type-Options = "nosniff";
    set resp.http.X-Frame-Options = "SAMEORIGIN";
    set resp.http.X-XSS-Protection = "1; mode=block";
}

sub set_cache_headers {
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }
}

8.2 调用子程序 #

vcl
sub vcl_recv {
    call remove_cookies;
    return (hash);
}

sub vcl_deliver {
    call add_security_headers;
    call set_cache_headers;
}

九、完整VCL示例 #

vcl
vcl 4.1;

import std;

# 后端定义
backend default {
    .host = "127.0.0.1";
    .port = "8080";
    .connect_timeout = 5s;
    .first_byte_timeout = 90s;
    .between_bytes_timeout = 2s;
}

# ACL定义
acl purge {
    "localhost";
    "192.168.1.0"/24;
}

# 初始化
sub vcl_init {
    # VMOD初始化
}

# 请求处理
sub vcl_recv {
    # PURGE处理
    if (req.method == "PURGE") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed."));
        }
        return (purge);
    }
    
    # BAN处理
    if (req.method == "BAN") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed."));
        }
        ban("req.url ~ " + req.url);
        return (synth(200, "Banned."));
    }
    
    # 方法检查
    if (req.method != "GET" && req.method != "HEAD") {
        return (pass);
    }
    
    # 移除静态资源Cookie
    if (req.url ~ "\.(css|js|png|gif|jpg|jpeg|ico|svg|woff|woff2)$") {
        unset req.http.Cookie;
        return (hash);
    }
    
    # API不缓存
    if (req.url ~ "^/api/") {
        return (pass);
    }
    
    # 健康检查
    if (req.url == "/healthcheck") {
        return (synth(200, "OK"));
    }
    
    return (hash);
}

# 缓存键计算
sub vcl_hash {
    hash_data(req.url);
    if (req.http.Host) {
        hash_data(req.http.Host);
    }
    return (lookup);
}

# 缓存命中
sub vcl_hit {
    if (obj.ttl >= 0s) {
        return (deliver);
    }
    if (obj.ttl + obj.grace > 0s) {
        return (deliver);
    }
    return (restart);
}

# 缓存未命中
sub vcl_miss {
    return (fetch);
}

# 后端请求
sub vcl_backend_fetch {
    set bereq.http.X-Forwarded-For = client.ip;
}

# 后端响应
sub vcl_backend_response {
    if (beresp.ttl <= 0s) {
        set beresp.ttl = 5m;
    }
    
    if (bereq.url ~ "\.(css|js|png|gif|jpg|jpeg|ico|svg|woff|woff2)$") {
        set beresp.ttl = 7d;
        unset beresp.http.Set-Cookie;
    }
    
    if (beresp.http.Set-Cookie) {
        set beresp.uncacheable = true;
        set beresp.ttl = 0s;
    }
    
    set beresp.grace = 1h;
}

# 响应处理
sub vcl_deliver {
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
        set resp.http.X-Cache-Hits = obj.hits;
    } else {
        set resp.http.X-Cache = "MISS";
    }
    
    unset resp.http.X-Powered-By;
    unset resp.http.Server;
}

# 合成响应
sub vcl_synth {
    set resp.http.Content-Type = "text/html; charset=utf-8";
    synthetic({"<!DOCTYPE html>
<html>
<head><title>"} + resp.status + " " + resp.reason + {"</title></head>
<body>
<h1>"} + resp.status + " " + resp.reason + {"</h1>
</body>
</html>"});
    return (deliver);
}

十、总结 #

本章我们学习了:

  1. VCL概述:声明式配置语言,状态机模型
  2. 基础语法:注释、字符串、数字、正则、运算符
  3. 数据类型:STRING、INT、DURATION、IP、BOOL等
  4. 变量系统:客户端、请求、后端、响应、对象变量
  5. 子程序详解:vcl_init到vcl_fini完整流程
  6. 内置函数:字符串、时间、状态返回
  7. std模块:常用工具函数
  8. 自定义子程序:模块化配置

掌握VCL语言后,让我们进入下一章,学习缓存策略!

最后更新:2026-03-28