200cdn.xyz
源站测试站点(静态 + 动态)

CDN 联调测试清单

你可以按下面顺序验证:解析 → 接入 → 回源 → 缓存 → Header → 静态资源加速。

1) DNS 解析

确认 200cdn.xyz 已解析到 CDN 接入域名(CNAME)或节点 IP(A)。

建议:dig 200cdn.xyznslookup 200cdn.xyz

2) 回源连通

访问首页 /,确认能看到页面;再访问接口:

3) Header 注入

访问 /api/headers/api/headers/forwarded-check,观察是否存在:

  • X-Forwarded-For / X-Forwarded-Host / X-Forwarded-Proto
  • Via / X-Cache / 自定义节点标识(视你的 CDN 实现而定)
3.1) 防盗链字段(referers)验证
  • POST /referer/admin/config:配置启用防盗链、允许直接访问、来源域名允许一致、允许/禁止来源域名、检查Origin、例外URL、限制URL
  • GET /referer/admin/status:查看当前防盗链策略
  • GET /referer/protected/referer/probe:观察命中结果与原因

完整步骤见:docs/REFERER_ANTI_HOTLINK_VERIFY.md

3.2) 防盗链可视化控制台
  • 打开 /referer/ui,在线修改防盗链字段并保存。
  • 通过探针查看 reason/sourceHost/sourceFrom 与 HTTP 状态。
  • 支持“一键回放 / 仅回放失败项 / 导出 JSON”。
4) 缓存验证

访问 /cacheable/a 两次,对比:

  • 是否命中缓存(CDN 侧 HIT)
  • 源站日志/回源次数是否减少
  • 响应头中的 Age / X-Cache 等是否变化
5) 静态资源

打开 /gallery,检查 CSS/JS/图片资源是否能正常加载;再在 CDN 配置静态缓存规则观察加速效果。

5.1) 大文件样本(PDF/DOCX/MP3/MP4/GZIP)

使用以下接口生成大文件样本,支持 sizeMB 参数(默认 20MB,最大 128MB):

  • /media/large.pdf?sizeMB=50
  • /media/large.docx?sizeMB=50
  • /media/large.mp3?sizeMB=50
  • /media/large.mp4?sizeMB=50
  • /media/large.svg?sizeMB=50
  • /media/large.png?sizeMB=50
  • /media/large.gif?sizeMB=50
  • /encoding/large.gz?sizeMB=50

可用 curl -I 校验 Content-Type/Content-Length,并结合 CDN 做大文件回源、缓存、断点续传验证。说明:large.svg 可直接浏览器打开;large.png/large.gif 为大体积二进制样本(主要用于分发/缓存/限速/Range 验证,不保证浏览器可渲染)。

6) FastCGI(CDN 网站设置)

目标:用现有 test.300cdn.xyz 站点验证 CDN 的 FastCGI 功能(节点本机 php-fpm),并通过一组固定脚本验证:连通性 / 环境变量 / PATH_INFO / POST 请求体 / 超时 / STDERR。

A. 在每个节点部署 php-fpm(监听 127.0.0.1:9070)并放置测试脚本

下面命令以 Debian/Ubuntu 为例;CentOS/RHEL 可用 yum install php-fpm 并改对应配置路径。

# 1) 安装
sudo apt-get update -y
sudo apt-get install -y php-fpm

