请求处理 #

一、请求处理概述 #

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

十一、总结 #

本章我们学习了:

  1. 请求处理概述:流程、职责
  2. 请求头处理:添加、修改、删除
  3. URL处理:重写、参数移除、规范化
  4. 重定向:301/302、HTTPS、条件重定向
  5. 请求过滤:方法、IP、User-Agent过滤
  6. 请求限流:IP、URL、Token限流
  7. 后端请求修改:vcl_backend_fetch
  8. 请求路由:URL、Host、Header路由
  9. 请求验证:基本验证、Token验证

掌握请求处理后,让我们进入下一章,学习响应处理!

最后更新:2026-03-28