1、引言
在混合开发和浏览器架构体系中,端侧下载拦截与管控一直是体现浏览器内核控制力的试金石。
当用户在前端页面点击一个下载链接时,往往并非直接发起到真实文件的请求,而是会经历多次的 301/302 重定向调度;更复杂的场景下,服务端的防盗链机制会严密校验请求的源地址(Referrer)。过去,由于底层内核封装过深,我们一旦拦截到 Web 的下载任务(WebDownloadItem),只能被动拿到最终解包出来的流或者目标地址,这导致开发者难以进行精准的源头审计,更无法在定制下载器时透传原始上下文给服务端以绕过安全防盗链。
为了彻底打通这一安全与溯源的壁垒,HarmonyOS NEXT 6.1.1 (API 24) 针对 ArkWeb 核心的下载控制模块(WebDownloadItem)新增了两道关键溯源接口:
getOriginalUrl():一针见血地还原引发下载动作的初始、最原始 URL,无视任何中间重定向乱象。getReferrerUrl():无缝提取引发下载的引荐页来源,为第三方定制下载器补充防盗链通行证。2、Kit能力介绍
本次更新隶属于 @kit.ArkWeb 套件中的下载委托层(WebDownloadDelegate)。ArkWeb 为开发者提供了接管浏览器默认下载行为的高阶权利。当开发者通过 webview.WebviewController.setDownloadDelegate 挂载了自定义的下载委托后,每一次下载任务的产生都会生成一个高度封装的 WebDownloadItem 对象,并抛给端侧由应用自行决定写入位置和进度分发。
3、Kit API介绍
在 API 24 之前,WebDownloadItem 主要提供了获取 GUID、获取下载进度百分比、甚至建议文件名(getSuggestedFileName)的能力。本次补齐了溯源闭环,这两个接口均仅支持在 Stage 模型下使用,无须特殊权限。
// 1. 获取下载文件的原始 URL 地址(重定向前用户点击或系统请求的第一现场地址)
getOriginalUrl(): string;
// 2. 获取下载文件的 referrer 地址(即用户在点击下载链接前停留的那个网页)
getReferrerUrl(): string;4、Kit 6.1 新增特性介绍
4.1 getOriginalUrl:拨开重定向的迷雾
许多大厂的下载链接采用统一分发调度策略,例如 https://d.example.com/latest,但在真实发起 HTTP 请求时,会返回 302 状态码,跳转到带有时效签名与 CDN 节点的真实长链接。
此时如果我们试图自行接管后续下载行为或用于统计归因,如果只拿到真实长链接,是无法与业务中台核对发版配置的。getOriginalUrl 就能将这个伪装褪去,直接告诉应用代码:用户点下的是那个 /latest。
4.2 getReferrerUrl:重塑防盗链请求的利器
很多企业的私有附件系统设置了严格的 Referrer 防盗链策略,如果直接把 WebDownloadItem 中的链接扔给第三方框架(如 request 模块)进行断点续传下载,由于缺失原始上下文,会直接遭遇 403 拒绝访问。
通过提取 getReferrerUrl(),开发者可以在自己构建的 HTTP Request Header 中原样塞入 Referer 字段,实现防盗链策略的完美“欺骗”与通过。
5、6.1新增特性项目实战
我们将以上特性整合成一个直观的 「ArkWeb 下载溯源防线控制舱」。
5.1 智能中控入口页:Index.ets
在我们的 Demo 中,准备了一个文本输入框来模拟发起 startDownload 请求,并隐藏了一个微型 Web 实例用于承载 Web 控制器内核。
5.2 核心特性控制舱:ArkWebDemo.ets
我们在控制舱内注册了 WebDownloadDelegate 监听器,并在其 onBeforeDownload 回调钩子内强行拦截下载流程,打出日志,最终指定将文件写入安全的应用沙盒中。
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct ArkWebDemo {
controller: webview.WebviewController = new webview.WebviewController();
delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate();
@State logs: string[] = [];
@State inputUrl: string = 'https://www.example.com/download/test.zip';
private appendLog(msg: string) {
let now = new Date();
let timeStr = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}.${now.getMilliseconds()}`;
this.logs.unshift(`[${timeStr}] ${msg}`);
}
aboutToAppear() {
this.appendLog('🚀 ArkWeb 下载溯源控制舱已初始化');
this.setupDownloadDelegate();
}
setupDownloadDelegate() {
try {
this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => {
// [核心新增能力 1] 获取未发生重定向前的起始请求地址
let originalUrl = webDownloadItem.getOriginalUrl();
// [核心新增能力 2] 获取触发此次下载事件的引荐页面来源
let referrerUrl = webDownloadItem.getReferrerUrl();
let guid = webDownloadItem.getGuid();
let fileName = webDownloadItem.getSuggestedFileName();
this.appendLog(`📥 拦截到下载请求 [Guid: ${guid}]`);
this.appendLog(`🔗 原始触发 URL: ${originalUrl}`);
this.appendLog(`🔎 Referrer 溯源: ${referrerUrl}`);
this.appendLog(`📄 建议文件名: ${fileName}`);
// 拼接沙盒下载路径:写入到应用的 el2 cache 目录
let downloadPath = "/data/storage/el2/base/cache/web/" + fileName;
this.appendLog(`📁 开始写入沙盒路径: ${downloadPath}`);
// 允许并启动内核接管写入流程
webDownloadItem.start(downloadPath);
});
this.delegate.onDownloadUpdated((item) => this.appendLog(`⏳ 进度: ${item.getPercentComplete()}%`));
this.delegate.onDownloadFailed((item) => this.appendLog(`❌ 下载失败 [Guid: ${item.getGuid()}]`));
this.delegate.onDownloadFinish((item) => this.appendLog(`✅ 下载完成 [Guid: ${item.getGuid()}]`));
// 绑定委托机制到控制台
this.controller.setDownloadDelegate(this.delegate);
this.appendLog('🛡️ 下载委托监听已挂载');
} catch (error) {
let err = error as BusinessError;
this.appendLog(`❌ 委托挂载失败 Msg: ${err.message}`);
}
}
triggerDownload() {
try {
this.appendLog(`🌐 尝试触发下载: ${this.inputUrl}`);
this.controller.startDownload(this.inputUrl);
} catch (error) {
let err = error as BusinessError;
this.appendLog(`❌ 触发下载异常 Msg: ${err.message}`);
}
}
build() {
Column() {
Text('ArkWeb 下载溯源防线').fontSize(22).fontWeight(FontWeight.Bold).margin({ top: 40, bottom: 20 })
TextInput({ text: this.inputUrl }).onChange((val) => this.inputUrl = val).width('90%').margin({ bottom: 10 })
Button('触发下载并溯源').onClick(() => this.triggerDownload()).backgroundColor('#007DFF').width('90%').margin({ bottom: 20 })
List({ space: 8 }) {
ForEach(this.logs, (log: string) => {
ListItem() { Text(log).fontSize(12).fontFamily('monospace').fontColor('#333333') }
}, (log: string) => log)
}.width('90%').height('40%').backgroundColor('#EFEFEF').padding(10).borderRadius(8)
// 隐形 Web 底座
Web({ src: 'www.example.com', controller: this.controller }).width(1).height(1).visibility(Visibility.Hidden)
}.width('100%').height('100%')
}
}6、运行效果
当在界面中点击 “触发下载并溯源” 后,控制台日志自下而上依次展示了完美的溯源链路:
- 委托成功挂载的初始提示。
- “尝试触发下载: https://www.example.com/...” 被打印。
onBeforeDownload回调被瞬间引爆,屏幕精准提取并打印出了🔗 原始触发 URL以及🔎 Referrer 溯源数据(由于此处是直连且无特殊重定向,两者往往一致或为空;但当在带有网页嵌套及重定向的真实前端交互中,它将精准刻画用户点击跳转图谱)。- 系统提取出
建议文件名,并将其与/data/storage/el2/base/cache/web/结合,将数据流稳定注入沙盒。 最终提示
✅ 下载完成。7、避坑指南
[!WARNING] 注意事项
生命周期闭环切勿遗漏 start 调用:当你在onBeforeDownload中拦截了系统内核抛出的webDownloadItem后,这相当于你彻底劫持了本次下载过程。此时,你必须显式地调用webDownloadItem.start(path)给出一个合法的文件写入路径;或者如果你决定不下载,需要进行相关销毁处理。如果拦截后既不保存路径也不调用任何操作,内核引擎会陷入等待流状态,可能会诱发底层资源隐形泄露或崩溃!
8、总结
通过 HarmonyOS 6.1.1 在 ArkWeb 下载项上补充的 getOriginalUrl 与 getReferrerUrl 的“侦测探针”,浏览器内核的安全控制颗粒度更上一层楼。它不仅让应用侧彻底摆脱了“无法感知前置重定向”的被动局面,还赋予了第三方下载器接力时完美复刻 HTTP 上下文(尤其是突破防盗链)的核心权力。这标志着 ArkWeb 正在成为一个真正具备全链路掌控感的高级浏览引擎。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。