请求处理 #
一、请求处理概述 #
1.1 请求处理流程 #
text
┌─────────────────────────────────────────────────────────┐
│ 请求处理流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ Client Request │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ vcl_recv │ ◄─── 接收请求 │
│ └──────┬──────┘ │
│ │ │
│ ├─── pass ──► 跳过缓存 │
│ │ │
│ ├─── pipe ──► 管道转发 │
│ │ │
│ ├─── synth ──► 合成响应 │
│ │ │
│ └─── hash ──► vcl_hash ──► 查找缓存 │
│ │
└─────────────────────────────────────────────────────────┘
1.2 vcl_recv职责 #
- 请求验证
- 请求修改
- 缓存决策
- 后端选择
- 访问控制
二、请求头处理 #
2.1 添加请求头 #
vcl
sub vcl_recv {
# 添加客户端IP
set req.http.X-Forwarded-For = client.ip;
# 添加原始Host
set req.http.X-Original-Host = req.http.Host;
# 添加请求ID
set req.http.X-Request-ID = req.xid;
# 添加协议信息
set req.http.X-Forwarded-Proto = "https";
}
2.2 修改请求头 #
vcl
sub vcl_recv {
# 修改Host头
set req.http.Host = "backend.example.com";
# 修改User-Agent
set req.http.User-Agent = "Varnish Proxy";
# 修改URL
set req.url = regsub(req.url, "^/old/", "/new/");
}
2.3 删除请求头 #
vcl
sub vcl_recv {
# 删除Cookie
unset req.http.Cookie;
# 删除认证头
unset req.http.Authorization;
# 删除代理相关头
unset req.http.X-Forwarded-Host;
unset req.http.X-Forwarded-Server;
}
2.4 条件修改 #
vcl
sub vcl_recv {
# 根据条件修改
if (req.http.Host == "old.example.com") {
set req.http.Host = "new.example.com";
}
# 根据URL修改
if (req.url ~ "^/api/v1/") {
set req.url = regsub(req.url, "^/api/v1/", "/api/v2/");
}
}
三、URL处理 #
3.1 URL重写 #
vcl
sub vcl_recv {
# 简单重写
set req.url = regsub(req.url, "^/old-path", "/new-path");
# 正则重写
set req.url = regsub(req.url, "^/category/([^/]+)/", "/cat/\1/");
# 全局替换
set req.url = regsuball(req.url, "/+", "/");
}
3.2 移除URL参数 #
vcl
sub vcl_recv {
# 移除跟踪参数
set req.url = regsuball(req.url, "(utm_[^&=]*=[^&]*&?)", "");
set req.url = regsuball(req.url, "(fbclid=[^&]*&?)", "");
set req.url = regsuball(req.url, "(gclid=[^&]*&?)", "");
# 清理尾部
set req.url = regsub(req.url, "(\?|&)$", "");
}
3.3 保留特定参数 #
vcl
sub vcl_recv {
# 只保留page和limit参数
set req.http.X-Original-URL = req.url;
# 解析参数并重建URL(需要复杂逻辑)
# 简化示例:移除所有参数
if (req.url ~ "^/api/") {
set req.url = regsub(req.url, "\?.*$", "");
}
}
3.4 URL规范化 #
vcl
sub vcl_recv {
# 移除重复斜杠
set req.url = regsuball(req.url, "//+", "/");
# 移除index.html
set req.url = regsub(req.url, "/index\.html$", "/");
# 移除尾部斜杠(可选)
set req.url = regsub(req.url, "/$", "");
# 小写URL(可选)
# 需要使用std模块
set req.url = std.tolower(req.url);
}
四、重定向 #
4.1 301永久重定向 #
vcl
sub vcl_recv {
# 域名重定向
if (req.http.Host == "old.example.com") {
return (synth(301, "Moved Permanently"));
}
# 路径重定向
if (req.url ~ "^/old-path") {
return (synth(301, "Moved Permanently"));
}
}
sub vcl_synth {
if (resp.status == 301) {
set resp.http.Location = "https://new.example.com" + req.url;
return (deliver);
}
}
4.2 302临时重定向 #
vcl
sub vcl_recv {
# 临时维护重定向
if (req.url ~ "^/maintenance") {
return (synth(302, "Found"));
}
}
sub vcl_synth {
if (resp.status == 302) {
set resp.http.Location = "https://example.com/maintenance.html";
return (deliver);
}
}
4.3 HTTPS重定向 #
vcl
sub vcl_recv {
# HTTP转HTTPS
if (req.http.X-Forwarded-Proto == "http") {
return (synth(301, "Moved Permanently"));
}
}
sub vcl_synth {
if (resp.status == 301 && req.http.X-Forwarded-Proto == "http") {
set resp.http.Location = "https://" + req.http.Host + req.url;
return (deliver);
}
}
4.4 基于规则的重定向 #
vcl
sub vcl_recv {
# 基于User-Agent重定向
if (req.http.User-Agent ~ "(?i)(mobile|android|iphone)" &&
req.url !~ "^/mobile/") {
return (synth(302, "Mobile Redirect"));
}
# 基于地理位置重定向
if (req.http.CF-IPCountry == "CN" && req.url !~ "^/cn/") {
return (synth(302, "Geo Redirect"));
}
}
sub vcl_synth {
if (resp.reason == "Mobile Redirect") {
set resp.http.Location = "https://m.example.com" + req.url;
}
if (resp.reason == "Geo Redirect") {
set resp.http.Location = "https://cn.example.com" + req.url;
}
return (deliver);
}
五、请求过滤 #
5.1 方法过滤 #
vcl
sub vcl_recv {
# 允许的方法
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "POST" &&
req.method != "PUT" &&
req.method != "DELETE" &&
req.method != "OPTIONS") {
return (synth(405, "Method Not Allowed"));
}
}
5.2 IP过滤 #
vcl
acl allowed {
"192.168.1.0"/24;
"10.0.0.0"/8;
}
acl blocked {
"192.168.100.0"/24;
}
sub vcl_recv {
# 黑名单
if (client.ip ~ blocked) {
return (synth(403, "Forbidden"));
}
# 白名单
if (req.url ~ "^/admin/" && client.ip !~ allowed) {
return (synth(403, "Forbidden"));
}
}
5.3 User-Agent过滤 #
vcl
sub vcl_recv {
# 阻止恶意爬虫
if (req.http.User-Agent ~ "(?i)(bot|crawler|spider|scraper)") {
return (synth(403, "Forbidden"));
}
# 阻止空User-Agent
if (!req.http.User-Agent) {
return (synth(403, "Forbidden"));
}
}
5.4 请求体大小限制 #
vcl
import bodyaccess;
sub vcl_recv {
# 限制请求体大小(需要VMOD)
if (req.method == "POST") {
if (req.http.Content-Length > 1048576) {
return (synth(413, "Payload Too Large"));
}
}
}
六、请求限流 #
6.1 基于IP限流 #
vcl
import vsthrottle;
sub vcl_recv {
# 每秒最多100请求
if (vsthrottle.is_denied("ip_" + client.ip, 100, 1s)) {
return (synth(429, "Too Many Requests"));
}
}
6.2 基于URL限流 #
vcl
import vsthrottle;
sub vcl_recv {
# API限流
if (req.url ~ "^/api/") {
if (vsthrottle.is_denied("api_" + client.ip, 50, 1s)) {
return (synth(429, "Too Many Requests"));
}
}
# 登录限流
if (req.url ~ "^/login" && req.method == "POST") {
if (vsthrottle.is_denied("login_" + client.ip, 5, 1m)) {
return (synth(429, "Too Many Requests"));
}
}
}
6.3 基于Token限流 #
vcl
import vsthrottle;
sub vcl_recv {
# API Token限流
if (req.http.X-API-Token) {
if (vsthrottle.is_denied("token_" + req.http.X-API-Token, 1000, 1h)) {
return (synth(429, "Too Many Requests"));
}
}
}
七、后端请求修改 #
7.1 vcl_backend_fetch #
vcl
sub vcl_backend_fetch {
# 修改后端请求头
set bereq.http.X-Forwarded-For = client.ip;
set bereq.http.X-Real-IP = client.ip;
# 设置超时
set bereq.first_byte_timeout = 30s;
set bereq.between_bytes_timeout = 5s;
# 移除不必要的头
unset bereq.http.Cookie;
unset bereq.http.Authorization;
}
7.2 条件修改 #
vcl
sub vcl_backend_fetch {
# 根据URL设置不同超时
if (bereq.url ~ "^/api/slow") {
set bereq.first_byte_timeout = 60s;
}
# 根据后端设置
if (bereq.backend == api_server) {
set bereq.http.X-API-Request = "true";
}
}
八、请求路由 #
8.1 基于URL路由 #
vcl
sub vcl_recv {
if (req.url ~ "^/api/") {
set req.backend_hint = api_backend;
} elseif (req.url ~ "^/static/") {
set req.backend_hint = static_backend;
} elseif (req.url ~ "^/admin/") {
set req.backend_hint = admin_backend;
} else {
set req.backend_hint = web_backend;
}
}
8.2 基于Host路由 #
vcl
sub vcl_recv {
if (req.http.Host == "api.example.com") {
set req.backend_hint = api_backend;
} elseif (req.http.Host == "admin.example.com") {
set req.backend_hint = admin_backend;
} else {
set req.backend_hint = web_backend;
}
}
8.3 基于Header路由 #
vcl
sub vcl_recv {
if (req.http.X-Backend == "api") {
set req.backend_hint = api_backend;
} elseif (req.http.X-Backend == "admin") {
set req.backend_hint = admin_backend;
}
}
九、请求验证 #
9.1 基本验证 #
vcl
sub vcl_recv {
# 检查必需头
if (!req.http.Host) {
return (synth(400, "Bad Request"));
}
# 检查URL格式
if (req.url ~ "[<>\"'|]") {
return (synth(400, "Bad Request"));
}
}
9.2 Token验证 #
vcl
sub vcl_recv {
# API Token验证
if (req.url ~ "^/api/") {
if (!req.http.X-API-Token) {
return (synth(401, "Unauthorized"));
}
# 验证Token(简化示例)
if (req.http.X-API-Token != "valid-token") {
return (synth(403, "Forbidden"));
}
}
}
9.3 签名验证 #
vcl
import digest;
sub vcl_recv {
# 验证请求签名
if (req.url ~ "^/api/") {
set req.http.X-Expected-Sig = digest.hmac_sha256(
"secret-key",
req.method + req.url + req.http.X-Timestamp
);
if (req.http.X-Signature != req.http.X-Expected-Sig) {
return (synth(403, "Invalid Signature"));
}
}
}
十、完整配置示例 #
vcl
vcl 4.1;
import std;
import vsthrottle;
# ACL定义
acl allowed {
"192.168.1.0"/24;
}
# 后端定义
backend default {
.host = "127.0.0.1";
.port = "8080";
}
# 请求处理
sub vcl_recv {
# HTTPS重定向
if (req.http.X-Forwarded-Proto == "http") {
return (synth(301, "HTTPS Redirect"));
}
# 方法验证
if (req.method != "GET" && req.method != "HEAD" && req.method != "POST") {
return (synth(405, "Method Not Allowed"));
}
# 限流
if (vsthrottle.is_denied("ip_" + client.ip, 100, 1s)) {
return (synth(429, "Too Many Requests"));
}
# 管理后台访问控制
if (req.url ~ "^/admin/" && client.ip !~ allowed) {
return (synth(403, "Forbidden"));
}
# URL规范化
set req.url = regsuball(req.url, "//+", "/");
set req.url = regsuball(req.url, "(utm_[^&=]*=[^&]*&?)", "");
# 添加请求头
set req.http.X-Forwarded-For = client.ip;
set req.http.X-Request-ID = req.xid;
# 静态资源处理
if (req.url ~ "\.(css|js|png|gif|jpg|jpeg|ico|svg)$") {
unset req.http.Cookie;
return (hash);
}
# 非GET/HEAD不缓存
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
return (hash);
}
# 后端请求处理
sub vcl_backend_fetch {
set bereq.http.X-Forwarded-For = client.ip;
set bereq.first_byte_timeout = 30s;
}
# 合成响应
sub vcl_synth {
# HTTPS重定向
if (resp.reason == "HTTPS Redirect") {
set resp.status = 301;
set resp.http.Location = "https://" + req.http.Host + req.url;
}
# 错误页面
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);
}
十一、总结 #
本章我们学习了:
- 请求处理概述:流程、职责
- 请求头处理:添加、修改、删除
- URL处理:重写、参数移除、规范化
- 重定向:301/302、HTTPS、条件重定向
- 请求过滤:方法、IP、User-Agent过滤
- 请求限流:IP、URL、Token限流
- 后端请求修改:vcl_backend_fetch
- 请求路由:URL、Host、Header路由
- 请求验证:基本验证、Token验证
掌握请求处理后,让我们进入下一章,学习响应处理!
最后更新:2026-03-28