响应处理 #
一、响应处理概述 #
1.1 响应处理流程 #
text
┌─────────────────────────────────────────────────────────┐
│ 响应处理流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ Backend Response │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │vcl_backend_response│ ◄─── 后端响应处理 │
│ └────────┬─────────┘ │
│ │ │
│ └──► 存储缓存 │
│ │
│ Cached Object │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ vcl_deliver │ ◄─── 返回响应 │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ Client Response │
│ │
└─────────────────────────────────────────────────────────┘
1.2 响应处理子程序 #
| 子程序 | 触发时机 | 主要用途 |
|---|---|---|
| vcl_backend_response | 收到后端响应 | 设置缓存策略 |
| vcl_deliver | 返回客户端 | 修改响应头 |
| vcl_synth | 合成响应 | 自定义错误页面 |
| vcl_backend_error | 后端错误 | 错误处理 |
二、响应头处理 #
2.1 添加响应头 #
vcl
sub vcl_deliver {
# 添加缓存状态
set resp.http.X-Cache = "HIT";
# 添加服务器信息
set resp.http.X-Served-By = "Varnish";
# 添加请求ID
set resp.http.X-Request-ID = req.xid;
# 添加处理时间
set resp.http.X-Response-Time = time.elapsed_ms() + "ms";
}
2.2 修改响应头 #
vcl
sub vcl_deliver {
# 修改Server头
set resp.http.Server = "MyServer/1.0";
# 修改Content-Type
if (resp.http.Content-Type ~ "text/html") {
set resp.http.Content-Type = "text/html; charset=utf-8";
}
}
2.3 删除响应头 #
vcl
sub vcl_deliver {
# 删除敏感头
unset resp.http.X-Powered-By;
unset resp.http.Server;
unset resp.http.X-AspNet-Version;
unset resp.http.X-AspNetMvc-Version;
unset resp.http.Via;
# 删除调试头
unset resp.http.X-Debug-Token;
unset resp.http.X-Debug-Token-Link;
}
2.4 条件修改 #
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";
}
# 根据内容类型设置
if (resp.http.Content-Type ~ "text/html") {
set resp.http.X-Content-Type = "html";
}
}
三、缓存状态头 #
3.1 基本缓存状态 #
vcl
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
}
3.2 详细缓存信息 #
vcl
sub vcl_deliver {
# 缓存状态
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
set resp.http.X-Cache-TTL = obj.ttl;
set resp.http.X-Cache-Grace = obj.grace;
} else {
set resp.http.X-Cache = "MISS";
}
# 后端信息
set resp.http.X-Backend = beresp.backend.name;
}
3.3 调试信息 #
vcl
sub vcl_deliver {
# 仅在调试模式下添加
if (req.http.X-Debug) {
set resp.http.X-Debug-URL = req.url;
set resp.http.X-Debug-Method = req.method;
set resp.http.X-Debug-Host = req.http.Host;
set resp.http.X-Debug-Client-IP = client.ip;
set resp.http.X-Debug-Request-ID = req.xid;
}
}
四、安全头配置 #
4.1 基本安全头 #
vcl
sub vcl_deliver {
# 防止MIME类型嗅探
set resp.http.X-Content-Type-Options = "nosniff";
# XSS保护
set resp.http.X-XSS-Protection = "1; mode=block";
# 点击劫持保护
set resp.http.X-Frame-Options = "SAMEORIGIN";
# Referrer策略
set resp.http.Referrer-Policy = "strict-origin-when-cross-origin";
}
4.2 Content-Security-Policy #
vcl
sub vcl_deliver {
# CSP策略
set resp.http.Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';";
}
4.3 HSTS #
vcl
sub vcl_deliver {
# HTTP严格传输安全
set resp.http.Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload";
}
4.4 Permissions-Policy #
vcl
sub vcl_deliver {
# 权限策略
set resp.http.Permissions-Policy = "geolocation=(), microphone=(), camera=()";
}
4.5 条件安全头 #
vcl
sub vcl_deliver {
# HTML页面添加完整安全头
if (resp.http.Content-Type ~ "text/html") {
set resp.http.X-Content-Type-Options = "nosniff";
set resp.http.X-XSS-Protection = "1; mode=block";
set resp.http.X-Frame-Options = "SAMEORIGIN";
set resp.http.Content-Security-Policy = "default-src 'self';";
}
# 静态资源简化安全头
if (resp.http.Content-Type ~ "image/|text/css|application/javascript") {
set resp.http.X-Content-Type-Options = "nosniff";
}
}
五、错误页面处理 #
5.1 自定义错误页面 #
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>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
h1 { color: #e74c3c; }
</style>
</head>
<body>
<h1>403 Forbidden</h1>
<p>Access to this resource is forbidden.</p>
</body>
</html>"});
} elseif (resp.status == 404) {
synthetic({"<!DOCTYPE html>
<html>
<head>
<title>404 Not Found</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
h1 { color: #e74c3c; }
</style>
</head>
<body>
<h1>404 Not Found</h1>
<p>The requested page was not found.</p>
</body>
</html>"});
} elseif (resp.status == 429) {
synthetic({"<!DOCTYPE html>
<html>
<head>
<title>429 Too Many Requests</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
h1 { color: #f39c12; }
</style>
</head>
<body>
<h1>429 Too Many Requests</h1>
<p>Please slow down your requests.</p>
</body>
</html>"});
} else {
synthetic({"<!DOCTYPE html>
<html>
<head>
<title>"} + resp.status + " " + resp.reason + {"</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
h1 { color: #e74c3c; }
</style>
</head>
<body>
<h1>"} + resp.status + " " + resp.reason + {"</h1>
<p>"} + resp.reason + {"</p>
</body>
</html>"});
}
return (deliver);
}
5.2 后端错误处理 #
vcl
sub vcl_backend_error {
set beresp.http.Content-Type = "text/html; charset=utf-8";
synthetic({"<!DOCTYPE html>
<html>
<head>
<title>503 Service Unavailable</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
h1 { color: #e74c3c; }
</style>
</head>
<body>
<h1>503 Service Unavailable</h1>
<p>The server is temporarily unavailable.</p>
<p>Please try again later.</p>
</body>
</html>"});
return (deliver);
}
5.3 JSON错误响应 #
vcl
sub vcl_synth {
if (req.http.Accept ~ "application/json") {
set resp.http.Content-Type = "application/json";
synthetic({"{"error":{"code":} + resp.status + {","message":"} + resp.reason + {"}}"});
} else {
set resp.http.Content-Type = "text/html; charset=utf-8";
synthetic({"<!DOCTYPE html>
<html>
<head><title>"} + resp.status + {"</title></head>
<body><h1>"} + resp.status + " " + resp.reason + {"</h1></body>
</html>"});
}
return (deliver);
}
六、压缩处理 #
6.1 Gzip压缩 #
Varnish本身不进行压缩,但可以处理已压缩的响应:
vcl
sub vcl_backend_response {
# 保留后端压缩
# Varnish会自动处理Accept-Encoding
# 确保压缩响应正确缓存
if (beresp.http.Content-Encoding == "gzip") {
# 已压缩响应
}
}
6.2 压缩协商 #
vcl
sub vcl_hash {
# 根据Accept-Encoding区分缓存
if (req.http.Accept-Encoding ~ "gzip") {
hash_data("gzip");
} elseif (req.http.Accept-Encoding ~ "deflate") {
hash_data("deflate");
} else {
hash_data("none");
}
}
6.3 移除压缩头(可选) #
vcl
sub vcl_recv {
# 移除Accept-Encoding以获取未压缩内容
if (req.url ~ "\.(css|js)$") {
unset req.http.Accept-Encoding;
}
}
七、缓存控制头 #
7.1 设置Cache-Control #
vcl
sub vcl_deliver {
# 静态资源
if (req.url ~ "\.(css|js|png|gif|jpg|jpeg|ico|svg|woff|woff2)$") {
set resp.http.Cache-Control = "public, max-age=31536000, immutable";
}
# HTML
elseif (resp.http.Content-Type ~ "text/html") {
set resp.http.Cache-Control = "public, max-age=300, must-revalidate";
}
# API
elseif (req.url ~ "^/api/") {
set resp.http.Cache-Control = "public, max-age=10";
}
}
7.2 设置Expires #
vcl
sub vcl_deliver {
# 设置过期时间
if (req.url ~ "\.(css|js|png|gif|jpg|jpeg|ico|svg)$") {
set resp.http.Expires = now + 365d;
}
}
7.3 ETag处理 #
vcl
sub vcl_deliver {
# 保留ETag用于条件请求
# Varnish自动处理If-None-Match
# 或移除ETag
unset resp.http.ETag;
}
八、响应体修改 #
8.1 使用synthetic #
vcl
sub vcl_synth {
set resp.http.Content-Type = "text/html";
synthetic({"<html><body>Custom Response</body></html>"});
return (deliver);
}
8.2 响应体注入 #
需要使用VMOD:
vcl
import bodyaccess;
sub vcl_deliver {
# 修改响应体(需要VMOD支持)
# 基本VCL不支持直接修改响应体
}
九、条件响应 #
9.1 基于客户端 #
vcl
sub vcl_deliver {
# 内部用户显示调试信息
if (client.ip ~ internal) {
set resp.http.X-Debug-Info = "enabled";
}
}
9.2 基于请求头 #
vcl
sub vcl_deliver {
# 移动端特殊处理
if (req.http.User-Agent ~ "Mobile") {
set resp.http.X-Mobile-Optimized = "true";
}
# API客户端
if (req.http.X-API-Client) {
set resp.http.X-API-Version = "1.0";
}
}
9.3 基于响应状态 #
vcl
sub vcl_deliver {
# 错误响应特殊处理
if (resp.status >= 400) {
set resp.http.Cache-Control = "no-cache, no-store, must-revalidate";
unset resp.http.ETag;
}
}
十、完整配置示例 #
vcl
vcl 4.1;
import std;
# 后端定义
backend default {
.host = "127.0.0.1";
.port = "8080";
}
# 后端响应处理
sub vcl_backend_response {
# 设置TTL
if (bereq.url ~ "\.(css|js|png|gif|jpg|jpeg|ico|svg|woff|woff2)$") {
set beresp.ttl = 7d;
unset beresp.http.Set-Cookie;
} elseif (beresp.http.Content-Type ~ "text/html") {
set beresp.ttl = 5m;
set beresp.grace = 1h;
} else {
set beresp.ttl = 5m;
}
# 错误响应不缓存
if (beresp.status >= 500) {
set beresp.ttl = 0s;
set beresp.uncacheable = true;
}
}
# 响应处理
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";
}
# 安全头
set resp.http.X-Content-Type-Options = "nosniff";
set resp.http.X-XSS-Protection = "1; mode=block";
set resp.http.X-Frame-Options = "SAMEORIGIN";
set resp.http.Referrer-Policy = "strict-origin-when-cross-origin";
# HTML页面额外安全头
if (resp.http.Content-Type ~ "text/html") {
set resp.http.Content-Security-Policy = "default-src 'self';";
set resp.http.Strict-Transport-Security = "max-age=31536000";
}
# 移除敏感头
unset resp.http.X-Powered-By;
unset resp.http.Server;
unset resp.http.Via;
# 添加请求ID
set resp.http.X-Request-ID = req.xid;
# Cache-Control
if (req.url ~ "\.(css|js|png|gif|jpg|jpeg|ico|svg|woff|woff2)$") {
set resp.http.Cache-Control = "public, max-age=31536000, immutable";
}
}
# 合成响应
sub vcl_synth {
set resp.http.Content-Type = "text/html; charset=utf-8";
# 安全头
set resp.http.X-Content-Type-Options = "nosniff";
set resp.http.X-Frame-Options = "DENY";
synthetic({"<!DOCTYPE html>
<html>
<head>
<title>"} + resp.status + " " + resp.reason + {"</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
.container { max-width: 600px; margin: 0 auto; background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #e74c3c; margin-bottom: 10px; }
p { color: #666; }
.code { font-size: 48px; font-weight: bold; color: #e74c3c; }
</style>
</head>
<body>
<div class="container">
<div class="code">"} + resp.status + {"</div>
<h1>"} + resp.reason + {"</h1>
<p>"} + resp.reason + {"</p>
</div>
</body>
</html>"});
return (deliver);
}
# 后端错误
sub vcl_backend_error {
set beresp.http.Content-Type = "text/html; charset=utf-8";
synthetic({"<!DOCTYPE html>
<html>
<head>
<title>503 Service Unavailable</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
.container { max-width: 600px; margin: 0 auto; background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #e74c3c; }
</style>
</head>
<body>
<div class="container">
<h1>503 Service Unavailable</h1>
<p>The server is temporarily unavailable.</p>
<p>Please try again later.</p>
</div>
</body>
</html>"});
return (deliver);
}
十一、总结 #
本章我们学习了:
- 响应处理概述:流程、子程序
- 响应头处理:添加、修改、删除
- 缓存状态头:HIT/MISS、调试信息
- 安全头配置:CSP、HSTS、X-Frame-Options
- 错误页面:自定义错误页面、JSON错误
- 压缩处理:Gzip协商
- 缓存控制头:Cache-Control、Expires
- 条件响应:基于客户端、请求头、状态
掌握响应处理后,让我们进入下一章,学习ESI边缘包含!
最后更新:2026-03-28