阿里云 ESA 函数和Pages 深度实战:用边缘函数实现毫秒级请求转发与重定向
背景:为什么传统重定向方案撑不住了
做过 Web 服务迭代的同学大概都遇到过这些场景:域名迁移、接口版本升级、多地域流量调度。传统方案要么在 Nginx 层写一堆 rewrite 规则,要么依赖 CDN 控制台的静态重定向配置,但这两种方式都有明显短板:
| 传统方案的瓶颈 | 边缘函数的优势 |
|---|---|
| 单台 Nginx 在高并发压力下,重定向延迟会随流量陡增,还要额外承担计算压力 | 通过全球 3200+ 边缘节点并行处理,离用户最近的节点直接响应 |
| 只支持基础字符串替换,没有正则捕获和动态决策能力 | 支持完整 JavaScript ES6 语法,可写正则、条件判断等复杂逻辑 |
| 修改配置后生效延迟高,回滚困难,没有环境隔离 | 版本化发布,支持测试环境、灰度环境、生产环境分级验证 |
| 逻辑集中在源站服务器,源站压力和带宽消耗大 | 请求在边缘节点直接被处理或转发,有效降低源站带宽和计算资源消耗 |
阿里云边缘安全加速(ESA)最近把原来的"边缘函数"能力全面升级为函数和Pages,深度集成了 Git 工作流、全球边缘网络和智能构建系统,不仅能跑 JS 边缘函数,还能直接托管静态站点、SPA、SSR 应用。这篇文章就用"请求转发 + 重定向"这个最常见的场景,带你走一遍从零到上线的完整流程,把控制台里每一个容易踩坑的细节都讲清楚。
准备工作:开通 ESA 并接入站点
把域名接入 ESA:
- 登录 ESA 控制台,进入站点管理,单击新增站点,输入你的域名(比如
example.com)。 - 选择获得加速与保护的区域,接入方式选择 CNAME。
- 选择套餐类型——如果是第一次使用,可以在阿里云官网试用中心搜索 "ESA" 领取试用资格,先跑通流程再决定要不要长期使用。
- 完成域名归属权验证:控制台会给一条 TXT 记录,你需要去云解析 DNS 控制台,在权威域名解析里添加一条主机记录为
_esaauth的 TXT 记录,值填控制台给出的验证码,等待解析生效后点击点击验证即可。 - 建议顺手在SSL/TLS > 边缘证书里开启 SSL/TLS 功能(可以直接申请免费证书),后续无论是访问函数域名还是站点本身,都推荐走 HTTPS,一是安全性有保障,二是现在浏览器对 HTTP 明文站点的提示越来越不友好。
另外要注意:记录生效有延迟,通常几分钟内会完成,如果验证一直失败,先检查记录是否添加到了正确的域名上。
核心实现:创建函数并编写转发/重定向逻辑
第一步:创建函数
在 ESA 控制台左侧导航栏找到边缘计算和AI > 函数和Pages,点击创建,创建方式选择函数模板(如果你想从零写,也可以选择自定义函数,后续自行编写全部逻辑)。
创建时需要注意几个容易被忽略的限制:
- 函数名称必须以小写英文字母开头,只能包含小写字母、数字和中划线,且不能以中划线结尾,长度限制在 2~41 个字符之间,一旦创建完成就无法修改,起名字的时候最好提前规划好命名规范。
- 规格决定了函数单次执行最多可用的 CPU 时间(不含等待网络请求响应的 I/O 时间),目前提供 5ms(默认)、50ms、100ms 三档,可用内存固定 128MB,RT(响应时间)最大值都是 120 秒。如果你的函数逻辑比较简单(比如纯字符串匹配和跳转),默认的 5ms 规格完全够用;但凡涉及稍复杂的计算或者多次数据处理,建议直接选 50ms 起步,避免函数因为 CPU 时间片耗尽被强制中断。
填写函数名称、描述并预览代码详情后单击提交,系统会自动生成一个临时公共域名,有效期 60 分钟,方便你先预览效果,不用等域名绑定完成就能看到函数是否跑得通。
第二步:配置触发器
触发器决定了什么样的请求会被转发给这个函数,ESA 提供两种绑定方式,很多教程只讲域名绑定,但路由方式其实更灵活,值得展开讲清楚。
域名绑定:把某个域名的全部流量都转发到函数。比如你已经在 ESA 添加了站点 example.com,想通过 function.example.com 访问函数,操作是:
- 进入函数详情页,切换到触发器页签,选择域名绑定,点击添加域名,填入
function.example.com。 - 绑定完成后 ESA 会自动在 DNS 记录里加一条 CNAME,不需要你手动去云解析控制台添加——这一点和很多人的直觉相反,如果你手动又加了一条同名记录反而可能导致解析冲突。
- 绑定后,
function.example.com、function.example.com/user、function.example.com/login等所有路径的请求都会进函数处理。
路由:只把某个域名下特定路径的流量转发到函数,其余路径继续走正常的加速回源或缓存流程。这对已经在线上跑的站点更实用,不需要单独占用一个子域名:
- 在触发器页签选择路由,点击添加路由,选择目标站点。
- 填写路由规则,支持用通配符
*匹配零个或多个字符,但只支持在开头或结尾加通配符,不支持中间通配符,也不支持?param=1这种参数匹配。几个实际例子:example.com/a*会匹配example.com/a、example.com/a1、example.com/api等,但不会匹配example.com/b。*.example.com/*会匹配任何发到www.example.com/或example.com/的请求。www.example.com/api/*会匹配www.example.com/api/users、www.example.com/api/products/123这类子路径请求。
- 路由规则区分大小写,
example.com/a和example.com/A会被当成两条完全不同的规则;如果业务本身路径不区分大小写,建议在函数代码里对pathname统一做一次.toLowerCase()处理,避免因为大小写不一致导致规则漏判。 - 如果多条规则都能匹配同一个请求,会优先命中更早配置的那条。
- 如果路由里填的是带前缀的域名(比如
*.example.com或www.example.com),这种情况下需要你手动去添加一条对应的解析记录,否则访问会失败——这正好和域名绑定的自动添加行为相反,很容易搞混。
第三步:编写真正的业务逻辑
需求场景设定成这样:
- 如果请求路径以
/esa/开头、以.html结尾,直接转发到目标页面(不改变浏览器地址栏)。 - 如果请求路径以
/product开头,301 重定向到产品列表页。 - 其他情况一律重定向到首页。
代码用 JavaScript ES6 语法编写,直接粘贴到控制台的代码页签:
// 定义正则表达式:Path部分以"/esa/"开头,以".html"结尾,用于命中"转发"规则
const esaPageRegex = /^\/esa\/.*\.html$/;
function handleRequest(request) {
const url = new URL(request.url);
const pathName = url.pathname.toLowerCase();
// 命中转发规则:直接fetch目标地址,地址栏不变
if (esaPageRegex.test(pathName)) {
const newUrl = 'https://www.aliyun.com/product/esa';
return fetch(new Request(newUrl, request));
}
// 命中重定向规则:返回301跳转
if (pathName.startsWith('/product')) {
const newUrl = 'https://www.aliyun.com/product/list';
return Response.redirect(newUrl, 301);
}
// 兜底规则
const newUrl = 'https://www.aliyun.com/';
return Response.redirect(newUrl, 301);
}
export default {
fetch(request) {
return handleRequest(request);
}
}
逐行拆解一下这段逻辑:new URL(request.url) 把原始请求地址解析成结构化对象,方便拿到 pathname 单独做判断,这里额外调用了 .toLowerCase() 统一大小写,避免路由规则的大小写敏感特性影响判断结果;两个 if 分支分别对应转发和重定向两种处理方式,顺序很关键——因为判断是按代码执行顺序走的,如果把兜底逻辑写在最前面,后面的判断分支永远不会被执行到。
转发(fetch) 和重定向(Response.redirect) 是两种完全不同的处理方式:前者是服务端代理,函数发起一次新的请求去拿目标内容再原样返回给客户端,用户感知不到地址变化;后者是标准的 HTTP 30x 跳转,由客户端浏览器自己发起第二次请求,地址栏会变。选型时一定要想清楚业务到底需要哪种——如果是想做隐藏真实来源的内容聚合,用转发;如果是纯粹的地址迁移,重定向更省资源也更符合 SEO 规范。
这里还要提前留意一个和转发场景强相关的限制:函数单次执行最多只能发起 4 次 fetch 子请求,如果你的转发逻辑需要串联多个上游服务,4 个请求很快就会用完,需要提前规划好调用链路。
第四步:调试、生成版本、分环境发布
控制台右侧自带一套调试工具,可以直接构造 HTTP 请求方法、请求头、请求体来测试函数逻辑,不需要真的发到线上就能看到返回结果。完整流程是:
- 代码写完后先点击保存到调试环境,然后在右侧工具栏构造请求方法、请求头、请求体,点击请求按钮,控制台会直接返回函数处理后的响应结果,方便反复调整。
- 调试通过后点击发布到测试环境——测试环境是独立的边缘测试节点,你可以通过绑定
Host的方式,用真实客户端发请求验证效果,这一步不会影响任何线上流量。 - 测试环境验证没问题后,点击生成正式版本,进入版本发布页签,选择目标版本,点击发布,可选发布到测试环境、灰度环境或生产环境。
这里有个官方建议的发布顺序很容易被忽略:先逐个发布到各个灰度环境,所有灰度环境都验证通过之后,再发布到生产环境,而不是测试完直接全量上线。灰度环境本质上是拿一小部分真实边缘节点先跑新版本,能在真正影响全量用户之前再拦一道风险。
效果验证
浏览器直接访问 https://function.example.com/esa/info.html,地址栏不变但显示的是转发后的内容,说明转发生效。也可以用命令行更直观地验证:
curl -I https://function.example.com/product
正常情况下应该能在返回头里看到 HTTP/1.1 301 Moved Permanently 和 Location: https://www.aliyun.com/product/list,如果状态码不对,大概率是路由规则没匹配上,回去检查路由的通配符写法和大小写。
使用限制一览
正式上线前,建议对照官方限制表逐条检查自己的业务场景会不会踩线:
| 限制项 | 限制值 | 影响说明 |
|---|---|---|
| 响应时间(RT) | 120 秒 | 函数单次执行的响应时间上限,等待 I/O 的时间也算在内 |
| 网关等待时间 | 10 秒 | 网关等待函数返回数据的超时时间,超时会主动断开连接并返回 504 |
| 代码包大小 | 4 MB | 每个函数的 JavaScript 代码文件体积上限 |
| 子请求数量 | 4 个 | 单次函数执行允许发起的 fetch 请求数量上限 |
| 开发语言 | JavaScript(ES6 语法) | 目前仅支持 JS,暂不支持其他语言 |
| 每账号函数数量 | 免费模式 20 个 / 付费模式 100 个 | 超出需要考虑切换模式或合并函数逻辑 |
| 每函数版本数量 | 免费模式 5 个 / 付费模式 20 个 | 版本数量达到上限后需要清理旧版本才能继续生成新版本 |
其中网关等待时间 10 秒和子请求数量 4 个这两项和本文的转发/重定向场景关系最直接:如果转发的目标源站响应慢,超过 10 秒会被网关直接掐断连接返回 504;如果一次请求里需要串行调用多个上游接口,超过 4 次子请求同样会失败,写业务逻辑时要提前把这两条线摸清楚。
踩坑记录
实际操作中容易踩的几个坑,提前列出来省得你走弯路:
- 函数名称创建后不可修改:起名字务必提前规划好,尤其是团队协作场景,建议提前约定命名规范(比如按业务模块+功能前缀)。
- 规格选错导致执行中断:默认的 5ms CPU 时间片对纯转发/重定向这种逻辑够用,但如果代码里有额外的字符串处理或多重判断,建议直接上 50ms 规格,避免线上偶发被截断。
- 域名绑定和路由的 DNS 处理方式完全相反:域名绑定会自动加 CNAME,你不需要手动操作;路由如果用了带前缀的域名匹配,则需要你自己手动去加解析记录,搞反了两种情况会导致要么解析冲突,要么访问不通。
- 路由不支持中间通配符:
example.com/*/path这种写法是不合法的,只能在开头或结尾用*。 - 多条路由规则冲突时优先匹配更早配置的规则:如果发现新加的路由规则没有生效,先看看是不是被一条更早配置的规则抢先命中了。
- 子请求数量和等待时间容易被忽略:4 个子请求、10 秒网关等待,这两条限制在做多接口聚合或者转发慢速源站时特别容易踩雷,提前设计好降级逻辑。
- CNAME 生效有延迟:域名绑定后的 DNS 记录生效通常要几分钟,遇到访问不通先别急着改配置,等等看。
总结
这套方案的核心价值在于把原本要靠 Nginx 或应用层代码处理的转发逻辑下沉到了边缘节点,既减少了源站压力,又把延迟降到了最低。从这次实战看,ESA 函数和Pages 在触发器配置(域名绑定/路由两种方式)、调试环境、版本管理、灰度发布这几个环节上的设计已经相当完整,细节抠得越仔细,线上踩坑的概率就越低。如果业务逻辑更重、需要更大算力,ESA 的边缘容器会是更合适的选择;如果需要在边缘做有状态的判断(比如灰度名单、简单鉴权),可以进一步了解边缘存储(EdgeKV)如何和函数配合使用。