mcpField
X
xvcl

xvcl

github.com/fastly开发3640 次安装1 周前更新
代码仓库

关于 xvcl

用于编写 Fastly VCL 的技能,借助 XVCL 转译器加入编译期元编程。它指导开发者编写 .xvcl 文件、编译为 VCL,并使用 Falco 在本地测试。

主要功能

  1. 01编译期常量与表达式
  2. 02循环、条件语句和模板
  3. 03函数与内联宏
  4. 04支持包含文件并检测循环引用
  5. 05将 .xvcl 编译为 .vcl
  6. 06使用 Falco 本地模拟 VCL

适用场景

  1. 01生成重复的后端或路由配置
  2. 02构建数据驱动的 VCL 表
  3. 03在本地编译并模拟 Fastly VCL 修改

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/fastly/fastly-agent-toolkit --skill xvcl

安装后在 Claude Code 中输入/xvcl确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
backends.md
caching.md
crypto.md
headers.md
strings.md
subroutines.md
tables-acls.md
testing-vcl.md
vcl-syntax.md
xvcl-directives.md

详细介绍

触发条件和范围

在以下情况下触发:XVCL、.xvcl 文件、VCL 转译器、VCL 元编程、VCL 上下文中的 #const/#for/#def/#inline、编写 VCL 脚本、编写 VCL 并在本地运行,或任何 Fastly VCL 编写任务。

以下情况不要触发:不涉及 XVCL 的现有 .vcl 文件调试、Fastly API/CLI 操作、Fastly Compute 或 Terraform——即使提到了 VCL 也不要触发。

如果用户明确要求使用纯 VCL,请保留 .vcl 文件,不要引入 XVCL。 请遵循用户明确指定的工具和测试模式,而不是默认示例。

使用 XVCL 编写 VCL

XVCL 是一种为 Fastly VCL 添加元编程功能的 VCL 转译器。编写 .xvcl 文件,编译为 .vcl,然后使用 Falco 测试或部署到 Fastly。所有 XVCL 结构都会在编译时解析,运行时开销为零。

快速开始

编译需要 uvx,由 uv 提供;本地检查使用 Falco。

# Compile with uvx
uvx xvcl main.xvcl -o main.vcl

# Lint the output
falco lint main.vcl

# Run locally — this is how you "run" VCL on your machine
falco simulate main.vcl
# Listens on localhost:3124 by default
# Then test with: curl http://localhost:3124/

除非用户明确要求使用其他测试模式,否则请编译并运行 falco simulate 进行本地执行。 仅进行 lint 不会执行 VCL。

最小可运行示例

后端命名:Fastly VCL 要求后端使用带 F_ 前缀的名称(例如,F_origin、F_api)。切勿使用 backend default——falco 会拒绝它。请始终在 vcl_recv 中显式设置 req.backend。

#const ORIGIN_HOST = "api.example.com"
#const REGIONS = [("us", "us.example.com"), ("eu", "eu.example.com")]

#for name, host in REGIONS
backend F_{{name}} {
  .host = "{{host}}";
  .port = "443";
  .ssl = true;
}
#endfor

sub vcl_recv {
  #FASTLY recv
  set req.backend = F_us;
  return (lookup);
}

sub vcl_deliver {
  #FASTLY deliver
  set resp.http.X-Served-By = "edge";
  return (deliver);
}

XVCL 指令摘要

阅读 xvcl-directives.md,了解每条指令的完整语法和示例。

常量 — #const
#const NAME = value              // type auto-inferred
#const NAME TYPE = value         // explicit type
#const TTL INTEGER = 3600
#const ORIGIN = "origin.example.com"
#const ENABLED BOOL = true
#const DOUBLE_TTL = TTL * 2      // expressions supported
#const BACKENDS = ["web1", "web2"] // lists
#const PAIRS = [("api", 8080), ("web", 80)] // tuples

常量仅在编译时有效,不会成为 VCL 变量。请始终使用 {{NAME}} 输出其值。在 VCL 中直接使用常量名称(例如,error 200 GREETING;)会原样保留为字符串字面量,从而生成无效的 VCL。请改用 error 200 "{{GREETING}}";。

在模板中使用:"{{TTL}}"、{{ORIGIN}}、backend F_{{name}} { ... }

模板表达式 — {{ }}
{{CONST_NAME}}                   // constant substitution
{{PORT * 2}}                     // arithmetic
{{hex(255)}}                     // → "0xff"
{{format(42, '05d')}}            // → "00042"
{{len(BACKENDS)}}                // list length
{{value if condition else other}} // ternary

内置函数:range()、len()、str()、int()、hex()、format()、enumerate()、min()、max()、abs()

For 循环 — #for / #endfor
#for i in range(5)               // 0..4
#for i in range(2, 8)            // 2..7
#for item in LIST_CONST          // iterate list
#for name, host in TUPLES        // tuple unpacking
#for idx, item in enumerate(LIST) // index + value
使用循环创建表

使用 #for 循环填充 VCL table 声明,以实现 O(1) 查找,而不是生成内联 if 链。

#const REDIRECTS = [
  ("/blog", "/articles"),
  ("/about-us", "/about"),
  ("/products/old-widget", "/products/widget-v2")
]

// O(1) hash-table lookup — the right pattern for data-driven VCL
table redirects STRING {
#for old_path, new_path in REDIRECTS
  "{{old_path}}": "{{new_path}}",
#endfor
}

sub vcl_recv {
  if (table.contains(redirects, req.url.path)) {
    error 801 table.lookup(redirects, req.url.path);
  }
}

优先使用 #for 循环填充 VCL table 声明,而不是生成内联 if 链。表可进行 O(1) 哈希查找,也是 Fastly 中处理任何数据驱动的路由、重定向或配置的惯用模式。