# 2) 配置监听端口 9070(避免与源站/Flask 9000 冲突)
sudo sed -i.bak 's/^listen\\s*=\\s*.*/listen = 127.0.0.1:9070/' /etc/php/*/fpm/pool.d/www.conf
sudo systemctl restart php*-fpm || sudo systemctl restart php-fpm

# 3) 确认监听成功
sudo ss -lntp | grep 9070 || sudo netstat -lntp | grep 9070

# 4) 放置测试脚本(路径固定:/tmp/php)
sudo mkdir -p /tmp/php

# health.php:健康检查(JSON,且带 from 字段)
sudo tee /tmp/php/health.php >/dev/null <<'PHP'
<?php
header('Content-Type: application/json; charset=utf-8');
header('X-Fastcgi-Verify: 200cdn-fastcgi-root');
echo json_encode(['ok'=>true,'from'=>'php-fpm-fastcgi','ts'=>time()], JSON_UNESCAPED_UNICODE);
PHP

# index.php:回显关键 CGI 环境变量(JSON)
sudo tee /tmp/php/index.php >/dev/null <<'PHP'
<?php
header('Content-Type: application/json; charset=utf-8');
$keys = [
  'DOCUMENT_ROOT','SCRIPT_FILENAME','SCRIPT_NAME','PATH_INFO','REQUEST_URI','QUERY_STRING',
  'REQUEST_METHOD','CONTENT_TYPE','CONTENT_LENGTH','REMOTE_ADDR','SERVER_NAME','SERVER_PORT','SERVER_PROTOCOL',
  'HTTP_HOST','HTTP_USER_AGENT','HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP','HTTP_X_CUSTOM_HEADER'
];
$out = [];
foreach ($keys as $k) { $out[strtolower($k)] = getenv($k) !== false ? getenv($k) : ''; }
echo json_encode($out, JSON_UNESCAPED_UNICODE);
PHP

# pathinfo.php:专门验证 PATH_INFO 提取
sudo tee /tmp/php/pathinfo.php >/dev/null <<'PHP'
<?php
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
  'script_filename' => getenv('SCRIPT_FILENAME') ?: '',
  'script_name' => getenv('SCRIPT_NAME') ?: '',
  'path_info' => getenv('PATH_INFO') ?: '',
  'request_uri' => getenv('REQUEST_URI') ?: '',
  'query_string' => getenv('QUERY_STRING') ?: ''
], JSON_UNESCAPED_UNICODE);
PHP

# post_echo.php:回显 POST 关键字段与 body 长度/预览
sudo tee /tmp/php/post_echo.php >/dev/null <<'PHP'
<?php
header('Content-Type: application/json; charset=utf-8');
$raw = file_get_contents('php://input');
echo json_encode([
  'request_method' => getenv('REQUEST_METHOD') ?: '',
  'content_type' => getenv('CONTENT_TYPE') ?: '',
  'content_length_server' => getenv('CONTENT_LENGTH') ?: '',
  'raw_body_len' => strlen($raw),
  'raw_body_preview' => substr($raw, 0, 256),
], JSON_UNESCAPED_UNICODE);
PHP

# slow.php:sleep N 秒(默认 5)
sudo tee /tmp/php/slow.php >/dev/null <<'PHP'
<?php
header('Content-Type: application/json; charset=utf-8');
$s = isset($_GET['s']) ? intval($_GET['s']) : 5;
if ($s < 0) $s = 0;
if ($s > 60) $s = 60;
sleep($s);
echo json_encode(['ok'=>true,'slept'=>$s,'from'=>'php-fpm-fastcgi'], JSON_UNESCAPED_UNICODE);
PHP

# stderr.php:向 STDERR 写内容(用于验证 CDN 转 500)
sudo tee /tmp/php/stderr.php >/dev/null <<'PHP'
<?php
file_put_contents('php://stderr', "fastcgi stderr demo\\n");
header('Content-Type: text/plain; charset=utf-8');
echo "this should not reach client when stderr triggers error";
PHP

B. 在 CDN 后台配置 FastCGI(网站设置 → FastCGI)

  • 启用 FastCGI:IsOn=true
  • 地址(Address)127.0.0.1:9070(每个节点本机)
  • DOCUMENT_ROOT/tmp/php
  • SCRIPT_FILENAME:建议填 /tmp/php{request.path}(让 /health.php/index.php 等按请求路径动态映射)
  • PATH_INFO 匹配正则(可选)^(.*\\.php)(/.*)$(用于框架 pathinfo 模式)
  • 读取超时时间(ReadTimeout):先填 30(后续再用 slow.php 验证 2 秒超时)

如果你不想用动态映射,也可以把 SCRIPT_FILENAME 固定为 /tmp/php/health.php,然后只验证 health.php 一个点(但覆盖面会少很多)。

C. 验证(curl 直接回放)

# 1) 连通性(应返回 JSON,且 from=php-fpm-fastcgi)
curl -s 'http://test.300cdn.xyz/health.php?_t=1'

# 2) 环境变量注入(看 document_root/script_filename/server_name/remote_addr 等)
curl -s 'http://test.300cdn.xyz/index.php?_t=2' | jq

# 3) PATH_INFO(需要你在 FastCGI 配置里填写 PATH_INFO 正则)
curl -s 'http://test.300cdn.xyz/pathinfo.php/user/123?_t=3' | jq

# 4) POST 请求体透传
curl -s -X POST 'http://test.300cdn.xyz/post_echo.php?_t=4' \
  -H 'Content-Type: application/json' \
  --data '{"key":"val"}' | jq

# 5) ReadTimeout(把 ReadTimeout 改成 2 秒后再跑;应在 ~2 秒返回 500)
time curl -sv 'http://test.300cdn.xyz/slow.php?s=5&_t=5' --max-time 10 2>&1 | grep -E 'HTTP|real'

# 6) STDERR(应返回 500)
curl -sv 'http://test.300cdn.xyz/stderr.php?_t=6' 2>&1 | grep -E 'HTTP|< '

D. 常见问题排查(必看)

  • 现象:nc -vz 127.0.0.1 9070 显示 Connection refused
    结论:本机 9070 没有进程在监听(不是防火墙拦截)。先用 sudo ss -lntp | grep 9070 确认 php-fpm 是否真的监听到 127.0.0.1:9070,再看 systemctl status php*-fpm / journalctl -u php*-fpm
  • 现象:访问 /health.php 返回 500/502,日志提示 connect timeout / no route / filtered
    结论:更像是防火墙/安全组拦截(DROP),需要检查节点的 OUTPUT 策略是否允许访问本机端口(有些节点默认 OUTPUT DROP)。可先在节点执行 iptables -S ufw-user-output 查看是否放行到 127.0.0.1:9070 的 TCP。最小放行示例:sudo iptables -I ufw-user-output 1 -o lo -p tcp --dport 9070 -j ACCEPT
  • 提醒:FastCGI 的 9070 不是对外服务端口
    FastCGI 地址填的是 127.0.0.1:9070(节点本机回环),外网不需要访问 9070,因此无需开入站安全组;真正对外的是你的站点 HTTP/HTTPS 端口(如 80/443/9443)。
  • 如果 php-fpm 默认监听 unix socket
    很多系统默认是 listen = /run/php/php-fpm.sock。你可以改为 TCP(本页推荐),也可以在 CDN FastCGI 里填 unix:/run/php/php-fpm.sock 或直接填绝对路径(以实际路径为准)。

如果你要“逐字段”验证(Address 端口补全/UnixSocket/PoolSize/PathInfoPattern 等),用例清单见:docs/FASTCGI_CDN_VERIFY.md

7) 源站超时等待上限

打开 /origin-timeout/ui,设置“最大等待时长”,再用探针模拟源站慢响应。

  • POST /origin-timeout/admin/set?wait=8s 设置等待预算
  • GET /origin-timeout/probe?originDelay=12s 当 delay > wait 时返回 504
7.1) 源站读取超时 / 并发 / 自动重试(更多设置字段)
  • GET /origin/timeout/read?ms=8000:首包延迟,用于 ReadTimeout
  • GET /origin/conn/hold?ms=15000 + /origin/conn/status:并发/空闲连接压测
  • GET /origin/flaky?key=demo&fail=2&code=502:前2次失败后成功,用于自动重试 50X/40X
  • GET /lb/tcp-udp/status:查看 connectFailPort(连接后立即断开),用于“连接失败/快速失败”验证
7.2) 请求限制(P2)逐字段验证
  • POST /req-limit/admin/config?enabled=1&maxConcurrent=2&maxConcurrentPerIp=1&singleConnKiBps=64&maxRequestKiB=16:开启并设置全部字段
  • GET /req-limit/admin/status:查看当前配置与运行并发计数
  • GET /req-limit/probe/hold?ms=8000:并发压测;超限应返回 429
  • GET /req-limit/probe/stream?seconds=5:观察单连接带宽限制(响应头 X-Req-Limit-KiBps
  • POST /req-limit/probe/upload:上传超出 maxRequestKiB 后应返回 413

完整步骤和命令见:docs/REQUEST_LIMIT_VERIFY.md

7.3) 内容压缩(网站设置 → 内容压缩)字段验证
  • POST /compression/admin/config:配置启用、扩展名、MimeType、算法顺序、长度、PartialContent、URL 例外/限制、匹配条件
  • GET /compression/admin/status:查看当前压缩策略快照
  • GET /compression/probe?path=/a.js&mime=application/javascript&size=4096:验证是否命中压缩
  • 查看响应头:X-Compression-Hit / X-Compression-Reason / X-Compression-Algorithm / Content-Encoding

完整步骤见:docs/COMPRESSION_WEBP_VERIFY.md

8) TCP/UDP 负载均衡回源验证

先访问 /lb/tcp-udp/status 获取端口(默认 TCP 10001、UDP 10002)。

  • echo 'ping' | nc -w 2 源站IP 10001(TCP)
  • echo -n 'ping' | nc -u -w 2 源站IP 10002(UDP)
9) Sticky 调度算法(三种参数)

在控制台把调度算法改为 Sticky 后,依次测试 Cookie / HTTP Header / URL 参数:

  • curl -s -c - http://200cdn.xyz/sticky/cookie
  • curl -s -H "X-Sticky-Key: user-a" http://200cdn.xyz/sticky/header
  • curl -s "http://200cdn.xyz/sticky/url?stickykey=user-a"

同一参数值应返回同一 bucket,用于验证粘滞调度键是否稳定生效。

10) 回源跟随(Follow Origin Redirect)

用于验证“回源跟随”开关是否生效(单跳/多跳)。

  • curl -sI "http://200cdn.xyz/origin-follow/start?code=302&hops=1"
  • curl -sI "http://200cdn.xyz/origin-follow/start?code=302&hops=3"
  • curl -sL "http://200cdn.xyz/origin-follow/start?code=302&hops=3"(跟随到 final)
  • curl -sI "http://200cdn.xyz/origin-follow/compare?expected=on&hops=3"
  • curl -sI "http://200cdn.xyz/origin-follow/compare?expected=off&hops=3"
11) 集群设置 / 节点设置 字段验证
  • POST /cluster/admin/config + GET /cluster/admin/status:调度、健康检查、重试、通知字段
  • GET /cluster/probe/dispatch?key=user-a:验证调度算法效果(如 hash 稳定性)
  • POST /node/admin/config + GET /node/admin/status:节点权重、连接、带宽、缓存、TLS/HTTP2/WS/IPv6
  • GET /node/probe/dispatch?key=user-a:节点禁用时应返回 503

完整字段级步骤见:docs/CLUSTER_NODE_FIELD_VERIFY.md;严格字段映射表见:docs/CLUSTER_NODE_FIELD_MAPPING.md

12) WebSocket 字段验证
  • POST /websocket/admin/config:配置启用WebSocket、允许所有来源域、传递请求来源域
  • GET /websocket/admin/status:查看当前策略
  • GET /websocket/probe:传 Origin 头检查是否允许及原因
  • /ws/ws/echo-headers:实际握手回显验证

完整步骤见:docs/WEBSOCKET_FIELD_VERIFY.md

13) WebP 字段验证
  • POST /webp/admin/config:配置启用 WebP、支持扩展名、支持 MimeType、最小/最大长度、匹配条件、Avif 开关
  • GET /webp/admin/status:查看当前 WebP 策略
  • GET /webp/probe:查看转换判定(X-WebP-Converted/Reason/Target
  • GET /webp/vary-accept:看真实 Accept 协商行为(Vary: Accept)

完整步骤见:docs/WEBP_FIELD_VERIFY.md

14) WAF 字段验证
  • POST /waf/admin/config:配置启用、人机识别方式、系统全局规则、条件参数/运算符/条件值、动作类型
  • GET /waf/admin/status:查看当前 WAF 策略和字段枚举
  • GET /waf/probe:查看匹配结果、动作和状态码(含 X-WAF-Matched/X-WAF-Action
  • POST /api/data:入站 JSON/Form 参数验证探针(可用于 requestJSON/requestForm 场景)
  • POST /api/upload:上传文件名验证探针(可用于 requestUpload 场景)
  • 兼容探针:/waf/sql/waf/xss/waf/path-traversal/*

完整步骤见:docs/WAF_FIELD_VERIFY.md

15) WAF 可视化控制台
  • 打开 /waf/ui,在线修改 WAF 字段并保存
  • 点击“发起探针”查看当前字段命中结果
  • 点击“一键回放”自动执行当前规则 + GET_302 动作验证
16) 出站规则(status / responseBody / responseHeader / bytesSent)样本验证
  • 状态码样本:/trigger-400/trigger-500/trigger-502/trigger-503/forbidden/not-found/redirect
  • 敏感内容样本:/api/user-info(身份证)、/api/contact(手机号)、/api/db-error(SQL错误)、/api/path-leak(路径)、/api/internal-info(内网IP)、/api/exception(Traceback)
  • 密钥与支付样本:/api/config(secret/password/token)、/api/payment(银行卡号)、/api/full-info(身份证+手机号)
  • 响应头样本:/api/helloServer/X-Powered-By)、/api/dataAccess-Control-Allow-Origin: *
  • 大响应体样本:/api/large-data?bytes=2097152(用于 bytesSent 阈值)

与 yuanzhan 对齐覆盖(新增)

以下路径用于一一对照 CDN 功能:缓存、图片/视频、压缩编码、重定向、CORS、流式、上传。

# Range 边界
curl -sI -H "Range: bytes=0-15" http://200cdn.xyz/range/binary
curl -sI -H "Range: bytes=0-1023" http://200cdn.xyz/media/video.mp4.range

# Range 严格边界矩阵(推荐都跑一遍)
# 1) 正常:首段 / 开区间 / 尾部区间(suffix-range)
curl -sI -H "Range: bytes=0-99" http://200cdn.xyz/range/strict
curl -sI -H "Range: bytes=100-" http://200cdn.xyz/range/strict
curl -sI -H "Range: bytes=-200" http://200cdn.xyz/range/strict

# 2) 越界 / 非法:应返回 416,并带 Content-Range: bytes */TOTAL
curl -sI -H "Range: bytes=9999999-10000000" http://200cdn.xyz/range/strict
curl -sI -H "Range: bytes=10-1" http://200cdn.xyz/range/strict
curl -sI -H "Range: items=0-10" http://200cdn.xyz/range/strict
curl -sI -H "Range: bytes=0-0,2-3" http://200cdn.xyz/range/strict

