HarmonyOS 鸿蒙Next中resourceHandler.didReceiveResponseBody拦截网络请求失败
HarmonyOS 鸿蒙Next中resourceHandler.didReceiveResponseBody拦截网络请求失败 基于schemeHandler.onRequestStart的resourceHandler来拦截Web内核的请求,并为被拦截的请求提供自定义的响应信息,按照官方文档(https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/web-scheme-handler)操作一直拦截不成功,还请各位大牛指教,多谢了哈。
Web({ src: '', controller: this.controller})
.javaScriptAccess(true)
.domStorageAccess(true)
.onControllerAttached(() => {
this.controller.loadUrl(this.url)
this.handleControllerAttached()
}) }
handleControllerAttached() {
try {
this.schemeHandler.onRequestStart((request: webview.WebSchemeHandlerRequest,resourceHandler: webview.WebResourceHandler) => {
const type = request.getRequestResourceType();
const stream = request.getHttpBodyStream()
if (!stream || type !== webview.WebResourceType.XHR) {
return false
}
if (request.getRequestUrl().includes('xx.yy.api')) {
stream.initialize().then(async () => {
if (!stream || stream.getSize() <= 0) {
return;
}
const buffer = await stream.read(stream.getSize()).catch(() => null)
const obj = arrayBufferToObject(buffer)
if (obj?.functionId == 'xxx_api') {
try {
// mock网络请求数据
const result: ESObject = this.getRawFileJsonData()
// 将Object格式的数据转化为ArrayBuffer格式的数据
let arrayBuffer = objectToArrayBuffer(result) as ArrayBuffer;
if (arrayBuffer && resourceHandler) {
// 调用didReceiveResponseBody将构造的响应体传递给被拦截的请求。
resourceHandler.didReceiveResponseBody(arrayBuffer);
// 调用didFinish通知Web组件被拦截的请求已经完成。
resourceHandler.didFinish();
} else {
resourceHandler.didFail(WebNetErrorList.ERR_FAILED);
}
} catch (error) {
}
}
}).catch((error: BusinessError) => {
})
return true
}
return false
})
this.schemeHandler.onRequestStop((request: webview.WebSchemeHandlerRequest) => {
console.info('getHttpBodyStream: onRequestStop');
});
this.controller.setWebSchemeHandler('https', this.schemeHandler);
} catch (error) {
}
}
更多关于HarmonyOS 鸿蒙Next中resourceHandler.didReceiveResponseBody拦截网络请求失败的实战教程也可以访问 https://www.itying.com/category-93-b0.html
【背景知识】 Web组件拦截H5页面的网络请求有以下三种方式:
- onInterceptRequest:可以拦截url并返回响应,但是获取的对象里面不包含请求体内容。
- 网络拦截接口(arkweb_scheme_handler.h):可以对Web组件发出的请求进行拦截,并为被拦截的请求提供自定义的响应头以及响应体,通过OH_ArkWebResourceRequest_*接口获取被拦截请求的信息。可以获取url、method、referrer、headers、resourceType等信息。
- WebSchemeHandler:可以拦截指定scheme的请求,可通过WebSchemeHandlerRequest.getHttpBodyStream()方法获取POST、PUT请求的数据体,支持BYTES、FILE、BLOB、CHUNKED类型的数据。
【解决方案】
- 通过网络拦截接口(arkweb_scheme_handler.h)可参考官方指南:拦截Web组件发起的网络请求。
- 通过WebSchemeHandler拦截获取请求体可参考以下代码:
import { WebNetErrorList, webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
import { buffer } from '@kit.ArkTS';
import { util } from '@kit.ArkTS';
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
schemeHandler: webview.WebSchemeHandler = new webview.WebSchemeHandler();
htmlData: string = "<html><body bgcolor=\"white\">Source:<pre>source</pre></body></html>";
aboutToAppear() {
// 配置Web开启调试模式
webview.WebviewController.setWebDebuggingAccess(true);
}
build() {
Column() {
Web({
src: 'https://developer.huawei.com/consumer/cn/doc/harmonyos-releases-V5/releasenotes-V5',
controller: this.controller
})
.onControllerAttached(() => {
try {
this.schemeHandler.onRequestStart((request: webview.WebSchemeHandlerRequest,
resourceHandler: webview.WebResourceHandler) => {
console.info("[schemeHandler] onRequestStart");
try {
// 可以指定请求不做拦截
if (request.getRequestUrl().endsWith("example.com")) {
return false;
}
// 获取请求头
let header = request.getHeader();
for (let i = 0; i < header.length; i++) {
console.info("[schemeHandler] onRequestStart header:" + header[i].headerKey + " " +
header[i].headerValue);
}
// 获取请求体
let stream = request.getHttpBodyStream();
if (stream) {
stream.initialize().then(() => {
if (!stream) {
console.error('[schemeHandler] HttpBodyStream initialize failed.')
return;
}
let size = stream.getSize();
console.info(`[schemeHandler] HttpBodyStream size is ${size}`);
stream.read(size).then((result: ArrayBuffer) => {
console.info(`[schemeHandler] HttpBodyStream buffer length is ${result.byteLength}`);
// 从buffer中转换请求体内容
let decoder = util.TextDecoder.create('utf-8')
let requestBodyStr = decoder.decodeToString(new Uint8Array(result));
console.info(`[schemeHandler] HttpBodyStream requestBody is ${requestBodyStr}`);
}).catch((error: BusinessError) => {
console.error(`[schemeHandler] HttpBodyStream read failed, message is ${error.message}`);
});
})
} else {
console.info("[schemeHandler] onRequestStart has no http body stream");
}
} catch (error) {
console.error(`[schemeHandler] ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
}
// 构造响应体返回
let response = new webview.WebSchemeHandlerResponse();
try {
response.setNetErrorCode(WebNetErrorList.NET_OK);
response.setStatus(200);
response.setStatusText("OK");
response.setMimeType("text/html");
response.setEncoding("utf-8");
response.setHeaderByName("header1", "value1", false);
} catch (error) {
console.error(`[schemeHandler] ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
}
// 调用didFinish/didFail前需要优先调用didReceiveResponse将构造的响应头传递给被拦截的请求。
let buf = buffer.from(this.htmlData)
try {
if (buf.length == 0) {
console.info("[schemeHandler] length 0");
resourceHandler.didReceiveResponse(response);
// 如果认为buf.length为0是正常情况,则调用resourceHandler.didFinish,否则调用resourceHandler.didFail
resourceHandler.didFail(WebNetErrorList.ERR_FAILED);
} else {
console.info("[schemeHandler] length 1");
resourceHandler.didReceiveResponse(response);
resourceHandler.didReceiveResponseBody(buf.buffer);
resourceHandler.didFinish();
}
} catch (error) {
console.error(`[schemeHandler] ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
}
return true;
})
this.schemeHandler.onRequestStop((request: webview.WebSchemeHandlerRequest) => {
console.info("[schemeHandler] onRequestStop");
});
this.controller.setWebSchemeHandler('https', this.schemeHandler);
} catch (error) {
console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
}
})
.javaScriptAccess(true)
.domStorageAccess(true)
}
}
}
更多关于HarmonyOS 鸿蒙Next中resourceHandler.didReceiveResponseBody拦截网络请求失败的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
拦截失败是因为您缺少了设置响应头的必要步骤,这是拦截流程中的核心环节。
问题根因分析
在您的代码中,直接调用了 resourceHandler.didReceiveResponseBody() 来发送响应体,但没有先调用 didReceiveResponse() 设置响应头。完整的拦截响应流程必须是:
- 首先设置响应头 →
didReceiveResponse(response) - 然后发送响应体 →
didReceiveResponseBody(data) - 最后完成请求 →
didFinish()或didFail()
您跳过了第1步,导致Web内核无法正确解析响应。
完整修复方案
handleControllerAttached() {
try {
this.schemeHandler.onRequestStart((request: webview.WebSchemeHandlerRequest, resourceHandler: webview.WebResourceHandler) => {
const type = request.getRequestResourceType();
const stream = request.getHttpBodyStream();
if (!stream || type !== webview.WebResourceType.XHR) {
return false;
}
if (request.getRequestUrl().includes('xx.yy.api')) {
stream.initialize().then(async () => {
if (!stream || stream.getSize() <= 0) {
return;
}
const buffer = await stream.read(stream.getSize()).catch(() => null);
const obj = arrayBufferToObject(buffer);
if (obj?.functionId == 'xxx_api') {
try {
// 【关键修复】首先创建并设置响应头
let response = new webview.WebSchemeHandlerResponse();
response.setStatus(200); // HTTP状态码
response.setMimeType('application/json'); // 根据实际数据类型设置
response.setCharset('utf-8');
response.setHeaderByName('content-length', '1024', false); // 根据实际数据长度设置
// 必须先调用didReceiveResponse设置响应头
resourceHandler.didReceiveResponse(response);
// mock网络请求数据
const result: ESObject = this.getRawFileJsonData();
let arrayBuffer = objectToArrayBuffer(result) as ArrayBuffer;
if (arrayBuffer && resourceHandler) {
// 现在可以安全地发送响应体
resourceHandler.didReceiveResponseBody(arrayBuffer);
resourceHandler.didFinish();
} else {
resourceHandler.didFail(WebNetErrorList.ERR_FAILED);
}
} catch (error) {
console.error('拦截处理异常:', error);
resourceHandler.didFail(WebNetErrorList.ERR_FAILED);
}
}
}).catch((error: BusinessError) => {
console.error('流初始化失败:', error);
});
return true; // 拦截此请求
}
return false; // 不拦截其他请求
});
this.schemeHandler.onRequestStop((request: webview.WebSchemeHandlerRequest) => {
console.info('请求结束:', request.getRequestUrl());
});
this.controller.setWebSchemeHandler('https', this.schemeHandler);
} catch (error) {
console.error('设置拦截器失败:', error);
}
}
技术要点说明
- 响应头必须优先设置:
didReceiveResponse()告诉Web内核准备接收响应- 缺少这一步会导致后续的响应体数据被忽略
- 响应头关键参数:
setStatus(200): 设置HTTP状态码(200表示成功)setMimeType(): 设置数据格式(JSON/HTML等)setCharset(): 设置字符编码setHeaderByName(): 设置Content-Length等头部信息
我这边即便设置了didReceiveResponse依然拦截不到接口请求: 完整代码如下:
Index.ets
import { WebNetErrorList, webview } from "@kit.ArkWeb";
import { BusinessError } from "@kit.BasicServicesKit";
import { buffer } from "@kit.ArkTS";
import { arrayBufferToObject, objectToArrayBuffer } from "./Utils";
import { common } from "@kit.AbilityKit";
@Entry
@Component
struct Index {
controller: webview.WebviewController = new webview.WebviewController();
schemeHandler: webview.WebSchemeHandler = new webview.WebSchemeHandler();
url: string = 'https://pro.m.jd.com/hm/active/3mvMVoZb71igim1FHko9mSTeAneK/index.html?has_native=0';
getRawFileJsonData(): ESObject {
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
let value: Uint8Array = context.resourceManager.getRawFileContentSync('mock.json');
let mockStr = buffer.from(value.buffer).toString();
let mockObj: ESObject = JSON.parse(mockStr)
return mockObj
}
build() {
Column() {
Web({ src: '', controller: this.controller }).onControllerAttached(() => {
this.controller.loadUrl(this.url)
try {
this.schemeHandler.onRequestStart((request: webview.WebSchemeHandlerRequest, resourceHandler: webview.WebResourceHandler) => {
const type = request.getRequestResourceType();
const stream = request.getHttpBodyStream()
if (!stream || type !== webview.WebResourceType.XHR) {
return false
}
if (request.getRequestUrl().includes('api.m.jd.com')) {
stream.initialize().then(async () => {
if (!stream || stream.getSize() <= 0) {
return;
}
const buffer = await stream.read(stream.getSize()).catch(() => null)
const obj = arrayBufferToObject(buffer)
console.info("[schemeHandler]: url", request.getRequestUrl());
console.info("[schemeHandler]: body", JSON.stringify(obj));
if (obj?.functionId == 'findBeanSceneNew') {
try {
// mock网络请求数据
const result: ESObject = this.getRawFileJsonData()
console.info("[schemeHandler]: response", JSON.stringify(result));
let arrayBuffer = objectToArrayBuffer(result) as ArrayBuffer;
const response = new webview.WebSchemeHandlerResponse();
// 设置网络错误代码为OK,表示请求成功。
response.setNetErrorCode(WebNetErrorList.NET_OK);
// 设置HTTP状态码为200,表示请求处理成功。
response.setStatus(200);
// 设置状态文本为"OK",用于描述状态码。
response.setStatusText("OK");
// 设置MIME类型为"application/json"
response.setMimeType("application/json");
// 设置编码方式为"utf-8",指明内容使用UTF-8编码。
response.setEncoding("utf-8");
// 根据实际数据长度设置
response.setHeaderByName('content-length', arrayBuffer.byteLength.toString(), false);
// 必须先调用didReceiveResponse设置响应头
resourceHandler.didReceiveResponse(response);
if (arrayBuffer && resourceHandler) {
// 调用didReceiveResponseBody将构造的响应体传递给被拦截的请求。
resourceHandler.didReceiveResponseBody(arrayBuffer);
// 调用didFinish通知Web组件被拦截的请求已经完成。
resourceHandler.didFinish();
} else {
resourceHandler.didFail(WebNetErrorList.ERR_FAILED);
}
} catch (error) {
console.error(`[schemeHandler]: ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
}
}
}).catch((error: BusinessError) => {
console.info(`[schemeHandler]: stream error: ${error.code}, Message: ${error.message}`);
})
return true
}
return false
})
this.schemeHandler.onRequestStop((request: webview.WebSchemeHandlerRequest) => {
console.info('[schemeHandler] onRequestStop');
});
this.controller.setWebSchemeHandler('https', this.schemeHandler);
} catch (error) {
console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
}
})
.javaScriptAccess(true)
.domStorageAccess(true)
}
}
}
Utils.ets
import { util } from "@kit.ArkTS";
import Url from '@ohos.url';
/**
* 将Object转换为ArrayBuffer
* @param obj 要转换的对象
* @returns 对应的ArrayBuffer
*/
export function objectToArrayBuffer(obj: Object): ArrayBuffer {
try {
// 1. 将对象序列化为JSON字符串
const jsonString: string = JSON.stringify(obj);
console.info('JSON String: ' + jsonString);
// 2. 使用TextEncoder将字符串编码为Uint8Array
const textEncoder = new util.TextEncoder();
const uint8Array: Uint8Array = textEncoder.encodeInto(jsonString);
// 3. 直接返回,让编译器推断类型
return uint8Array.buffer;
} catch (error) {
console.error('Error converting Object to ArrayBuffer: ' + error.message);
return new ArrayBuffer(0);
}
}
interface GeneratedObjectLiteralInterface_1 {
appId: string | null;
functionId: string | null;
bodyJsonString: string | null;
}
export function arrayBufferToObject(buffer: ArrayBuffer | null): GeneratedObjectLiteralInterface_1 | null {
try {
if (buffer === null) {
return null
}
// 1. 将ArrayBuffer转换为Uint8Array
const uint8Array: Uint8Array = new Uint8Array(buffer);
// 2. 使用TextDecoder将Uint8Array解码为字符串
const textDecoder: util.TextDecoder = new util.TextDecoder;
const jsonString: string = textDecoder.decode(uint8Array);
const urlParams = new Url.URLParams(jsonString);
const appId = urlParams.get('appid');
const functionId = urlParams.get('functionId');
const bodyJsonString = urlParams.get('body');
// 3. 将JSON字符串解析为Object
const obj: GeneratedObjectLiteralInterface_1 = {
appId: appId,
functionId: functionId,
bodyJsonString: bodyJsonString
}
return obj;
} catch (error) {
console.error('Error converting ArrayBuffer to Object: ' + error.message);
return null; // 返回一个空对象或者在失败时根据需求处理
}
}
mock.json
{
"code": "0",
"data": {
"status": "这是web容器填充的数据",
"userState": "1",
"beanUserType": 1,
"totalUserBean": "47",
"continuousDays": "1",
"tomorrowSendBeans": 0,
"todaySendBeans": 0
}
}
报错原因:resourceHandler.didReceiveResponseBody拦截网络请求失败,可以通过错误码来分析,请求拦截了,说明请求成功了,而拦截失败,说明请求失败了,请求失败的原因可以追踪下错误信息,大概原因是响应头构造问题
解决方案:在resourceHandler.didFinish();方法之前必须调用resourceHandler.didReceiveResponse(response);
详细步骤:
//此处省略....
// 调用didReceiveResponse将构造的响应头传递给被拦截的请求。
resourceHandler.didReceiveResponse(response);
// 调用didReceiveResponseBody将构造的响应体传递给被拦截的请求。
resourceHandler.didReceiveResponseBody(buf.buffer);
// 调用didFinish通知Web组件被拦截的请求已经完成。
resourceHandler.didFinish();
//.......
我看官网有完整示例 你要不下载下来看看 GuideSnippets: HarmonyOS 指南代码同源仓。 - Gitee.com
你可以从下面两个方面直接定位问题 这样好找到答案
- 打印拦截过程日志:在
onRequestStart中打印url、type、是否进入拦截逻辑,确认目标请求是否被捕获; - 简化拦截逻辑:先忽略请求体解析,直接返回固定 mock 数据,验证基础拦截是否生效;
从以下问题进行一些检查和改进
1.根据官方API定义,调用didReceiveResponseBody前必须通过didReceiveResponse()设置响应头。未设置会导致内核无法正确处理响应数据。
2.setWebSchemeHandler默认不支持拦截https协议(需使用自定义scheme如myapp://)。若需拦截标准协议,需检查系统兼容性配置。
3.stream.initialize()为异步操作,可能导致回调执行时resourceHandler已失效。需在Promise链中保持Handler有效性。
在鸿蒙Next中,resourceHandler.didReceiveResponseBody 拦截网络请求失败通常与资源管理器配置或拦截器生命周期有关。请检查 ResourceManager 是否正确初始化,并确保拦截器在请求发起前已注册。同时,确认网络权限已开启,且拦截逻辑未在异步操作中失效。
根据你提供的代码,拦截失败的核心原因在于异步处理逻辑与 resourceHandler 的生命周期管理存在问题。
主要问题分析:
-
didReceiveResponseBody调用时机不当:你在stream.initialize().then()这个异步回调中调用resourceHandler.didReceiveResponseBody()。onRequestStart回调是同步的,它需要你立即决定是否接管请求(返回true或false)。然而,你返回true接管请求后,实际的响应体 (arrayBuffer) 却在未来的某个异步时刻才准备好并调用didReceiveResponseBody。这很可能导致 Web 内核在等待响应时超时或状态不一致,从而拦截失败。 -
resourceHandler可能已失效:在异步操作执行期间,如果原始网络请求发生错误、被取消或超时,与之关联的resourceHandler可能会变为无效状态。此时再调用其didReceiveResponseBody或didFinish方法将不起作用。
修改建议:
你需要确保在 onRequestStart 回调中同步地完成对请求的响应,或者以同步方式启动一个能立即提供响应数据的机制。
重构方案示例:
handleControllerAttached() {
try {
this.schemeHandler.onRequestStart((request: webview.WebSchemeHandlerRequest, resourceHandler: webview.WebResourceHandler) => {
const type = request.getRequestResourceType();
const url = request.getRequestUrl();
// 1. 更精确地筛选需要拦截的请求
if (type !== webview.WebResourceType.XHR || !url.includes('xx.yy.api')) {
return false; // 不拦截
}
// 2. 【关键】尝试同步或快速获取Mock数据,避免复杂异步链
// 例如,你的 `getRawFileJsonData` 方法应该是同步的,或返回一个预先准备好的缓存数据。
let mockDataResult: ESObject | null = null;
let targetFunctionId: string | null = null;
// 2.1 尝试快速读取请求体(对于POST请求)
const stream = request.getHttpBodyStream();
if (stream) {
// 注意:这里简化处理,实际生产环境需要更完善的异步转同步或快速解析逻辑。
// 理想情况是能同步解析出 functionId,或者拦截逻辑不依赖请求体。
// 如果必须依赖异步读取请求体,则此方案不适用,需考虑其他拦截策略。
// 以下仅为示例,表示“如果能快速得到结果”:
// const buffer = someSyncOrVeryFastRead(stream);
// const obj = arrayBufferToObject(buffer);
// targetFunctionId = obj?.functionId;
}
// 2.2 判断是否需要拦截(这里假设通过URL已足够判断,或已同步获取到targetFunctionId)
// if (url.includes('specific_api_path') || targetFunctionId === 'xxx_api') {
if (url.includes('xxx_api')) { // 示例:直接通过URL判断
mockDataResult = this.getRawFileJsonData(); // 假设这是同步方法
}
if (mockDataResult) {
// 3. 【关键】同步地构造响应并提交
try {
const arrayBuffer = objectToArrayBuffer(mockDataResult) as ArrayBuffer;
if (arrayBuffer) {
// 立即调用,无需等待异步操作
resourceHandler.didReceiveResponseBody(arrayBuffer);
resourceHandler.didFinish();
return true; // 已成功拦截并处理
} else {
resourceHandler.didFail(WebNetErrorList.ERR_FAILED);
return true; // 已接管,但告知失败
}
} catch (error) {
resourceHandler.didFail(WebNetErrorList.ERR_FAILED);
return true;
}
}
// 4. 如果不满足拦截条件,或无法同步提供数据,则返回false,让请求继续。
return false;
});
this.controller.setWebSchemeHandler('https', this.schemeHandler);
} catch (error) {
console.error('Failed to setup scheme handler:', error);
}
}
核心要点总结:
- 同步响应:
onRequestStart中的拦截决策和响应提交应尽可能同步完成。 - 简化逻辑:避免在
onRequestStart中执行耗时的异步操作(如文件读取、网络请求等)。Mock数据应预先加载到内存中。 - 准确筛选:使用
request.getRequestUrl()和request.getRequestResourceType()精确识别需要拦截的请求。如果必须依赖请求体 (getHttpBodyStream) 内容做判断,你需要评估其读取速度,或者重新设计拦截策略(例如,在更早的阶段通过其他方式标记请求)。 - 生命周期:一旦返回
true,必须在该同步上下文或一个能确保resourceHandler有效的极短时间内调用didReceiveResponseBody和didFinish(或didFail)。
请根据上述要点调整你的代码,重点是将异步的Mock数据准备过程提前,在 onRequestStart 中只进行同步的判断和响应。

