1# 拦截Web组件发起的网络请求 2<!--Kit: ArkWeb--> 3<!--Subsystem: Web--> 4<!--Owner: @aohui--> 5<!--Designer: @yaomingliu--> 6<!--Tester: @ghiker--> 7<!--Adviser: @HelloCrease--> 8 9[网络拦截接口(arkweb_scheme_handler.h)](../reference/apis-arkweb/capi-arkweb-scheme-handler-h.md)可以对Web组件发出的请求进行拦截,并为被拦截的请求提供自定义的响应头以及响应体。 10 11## 为Web组件设置网络拦截器 12 13为指定的Web组件或者ServiceWorker设置ArkWeb_SchemeHandler,当Web内核发出相应scheme请求的时候,会触发ArkWeb_SchemeHandler的回调。需要在Web组件初始化之后设置网络拦截器。 14 15请求开始时回调ArkWeb_OnRequestStart,请求结束时回调ArkWeb_OnRequestStop。 16 17若想要拦截Web组件发出的第一个请求,可以通过[initializeWebEngine](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#initializewebengine)方法提前进行初始化Web组件,再设置拦截器实现拦截。详细代码请参考[完整示例](#完整示例)。 18 19 ```c++ 20 // 创建一个ArkWeb_SchemeHandler对象。 21 ArkWeb_SchemeHandler *schemeHandler; 22 OH_ArkWeb_CreateSchemeHandler(&schemeHandler); 23 24 // 为ArkWeb_SchemeHandler设置ArkWeb_OnRequestStart与ArkWeb_OnRequestStop回调。 25 OH_ArkWebSchemeHandler_SetOnRequestStart(schemeHandler, OnURLRequestStart); 26 OH_ArkWebSchemeHandler_SetOnRequestStop(schemeHandler, OnURLRequestStop); 27 28 // 拦截webTag为“scheme-handler”的Web组件发出的scheme为“https”的请求。 29 OH_ArkWeb_SetSchemeHandler("https", "scheme-handler", schemeHandler); 30 OH_ArkWebServiceWorker_SetSchemeHandler("https", schemeHandler); 31 ``` 32 33也可以拦截非Web组件内置scheme的请求。 34 35 ```c++ 36 // 创建一个ArkWeb_SchemeHandler对象。 37 ArkWeb_SchemeHandler *schemeHandler; 38 OH_ArkWeb_CreateSchemeHandler(&schemeHandler); 39 40 // 为ArkWeb_SchemeHandler设置ArkWeb_OnRequestStart与ArkWeb_OnRequestStop回调。 41 OH_ArkWebSchemeHandler_SetOnRequestStart(schemeHandler, OnURLRequestStart); 42 OH_ArkWebSchemeHandler_SetOnRequestStop(schemeHandler, OnURLRequestStop); 43 44 // 拦截webTag为“scheme-handler”的Web组件发出的scheme为“custom”的请求。 45 OH_ArkWeb_SetSchemeHandler("custom", "scheme-handler", schemeHandler); 46 OH_ArkWebServiceWorker_SetSchemeHandler("custom", schemeHandler); 47 ``` 48 49## 设置自定义scheme需要遵循的规则 50 51如果要拦截自定义scheme的请求,需要提前将自定义scheme注册到Web内核。需要在Web组件初始化之前进行注册,初始化后再注册会失败。 52 53 ```c++ 54 // 注册“custom“ scheme到Web组件,并指定该scheme需要遵循标准的scheme规则,允许该scheme发出跨域请求。 55 OH_ArkWeb_RegisterCustomSchemes("custom", ARKWEB_SCHEME_OPTION_STANDARD | ARKWEB_SCHEME_OPTION_CORS_ENABLED); 56 // 注册“custom-local” scheme到Web组件,并指定该scheme需要遵循与“file” scheme一样的规则。 57 OH_ArkWeb_RegisterCustomSchemes("custom-local", ARKWEB_SCHEME_OPTION_LOCAL); 58 // 注册“custom-csp-bypassing”到Web组件,并指定该scheme需要遵循标准的scheme规则,允许忽略CSP检查。 59 OH_ArkWeb_RegisterCustomSchemes("custom-csp-bypassing", ARKWEB_SCHEME_OPTION_CSP_BYPASSING | ARKWEB_SCHEME_OPTION_STANDARD); 60 // 注册“custom-isolated”到Web组件,并指定该scheme的请求必须从相同scheme加载的网页中发起。 61 OH_ArkWeb_RegisterCustomSchemes("custom-isolated", ARKWEB_SCHEME_OPTION_DISPLAY_ISOLATED); 62 ``` 63 64由于注册scheme需要在Web组件初始化前完成,而网络拦截器需要在Web组件初始化之后设置,建议在EntryAbility的onCreate方法中调用c++接口注册scheme。 65完成scheme注册后,通过[initializeWebEngine](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#initializewebengine)初始化Web组件,然后设置网络拦截器。 66 67 ```ts 68 export default class EntryAbility extends UIAbility { 69 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 70 // 注册scheme的配置。 71 testNapi.registerCustomSchemes(); 72 // 初始化Web组件内核,该操作会初始化Browser进程以及创建BrowserContext。 73 webview.WebviewController.initializeWebEngine(); 74 // 创建并设置ArkWeb_SchemeHandler。 75 testNapi.setSchemeHandler(); 76 } 77 ... 78 }; 79 ``` 80 81> **说明:** 82> 83> registerCustomSchemes必须在[initializeWebEngine](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#initializewebengine)方法调用前注册。 84 85## 获取被拦截请求的请求信息 86 87通过OH_ArkWebResourceRequest_*接口获取被拦截请求的信息。可以获取url、method、referrer、headers、resourceType等信息。 88 89 ```c++ 90 char* url; 91 OH_ArkWebResourceRequest_GetUrl(resourceRequest_, &url); 92 OH_ArkWeb_ReleaseString(url); 93 94 char* method; 95 OH_ArkWebResourceRequest_GetMethod(resourceRequest_, &method); 96 OH_ArkWeb_ReleaseString(method); 97 98 int32_t resourceType = OH_ArkWebResourceRequest_GetResourceType(resourceRequest_); 99 100 char* frameUrl; 101 OH_ArkWebResourceRequest_GetFrameUrl(resourceRequest_, &frameUrl); 102 OH_ArkWeb_ReleaseString(frameUrl); 103 ... 104 ``` 105 106支持获取PUT/POST类请求的上传数据。数据类型支持BYTES、FILE、BLOB和CHUNKED。 107 108 ```c++ 109 // 获取被拦截请求的上传数据。 110 OH_ArkWebResourceRequest_GetHttpBodyStream(resourceRequest(), &stream_); 111 // 设置读取上传数据的读回调。 112 OH_ArkWebHttpBodyStream_SetReadCallback(stream_, ReadCallback); 113 // 初始化ArkWeb_HttpBodyStream,其它OH_ArkWebHttpBodyStream*函数需要在初始化进行调用。 114 OH_ArkWebHttpBodyStream_Init(stream_, InitCallback); 115 ``` 116 117## 为被拦截的请求提供自定义的响应体 118 119网络拦截支持在worker线程以流方式为被拦截的请求提供自定义的响应体。也可用特定的[网络错误码(arkweb_net_error_list.h)](../reference/apis-arkweb/capi-arkweb-net-error-list-h.md)结束当前被拦截的请求。 120 121 ```c++ 122 // 为被拦截的请求创建一个响应头。 123 ArkWeb_Response *response; 124 OH_ArkWeb_CreateResponse(&response); 125 126 // 设置HTTP状态码为200。 127 OH_ArkWebResponse_SetStatus(response, 200); 128 // 设置响应体的编码格式。 129 OH_ArkWebResponse_SetCharset(response, "UTF-8"); 130 // 设置响应体的大小。 131 OH_ArkWebResponse_SetHeaderByName(response, "content-length", "1024", false); 132 // 将为被拦截的请求创建的响应头传递给Web组件。 133 OH_ArkWebResourceHandler_DidReceiveResponse(resourceHandler, response); 134 135 // 该函数可以调用多次,数据可以分多份来传递给Web组件。 136 OH_ArkWebResourceHandler_DidReceiveData(resourceHandler, buffer, bufLen); 137 138 // 读取响应体结束,当然如果希望该请求失败的话也可以通过调用OH_ArkWebResourceHandler_DidFailWithError(resourceHandler_, errorCode); 139 // 传递给Web组件一个错误码并结束该请求。 140 OH_ArkWebResourceHandler_DidFinish(resourceHandler); 141 ``` 142 143从API version 20开始,如果希望返回一个网络错误码来结束本次网络请求,也可以直接调用OH_ArkWebResourceHandler_DidFailWithErrorV2接口返回一个默认的网络错误码ARKWEB_ERR_CONNECTION_FAILED并结束该网络请求,错误码详情参考[网络错误码(arkweb_net_error_list.h)](../reference/apis-arkweb/capi-arkweb-net-error-list-h.md)。 144 ```c++ 145 // 直接返回网络错误码ARKWEB_ERR_CONNECTION_FAILED结束该请求。 146 OH_ArkWebResourceHandler_DidFailWithErrorV2(resourceHandler_, ARKWEB_ERR_FAILED, true); 147 ``` 148 149## 完整示例 150 151使用DevEco Studio创建一个默认的Native C++工程,需要提前准备一个mp4文件,命名为test.mp4,并将其放到main/resources/rawfile下。 152 153main/ets/pages/index.ets 154```ts 155import testNapi from 'libentry.so'; 156import { webview } from '@kit.ArkWeb'; 157import { resourceManager } from '@kit.LocalizationKit'; 158 159@Entry 160@Component 161struct Index { 162 mycontroller: webview.WebviewController = new webview.WebviewController("scheme-handler"); 163 164 build() { 165 Row() { 166 Column() { 167 Button("goback").onClick( event => { 168 this.mycontroller.backward(); 169 }) 170 171 Web({ src: $rawfile("test.html"), controller: this.mycontroller}) 172 .javaScriptAccess(true) 173 .width('100%') 174 .height('100%') 175 .databaseAccess(true) 176 .fileAccess(false) 177 .domStorageAccess(true) 178 .cacheMode(CacheMode.Default) 179 .onPageBegin( event => { 180 testNapi.initResourceManager(this.getUIContext().getHostContext()!.resourceManager); 181 }) 182 } 183 .width('100%') 184 } 185 .height('100%') 186 } 187} 188``` 189 190main/ets/entryability/EntryAbility.ets 191```ts 192import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 193import { hilog } from '@kit.PerformanceAnalysisKit'; 194import { window } from '@kit.ArkUI'; 195import testNapi from 'libentry.so'; 196import { webview } from '@kit.ArkWeb'; 197 198export default class EntryAbility extends UIAbility { 199 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 200 // 注册三方协议的配置。 201 testNapi.registerCustomSchemes(); 202 // 初始化Web组件内核,该操作会初始化Browser进程以及创建BrowserContext。 203 webview.WebviewController.initializeWebEngine(); 204 // 设置SchemeHandler。 205 testNapi.setSchemeHandler(); 206 } 207 208 onDestroy(): void { 209 210 } 211 212 onWindowStageCreate(windowStage: window.WindowStage): void { 213 windowStage.loadContent('pages/Index', (err, data) => { 214 if (err.code) { 215 return; 216 } 217 }); 218 } 219 220 onWindowStageDestroy(): void { 221 222 } 223 224 onForeground(): void { 225 226 } 227 228 onBackground(): void { 229 230 } 231}; 232``` 233 234main/cpp/hello.cpp 235```c++ 236#include "hilog/log.h" 237#include "napi/native_api.h" 238#include "rawfile_request.h" 239#include "rawfile/raw_file_manager.h" 240#include "web/arkweb_scheme_handler.h" 241#include "web/arkweb_net_error_list.h" 242 243#undef LOG_TAG 244#define LOG_TAG "ss-handler" 245 246ArkWeb_SchemeHandler *g_schemeHandler; 247ArkWeb_SchemeHandler *g_schemeHandlerForSW; 248NativeResourceManager *g_resourceManager; 249 250// 注册三方协议的配置,需要在Web内核初始化之前调用,否则会注册失败。 251static napi_value RegisterCustomSchemes(napi_env env, napi_callback_info info) 252{ 253 OH_LOG_INFO(LOG_APP, "register custom schemes"); 254 OH_ArkWeb_RegisterCustomSchemes("custom", ARKWEB_SCHEME_OPTION_STANDARD | ARKWEB_SCHEME_OPTION_CORS_ENABLED); 255 OH_ArkWeb_RegisterCustomSchemes("custom-local", ARKWEB_SCHEME_OPTION_LOCAL); 256 OH_ArkWeb_RegisterCustomSchemes("custom-csp-bypassing", ARKWEB_SCHEME_OPTION_CSP_BYPASSING | ARKWEB_SCHEME_OPTION_STANDARD); 257 OH_ArkWeb_RegisterCustomSchemes("custom-isolated", ARKWEB_SCHEME_OPTION_DISPLAY_ISOLATED); 258 return nullptr; 259} 260 261// 请求开始的回调,在该函数中我们创建一个RawfileRequest来实现对Web内核请求的拦截。 262void OnURLRequestStart(const ArkWeb_SchemeHandler *schemeHandler, 263 ArkWeb_ResourceRequest *resourceRequest, 264 const ArkWeb_ResourceHandler *resourceHandler, 265 bool *intercept) 266{ 267 *intercept = true; 268 RawfileRequest* request = new RawfileRequest(resourceRequest, resourceHandler, g_resourceManager); 269 OH_ArkWebResourceRequest_SetUserData(resourceRequest, request); 270 request->Start(); 271} 272 273// 请求结束的回调,在该函数中我们需要标记RawfileRequest已经结束了,内部不应该再使用ResourceHandler。 274void OnURLRequestStop(const ArkWeb_SchemeHandler *schemeHandler, 275 const ArkWeb_ResourceRequest *request) 276{ 277 if (!request) { 278 OH_LOG_ERROR(LOG_APP, "on request stop request is nullptr."); 279 return; 280 } 281 282 RawfileRequest *rawfileRequest = (RawfileRequest *)OH_ArkWebResourceRequest_GetUserData(request); 283 if (rawfileRequest) { 284 rawfileRequest->Stop(); 285 delete rawfileRequest; 286 } 287} 288 289void OnURLRequestStartForSW(const ArkWeb_SchemeHandler *schemeHandler, 290 ArkWeb_ResourceRequest *resourceRequest, 291 const ArkWeb_ResourceHandler *resourceHandler, 292 bool *intercept) 293{ 294 *intercept = true; 295 RawfileRequest* request = new RawfileRequest(resourceRequest, resourceHandler, g_resourceManager); 296 OH_ArkWebResourceRequest_SetUserData(resourceRequest, request); 297 request->Start(); 298} 299 300void OnURLRequestStopForSW(const ArkWeb_SchemeHandler *schemeHandler, 301 const ArkWeb_ResourceRequest *request) 302{ 303 if (!request) { 304 OH_LOG_ERROR(LOG_APP, "on request stop request is nullptr."); 305 return; 306 } 307 308 RawfileRequest *rawfileRequest = (RawfileRequest *)OH_ArkWebResourceRequest_GetUserData(request); 309 if (rawfileRequest) { 310 rawfileRequest->Stop(); 311 delete rawfileRequest; 312 } 313} 314 315// 设置SchemeHandler。 316static napi_value SetSchemeHandler(napi_env env, napi_callback_info info) 317{ 318 OH_LOG_INFO(LOG_APP, "set scheme handler"); 319 OH_ArkWeb_CreateSchemeHandler(&g_schemeHandler); 320 OH_ArkWeb_CreateSchemeHandler(&g_schemeHandlerForSW); 321 322 OH_ArkWebSchemeHandler_SetOnRequestStart(g_schemeHandler, OnURLRequestStart); 323 OH_ArkWebSchemeHandler_SetOnRequestStop(g_schemeHandler, OnURLRequestStop); 324 325 OH_ArkWebSchemeHandler_SetOnRequestStart(g_schemeHandlerForSW, OnURLRequestStart); 326 OH_ArkWebSchemeHandler_SetOnRequestStop(g_schemeHandlerForSW, OnURLRequestStop); 327 328 OH_ArkWeb_SetSchemeHandler("custom", "scheme-handler", g_schemeHandler); 329 OH_ArkWeb_SetSchemeHandler("custom-csp-bypassing", "scheme-handler", g_schemeHandler); 330 OH_ArkWeb_SetSchemeHandler("custom-isolated", "scheme-handler", g_schemeHandler); 331 OH_ArkWeb_SetSchemeHandler("custom-local", "scheme-handler", g_schemeHandler); 332 OH_ArkWeb_SetSchemeHandler("https", "scheme-handler", g_schemeHandler); 333 OH_ArkWeb_SetSchemeHandler("http", "scheme-handler", g_schemeHandler); 334 335 OH_ArkWebServiceWorker_SetSchemeHandler("https", g_schemeHandlerForSW); 336 return nullptr; 337} 338 339static napi_value InitResourceManager(napi_env env, napi_callback_info info) 340{ 341 size_t argc = 2; 342 napi_value argv[2] = {nullptr}; 343 napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); 344 g_resourceManager = OH_ResourceManager_InitNativeResourceManager(env, argv[0]); 345 return nullptr; 346} 347 348EXTERN_C_START 349static napi_value Init(napi_env env, napi_value exports) 350{ 351 napi_property_descriptor desc[] = { 352 {"setSchemeHandler", nullptr, SetSchemeHandler, nullptr, nullptr, nullptr, napi_default, nullptr}, 353 {"initResourceManager", nullptr, InitResourceManager, nullptr, nullptr, nullptr, napi_default, nullptr}, 354 {"registerCustomSchemes", nullptr, RegisterCustomSchemes, nullptr, nullptr, nullptr, napi_default, nullptr} 355 }; 356 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 357 return exports; 358} 359EXTERN_C_END 360 361static napi_module demoModule = { 362 .nm_version = 1, 363 .nm_flags = 0, 364 .nm_filename = nullptr, 365 .nm_register_func = Init, 366 .nm_modname = "entry", 367 .nm_priv = ((void*)0), 368 .reserved = { 0 }, 369}; 370 371extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 372{ 373 napi_module_register(&demoModule); 374} 375``` 376 377 378main/cpp/CMakeLists.txt 379```text 380# the minimum version of CMake. 381cmake_minimum_required(VERSION 3.4.1) 382project(schemehandler) 383 384set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 385 386if(DEFINED PACKAGE_INFO_FILE) 387 include(${PACKAGE_INFO_FILE}) 388endif() 389 390include_directories(${NATIVERENDER_ROOT_PATH} 391 ${NATIVERENDER_ROOT_PATH}/include) 392 393add_library(entry SHARED rawfile_request.cpp hello.cpp) 394target_link_libraries(entry PUBLIC librawfile.z.so libace_napi.z.so libohweb.so libhilog_ndk.z.so) 395``` 396 397main/cpp/types/index.d.ts 398```ts 399export const registerCustomSchemes: () => void; 400export const setSchemeHandler: () => void; 401export const initResourceManager: (resmgr: resourceManager.ResourceManager) => void; 402``` 403 404main/cpp/rawfile_request.h 405```c++ 406#ifndef RAWFILE_REQUEST_H 407#define RAWFILE_REQUEST_H 408 409#include <mutex> 410#include <string> 411 412#include <rawfile/raw_file_manager.h> 413#include "web/arkweb_scheme_handler.h" 414#include "web/arkweb_net_error_list.h" 415 416class RawfileRequest { 417public: 418 RawfileRequest(const ArkWeb_ResourceRequest *resourceRequest, 419 const ArkWeb_ResourceHandler *resourceHandler, 420 const NativeResourceManager* resourceManager); 421 ~RawfileRequest(); 422 423 void Start(); 424 void Stop(); 425 void ReadRawfileDataOnWorkerThread(); 426 427 const ArkWeb_ResourceHandler *resourceHandler() { return resourceHandler_; } 428 const ArkWeb_ResourceRequest *resourceRequest() { return resourceRequest_; } 429 const NativeResourceManager *resourceManager() { return resourceManager_; } 430 ArkWeb_Response *response() { return response_; } 431 ArkWeb_HttpBodyStream *stream() { return stream_; } 432 const std::string rawfilePath() { return rawfilePath_; } 433 434 void DidReceiveResponse(); 435 void DidReceiveData(const uint8_t *buffer, int64_t bufLen); 436 void DidFinish(); 437 void DidFailWithError(ArkWeb_NetError errorCode); 438 void DidFailWithErrorV2(ArkWeb_NetError errorCode, bool completeIfNoResponse); 439 440private: 441 const ArkWeb_ResourceRequest *resourceRequest_{nullptr}; 442 const ArkWeb_ResourceHandler *resourceHandler_{nullptr}; 443 const NativeResourceManager *resourceManager_{nullptr}; 444 ArkWeb_Response *response_; 445 bool stopped_{false}; 446 std::string rawfilePath_; 447 ArkWeb_HttpBodyStream *stream_{nullptr}; 448 std::mutex mutex_; 449}; 450 451#endif // RAWFILE_REQUEST_H 452``` 453 454main/cpp/rawfile_request.cpp 455```c++ 456#include "rawfile_request.h" 457 458#include "threads.h" 459 460#include "hilog/log.h" 461#include "rawfile/raw_file.h" 462#include "rawfile/raw_file_manager.h" 463 464#undef LOG_TAG 465#define LOG_TAG "ss-handler" 466 467namespace { 468 469uint8_t buffer[1024]; 470cnd_t http_body_cnd; 471mtx_t http_body_mtx; 472 473// HttpBodyStream的读回调。 474void ReadCallback(const ArkWeb_HttpBodyStream *httpBodyStream, uint8_t* buffer, int bytesRead) 475{ 476 OH_LOG_INFO(LOG_APP, "read http body back."); 477 bool isEof = OH_ArkWebHttpBodyStream_IsEof(httpBodyStream); 478 if (!isEof && bytesRead != 0) { 479 memset(buffer, 0, 1000); 480 OH_ArkWebHttpBodyStream_Read(httpBodyStream, buffer, 1000); 481 } else { 482 RawfileRequest *rawfileRequest = (RawfileRequest *)OH_ArkWebHttpBodyStream_GetUserData(httpBodyStream); 483 if (rawfileRequest) { 484 rawfileRequest->ReadRawfileDataOnWorkerThread(); 485 cnd_signal(&http_body_cnd); 486 } 487 } 488} 489 490int ReadHttpBodyOnWorkerThread(void* userData) 491{ 492 memset(buffer, 0, 1000); 493 ArkWeb_HttpBodyStream *httpBodyStream = (ArkWeb_HttpBodyStream *)userData; 494 OH_ArkWebHttpBodyStream_Read(httpBodyStream, buffer, 1000); 495 cnd_init(&http_body_cnd); 496 mtx_init(&http_body_mtx, mtx_plain); 497 cnd_wait(&http_body_cnd, &http_body_mtx); 498 return 0; 499} 500 501int ReadRawfileOnWorkerThread(void* userData) 502{ 503 RawfileRequest * rawfileRequest = (RawfileRequest *)userData; 504 if (rawfileRequest) { 505 rawfileRequest->ReadRawfileDataOnWorkerThread(); 506 } 507 return 0; 508} 509 510// ArkWeb_HttpBodyStream的初始化回调。 511void InitCallback(const ArkWeb_HttpBodyStream *httpBodyStream, ArkWeb_NetError result) 512{ 513 OH_LOG_INFO(LOG_APP, "init http body stream done %{public}d.", result); 514 bool isChunked = OH_ArkWebHttpBodyStream_IsChunked(httpBodyStream); 515 OH_LOG_INFO(LOG_APP, "http body stream is chunked %{public}d.", isChunked); 516 thrd_t th; 517 if (thrd_create(&th, ReadHttpBodyOnWorkerThread, (void *)httpBodyStream) != thrd_success) { 518 OH_LOG_ERROR(LOG_APP, "create thread failed."); 519 return; 520 } 521 522 if (thrd_detach(th) != thrd_success) { 523 OH_LOG_ERROR(LOG_APP, "detach thread failed."); 524 } 525} 526 527const int blockSize = 1024 * 8; 528 529} // namespace 530 531RawfileRequest::RawfileRequest(const ArkWeb_ResourceRequest *resourceRequest, 532 const ArkWeb_ResourceHandler *resourceHandler, 533 const NativeResourceManager* resourceManager) 534 : resourceRequest_(resourceRequest), 535 resourceHandler_(resourceHandler), 536 resourceManager_(resourceManager) {} 537 538RawfileRequest::~RawfileRequest() { 539 if (stream_) { 540 OH_ArkWebResourceRequest_DestroyHttpBodyStream(stream_); 541 } 542} 543 544void RawfileRequest::Start() 545{ 546 OH_LOG_INFO(LOG_APP, "start a rawfile request."); 547 char* url; 548 OH_ArkWebResourceRequest_GetUrl(resourceRequest_, &url); 549 std::string urlStr(url); 550 std::size_t position = urlStr.rfind('/'); 551 if (position != std::string::npos) { 552 rawfilePath_ = urlStr.substr(position + 1); 553 } 554 OH_ArkWeb_ReleaseString(url); 555 556 OH_ArkWeb_CreateResponse(&response_); 557 OH_ArkWebResourceRequest_GetHttpBodyStream(resourceRequest(), &stream_); 558 if (stream_) { 559 OH_LOG_ERROR(LOG_APP, "have http body stream"); 560 OH_ArkWebHttpBodyStream_SetUserData(stream_, this); 561 OH_ArkWebHttpBodyStream_SetReadCallback(stream_, ReadCallback); 562 OH_ArkWebHttpBodyStream_Init(stream_, InitCallback); 563 } else { 564 thrd_t th; 565 if (thrd_create(&th, ReadRawfileOnWorkerThread, (void *)this) != thrd_success) { 566 OH_LOG_ERROR(LOG_APP, "create thread failed."); 567 return; 568 } 569 570 if (thrd_detach(th) != thrd_success) { 571 OH_LOG_ERROR(LOG_APP, "detach thread failed."); 572 } 573 } 574} 575 576// 在worker线程中读取rawfile,并通过ResourceHandler返回给Web内核。 577void RawfileRequest::ReadRawfileDataOnWorkerThread() 578{ 579 OH_LOG_INFO(LOG_APP, "read rawfile in worker thread."); 580 const struct UrlInfo { 581 std::string resource; 582 std::string mimeType; 583 } urlInfos[] = { 584 {"test.html", "text/html"}, 585 {"video.html", "text/html"}, 586 {"isolated.html", "text/html"}, 587 {"csp_bypassing.html", "text/html"}, 588 {"post_data.html", "text/html"}, 589 {"chunked_post_stream.html", "text/html"}, 590 {"local.html", "text/html"}, 591 {"service_worker.html", "text/html"}, 592 {"csp_script.js", "text/javascript"}, 593 {"sw.js", "text/javascript"}, 594 {"isolated_script.js", "text/javascript"}, 595 {"local_script.js", "text/javascript"}, 596 {"test.mp4", "video/mp4"}, 597 {"xhr", "application/json"} 598 }; 599 600 if (!resourceManager()) { 601 OH_LOG_ERROR(LOG_APP, "read rawfile error, resource manager is nullptr."); 602 return; 603 } 604 605 RawFile *rawfile = OH_ResourceManager_OpenRawFile(resourceManager(), rawfilePath().c_str()); 606 if (!rawfile) { 607 OH_ArkWebResponse_SetStatus(response(), 404); 608 } else { 609 OH_ArkWebResponse_SetStatus(response(), 200); 610 } 611 612 for (auto &urlInfo : urlInfos) { 613 if (urlInfo.resource == rawfilePath()) { 614 OH_ArkWebResponse_SetMimeType(response(), urlInfo.mimeType.c_str()); 615 break; 616 } 617 } 618 OH_ArkWebResponse_SetCharset(response(), "UTF-8"); 619 620 long len = OH_ResourceManager_GetRawFileSize(rawfile); 621 OH_ArkWebResponse_SetHeaderByName(response(), "content-length", std::to_string(len).c_str(), false); 622 DidReceiveResponse(); 623 624 long consumed = 0; 625 uint8_t buffer[blockSize]; 626 while (true) { 627 int ret = OH_ResourceManager_ReadRawFile(rawfile, buffer, blockSize); 628 OH_LOG_INFO(LOG_APP, "read rawfile %{public}d bytes.", ret); 629 if (ret == 0) { 630 break; 631 } 632 consumed += ret; 633 OH_ResourceManager_SeekRawFile(rawfile, consumed, 0); 634 DidReceiveData(buffer, ret); 635 memset(buffer, 0, blockSize); 636 } 637 638 OH_ResourceManager_CloseRawFile(rawfile); 639 DidFinish(); 640} 641 642void RawfileRequest::Stop() 643{ 644 OH_LOG_INFO(LOG_APP, "stop the rawfile request."); 645 std::lock_guard<std::mutex> guard(mutex_); 646 stopped_ = true; 647 if (response_) { 648 OH_ArkWeb_DestroyResponse(response_); 649 } 650 OH_ArkWebResourceRequest_Destroy(resourceRequest_); 651 OH_ArkWebResourceHandler_Destroy(resourceHandler_); 652} 653 654void RawfileRequest::DidReceiveResponse() 655{ 656 OH_LOG_INFO(LOG_APP, "did receive response."); 657 std::lock_guard<std::mutex> guard(mutex_); 658 if (!stopped_) { 659 OH_ArkWebResourceHandler_DidReceiveResponse(resourceHandler_, response_); 660 } 661} 662 663void RawfileRequest::DidReceiveData(const uint8_t *buffer, int64_t bufLen) 664{ 665 OH_LOG_INFO(LOG_APP, "did receive data."); 666 std::lock_guard<std::mutex> guard(mutex_); 667 if (!stopped_) { 668 OH_ArkWebResourceHandler_DidReceiveData(resourceHandler_, buffer, bufLen); 669 } 670} 671 672void RawfileRequest::DidFinish() 673{ 674 OH_LOG_INFO(LOG_APP, "did finish."); 675 std::lock_guard<std::mutex> guard(mutex_); 676 if (!stopped_) { 677 OH_ArkWebResourceHandler_DidFinish(resourceHandler_); 678 } 679} 680 681void RawfileRequest::DidFailWithError(ArkWeb_NetError errorCode) 682{ 683 OH_LOG_INFO(LOG_APP, "did finish with error %{public}d.", errorCode); 684 if (!stopped_) { 685 OH_ArkWebResourceHandler_DidFailWithError(resourceHandler_, errorCode); 686 } 687} 688 689void RawfileRequest::DidFailWithErrorV2(ArkWeb_NetError errorCode, bool completeIfNoResponse) 690{ 691 OH_LOG_INFO(LOG_APP, "did finish with error %{public}d.", errorCode); 692 if (!stopped_) { 693 OH_ArkWebResourceHandler_DidFailWithErrorV2(resourceHandler_, errorCode, completeIfNoResponse); 694 } 695} 696``` 697 698main/resources/rawfile/test.html 699```html 700<html> 701<head> 702<meta name="viewport" content="width=device-width,initial-scale=1"> 703</head> 704 705<body> 706<h1> 网络拦截测试demo</h1> 707<a href="https://www.example.com/video.html">拦截视频资源请求,读取本地mp4文件</a><br/> 708<a href="https://www.example.com/csp_bypassing.html">测试三方协议忽略csp检查,并成功拦截</a><br/> 709<a href="https://www.example.com/isolated.html">测试拦截设置ISOLATED属性的三方协议</a><br/> 710<a href="https://www.example.com/local.html">测试拦截设置LOCAL属性的三方协议</a><br/> 711<a href="https://www.example.com/service_worker.html">测试拦截service worker触发的请求</a><br/> 712<a href="https://www.example.com/post_data.html">测试读取blob类型http body stream</a><br/> 713<a href="https://www.example.com/chunked_post_stream.html">测试读取chunked类型http body stream</a> 714</body> 715</html> 716``` 717 718main/resources/rawfile/cat.svg 719``` 720<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.37 10.79"><path d="M12.8 10.18l-.8-.8c-.98-.8-.86-1.92-.87-2.04-.02-.1-.02-.58.02-.74.04-.15 0-.32 0-.32.28-1.18 1.2-.85 1.2-.85.38.04.4-.33.4-.33.25-.13.2-.4.2-.4l-.47-.48c-.18-.48-.7-.6-.7-.6.08-.48-.17-.78-.17-.78-.03.14-.58.72-.62.73-.63.15-.43.26-.83.55-.4.28-1.26.63-1.64.43-.37-.2-3.5-.5-4.86-.5-.4 0-.7.1-.95.2-.23-.16-.52-.52-.73-1.02-.3-.74-.36-1.48-.12-1.98.13-.27.28-.42.44-.45.23-.05.52.16.6.24.17.14.42.13.56-.03.15-.15.14-.4-.02-.55C3.38.4 2.8-.1 2.14.02c-.42.08-.76.38-1 .9-.34.7-.3 1.66.1 2.6.18.44.47.93.83 1.25-.1.13-.13.23-.13.23-.12.27-.44.9-.33 1.45.13.56-.22.82-.3.88-.05.07-.73.47-.73.47L0 9.78c-.08.38.43.6.43.6.18-.03.2-.63.2-.63l.44-1.04 1.66-.6s0 .7-.02.83-.1.35-.1.35c.08.46 1.2 1.5 1.2 1.5h.85v-.26c-.07-.3-.5-.16-.5-.16l-.62-.95c.66-.5.93-1.38.93-1.38.3.26 1.8-.22 1.8-.22l.9.1-.25 2.1c-.07.5.05.68.05.68h.4c.3 0 .48.03.48-.27 0-.28-.4-.23-.4-.23l1-1.95c.93-.58 1.53.26 1.53.26l.05.3c.37.53 2.38 1.9 2.38 1.9h1v-.3c-.18-.32-.6-.2-.6-.2z"/></svg> 721``` 722 723main/resources/rawfile/csp_bypassing.html 724```html 725<html> 726<head> 727<meta name="viewport" content="width=device-width,initial-scale=1"> 728<meta http-equiv="Content-Security-Policy" content="default-src 'self'; media-src 'self'"> 729</head> 730<body> 731<p>scheme: custom-csp-bypassing</p> 732<p>options: ARKWEB_SCHEME_OPTION_CSP_BYPASSING | ARKWEB_SCHEME_OPTION_STANDARD</p> 733<script src="custom-csp-bypassing://www.example.com/csp_script.js"></script> 734</body> 735</html> 736``` 737 738main/resources/rawfile/csp_script.js 739```js 740const body = document.body; 741const element = document.createElement('div'); 742element.textContent = 'csp_script.js bypass the csp rules'; 743body.appendChild(element); 744``` 745 746main/resources/rawfile/isolated_script.js 747```js 748const element = document.getElementById('isolated_test'); 749element.textContent = 'isolated_script.js not blocked'; 750``` 751 752main/resources/rawfile/isolated.html 753```html 754<html> 755<head> 756<meta name="viewport" content="width=device-width,initial-scale=1"> 757</head> 758<body> 759<p>scheme: custom-isolated</p> 760<p>options: ARKWEB_SCHEME_OPTION_DISPLAY_ISOLATED</p> 761<div id="isolated_test">isolated_script.js 被拦截</div> 762<script src="custom-isolated://www.example.com/isolated_script.js"></script> 763</body> 764</html> 765``` 766 767main/resources/rawfile/local_script.js 768```js 769const element = document.getElementById('local_test'); 770element.textContent = 'local_script.js not blocked.'; 771``` 772 773main/resources/rawfile/local.html 774```html 775<html> 776<head> 777<meta name="viewport" content="width=device-width,initial-scale=1"> 778</head> 779<body> 780<p>scheme: custom-local</p> 781<p>options: ARKWEB_SCHEME_OPTION_LOCAL</p> 782<div id="local_test">local_script.js 被拦截</div> 783<script src="custom-local://www.example.com/local_script.js"></script> 784</body> 785</html> 786``` 787 788main/resources/rawfile/post_data.html 789```html 790<html> 791<head> 792<meta name="viewport" content="width=device-width,initial-scale=1"> 793<script> 794 function textPostXhr(url) { 795 var formData = new FormData(); 796 var myBlob = new Blob(["This is my blob content"], {type : "text/plain"}); 797 formData.append("upload", myBlob); 798 var xhr = new XMLHttpRequest(); 799 xhr.open('POST', url, true); 800 xhr.send(formData); 801 xhr.onreadystatechange = function (err) { 802 console.log(err.target.status); 803 } 804 } 805 function textPutXhr(url) { 806 var formData = new FormData(); 807 var myBlob = new Blob(["This is my blob content"], {type : "text/plain"}); 808 formData.append("upload", myBlob); 809 var xhr = new XMLHttpRequest(); 810 xhr.open('PUT', url, true); 811 xhr.send(formData); 812 xhr.onreadystatechange = function (err) { 813 console.log(err.target.status); 814 } 815 } 816</script> 817</head> 818<body> 819<div onclick="textPostXhr('https://www.example.com/xhr')">test xhr post</div> 820<div onclick="textPutXhr('https://www.example.com/xhr')">test xhr put</div> 821</body> 822</html> 823``` 824 825main/resources/rawfile/service_worker.html 826```html 827<html> 828<head> 829<meta name="viewport" content="width=device-width,initial-scale=1"> 830<script> 831function registerSuccess() { 832 const body = document.body; 833 const element = document.createElement('div'); 834 element.textContent = 'register sw successful.'; 835 body.appendChild(element); 836} 837 838navigator.serviceWorker.register('/sw.js') 839 .then(reg => registerSuccess()) 840 .catch(error => console.log('failed!', error)) 841</script> 842</head> 843<body> 844</body> 845</html> 846``` 847 848main/resources/rawfile/sw.js 849```js 850self.addEventListener('install', event => { 851 console.log('v1 installing'); 852 event.waitUntil( 853 caches.open('static-v1').then(cache => cache.add('/cat.svg')) 854 ); 855}); 856 857self.addEventListener('activate', event => { 858 console.log("v1 now ready to handle fetches."); 859}); 860``` 861 862main/resources/rawfile/video.html 863```html 864<html> 865<head> 866<meta name="viewport" content="width=device-width,initial-scale=1"> 867</head> 868<body> 869<video width="400" height="400" controls> 870 <source src="https://www.example.com/test.mp4" type="video/mp4"> 871</video> 872</body> 873</html> 874``` 875 876main/resources/rawfile/chunked_post_stream.html 877```html 878<html> 879<head> 880<meta name="viewport" content="width=device-width,initial-scale=1"> 881</head> 882<script> 883let uploaded = 0; 884let buf = new Uint8Array(1024 * 50); 885let start = Date.now(); 886var rs = new ReadableStream({ 887 pull(ctrl) { 888 uploaded += buf.byteLength; 889 crypto.getRandomValues(buf); 890 ctrl.enqueue(buf); 891 if ((start + 1000) < Date.now()) ctrl.close(); 892 } 893}); 894 895function test() { 896 fetch('https://www.example.com/xhr', { 897 method: 'POST', 898 body: rs, 899 duplex: 'half' 900 }).then(r => r.json()).then(console.log); 901} 902</script> 903<body> 904<div onclick="test()">test post chunked http body.</div> 905</body> 906</html> 907``` 908 909main/resources/rawfile/xhr 910``` 911{} 912``` 913