条件语句 — #if / #elif / #else / #endif
#if PRODUCTION
  set req.http.X-Env = "prod";
#elif STAGING
  set req.http.X-Env = "staging";
#else
  set req.http.X-Env = "dev";
#endif

支持:布尔常量、比较运算符(==、!=、<、>)、逻辑运算符(and、or、not)。

变量简写 — #let
#let cache_key STRING = req.url.path;
// expands to:
// declare local var.cache_key STRING;
// set var.cache_key = req.url.path;
函数 — #def / #enddef
// Single return value
#def normalize_path(path STRING) -> STRING
  declare local var.result STRING;
  set var.result = std.tolower(path);
  return var.result;
#enddef

// Tuple return (multiple values)
#def parse_pair(s STRING) -> (STRING, STRING)
  declare local var.key STRING;
  declare local var.value STRING;
  set var.key = regsub(s, ":.*", "");
  set var.value = regsub(s, "^[^:]*:", "");
  return var.key, var.value;
#enddef

// Call sites
set var.clean = normalize_path(req.url.path);
set var.k, var.v = parse_pair("host:example.com");

函数会编译为 VCL 子例程,参数通过 req.http.X-Func-* 标头传递。

内联宏 — #inline / #endinline
#inline cache_key(url, host)
digest.hash_md5(url + "|" + host)
#endinline

// Zero-overhead text substitution. Auto-parenthesizes arguments
// containing operators to prevent precedence bugs.
set req.hash += cache_key(req.url, req.http.Host);
引入文件 — #include
#include "includes/backends.xvcl"  // relative path
#include <stdlib/security.xvcl>    // include path (-I)

仅引入一次。检测到循环引入时会报告错误。

编译

# Basic
uvx xvcl input.xvcl -o output.vcl

# With include paths
uvx xvcl main.xvcl -o main.vcl -I ./includes -I ./shared

# Debug mode (shows expansion traces)
uvx xvcl main.xvcl -o main.vcl --debug

# Source maps (adds BEGIN/END INCLUDE markers)
uvx xvcl main.xvcl -o main.vcl --source-maps
选项说明
-o, --output输出文件(默认:将 .xvcl 替换为 .vcl)
-I, --include添加引入文件搜索路径(可重复指定)
--debug / -v显示展开跟踪
--source-maps添加源代码位置注释
--error-format错误输出格式:text(默认)或 json

常见错误

  • 在 VCL 中使用裸常量名:error 200 GREETING; 会作为字面字符串传递。请使用带模板语法的 error 200 "{{GREETING}}";。
  • 生成 if 链而非表:当需要基于数据进行路由或重定向时,始终使用 #for 填充 VCL table,而不是内联 if 链。if 链的复杂度为 O(n),表的复杂度为 O(1)。
  • 忘记 #FASTLY 宏:每个 VCL 子例程(vcl_recv、vcl_fetch、vcl_deliver、vcl_error、vcl_hit、vcl_miss、vcl_pass)都需要在顶部添加 #FASTLY recv(或相应的名称)。
  • 使用 backend default:Fastly VCL 要求后端名称带 F_ 前缀。请使用 backend F_origin { ... },并设置 set req.backend = F_origin;。

VCL 注意事项

容易出错的 VCL 运行时问题:

  • 没有取模运算符:VCL 没有 % 运算符。要拆分流量,请对哈希摘要使用 substr():if (substr(digest.hash_sha256(client.ip), 0, 1) ~ "^[0-7]$") 可实现约 50% 的拆分。或者使用 randomint(0, 99) < 50。
  • 必须在 vcl_fetch 中设置 Vary:Vary 标头控制缓存键。只在 vcl_deliver 中设置为时已晚——对象已在没有 Vary 维度的情况下缓存。始终在 vcl_fetch 中追加 Vary(也可以在 vcl_deliver 中同步设置,以便客户端看到相应标头)。切勿覆盖现有的 Vary:请先检查,再追加。
  • 在 falco 测试中,req.url.path 为只读:在测试子例程中,请使用 set req.url = "/path",而不是 set req.url.path = "/path"。.path 属性由 req.url 计算得出,不能直接设置。
  • req.request 已弃用:请改用 req.method。Falco 同时接受两者,但 req.method 是较新的写法。
  • Cookie 解析:请使用 subfield(req.http.Cookie, "name", ";"),而不是正则表达式。类似 Cookie ~ "name=(\w+)" 的正则表达式可能会错误匹配带有相似前缀的 Cookie(例如 name_v2=X)。

参考资料

有关 VCL 基础知识(请求生命周期、返回操作、变量类型),请参阅以下 VCL 语法和子例程参考资料。

实现特定功能前,请完整阅读相关参考文件。

主题文件适用场景
XVCL 指令xvcl-directives.md编写任何 XVCL 代码 — 所有指令的完整语法
VCL 语法vcl-syntax.md使用数据类型、运算符和控制流
子例程subroutines.md理解请求生命周期和自定义子例程
标头headers.md操作 HTTP 标头
后端backends.md配置源站、director 和健康检查
缓存caching.md设置 TTL、宽限期和缓存键
字符串strings.md使用字符串操作函数
加密crypto.md哈希、HMAC、base64 编码
表/ACLtables-acls.md查找表、访问控制列表
测试 VCLtesting-vcl.md编写单元测试、断言和测试辅助工具

项目结构

vcl/
├── main.xvcl
├── config.xvcl          # shared constants
└── includes/
    ├── backends.xvcl
    ├── security.xvcl
    ├── routing.xvcl
    └── caching.xvcl

评论

支持 Markdown