HTTP 云函数路由
基于 @cloudbase/functions-framework 框架可以将一个大函数拆分成多个子函数,并通过请求路径将不同的请求路由到不同的处理函数
为什么需要函数路由
传统的云函数开发模式中,每个函数都需要独立部署和运行,这会带来以下问题:
- 资源浪费:每个函数独占一个实例,即使请求量很小也需要保持运行
- 冷启动频繁:多个函数意味着更多的冷启动次数
- 部署复杂:需要分别部署和管理多个函数
- 成本较高:更多的实例数量带来更高的资源成本
使用函数路由可以解决这些问题:
- 资源共享:多个函数共享同一实例,充分利用计算资源
- 降低成本:减少实例数量,降低冷启动次数
- 简化部署:一次部署多个函数,统一管理
- 提升性能:共享内存和连接池等资源,提高响应速度
前置条件
使用函数路由功能需要安装 @cloudbase/functions-framework 依赖:
npm install @cloudbase/functions-framework
该框架提供了函数加载、路由分发、请求处理等核心能力。
快速开始
第一步:创建项目结构
创建一个支持多函数的项目目录:
my-functions/
├── package.json # 项目配置
├── scf_bootstrap # 启动脚本
├── cloudbase-functions.json # 函数路由配置(关键)
├── index.js # 默认函数入口
├── user/ # user 函数目录
│ └── index.js
└── order/ # order 函数目录
└── index.js
第二步:创建函数代码
默认函数(index.js):
exports.main = function (event, context) {
return {
message: 'Hello from default function',
path: context.httpContext.url,
};
};
user 函数(user/index.js):
exports.main = function (event, context) {
return {
message: 'User API',
path: context.httpContext.url,
method: context.httpContext.httpMethod,
};
};
order 函数(order/index.js):
exports.main = function (event, context) {
return {
message: 'Order API',
path: context.httpContext.url,
};
};
第三步:配置函数路由
创建 cloudbase-functions.json 配置文件:
{
"functionsRoot": ".",
"functions": [
{
"name": "default",
"directory": "."
},
{
"name": "user",
"directory": "user",
"triggerPath": "/api/user" // triggerPath 是 routes 的简化写法,可以直接在函数定义中指定触发路径
},
{
"name": "order",
"directory": "order",
"triggerPath": "/api/order"
}
],
"routes": [
{
"functionName": "echo",
"path": "/echo"
}
]
}
第四步:配置 package.json
{
"name": "my-functions",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@cloudbase/functions-framework": "latest"
}
}
第五步:创建启动脚本
创建 scf_bootstrap 文件(无扩展名):
#!/bin/bash
node node_modules/@cloudbase/functions-framework/bin/tcb-ff.js \
--port=9000 \
--logDirname=/tmp/logs \
--interceptOutput=false \
--extendedContextKey=X-Cloudbase-Context \
--functionsConfigFile=cloudbase-functions.json
⚠️ 重要:
scf_bootstrap文件需要具有可执行权限。在 Linux/macOS 系统中使用chmod +x scf_bootstrap命令设置权限。
启动参数说明:
| 参数 | 说明 | 默认值 |
|---|---|---|
--port | 函数监听端口 | 9000 |
--logDirname | 日志存储目录 | /tmp/logs |
--interceptOutput | 是否拦截标准输出 | false |
--extendedContextKey | 扩展上下文请求头键名 | X-Cloudbase-Context |
--functionsConfigFile | 函数路由配置文件路径 | cloudbase-functions.json |
第六步:安装依赖
npm install
第七步:本地测试
启动函数服务:
./scf_bootstrap
测试不同路由:
# 测试默认函数
curl http://localhost:9000/
# 测试 user 函数
curl http://localhost:9000/api/user
# 测试 order 函数
curl http://localhost:9000/api/order
第八步:部署到云端
使用云开发 CLI 或控制台部署函数,部署后访问:
# 默认函数
https://your-function.run.tcloudbase.com/
# user 函数
https://your-function.run.tcloudbase.com/api/user
# order 函数
https://your-function.run.tcloudbase.com/api/order
路由匹配规则
理解路由匹配规则对于正确配置函数路由至关重要:
1. 前缀匹配
路由匹配采用前缀匹配模式。当请求路径以某个路由规则的 path 开头时,即可匹配该路由。
示例:
{
"routes": [
{
"functionName": "user",
"path": "/api/user"
}
]
}
以下请求路径都会匹配到 user 函数:
/api/user✅/api/user/✅/api/user/profile✅/api/user/list✅
以下请求路径不会匹配:
/api/users❌(不是以/api/user开头)/api❌(路径不完整)
2. 最长匹配
当多个路由规则都能匹配同一请求路径时,框架会选择最长的那个路由规则。
示例:
{
"routes": [
{
"functionName": "user",
"path": "/api/user"
},
{
"functionName": "userProfile",
"path": "/api/user/profile"
}
]
}
路由结果:
/api/user→user函数/api/user/list→user函数/api/user/profile→userProfile函数(最长匹配)/api/user/profile/detail→userProfile函数(最长匹配)
3. 末尾斜杠处理
路径末尾的 / 不影响匹配结果,/api/user 和 /api/user/ 被视为等价。
示例:
{
"routes": [
{
"functionName": "user",
"path": "/api/user"
}
]
}
以下路径都会匹配:
/api/user✅/api/user/✅
4. 路径单元精确匹配
路由匹配以 / 分隔为路径单元,每个路径单元需要精确匹配。
示例:
{
"routes": [
{
"functionName": "user",
"path": "/api/user"
}
]
}
路由结果:
/api/user✅/api/users❌(users与user不匹配)/api/user123❌(user123与user不匹配)
5. 一函数多路由
一个函数可以配置多个触发路径,从不同的 URL 路径访问同一个函数。
示例:
{
"routes": [
{
"functionName": "user",
"path": "/api/user"
},
{
"functionName": "user",
"path": "/v1/user"
},
{
"functionName": "user",
"path": "/v2/user"
}
]
}
以上三个路径都会路由到 user 函数。
6. 一路由一函数
同一个路径只能指向一个函数,不能同时指向多个函数。
错误示例:
{
"routes": [
{
"functionName": "userA",
"path": "/api/user"
},
{
"functionName": "userB",
"path": "/api/user"
}
]
}
⚠️ 注意:上述配置会导致路由冲突,框架会报错。
7. 默认路由
可以配置一个 path 为 / 的路由作为默认路由,处理所有未匹配的请求。
示例:
{
"routes": [
{
"functionName": "user",
"path": "/api/user"
},
{
"functionName": "order",
"path": "/api/order"
},
{
"functionName": "default",
"path": "/"
}
]
}
路由结果:
/api/user→user函数/api/order→order函数/→default函数/about→default函数/health→default函数
进阶用法
使用 triggerPath 简化配置
triggerPath 是 routes 的简化写法,可以直接在函数定义中指定触发路径。
对比:
使用 triggerPath:
{
"functions": [
{
"name": "user",
"directory": "user",
"triggerPath": "/api/user"
}
],
"routes": []
}
等价于:
{
"functions": [
{
"name": "user",
"directory": "user"
}
],
"routes": [
{
"functionName": "user",
"path": "/api/user"
}
]
}
💡 提示:推荐使用
triggerPath,配置更简洁。如果需要一个函数对应多个路由,则需要使用routes配置。
结合 Express/Koa 框架
函数路由可以与 Express、Koa 等 Web 框架结合使用,每个子函数可以是一个完整的 Web 应用。
示例:
// user/index.js
const express = require('express');
const app = express();
app.get('/profile', (req, res) => {
res.json({ user: 'profile' });
});
app.get('/list', (req, res) => {
res.json({ users: [] });
});
exports.main = app;
访问:
https://your-function.api.tcloudbasegateway.com/v1/functions/{function-name}/api/user/profile?webfn=truehttps://your-function.api.tcloudbasegateway.com/v1/functions/{function-name}/api/user/list?webfn=true
混合使用 SSE 和 WebSocket
不同的子函数可以处理不同类型的请求,比如一个函数处理 HTTP 请求,另一个处理 WebSocket 连接。
示例:
.
├── cloudbase-functions.json
├── index.js # HTTP 函数
└── ws/ # WebSocket 函数
└── index.js
cloudbase-functions.json:
{
"functionsRoot": ".",
"functions": [
{
"name": "default",
"directory": "."
},
{
"name": "ws",
"directory": "ws",
"triggerPath": "/ws"
}
]
}
VSCode 编辑器支持
安装 @cloudbase/functions-framework 依赖后,可以在 VSCode 中获得 cloudbase-functions.json 的 schema 自动提示。
在项目根目录创建 .vscode/settings.json 文件:
{
"json.schemas": [
{
"fileMatch": ["cloudbase-functions.json"],
"url": "./node_modules/@cloudbase/functions-framework/functions-schema.json"
}
]
}
配置后,在编辑 cloudbase-functions.json 文件时会自动提示字段和类型。
完整示例
项目结构
my-api-service/
├── .vscode/
│ └── settings.json # VSCode 配置
├── package.json # 项目配置
├── scf_bootstrap # 启动脚本
├── cloudbase-functions.json # 函数路由配置
├── index.js # 默认函数
├── user/ # 用户 API
│ └── index.js
├── order/ # 订单 API
│ └── index.js
├── product/ # 商品 API
│ └── index.js
└── sse/ # SSE 流式响应
└── index.js
cloudbase-functions.json
{
"functionsRoot": ".",
"functions": [
{
"name": "default",
"directory": ".",
"triggerPath": "/"
},
{
"name": "user",
"directory": "user",
"triggerPath": "/api/user"
},
{
"name": "order",
"directory": "order",
"triggerPath": "/api/order"
},
{
"name": "product",
"directory": "product",
"triggerPath": "/api/product"
},
{
"name": "sse",
"directory": "sse",
"triggerPath": "/stream"
}
]
}
常见问题
1. 路由配置不生效
问题:配置了路由规则,但访问时仍然路由到错误的函数。
解决方案:
- 检查
cloudbase-functions.json文件是否在正确的位置 - 检查
scf_bootstrap启动脚本中是否指定了--functionsConfigFile参数 - 检查路由规则的顺序,确保最长匹配规则在前
- 使用
console.log输出context.httpContext.url查看实际请求路径
2. 函数找不到
问题:启动时报错:Function xxx not found。
解决方案:
- 检查
functions[].directory路径是否正确 - 检查
functions[].source文件是否存在 - 检查
functions[].target导出的函数名是否正确 - 确保
functionsRoot设置正确
3. 路由冲突
问题:启动时报错:Route conflict。
解决方案:
- 检查是否有多个路由规则指向同一路径
- 检查
triggerPath和routes中是否有重复配置 - 确保每个路径只对应一个函数
4. 默认函数不生效
问题:配置了 path: "/" 的默认路由,但访问时返回 404。
解决方案:
- 确保默认路由配置在
routes数组的最后 - 检查是否有其他路由规则覆盖了默认路由
- 确保默认函数的
directory和source配置正确
5. 本地开发时热更新不生效
问题:修改函数代码后,需要重启服务才能生效。
解决方案:
@cloudbase/functions-framework 框架暂不支持热更新,需要手动重启服务:
# 停止服务(Ctrl+C)
# 重新启动
./scf_bootstrap