# 3) HEAD + Range:应返回 206,且包含 Content-Range/Content-Length
curl -sI -X HEAD -H "Range: bytes=0-15" http://200cdn.xyz/range/strict

# 4) If-Range(ETag 命中 -> 206;不命中 -> 忽略 Range 返回 200)
curl -sI -H 'If-Range: "200cdn-range-v1"' -H "Range: bytes=0-15" http://200cdn.xyz/range/strict
curl -sI -H 'If-Range: "not-match"' -H "Range: bytes=0-15" http://200cdn.xyz/range/strict

# 5) If-Range(HTTP-date 命中/不命中)
curl -sI -H "If-Range: Sat, 01 Jun 2024 12:00:00 GMT" -H "Range: bytes=0-15" http://200cdn.xyz/range/strict
curl -sI -H "If-Range: Sat, 01 Jun 2022 12:00:00 GMT" -H "Range: bytes=0-15" http://200cdn.xyz/range/strict

# 6) If-Match / If-None-Match + Range 组合
curl -sI -H 'If-Match: "200cdn-conditional-v1"' -H "Range: bytes=0-31" http://200cdn.xyz/range/conditional
curl -sI -H 'If-Match: "not-match"' -H "Range: bytes=0-31" http://200cdn.xyz/range/conditional
curl -sI -H 'If-None-Match: "200cdn-conditional-v1"' -H "Range: bytes=0-31" http://200cdn.xyz/range/conditional
curl -sI -H 'If-None-Match: "not-match"' -H "Range: bytes=0-31" http://200cdn.xyz/range/conditional

