CloudFront 按国家展示不同内容

验证:未记录
适用版本
未记录
验证范围
尚未登记验证记录。
本文缩写(英文全称与中文含义)
缩写 英文全称/名称 中文含义
S3 Simple Storage Service AWS 对象存储服务

同一个 https://example.com/,美国用户看到美国版本,日本用户看到日本版本,其他地区显示默认版本。核心是 识别国家 → 选择内容 → 隔离缓存。

1. 方案选择#

需求 方案 关键配置
S3 静态页面按国家变化,URL 不变 CloudFront Functions 在 viewer-request 改写 URI 国家 Header + 各版本独立对象路径;本文实战
跳转到 /en/、/ja/ 等明确语言路径 CloudFront Functions 返回 302 优先尊重用户选择,避免循环跳转;临时定位不使用永久 301
ALB/ECS/API 返回国家差异化内容 源站读取 CloudFront-Viewer-Country 缓存开启时将国家纳入 Cache Policy
不同国家使用不同源站 Functions runtime 2.0 的 origin 修改能力,或 Lambda@Edge origin-request 明确源站权限,并将影响内容的国家/版本纳入缓存键
限制某些国家访问 CloudFront Geo Restriction 或 WAF Geo Match 这是访问控制,不负责生成本地化页面

CloudFront 根据请求来源 IP 生成两位国家/地区代码,例如 US、JP、HK。国家不等于语言,VPN、代理和 IP 定位误差也会影响结果;提供默认内容和用户手动选择入口。此功能不能独立承担身份认证、版权授权或数据驻留合规。地理 Header 说明

2. 缓存与执行阶段#

用户请求 /
  → CloudFront 识别国家
  → viewer-request Function 改写为 /_geo/jp/index.html
  → 按改写后的 URI、国家和压缩格式查缓存
  → 命中:直接返回
  → 未命中:通过 OAC 获取私有 S3 对象并缓存
  • Cache Policy:决定缓存键;其中指定的 Header 也会转发到源站。
  • Origin Request Policy:只决定额外转发哪些信息,不会自动隔离缓存。如果源站根据国家返回不同内容,只转发国家但不分缓存,会造成内容串用。
  • CloudFront Functions:可在 viewer-request 读取国家 Header,但必须通过 Cache Policy 或 Origin Request Policy 配置该 Header。不要将这一点与 Lambda@Edge 混淆;Lambda@Edge 的 viewer-request 阶段拿不到 CloudFront 后加的国家 Header,使用 origin-request 阶段。
  • 改写 URI 不会重新选择 Cache Behavior 或 Origin;多源站需显式修改源站配置。

参考:Functions 官方国家示例及策略前提、缓存策略、Lambda@Edge 限制、URI 改写语义。

本文为了配置直观,把国家加入缓存键;同一国家的请求复用缓存。若多个国家共用一个版本,可改为 Origin Request Policy 提供国家,再由 viewer-request 改写为有限的版本路径,以减少缓存碎片。前提是所有影响响应的差异都已编码到路径或其他缓存键中。

3. Hands-on:部署独立实验#

准备 AWS CLI v2、Bash、jq,以及可创建 CloudFormation、S3、CloudFront 资源的实验身份。使用标准 AWS 商业区域;以下创建独立资源,会产生 S3、CloudFront 和 Functions 使用费用,不需要 EC2、NAT 或自定义域名。

下载 geo-lab.yaml 到当前目录。模板创建私有 S3、OAC、国家缓存策略、自动发布到 LIVE 的 Function,以及关联该 Function 的 Distribution;S3 仅授权该 Distribution 读取对象。OAC 使用 S3 REST endpoint,不启用 S3 Website Hosting。OAC 说明

set -euo pipefail
export AWS_PROFILE=lab
export AWS_REGION=ap-southeast-1
export AWS_PAGER=''
export GEO_STACK=cloudfront-geo-lab

aws sts get-caller-identity
aws cloudformation deploy --stack-name "$GEO_STACK" --template-file geo-lab.yaml

aws cloudformation describe-stacks --stack-name "$GEO_STACK" --query 'Stacks[0].Outputs' --output json > geo-outputs.json

