API Gateway REST API 响应流式传输实战:TTFB 降低 86%¶
Lab 信息
- 难度: ⭐⭐ 中级
- 预估时间: 60 分钟
- 预估费用: $1-2(含清理)
- Region: us-east-1
- 最后验证: 2026-03-27
背景¶
在 GenAI 应用中,用户发送 prompt 后需要等待完整响应生成才能看到结果——有时长达数十秒。这种"空白等待"严重影响用户体验。
2025 年 11 月,AWS 为 API Gateway REST API 推出了 Response Streaming 功能。核心价值三点:
- 降低 TTFB — 响应生成的同时即开始传输,用户实时看到增量内容
- 突破 29 秒超时 — 流式模式下 timeout 可设置到 15 分钟
- 突破 10MB payload 限制 — 可直接流式传输大文件
此前只有 Lambda Function URL 和 HTTP API 支持类似能力。REST API 现在补齐了这一短板,已有 REST API 的用户无需迁移就能获得流式能力,同时保留 WAF、Usage Plans、API Keys 等丰富功能。
前置条件¶
- AWS 账号(需要 Lambda、API Gateway、IAM 权限)
- AWS CLI v2 已配置
- 如果测试 Bedrock 场景,需要开通 Claude 3 Haiku 模型访问
核心概念¶
一张表搞定:Buffered vs Stream¶
| 对比项 | Buffered(默认) | Stream |
|---|---|---|
| 传输行为 | 等待完整响应后一次发送 | 边生成边传输 |
| TTFB | 等于响应生成时间 | 远小于响应生成时间 |
| 最大 Timeout | 29 秒 | 15 分钟 |
| 最大 Payload | 10 MB | 无硬性上限(>10MB 部分限速 2MB/s) |
| 支持的集成类型 | 所有类型 | 仅 HTTP_PROXY / AWS_PROXY |
| Endpoint Caching | ✅ | ❌ |
| Content Encoding | ✅ | ❌ |
| VTL Response Mapping | ✅ | ❌ |
| Binary Payload | 需配置 binary media types | 自动支持 |
关键限制¶
- Idle Timeout:Regional/Private 端点 5 分钟,Edge-optimized 端点 30 秒
- 仅支持 response streaming,不支持 request streaming
- Lambda 连接关闭后,Lambda 函数可能继续执行(注意 timeout 设置)
Lambda URI 路径差异¶
这是最容易犯错的地方:
# 标准(buffered)调用
arn:aws:apigateway:{region}:lambda:path/2015-03-31/functions/{arn}/invocations
# 流式调用
arn:aws:apigateway:{region}:lambda:path/2021-11-15/functions/{arn}/response-streaming-invocations
动手实践¶
Step 1: 创建 Lambda 执行角色¶
# 创建信任策略文件
cat > /tmp/lambda-trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
EOF
# 创建角色
aws iam create-role \
--role-name apigw-streaming-lambda-role \
--assume-role-policy-document file:///tmp/lambda-trust-policy.json \
--region us-east-1
# 附加基础执行权限
aws iam attach-role-policy \
--role-name apigw-streaming-lambda-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Step 2: 创建流式 Lambda 函数¶
// streaming-basic.mjs — 使用 awslambda.streamifyResponse
export const handler = awslambda.streamifyResponse(
async (event, responseStream, context) => {
const metadata = {
statusCode: 200,
headers: { "Content-Type": "text/plain" }
};
// HttpResponseStream.from 自动添加 8-null-byte 分隔符
responseStream = awslambda.HttpResponseStream.from(
responseStream, metadata
);
const chunks = 10;
const delayMs = parseInt(
event.queryStringParameters?.delay || "500"
);
for (let i = 0; i < chunks; i++) {
responseStream.write(
`Chunk ${i + 1}/${chunks} at ${new Date().toISOString()}\n`
);
if (i < chunks - 1) {
await new Promise(r => setTimeout(r, delayMs));
}
}
responseStream.end();
}
);
部署函数:
zip -j streaming-basic.zip streaming-basic.mjs
aws lambda create-function \
--function-name apigw-streaming-basic \
--runtime nodejs20.x \
--handler streaming-basic.handler \
--role arn:aws:iam::<ACCOUNT_ID>:role/apigw-streaming-lambda-role \
--zip-file fileb://streaming-basic.zip \
--timeout 30 \
--memory-size 128 \
--region us-east-1
创建对照组——标准(buffered)Lambda:
// buffered-basic.mjs — 标准 handler,返回完整响应
export const handler = async (event, context) => {
const chunks = 10;
const delayMs = parseInt(
event.queryStringParameters?.delay || "500"
);
let body = "";
for (let i = 0; i < chunks; i++) {
body += `Chunk ${i + 1}/${chunks} at ${new Date().toISOString()}\n`;
if (i < chunks - 1) {
await new Promise(r => setTimeout(r, delayMs));
}
}
return {
statusCode: 200,
headers: { "Content-Type": "text/plain" },
body: body
};
};
Step 3: 创建 REST API 并配置 Streaming¶
# 创建 REST API
aws apigateway create-rest-api \
--name "streaming-test" \
--endpoint-configuration types=REGIONAL \
--region us-east-1
# 获取 root resource ID
aws apigateway get-resources \
--rest-api-id <API_ID> \
--region us-east-1
# 创建 /stream 资源
aws apigateway create-resource \
--rest-api-id <API_ID> \
--parent-id <ROOT_ID> \
--path-part stream \
--region us-east-1
# 创建 GET 方法
aws apigateway put-method \
--rest-api-id <API_ID> \
--resource-id <STREAM_RESOURCE_ID> \
--http-method GET \
--authorization-type NONE \
--region us-east-1
# 关键配置:设置 STREAM 模式和流式 Lambda URI
aws apigateway put-integration \
--rest-api-id <API_ID> \
--resource-id <STREAM_RESOURCE_ID> \
--http-method GET \
--type AWS_PROXY \
--integration-http-method POST \
--uri "arn:aws:apigateway:us-east-1:lambda:path/2021-11-15/functions/<LAMBDA_ARN>/response-streaming-invocations" \
--response-transfer-mode STREAM \
--timeout-in-millis 30000 \
--region us-east-1
注意两个关键参数:
--response-transfer-mode STREAM— 启用流式响应- URI 中使用
/response-streaming-invocations而非/invocations
对照组使用标准配置:
# /buffered 资源使用标准 invocations
aws apigateway put-integration \
--rest-api-id <API_ID> \
--resource-id <BUFFERED_RESOURCE_ID> \
--http-method GET \
--type AWS_PROXY \
--integration-http-method POST \
--uri "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/<LAMBDA_ARN>/invocations" \
--timeout-in-millis 29000 \
--region us-east-1
别忘了添加 Lambda 权限和部署 API:
# 授权 API Gateway 调用 Lambda
aws lambda add-permission \
--function-name apigw-streaming-basic \
--statement-id apigateway-invoke \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:us-east-1:<ACCOUNT_ID>:<API_ID>/*" \
--region us-east-1
# 部署 API
aws apigateway create-deployment \
--rest-api-id <API_ID> \
--stage-name test \
--region us-east-1
Step 4: 测试对比¶
BASE_URL="https://<API_ID>.execute-api.us-east-1.amazonaws.com/test"
# 测试 Streaming TTFB
curl -s -o /dev/null -w "TTFB:%{time_starttransfer} Total:%{time_total}\n" \
"$BASE_URL/stream?delay=500"
# 测试 Buffered TTFB
curl -s -o /dev/null -w "TTFB:%{time_starttransfer} Total:%{time_total}\n" \
"$BASE_URL/buffered?delay=500"
测试结果¶
对比实验 1:TTFB(10 次平均值)¶
Lambda 生成 10 个 chunk,每个间隔 500ms,总计约 5 秒执行时间。
| 模式 | 平均 TTFB | 平均 Total | TTFB 改善 |
|---|---|---|---|
| Streaming | 0.73s | 5.25s | — |
| Buffered | 5.22s | 5.22s | — |
| 差异 | -4.49s | +0.03s | ↓ 86% |
关键发现:Streaming 模式下,第一个字节在 0.73 秒内到达客户端;Buffered 模式下需要等待全部 5 秒。总传输时间几乎相同,但用户体验截然不同。
对比实验 2:突破 29 秒超时限制¶
| 场景 | Streaming | Buffered |
|---|---|---|
| 45 秒 Lambda 执行 | ✅ 成功(TTFB 1.0s) | ❌ 超时(最大 29s) |
| timeout 可设置范围 | 50ms - 900,000ms | 50ms - 29,000ms |
对比实验 3:大 Payload 传输¶
| Payload | TTFB | 总时间 | 平均速率 |
|---|---|---|---|
| 5 MB | 0.73s | 2.41s | 2.07 MB/s |
| 10 MB | 0.70s | 2.74s | 3.65 MB/s |
| 15 MB ⭐ | 0.69s | 4.84s | 3.09 MB/s |
| 20 MB ⭐ | 0.70s | 7.16s | 2.79 MB/s |
⭐ 超过旧版 10MB 限制的 payload 均成功传输。超过 10MB 的部分可观察到限速效果(2MB/s),与官方文档一致。
边界测试:Idle Timeout¶
| Idle 时间 | Regional 端点结果 |
|---|---|
| 4 分钟 | ✅ 正常 — 第二个 chunk 成功到达 |
| 6 分钟 | ❌ 连接在 ~310s 后被切断 |
Regional 端点的 5 分钟 idle timeout 已实测确认。连接被静默切断——没有错误消息,因为 HTTP 200 状态码已经发送。
GenAI 实战:Bedrock 流式推理¶
通过 Lambda 调用 Bedrock Claude 3 Haiku 流式推理,经 API Gateway 流式传输到客户端:
| 指标 | 数值(6 次平均) |
|---|---|
| TTFB | 0.47s |
| 总时间 | 3.09s |
用户在 0.47 秒内就能看到第一个 token 开始输出——这对 AI 应用的交互体验至关重要。
踩坑记录¶
注意事项
-
Lambda URI 路径必须区分 — Streaming 用
/response-streaming-invocations,Buffered 用/invocations。用错了会得到 500 错误。(已查文档确认) -
流式 Lambda 必须用
streamifyResponse包裹 handler — 不能用标准 handler 格式(返回 JSON 对象)。反过来,Buffered 端点不能用streamifyResponse的函数。 -
Idle timeout 是静默断开 — 超过 idle timeout 后连接直接关闭,客户端收不到任何错误信息。生产环境务必实现心跳机制。(已查文档确认:Regional/Private 5 分钟,Edge-optimized 30 秒)
-
API Gateway 部署传播有延迟 — 新增 resource 后
create-deployment可能需要几秒才能生效。如果返回 403 "Missing Authentication Token",等待后重试或手动update-stage指定 deploymentId。(实测发现,官方未记录) -
Lambda 可能继续执行 — 客户端或 API Gateway 断开连接后,Lambda 函数可能继续运行直到超时。生产环境中需要适当设置 Lambda timeout。(已查文档确认)
费用明细¶
| 资源 | 单价 | 用量 | 费用 |
|---|---|---|---|
| Lambda 调用 | $0.20/100 万次 | ~100 次 | ~$0.00 |
| Lambda 计算 | $0.0000166667/GB·s | ~500 GB·s | ~$0.01 |
| API Gateway 请求 | $3.50/100 万次 | ~100 次 | ~$0.00 |
| API Gateway Streaming | $0.01/GB | ~0.5 GB | ~$0.01 |
| Bedrock Claude 3 Haiku | ~$0.00025/1K input + $0.00125/1K output | ~10 次 | ~$0.05 |
| 合计 | ~$0.07 |
清理资源¶
# 1. 删除 REST API(包含所有资源、方法、Stage)
aws apigateway delete-rest-api \
--rest-api-id <API_ID> \
--region us-east-1
# 2. 删除所有 Lambda 函数
for fn in apigw-streaming-basic apigw-streaming-basic-buffered \
apigw-streaming-long-run apigw-streaming-bedrock \
apigw-streaming-large-payload apigw-streaming-idle-test; do
aws lambda delete-function --function-name $fn --region us-east-1
done
# 3. 删除 IAM 角色(先删内联策略和托管策略)
aws iam delete-role-policy \
--role-name apigw-streaming-lambda-role \
--policy-name bedrock-invoke
aws iam detach-role-policy \
--role-name apigw-streaming-lambda-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role --role-name apigw-streaming-lambda-role
务必清理
Lab 完成后请执行清理步骤。虽然费用极低,但长期闲置的 API Gateway 仍会产生不必要的安全暴露面。
结论与建议¶
适用场景¶
- ✅ GenAI/LLM 应用 — 流式输出 token,大幅提升交互体验
- ✅ 大文件传输 — 不再需要 S3 预签名 URL 作为中间方案
- ✅ 长时间运行任务 — 实时进度更新,最长支持 15 分钟
- ✅ SSE(Server-Sent Events) — 增量推送场景
不适用场景¶
- ❌ 需要 Endpoint Caching 的 API
- ❌ 需要 VTL Response Mapping 的复杂转换
- ❌ Edge-optimized endpoint 的长 idle 场景(30 秒限制太短)
生产环境建议¶
- 实现心跳机制 — 避免 idle timeout 断连,每 2-3 分钟发送一次心跳数据
- 设置合理的 Lambda timeout — 比 API Gateway timeout 略短,确保函数不会在断连后持续运行
- 监控 TTFB 指标 — 启用 CloudWatch 指标监控流式 API 的性能
- 客户端做好断连重试 — idle timeout 断开是静默的,客户端需要检测并重试