# 7) 301/302 + Range 继承(观察 CDN 是否透传 Range 到跳转后的请求)
curl -sI -H "Range: bytes=0-31" http://200cdn.xyz/redirect/range/301
curl -sI -L -H "Range: bytes=0-31" http://200cdn.xyz/redirect/range/301
curl -sI -L -H "Range: bytes=0-31" http://200cdn.xyz/redirect/range/302
curl -sI -L -H "Range: bytes=0-31" http://200cdn.xyz/redirect/range-hop/302

# ETag / 304
etag=$(curl -sI http://200cdn.xyz/cache/etag | tr -d "\r" | awk -F": " "/^ETag:/{print $2;exit}")
curl -sI -H "If-None-Match: $etag" http://200cdn.xyz/cache/etag

# Last-Modified / 304
curl -sI -H "If-Modified-Since: Sat, 01 Jun 2024 12:00:00 GMT" http://200cdn.xyz/cache/lastmodified

POST 回显(用于调试请求体和头)

把请求 JSON 发到 /api/echo,源站会把解析结果回显出来。

全站字段严格总表:docs/CDN_FIELD_MAPPING_STRICT.md

curl -s -X POST http://200cdn.xyz/api/echo \\
  -H 'Content-Type: application/json' \\
  -d '{"name":"cdn-test","n":1}' | jq