GEO_BUCKET=$(jq -r '.[] | select(.OutputKey=="BucketName") | .OutputValue' geo-outputs.json)
GEO_DIST=$(jq -r '.[] | select(.OutputKey=="DistributionId") | .OutputValue' geo-outputs.json)
GEO_DOMAIN=$(jq -r '.[] | select(.OutputKey=="DomainName") | .OutputValue' geo-outputs.json)
GEO_FUNCTION=$(jq -r '.[] | select(.OutputKey=="FunctionName") | .OutputValue' geo-outputs.json)
aws cloudfront wait distribution-deployed --id "$GEO_DIST"

部署需要等待边缘配置传播。模板只接受 / 和 /index.html,其他路径返回 404;它是落地页实验,接入完整网站时需另行定义静态资源和各页面的路由。

4. 上传四个内容版本#

mkdir -p geo-site/_geo/{us,jp,hk,default}
cat > geo-site/_geo/us/index.html <<'HTML'
<!doctype html><html lang="en-US"><meta charset="utf-8">
<title>US Store</title><h1>US edition</h1><p>Currency: USD</p></html>
HTML
cat > geo-site/_geo/jp/index.html <<'HTML'
<!doctype html><html lang="ja"><meta charset="utf-8">
<title>日本ストア</title><h1>JP edition</h1><p>通貨: JPY</p></html>
HTML
cat > geo-site/_geo/hk/index.html <<'HTML'
<!doctype html><html lang="zh-Hant"><meta charset="utf-8">
<title>香港商店</title><h1>HK edition</h1><p>貨幣:HKD</p></html>
HTML
cat > geo-site/_geo/default/index.html <<'HTML'
<!doctype html><html lang="en"><meta charset="utf-8">
<title>Global Store</title><h1>Global edition</h1><p>Select your market.</p></html>
HTML

aws s3 cp geo-site/ "s3://$GEO_BUCKET/" --recursive --content-type 'text/html; charset=utf-8' --cache-control 'public,max-age=0,s-maxage=300'

浏览器缓存立即过期,CloudFront 可缓存 300 秒,减少用户切换网络后继续看到浏览器旧版本的情况。模板 MinTTL 为 0,MaxTTL 为 3600;Query String 和 Cookie 均不参与缓存,也不影响页面选择。

模板中的核心逻辑如下,完整代码还包含非实验路径的 404 处理:

var header = request.headers['cloudfront-viewer-country'];
var country = header ? header.value : '';
var variant = 'default';
if (country === 'US') variant = 'us';
else if (country === 'JP') variant = 'jp';
else if (country === 'HK') variant = 'hk';
request.uri = '/_geo/' + variant + '/index.html';

只从固定白名单选择路径;缺失或未知代码回退默认版本。这里是内部改写,浏览器地址保持 /,不会发起第二次跳转。

5. 验证函数分支#

先用 CloudFront TestFunction 验证输入与输出,再做真实网络验证。测试事件中的国家是人工构造的,不代表 IP 定位已经验证。

cat > geo-event.json <<'JSON'
{
  "version": "1.0",
  "context": {"eventType": "viewer-request"},
  "viewer": {"ip": "198.51.100.10"},
  "request": {
    "method": "GET",
    "uri": "/",
    "querystring": {},
    "headers": {
      "host": {"value": "example.com"},
      "cloudfront-viewer-country": {"value": "US"}
    },
    "cookies": {}
  }
}
JSON

GEO_ETAG=$(aws cloudfront describe-function --name "$GEO_FUNCTION" --stage LIVE --query ETag --output text)

for GEO_COUNTRY in US JP HK DE; do
  jq --arg country "$GEO_COUNTRY" '.request.headers["cloudfront-viewer-country"].value = $country' geo-event.json > geo-test.json
  aws cloudfront test-function --name "$GEO_FUNCTION" --stage LIVE --if-match "$GEO_ETAG" --event-object fileb://geo-test.json > geo-result.json
  jq -e '(.TestResult.FunctionErrorMessage // "") == ""' geo-result.json
  jq -r '.TestResult.FunctionOutput | fromjson | .uri' geo-result.json
done

依次应输出 /_geo/us/index.html、/_geo/jp/index.html、/_geo/hk/index.html、/_geo/default/index.html。额外删除国家 Header 测默认分支,把 uri 改为 /_geo/us/index.html 测 404。CLI 使用 fileb:// 传入事件文件。TestFunction 说明

6. 验证真实访问与缓存#

