HarmonyOS Web 组件跨域实战:本地资源为什么“加载不了”,以及两套最稳的解决方案

HarmonyOS Web 组件跨域实战:本地资源为什么“加载不了”,以及两套最稳的解决方案

鸿蒙第四期开发者活动
在 ArkTS 侧把异步逻辑包装成 Promise,Native 侧拿到这个 Promise 对象,然后给它挂 then / catch,在回调里获取异步结果或异常。

本文用一个完整示例,走一遍从 ArkTS 到 Native 的链路。


1. 整体思路

  1. ArkTS 侧:

    • 调用 Native 接口时,把一个 callback 传给 Native。
    • Native 调用这个 callback,ArkTS 在 callback 里 返回一个 Promise 对象
    • Promise 内部用 setTimeout 模拟异步,最终 resolvereject
  2. Native 侧:

    • 通过 napi_call_function 调用 ArkTS 传入的 callback,得到 Promise 对象
    • napi_get_named_property 拿到 Promise 的 then 和 `cat

我第一次在 HarmonyOS 里做离线 H5(把页面和静态资源都放到 rawfile/resfile)的时候,踩到的第一个大坑就是“跨域”。

当时的现象特别像玄学:

  • index.html 能打开
  • js/script.jsimg/logo.png、甚至接口请求会报错
  • DevTools 里一堆熟悉的字眼:CORS、blocked、origin null
  • 页面看起来像“白屏 / 半残”

后来把官方文档啃了一遍才明白:这不是你写错了,而是 ArkWeb 内核为了安全,默认会拦截 file/resource 这类本地协议的跨域访问华为开发者+1

这篇我就按“像人写的、能落地”的方式,把官方方案拆开讲清楚,并给你实战建议(什么时候用方案一,什么时候用方案二)。


一、问题根源:不是“跨域请求”,而是“本地协议的源不靠谱”

你在浏览器里做跨域,多半是 https://a.com 去请求 https://b.com

但在 HarmonyOS 离线 H5 的场景里,很多时候你的页面来源其实是:

  • resource://...
  • file://...

这类 URL 在 Web 内核里经常会对应到 origin 为 null 或特殊协议,然后一旦它去加载别的资源(脚本/图片/Fetch 请求),就容易被同源策略 + CORS 规则卡住。

官方文档的表达更直接:为了成功访问跨域资源,建议用 http/https 替代 file/resource,并配合拦截替换资源华为开发者+1


二、官方给的两套解法(我用下来都挺稳)

方案一:把本地资源“伪装成” http/https 域名 + onInterceptRequest 拦截返回本地内容

官方思路是:

  1. Web 加载的 URL 用 http/https(比如 https://www.example.com/index.html
  2. 这个域名是你“自定义构造”的,仅供你自己项目使用,避免和真实互联网域名冲突 华为开发者+1
  3. 然后用 onInterceptRequest 把对这个域名的请求全部拦截下来
  4. 你在拦截里读取 rawfile/resfile 的本地文件,手动拼成响应返回给 Web

这样 Web 内核看到的是 “正常的 https 页面 + 同源静态资源”,跨域自然就不拦了。华为开发者+1

什么时候我建议用方案一?

  • 你是 前端工程化产物(Vue/React 打包后资源多、路径复杂)
  • 你还要让后端 给这个“自定义域名”加白名单(非常常见)思否
  • 你希望统一成“线上/离线同域”,减少差异

一个“能跑的骨架”(示意)

代码写法会随 API version 略有差异,但思路固定:拦截 → 读取本地文件 → 返回 WebResourceResponse

import { webview } from '@kit.ArkWeb';

const FAKE_HOST = 'www.example.com';

Web({ src: `https://${FAKE_HOST}/index.html`, controller: this.controller })
  .onInterceptRequest((event) => {
    const url = event.request.getRequestUrl?.() ?? '';
    if (!url.includes(FAKE_HOST)) return null; // 不拦截其它请求

    // 1) 把 https://www.example.com/js/app.js 映射到 rawfile/web/js/app.js
    // 2) 读本地文件
    // 3) 返回 WebResourceResponse(设置 mimeType/encoding/statusCode 等)
    // ——这里省略具体读取代码,避免篇幅爆炸
    return this.buildLocalResponse(url);
  });

