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);
}
十、总结 #
本章我们学习了:
- VCL概述:声明式配置语言,状态机模型
- 基础语法:注释、字符串、数字、正则、运算符
- 数据类型:STRING、INT、DURATION、IP、BOOL等
- 变量系统:客户端、请求、后端、响应、对象变量
- 子程序详解:vcl_init到vcl_fini完整流程
- 内置函数:字符串、时间、状态返回
- std模块:常用工具函数
- 自定义子程序:模块化配置
掌握VCL语言后,让我们进入下一章,学习缓存策略!
最后更新:2026-03-28