curl -fsS -D geo-headers.txt "https://$GEO_DOMAIN/" -o geo-page.html
cat geo-page.html
cat geo-headers.txt

# 在同一出口重复请求,观察 X-Cache、Age 和页面内容。
curl -fsS -D - "https://$GEO_DOMAIN/"

# 两项均应失败:直接访问内部路径为 404,匿名直连 S3 为 403。
curl -sS -o /dev/null -w '%{http_code}\n' "https://$GEO_DOMAIN/_geo/us/index.html"
curl -sS -o /dev/null -w '%{http_code}\n' "https://$GEO_BUCKET.s3.$AWS_REGION.amazonaws.com/_geo/us/index.html"
验收项 预期
美国 / 日本 / 香港真实出口访问 / 分别返回 US / JP / HK edition
其他国家出口 Global edition
同一出口重复访问 传播稳定后通常可见 Hit from cloudfront;同一国家内容保持一致
不同国家分别预热后重复请求 各自内容正确,不被另一国家的版本覆盖
非白名单路径 404
匿名访问 S3 对象 403

可用对应地区的测试主机或受控 VPN 出口验证,但云 Region 名称不能代替实际 IP 定位结果。curl -H 'CloudFront-Viewer-Country: JP' 不是可靠的国家模拟方式,线上由 CloudFront 生成该信息。X-Amz-Cf-Pop 表示边缘站点,不等于用户国家;不同出口也可能命中不同缓存节点,缓存分区是否正确还要检查实际 Cache Policy。

若全部返回默认版本,检查国家 Header 是否加入已关联的策略、Function 是否为 LIVE、Distribution 是否已部署。若 S3 返回 403,检查对象 Key、OAC 和 Bucket Policy 的 SourceArn,不要通过开放 Bucket 来修复。

7. 动态源站与完整网站如何扩展#

ALB/ECS 动态应用:保留国家 Cache Policy,让应用根据 CloudFront-Viewer-Country 选择公开内容。若继续使用同一 URL,不能只在 Origin Request Policy 中转发国家。登录态、购物车和个人价格使用单独的禁缓存 Behavior,按需转发认证信息;不要把用户私有响应存入公共缓存。

完整静态网站:例如将 /products/ 映射到 /_geo/jp/products/index.html,共享资源 /assets/* 使用独立 Behavior 和不含国家的缓存策略。先定义路径规则与允许范围,避免无条件重复加前缀;所有国家版本发布完毕再切换路由。

语言站点:优先用户选择,其次才按国家推荐。若通过 Cookie 选版本,必须在缓存查找前将选择归一化到版本路径,或将相关 Cookie 纳入缓存键。面向搜索引擎可采用明确语言 URL 和 hreflang,不强制所有访问者跳转。

多源站:Functions runtime 2.0 已支持修改源站,见 AWS 官方示例。国家之间内容不同时,仅修改源站地址不能代替缓存隔离;Lambda@Edge origin-request 只在回源时执行,命中缓存时不会重新选择内容。

8. 更新、回滚与清理#

修改页面后重新上传;修改国家映射时更新本地模板并重新执行 cloudformation deploy,等待 Distribution 部署完成。生产变更先在测试 Distribution 验证,再发布;保留旧模板与内容版本用于回滚。

实验中需要立即清理旧内容或旧 403 缓存时:

GEO_INVALIDATION=$(aws cloudfront create-invalidation --distribution-id "$GEO_DIST" --paths '/*' --query Invalidation.Id --output text)
aws cloudfront wait invalidation-completed --distribution-id "$GEO_DIST" --id "$GEO_INVALIDATION"

/* 适合小型实验;生产优先版本化文件名,并评估失效范围、回源压力和费用。恢复旧模板及页面后,同样处理残留缓存。

实验结束后,只清空此模板生成的测试 Bucket,再删除 Stack;不要将生产 Bucket 代入:

aws s3 rm "s3://$GEO_BUCKET/" --recursive
aws cloudformation delete-stack --stack-name "$GEO_STACK"
aws cloudformation wait stack-delete-complete --stack-name "$GEO_STACK"

CloudFront 停用与删除需要传播时间;若 waiter 超时,检查 Stack Events 和删除状态。本文模板未开启 S3 Versioning,若自行开启,删除前还需清理对象历史版本和删除标记。

搜索文档

输入关键词,搜索所有文档。

↑ ↓ 选择 · Enter 打开 · Esc 关闭