实战建议:

  • 路径映射要做得“可预期”:别在代码里到处 if-else
  • Content-Type(mimeType)要对:js/css/png/svg/json 不同类型都别乱返回
  • 线上接口请求如果仍然跨域,还是得后端配 CORS(别指望客户端把网络跨域也“魔法解决”)

方案二:setPathAllowingUniversalAccess 设置“允许跨域访问的本地路径白名单”

如果你的业务就是单纯离线资源加载,且你更愿意保持 file:// 访问方式,官方还有第二套更“直接”的方案:

通过 setPathAllowingUniversalAccess() 设置一个路径列表。当使用 file 协议访问该列表中的资源时,允许进行跨域访问本地文件。并且一旦设置了该列表,file 协议将仅限访问列表内资源(会覆盖 fileAccess 的行为)。 华为开发者+1

这段话信息量很大,我给你翻译成“人话”:

  • 你把几个目录加入白名单
  • 以后 Web 用 file:// 访问这些目录下的资源,就不会被跨域拦
  • 但也更严格:file 只能访问你给的那些目录,防止乱读文件

路径怎么写?

常见就是应用的文件目录 / 资源目录。很多资料会给出类似路径示例(比如从 Context.filesDir/resourceDir 拿到的目录及其子目录)。InfoQ+1

什么时候我建议用方案二?

  • 你的离线页面结构简单
  • 资源都在可控目录
  • 不想维护 onInterceptRequest 的映射和响应拼装
  • 你明确只想解决 “file/resource 本地跨域加载失败”,不想引入“伪域名”

三、你该怎么选?(我真实项目的选型经验)

我一般按下面这个规则走:

  • Vue/React 离线包、资源一大堆、还要兼容后端白名单 → 选方案一(伪 https 域名 + onInterceptRequest)华为开发者+1
  • 轻量离线页、资源少、纯本地展示为主 → 选方案二(setPathAllowingUniversalAccess)华为开发者+1

另外还有一个容易忽略的点:
如果你用了自定义协议(比如 myapp://),并且还想让它参与跨域/fetch 之类的行为,可能需要额外给协议授权(例如 customizeSchemes 相关能力),不然你会看到“只有 http/https/某些协议才支持跨域”的那类报错。bbs.itying.com+1


四、排查清单(遇到白屏我就按这个顺序查)

  1. 控制台是否出现 CORS blocked / origin null 之类报错
  2. 资源 URL 是不是 resource:// / file:// 在互相引用(典型跨域触发点)
  3. 如果用方案一:
    • onInterceptRequest 是否拦截到了(日志打出来)
    • mimeType 是否正确(js 被当成 text/plain 也可能直接炸)
  4. 如果用方案二:
    • 白名单路径是否覆盖到了你真实访问的文件路径
    • 是否因为设置白名单后,file 反而被限制访问其它目录了 华为开发者+1

五、收个尾:跨域不是“麻烦”,是你在做更安全的离线 Web

说实话,我后来反而挺能理解这个限制:
离线 H5 一旦能随便跨域读文件、随便访问资源目录,那安全边界就很容易被打穿。

所以 HarmonyOS 这块给你两条路:

  • 要么 把离线资源“包装成正常网站”(方案一)华为开发者+1
  • 要么 明确告诉系统哪些目录可以被 file 跨域访问(方案二)华为开发者+1

你只要选对方案,离线 Web 的稳定性会提升非常明显。

posted @ 2025-12-18 16:28  骑老爷爷过马路  阅读(1)  评论(0)    收藏  举报