diff --git a/frameworks/js/napi/http/BUILD.gn b/frameworks/js/napi/http/BUILD.gn index 95f7e93ab275ee0d7409d042d018a8ea76f9e021..68c3a2365858823953bc7ff3c985c1ecac01fbf1 100644 --- a/frameworks/js/napi/http/BUILD.gn +++ b/frameworks/js/napi/http/BUILD.gn @@ -157,6 +157,7 @@ ohos_shared_library("http") { if (defined(global_parts_info) && defined(global_parts_info.communication_netmanager_base) && global_parts_info.communication_netmanager_base) { + NETSTACK_NAPI_ROOT = "$SUBSYSTEM_DIR/netstack/frameworks/js/napi/" external_deps += [ "netmanager_base:net_conn_manager_if", "netmanager_base:netsys_client", @@ -169,6 +170,7 @@ ohos_shared_library("http") { defines = [ "HAS_NETMANAGER_BASE=1", "HAS_NETSTACK_CHR=1", + "ENABLE_HTTP_INTERCEPT=1" ] sources += [ "$NETSTACK_DIR/utils/netstack_chr_client/src/netstack_chr_client.cpp", @@ -176,6 +178,7 @@ ohos_shared_library("http") { "$NETSTACK_DIR/utils/http_over_curl/src/epoll_multi_driver.cpp", "$NETSTACK_DIR/utils/http_over_curl/src/epoll_request_handler.cpp", "$NETSTACK_DIR/utils/http_over_curl/src/http_handover_handler.cpp", + "http_interceptor/src/http_interceptor.cpp", ] defines += [ "HTTP_HANDOVER_FEATURE", @@ -183,6 +186,18 @@ ohos_shared_library("http") { ] include_dirs += [ "$NETSTACK_DIR/interfaces/innerkits/http_client/include", + "$NETSTACK_NAPI_ROOT/http/async_context/include", + "$NETSTACK_NAPI_ROOT/http/async_work/include", + "$NETSTACK_NAPI_ROOT/http/cache/base64/include", + "$NETSTACK_NAPI_ROOT/http/cache/cache_constant/include", + "$NETSTACK_NAPI_ROOT/http/cache/cache_proxy/include", + "$NETSTACK_NAPI_ROOT/http/cache/cache_strategy/include", + "$NETSTACK_NAPI_ROOT/http/cache/lru_cache/include", + "$NETSTACK_NAPI_ROOT/http/constant/include", + "$NETSTACK_NAPI_ROOT/http/http_exec/include", + "$NETSTACK_NAPI_ROOT/http/http_module/include", + "$NETSTACK_NAPI_ROOT/http/options/include", + "$NETSTACK_NAPI_ROOT/http/http_interceptor/include", ] } else { defines = [ diff --git a/frameworks/js/napi/http/async_context/include/request_context.h b/frameworks/js/napi/http/async_context/include/request_context.h index 2dd1810af7744d577aee3cb0a76521d7f5f040b0..1f6d66bef8582006cddb43d4fe0c1c3ad8c26b30 100644 --- a/frameworks/js/napi/http/async_context/include/request_context.h +++ b/frameworks/js/napi/http/async_context/include/request_context.h @@ -23,11 +23,15 @@ #include "base_context.h" #include "http_request_options.h" #include "http_response.h" +#include "hi_app_event_report.h" #include "timing.h" #if HAS_NETMANAGER_BASE #include "netstack_network_profiler.h" #endif #include "request_tracer.h" +#if ENABLE_HTTP_INTERCEPT +#include "http_interceptor.h" +#endif #ifdef HTTP_HANDOVER_FEATURE struct HttpHandoverInfo; #endif @@ -53,6 +57,10 @@ struct CertsPath { std::string certFile; }; +#if ENABLE_HTTP_INTERCEPT +class HttpInterceptor; +#endif + class RequestContext final : public BaseContext { public: friend class HttpExec; @@ -67,6 +75,12 @@ public: void ParseParams(napi_value *params, size_t paramsCount) override; +#if ENABLE_HTTP_INTERCEPT + void SetInterceptorRefs(std::unordered_map interceptorRefs); + + HttpInterceptor *GetInterceptor(); +#endif + HttpRequestOptions options; HttpResponse response; @@ -145,6 +159,8 @@ public: void SetCurlHandle(CURL *handle); + CURL *GetCurlHandle(); + void SendNetworkProfiler(); RequestTracer::Trace &GetTrace(); @@ -161,9 +177,11 @@ public: std::string GetPinnedPubkey() const; + std::unordered_map interceptorRefs_; + #ifdef HTTP_HANDOVER_FEATURE void SetRequestHandoverInfo(const HttpHandoverInfo &httpHandoverInfo); - + std::string GetRequestHandoverInfo(); #endif private: @@ -197,6 +215,10 @@ private: #ifdef HTTP_HANDOVER_FEATURE std::string httpHandoverInfoStr_ = "no handover"; #endif +#if ENABLE_HTTP_INTERCEPT + std::unique_ptr interceptor_ = nullptr; +#endif + RequestTracer::Trace trace_; bool CheckParamsType(napi_value *params, size_t paramsCount); diff --git a/frameworks/js/napi/http/async_context/src/request_context.cpp b/frameworks/js/napi/http/async_context/src/request_context.cpp index 1f0e887adc8a81ddd6ba1059cf3c72b582dbac08..318ce0c3d64bae730163618eac1b2e022e61f96c 100755 --- a/frameworks/js/napi/http/async_context/src/request_context.cpp +++ b/frameworks/js/napi/http/async_context/src/request_context.cpp @@ -169,6 +169,20 @@ void RequestContext::ParseParams(napi_value *params, size_t paramsCount) } } +#if ENABLE_HTTP_INTERCEPT +void RequestContext::SetInterceptorRefs(std::unordered_map interceptorRefs) +{ + interceptorRefs_.clear(); + interceptorRefs_ = std::move(interceptorRefs); + interceptor_ = std::make_unique(interceptorRefs_); +} + +HttpInterceptor *RequestContext::GetInterceptor() +{ + return interceptor_.get(); +} +#endif + bool RequestContext::CheckParamsType(napi_value *params, size_t paramsCount) { if (paramsCount == PARAM_JUST_URL) { @@ -1005,6 +1019,11 @@ void RequestContext::SetCurlHandle(CURL *handle) curlHandle_ = handle; } +CURL *RequestContext::GetCurlHandle() +{ + return curlHandle_; +} + void RequestContext::SendNetworkProfiler() { #if HAS_NETMANAGER_BASE @@ -1081,7 +1100,7 @@ void RequestContext::SetRequestHandoverInfo(const HttpHandoverInfo &httpHandover httpHandoverInfoStr_ += ", isStream:"; httpHandoverInfoStr_ += this->IsRequestInStream() ? "true" : "false"; } - + std::string RequestContext::GetRequestHandoverInfo() { return httpHandoverInfoStr_; diff --git a/frameworks/js/napi/http/http_exec/include/http_exec.h b/frameworks/js/napi/http/http_exec/include/http_exec.h index 54ce0a99ee95936c3ee512ea61e69c825d4b9502..011089f3fd26be256b29d2c51da1da8114f6a55b 100644 --- a/frameworks/js/napi/http/http_exec/include/http_exec.h +++ b/frameworks/js/napi/http/http_exec/include/http_exec.h @@ -63,6 +63,9 @@ public: static bool ExecRequest(RequestContext *context); + static bool HandleInitialRequestPostProcessing( + RequestContext *context, HiAppEventReport hiAppEventReport, int64_t &limitSdkReport); + static napi_value BuildRequestCallback(RequestContext *context); static napi_value RequestCallback(RequestContext *context); @@ -81,6 +84,8 @@ public: static void AsyncWorkRequestCallback(napi_env env, napi_status status, void *data); + static bool GetCurlDataFromHandle(CURL *handle, RequestContext *context, CURLMSG curlMsg, CURLcode result); + #if !HAS_NETMANAGER_BASE static bool Initialize(); @@ -91,6 +96,16 @@ public: static void AsyncRunRequest(RequestContext *context); + static void EnqueueCallback(RequestContext *context); + + static std::map MakeHeaderWithSetCookie(RequestContext *context); + + static void ResponseHeaderCallback(uv_work_t *work, int status); + + static void ProcessResponseBodyAndEmitEvents(RequestContext *context); + + static void ProcessResponseHeadersAndEmitEvents(RequestContext *context); + private: static bool SetOption(CURL *curl, RequestContext *context, struct curl_slist *requestHeader); @@ -130,14 +145,14 @@ private: static bool AddCurlHandle(CURL *handle, RequestContext *context); + static bool SetFollowLocation(CURL *handle, RequestContext *context); + #if HAS_NETMANAGER_BASE static void HandleCurlData(CURLMsg *msg, RequestContext *context); #else static void HandleCurlData(CURLMsg *msg); #endif - static bool GetCurlDataFromHandle(CURL *handle, RequestContext *context, CURLMSG curlMsg, CURLcode result); - static double GetTimingFromCurl(CURL *handle, CURLINFO info); static void CacheCurlPerformanceTiming(CURL *handle, RequestContext *context); @@ -186,6 +201,8 @@ private: static bool SetIpResolve(CURL *curl, RequestContext *context); + static void FinalResponseProcessing(RequestContext *requestContext); + struct RequestInfo { RequestInfo() = delete; ~RequestInfo() = default; diff --git a/frameworks/js/napi/http/http_exec/src/http_exec.cpp b/frameworks/js/napi/http/http_exec/src/http_exec.cpp index 0751b2ee74ac61114be3e2defdb3239913ff3ffa..79f25ce5cb85c47a100807f81c222b9901a52a45 100755 --- a/frameworks/js/napi/http/http_exec/src/http_exec.cpp +++ b/frameworks/js/napi/http/http_exec/src/http_exec.cpp @@ -223,20 +223,11 @@ void HttpExec::SetRequestInfoCallbacks(HttpOverCurl::TransferCallbacks &callback } #endif -void HttpExec::AsyncWorkRequestCallback(napi_env env, napi_status status, void *data) +void HttpExec::FinalResponseProcessing(RequestContext *requestContext) { - if (status != napi_ok) { - return; - } - if (!data) { - return; - } - if (reinterpret_cast(data)->magicNumber_ != MAGIC_NUMBER) { - return; - } - std::unique_ptr context(static_cast(data), - RequestContextDeleter); - napi_value argv[EVENT_PARAM_TWO] = {nullptr}; + std::unique_ptr context(requestContext, RequestContextDeleter); + napi_value argv[EVENT_PARAM_TWO] = { nullptr }; + auto env = context->GetEnv(); if (context->IsParseOK() && context->IsExecOK()) { argv[EVENT_PARAM_ZERO] = NapiUtils::GetUndefined(env); argv[EVENT_PARAM_ONE] = HttpExec::RequestCallback(context.get()); @@ -267,6 +258,31 @@ void HttpExec::AsyncWorkRequestCallback(napi_env env, napi_status status, void * (void)NapiUtils::CallFunction(env, undefined, func, EVENT_PARAM_TWO, argv); } } + +void HttpExec::AsyncWorkRequestCallback(napi_env env, napi_status status, void *data) +{ + if (status != napi_ok) { + return; + } + if (!data) { + return; + } + auto context = reinterpret_cast(data); + if (context->magicNumber_ != MAGIC_NUMBER) { + return; + } + auto handleFinalResponseProcessing = std::bind(FinalResponseProcessing, context); +#if ENABLE_HTTP_INTERCEPT + auto interceptor = context->GetInterceptor(); + if (interceptor != nullptr && interceptor->IsFinalResponseInterceptor()) { + auto interceptorCallback = interceptor->GetFinalResponseInterceptorCallback(); + interceptorCallback(context, handleFinalResponseProcessing); + NETSTACK_LOGD("wangz::HttpExec::context->finalResponseInterceptorCallback_ success"); + return; + } +#endif + handleFinalResponseProcessing(); +} #if HAS_NETMANAGER_BASE bool SetTraceOptions(CURL *curl, RequestContext *context) { @@ -324,6 +340,17 @@ bool SetTraceOptions(CURL *curl, RequestContext *context) } #endif +#if ENABLE_HTTP_INTERCEPT +bool HttpExec::SetFollowLocation(CURL *curl, RequestContext *context) +{ + auto interceptor = context->GetInterceptor(); + if (interceptor != nullptr && interceptor->IsRedirectionInterceptor()) { + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_FOLLOWLOCATION, 0L, context); + } + return true; +} +#endif + bool HttpExec::AddCurlHandle(CURL *handle, RequestContext *context) { #if HAS_NETMANAGER_BASE @@ -346,7 +373,9 @@ bool HttpExec::AddCurlHandle(CURL *handle, RequestContext *context) name << HTTP_REQ_TRACE_NAME << "_" << std::this_thread::get_id() << (isDebugMode ? ("_" + urlWithoutParam) : ""); SetTraceOptions(handle, context); SetServerSSLCertOption(handle, context); - +#if ENABLE_HTTP_INTERCEPT + SetFollowLocation(handle, context); +#endif static HttpOverCurl::EpollRequestHandler requestHandler; HttpOverCurl::TransferCallbacks callbacks; SetRequestInfoCallbacks(callbacks); @@ -450,7 +479,12 @@ bool HttpExec::GetCurlDataFromHandle(CURL *handle, RequestContext *context, CURL context->SetErrorCode(code); return false; } - +#if ENABLE_HTTP_INTERCEPT + auto interceptor = context->GetInterceptor(); + if (interceptor != nullptr && interceptor->IsRedirectionInterceptor()) { + context->response.SetCookies(""); + } +#endif std::unique_ptr cookiesHandle(cookies, curl_slist_free_all); while (cookies) { context->response.AppendCookies(cookies->data, strlen(cookies->data)); @@ -653,53 +687,139 @@ static bool ExecRequestCheck(RequestContext *context) return true; } -bool HttpExec::ExecRequest(RequestContext *context) +void HttpExec::EnqueueCallback(RequestContext *context) { - HiAppEventReport hiAppEventReport("NetworkKit", "HttpRequest"); - if (!ExecRequestCheck(context)) { - return false; - } - if (context->GetSharedManager()->IsEventDestroy()) { - return false; + if (context->GetSharedManager()) { + auto env = context->GetEnv(); + auto moduleId = context->GetModuleId(); + if (context->IsRequestInStream()) { + NapiUtils::CreateUvQueueWorkByModuleId( + env, std::bind(AsyncWorkRequestInStreamCallback, env, napi_ok, context), moduleId); + } else { + NapiUtils::CreateUvQueueWorkByModuleId( + env, std::bind(AsyncWorkRequestCallback, env, napi_ok, context), moduleId); + } } +} + +bool HttpExec::HandleInitialRequestPostProcessing( + RequestContext *context, HiAppEventReport hiAppEventReport, int64_t &limitSdkReport) +{ context->options.SetRequestTime(HttpTime::GetNowTimeGMT()); CacheProxy proxy(context->options); + if (context->IsUsingCache() && proxy.ReadResponseFromCache(context)) { - if (context->GetSharedManager()) { - if (context->IsRequestInStream()) { - NapiUtils::CreateUvQueueWorkByModuleId( - context->GetEnv(), std::bind(AsyncWorkRequestInStreamCallback, context->GetEnv(), napi_ok, context), - context->GetModuleId()); - } else { - NapiUtils::CreateUvQueueWorkByModuleId( - context->GetEnv(), std::bind(AsyncWorkRequestCallback, context->GetEnv(), napi_ok, context), - context->GetModuleId()); - } + auto handleCacheCheckedPostProcessing [[maybe_unused]] = std::bind( + [](RequestContext *ctx) { + EnqueueCallback(ctx); + return true; + }, + context); + auto blockCacheCheckedPostProcessing [[maybe_unused]] = std::bind( + [](RequestContext *ctx) { + EnqueueCallback(ctx); + return false; + }, + context); +#if ENABLE_HTTP_INTERCEPT + auto interceptor = context->GetInterceptor(); + if (interceptor != nullptr && interceptor->IsCacheCheckedInterceptor()) { + auto interceptorCallback = interceptor->GetCacheCheckedInterceptorCallback(); + interceptorCallback(context, handleCacheCheckedPostProcessing, blockCacheCheckedPostProcessing); + NETSTACK_LOGD("wangz::HttpExec::context->cacheCheckedInterceptorCallback_ success"); + return true; } - return true; +#endif + return handleCacheCheckedPostProcessing(); } + if (!RequestWithoutCache(context)) { context->SetErrorCode(NapiUtils::NETSTACK_NAPI_INTERNAL_ERROR); - if (context->GetSharedManager()) { - if (context->IsRequestInStream()) { - NapiUtils::CreateUvQueueWorkByModuleId( - context->GetEnv(), std::bind(AsyncWorkRequestInStreamCallback, context->GetEnv(), napi_ok, context), - context->GetModuleId()); - } else { - NapiUtils::CreateUvQueueWorkByModuleId( - context->GetEnv(), std::bind(AsyncWorkRequestCallback, context->GetEnv(), napi_ok, context), - context->GetModuleId()); - } - } + EnqueueCallback(context); return false; } - if (g_limitSdkReport == 0) { + + if (limitSdkReport == 0) { hiAppEventReport.ReportSdkEvent(RESULT_SUCCESS, ERR_NONE); - g_limitSdkReport = 1; + limitSdkReport = 1; } + return true; } +void HttpExec::ProcessResponseBodyAndEmitEvents(RequestContext *context) +{ + if (context == nullptr || !context->GetSharedManager()) { + return; + } + if (context->GetSharedManager()->IsEventDestroy()) { + context->StopAndCacheNapiPerformanceTiming(HttpConstant::RESPONSE_BODY_TIMING); + return; + } + if (context->IsRequestInStream()) { + NapiUtils::CreateUvQueueWorkByModuleId( + context->GetEnv(), std::bind(OnDataReceive, context->GetEnv(), napi_ok, context), context->GetModuleId()); + } + context->StopAndCacheNapiPerformanceTiming(HttpConstant::RESPONSE_BODY_TIMING); +} + +void HttpExec::ProcessResponseHeadersAndEmitEvents(RequestContext *context) +{ + context->GetTrace().Tracepoint(TraceEvents::RECEIVING); + if (context->GetSharedManager()->IsEventDestroy()) { + context->StopAndCacheNapiPerformanceTiming(HttpConstant::RESPONSE_HEADER_TIMING); + return; + } + if (CommonUtils::EndsWith(context->response.GetRawHeader(), HttpConstant::HTTP_RESPONSE_HEADER_SEPARATOR)) { + context->response.ParseHeaders(); + if (context->GetSharedManager()) { + auto headerMap = new std::map(MakeHeaderWithSetCookie(context)); + context->GetSharedManager()->EmitByUvWithoutCheckShared( + ON_HEADER_RECEIVE, headerMap, ResponseHeaderCallback); + auto headersMap = new std::map(MakeHeaderWithSetCookie(context)); + context->GetSharedManager()->EmitByUvWithoutCheckShared( + ON_HEADERS_RECEIVE, headersMap, ResponseHeaderCallback); + } + } + context->StopAndCacheNapiPerformanceTiming(HttpConstant::RESPONSE_HEADER_TIMING); +} + +bool HttpExec::ExecRequest(RequestContext *context) +{ + HiAppEventReport hiAppEventReport("NetworkKit", "HttpRequest"); + + if (!ExecRequestCheck(context)) { + return false; + } + + if (context->GetSharedManager()->IsEventDestroy()) { + return false; + } + + auto continueCallback = + std::bind(&HandleInitialRequestPostProcessing, context, hiAppEventReport, std::ref(g_limitSdkReport)); + + auto blockCallback [[maybe_unused]] = std::bind( + [](RequestContext *ctx) { + ProcessResponseHeadersAndEmitEvents(ctx); + ProcessResponseBodyAndEmitEvents(ctx); + EnqueueCallback(ctx); + }, + context); + +#if ENABLE_HTTP_INTERCEPT + auto interceptor = context->GetInterceptor(); + if (interceptor != nullptr && interceptor->IsInitialRequestInterceptor()) { + auto interceptorCallback = interceptor->GetInitialRequestInterceptorCallback(); + interceptorCallback(context, continueCallback, blockCallback); + NETSTACK_LOGD("wangz::HttpExec::context->initialRequestInterceptorCallback_ success"); + return true; + } +#endif + + return continueCallback(); +} + napi_value HttpExec::BuildRequestCallback(RequestContext *context) { napi_value object = NapiUtils::CreateObject(context->GetEnv()); @@ -1169,7 +1289,7 @@ static bool LoadCaCertFromString(X509_STORE *store, const std::string &certData) BIO_free(cbio); return false; } - + /* add each entry from PEM file to x509_store */ for (int i = 0; i < static_cast(sk_X509_INFO_num(inf)); ++i) { auto itmp = sk_X509_INFO_value(inf, i); @@ -1184,7 +1304,7 @@ static bool LoadCaCertFromString(X509_STORE *store, const std::string &certData) return false; } } - + return true; } #endif // HTTP_MULTIPATH_CERT_ENABLE @@ -1597,6 +1717,18 @@ size_t HttpExec::OnWritingMemoryBody(const void *data, size_t size, size_t memBy context->StopAndCacheNapiPerformanceTiming(HttpConstant::RESPONSE_BODY_TIMING); return size * memBytes; } + +#if ENABLE_HTTP_INTERCEPT + auto interceptor = context->GetInterceptor(); + if (interceptor != nullptr && interceptor->IsRedirectionInterceptor()) { + int statusCode = context->response.GetResponseCode(); + if (statusCode >= 300 && statusCode < 400) { + context->response.SetResult(const_cast(static_cast(data))); + return size * memBytes; + } + } +#endif + if (context->response.GetResult().size() > context->options.GetMaxLimit() || size * memBytes > context->options.GetMaxLimit()) { NETSTACK_LOGE("response data exceeds the maximum limit"); @@ -1637,7 +1769,7 @@ static void MakeHeaderWithSetCookieArray(napi_env env, napi_value header, std::m } } -static void ResponseHeaderCallback(uv_work_t *work, int status) +void HttpExec::ResponseHeaderCallback(uv_work_t *work, int status) { (void)status; @@ -1660,7 +1792,7 @@ static void ResponseHeaderCallback(uv_work_t *work, int status) work = nullptr; } -static std::map MakeHeaderWithSetCookie(RequestContext * context) +std::map HttpExec::MakeHeaderWithSetCookie(RequestContext *context) { std::map tempMap = context->response.GetHeader(); std::string setCookies; diff --git a/frameworks/js/napi/http/http_interceptor/include/http_interceptor.h b/frameworks/js/napi/http/http_interceptor/include/http_interceptor.h new file mode 100644 index 0000000000000000000000000000000000000000..eee0af88317770e672face7999737631febc4e75 --- /dev/null +++ b/frameworks/js/napi/http/http_interceptor/include/http_interceptor.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2024-2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMMUNICATIONNETSTACK_HTTP_INTERCEPTOR_H +#define COMMUNICATIONNETSTACK_HTTP_INTERCEPTOR_H + +#include +#include +#include +#include "curl/curl.h" +#include "base_context.h" +#include "http_request_options.h" +#include "http_response.h" +#include "hi_app_event_report.h" +#include "timing.h" +#include "request_info.h" +#include "request_context.h" + +namespace OHOS::NetStack::Http { + +class RequestContext; +using RequestInterceptor = std::function, std::function)>; +using RedirectionInterceptor = std::function, std::function, HttpOverCurl::RedirectionInterceptorInfo *)>; +using FinalResponseInterceptor = std::function)>; + +class HttpInterceptor { +public: + friend class HttpExec; + + HttpInterceptor() = delete; + + explicit HttpInterceptor(const std::unordered_map &interceptorRefs) + { + SetInterceptorRefs(interceptorRefs); + } + + ~HttpInterceptor(); + + bool IsInitialRequestInterceptor() const; + bool IsRedirectionInterceptor() const; + bool IsFinalResponseInterceptor() const; + bool IsCacheCheckedInterceptor() const; + bool IsConnectNetworkInterceptor() const; + + const RequestInterceptor &GetInitialRequestInterceptorCallback() const; + const RedirectionInterceptor &GetRedirectionInterceptorCallback() const; + const FinalResponseInterceptor &GetFinalResponseInterceptorCallback() const; + const RequestInterceptor &GetCacheCheckedInterceptorCallback() const; + const RequestInterceptor &GetConnectNetworkInterceptorCallback() const; + +private: + struct InitialRequestInterceptorHandle { + std::function after; + RequestContext *context; + std::function block; + napi_value reqContext; + napi_value resContext; + ~InitialRequestInterceptorHandle() = default; + }; + struct CacheCheckedInterceptorHandle { + std::function after; + RequestContext *context; + std::function block; + napi_value reqContext; + napi_value resContext; + ~CacheCheckedInterceptorHandle() = default; + }; + struct FinalResponseInterceptorHandle { + std::function after; + RequestContext *context; + napi_value reqContext; + napi_value resContext; + ~FinalResponseInterceptorHandle() = default; + }; + struct RedirectionInterceptorHandle { + std::function handleRedirect; + std::function handleCompletion; + RequestContext *context; + HttpOverCurl::RedirectionInterceptorInfo *handleInfo; + napi_value reqContext; + napi_value resContext; + RedirectionInterceptorHandle(std::function redirect, std::function completion, + HttpOverCurl::RedirectionInterceptorInfo *info, napi_value requestContext, napi_value responseContext) + : handleRedirect(std::move(redirect)), handleCompletion(std::move(completion)), context(nullptr), + handleInfo(info), reqContext(requestContext), resContext(responseContext) + { + } + ~RedirectionInterceptorHandle() = default; + }; + + RequestInterceptor initialRequestInterceptorCallback_ = nullptr; + RedirectionInterceptor redirectionInterceptorCallback_ = nullptr; + FinalResponseInterceptor finalResponseInterceptorCallback_ = nullptr; + RequestInterceptor cacheCheckedInterceptorCallback_ = nullptr; + RequestInterceptor connectNetworkInterceptorCallback_ = nullptr; + + void SetInterceptorRefs(std::unordered_map interceptorRefs); + + void SetInitialRequestInterceptor(); + void SetRedirectionInterceptor(); + void SetFinalResponseInterceptor(); + void SetCacheCheckedInterceptor(); + void SetConnectNetworkInterceptor(); + + static void ApplyContinueInitialRequestInterceptor(napi_env env, InitialRequestInterceptorHandle *handle); + static void ApplyBlockInitialRequestInterceptor(napi_env env, InitialRequestInterceptorHandle *handle); + static napi_value CreateRequestContextInitialRequestInterceptor(napi_env env, RequestContext *context); + static napi_value CreateResponseContextInitialRequestInterceptor(napi_env env, RequestContext *context); + static void HandlePromiseThenInitialRequestInterceptor( + napi_env env, InitialRequestInterceptorHandle *handle, napi_value promise); + static void HandlePromiseInitialRequestInterceptor(napi_env env, InitialRequestInterceptorHandle *handle); + + static void ApplyContinueRedirectionInterceptor(napi_env env, RedirectionInterceptorHandle *handle); + static void ApplyBlockRedirectionInterceptor(napi_env env, RedirectionInterceptorHandle *handle); + static napi_value CreateRequestContextRedirectionInterceptor( + napi_env env, RequestContext *context, HttpOverCurl::RedirectionInterceptorInfo *handleInfo); + static napi_value CreateResponseContextRedirectionInterceptor( + napi_env env, RequestContext *context, HttpOverCurl::RedirectionInterceptorInfo *handleInfo); + static void HandlePromiseThenRedirectionInterceptor( + napi_env env, RedirectionInterceptorHandle *handle, napi_value promise); + static void HandlePromiseRedirectionInterceptor(napi_env env, RedirectionInterceptorHandle *handle); + + static void ApplyContinueFinalResponseInterceptor(napi_env env, FinalResponseInterceptorHandle *handle); + static void ApplyBlockFinalResponseInterceptor(napi_env env, FinalResponseInterceptorHandle *handle); + static napi_value CreateRequestContextFinalResponseInterceptor(napi_env env, RequestContext *context); + static napi_value CreateResponseContextFinalResponseInterceptor(napi_env env, RequestContext *context); + static void HandlePromiseThenFinalResponseInterceptor( + napi_env env, FinalResponseInterceptorHandle *handle, napi_value promise); + static void HandlePromiseFinalResponseInterceptor(napi_env env, FinalResponseInterceptorHandle *handle); + + static void ApplyContinueCacheCheckedInterceptor(napi_env env, CacheCheckedInterceptorHandle *handle); + static void ApplyBlockCacheCheckedInterceptor(napi_env env, CacheCheckedInterceptorHandle *handle); + static napi_value CreateRequestContextCacheCheckedInterceptor(napi_env env, RequestContext *context); + static napi_value CreateResponseContextCacheCheckedInterceptor(napi_env env, RequestContext *context); + static void HandlePromiseThenCacheCheckedInterceptor( + napi_env env, CacheCheckedInterceptorHandle *handle, napi_value promise); + static void HandlePromiseCacheCheckedInterceptor(napi_env env, CacheCheckedInterceptorHandle *handle); + + static void ApplyContinueConnectNetworkInterceptor(napi_env env, InitialRequestInterceptorHandle *handle); + static void ApplyBlockConnectNetworkInterceptor(napi_env env, InitialRequestInterceptorHandle *handle); + static napi_value CreateRequestContextConnectNetworkInterceptor(napi_env env, RequestContext *context); + static napi_value CreateResponseContextConnectNetworkInterceptor(napi_env env, RequestContext *context); + static void HandlePromiseThenConnectNetworkInterceptor( + napi_env env, InitialRequestInterceptorHandle *handle, napi_value promise); + static void HandlePromiseConnectNetworkInterceptor(napi_env env, InitialRequestInterceptorHandle *handle); +}; +} // namespace OHOS::NetStack::Http + +#endif /* COMMUNICATIONNETSTACK_HTTP_INTERCEPTOR_H */ diff --git a/frameworks/js/napi/http/http_interceptor/src/http_interceptor.cpp b/frameworks/js/napi/http/http_interceptor/src/http_interceptor.cpp new file mode 100755 index 0000000000000000000000000000000000000000..f189c5283c566d59912dd6494be358a51191fcae --- /dev/null +++ b/frameworks/js/napi/http/http_interceptor/src/http_interceptor.cpp @@ -0,0 +1,1243 @@ +/* + * Copyright (c) 2024-2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "http_interceptor.h" + +#include +#include +#include +#include +#include +#include + +#include "constant.h" +#include "http_exec.h" +#include "http_tls_config.h" +#include "napi_utils.h" +#include "netstack_common_utils.h" +#include "netstack_log.h" +#include "request_tracer.h" +#include "secure_char.h" +#include "timing.h" + + +namespace OHOS::NetStack::Http { + +HttpInterceptor::~HttpInterceptor() { } + +bool HttpInterceptor::IsInitialRequestInterceptor() const +{ + return static_cast(initialRequestInterceptorCallback_); +} + +bool HttpInterceptor::IsRedirectionInterceptor() const +{ + return static_cast(redirectionInterceptorCallback_); +} + +bool HttpInterceptor::IsFinalResponseInterceptor() const +{ + return static_cast(finalResponseInterceptorCallback_); +} + +bool HttpInterceptor::IsCacheCheckedInterceptor() const +{ + return static_cast(cacheCheckedInterceptorCallback_); +} + +bool HttpInterceptor::IsConnectNetworkInterceptor() const +{ + return static_cast(connectNetworkInterceptorCallback_); +} + +void HttpInterceptor::SetInterceptorRefs(std::unordered_map interceptorRefs) +{ + for (const auto &[key, ref] : interceptorRefs) { + if (ref == nullptr) { + continue; + } + if (key == "INITIAL_REQUEST") { + SetInitialRequestInterceptor(); + } else if (key == "REDIRECTION") { + SetRedirectionInterceptor(); + } else if (key == "FINAL_RESPONSE") { + SetFinalResponseInterceptor(); + } else if (key == "READ_CACHE") { + SetCacheCheckedInterceptor(); + } else if (key == "CONNECT_NETWORK") { + SetConnectNetworkInterceptor(); + } + } +} + +const RequestInterceptor &HttpInterceptor::GetInitialRequestInterceptorCallback() const +{ + return initialRequestInterceptorCallback_; +} + +const RedirectionInterceptor &HttpInterceptor::GetRedirectionInterceptorCallback() const +{ + return redirectionInterceptorCallback_; +} + +const FinalResponseInterceptor &HttpInterceptor::GetFinalResponseInterceptorCallback() const +{ + return finalResponseInterceptorCallback_; +} + +const RequestInterceptor &HttpInterceptor::GetCacheCheckedInterceptorCallback() const +{ + return cacheCheckedInterceptorCallback_; +} + +const RequestInterceptor &HttpInterceptor::GetConnectNetworkInterceptorCallback() const +{ + return connectNetworkInterceptorCallback_; +} + +// InitialRequestInterceptor +void HttpInterceptor::ApplyContinueInitialRequestInterceptor(napi_env env, InitialRequestInterceptorHandle *handle) +{ + auto newUrl = NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, handle->reqContext, "url")); + handle->context->options.SetUrl(newUrl); + NETSTACK_LOGD("RequestContext::ApplyRequest: updated URL = %{public}s", newUrl.c_str()); + + napi_value newHeadObj = NapiUtils::GetNamedProperty(env, handle->reqContext, "head"); + if (newHeadObj != nullptr) { + for (const auto &key : NapiUtils::GetPropertyNames(env, newHeadObj)) { + std::string value = + NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, newHeadObj, key.c_str())); + handle->context->options.SetHeader(key, value); + NETSTACK_LOGD("Updated header: %{public}s = %{public}s", key.c_str(), value.c_str()); + } + } + + napi_value bodyValue = NapiUtils::GetNamedProperty(env, handle->reqContext, "body"); + if (bodyValue == nullptr) { + handle->context->options.SetBody("", 0); + NETSTACK_LOGD("Body is null, using empty body"); + } else { + napi_valuetype bodyType = NapiUtils::GetValueType(env, bodyValue); + if (bodyType == napi_string) { + std::string bodyStr = NapiUtils::GetStringFromValueUtf8(env, bodyValue); + handle->context->options.SetBody(bodyStr.data(), bodyStr.size()); + NETSTACK_LOGD("Updated string body, length=%{public}zu", bodyStr.size()); + } else if (NapiUtils::ValueIsArrayBuffer(env, bodyValue)) { + size_t bufferLength = 0; + void *bufferData = NapiUtils::GetInfoFromArrayBufferValue(env, bodyValue, &bufferLength); + if (bufferData != nullptr && bufferLength > 0) { + handle->context->options.SetBody(bufferData, bufferLength); + NETSTACK_LOGD("Updated array buffer body, length=%{public}zu", bufferLength); + } else { + NETSTACK_LOGE("Invalid array buffer data or length"); + } + } else { + NETSTACK_LOGE("Unsupported body type: %{public}d", bodyType); + } + } + + handle->after(); +} + +void HttpInterceptor::ApplyBlockInitialRequestInterceptor(napi_env env, InitialRequestInterceptorHandle *handle) +{ + napi_value resContext = handle->resContext; + + if (NapiUtils::HasNamedProperty(env, resContext, "header")) { + std::string headerRawStr = + NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, resContext, "header")); + if (!headerRawStr.empty()) { + bool hasSeparator = CommonUtils::EndsWith(headerRawStr, HttpConstant::HTTP_RESPONSE_HEADER_SEPARATOR); + if (!hasSeparator) { + headerRawStr += HttpConstant::HTTP_RESPONSE_HEADER_SEPARATOR; + } + } + handle->context->response.SetRawHeader(headerRawStr); + NETSTACK_LOGD("Set raw header, length=%{public}zu", headerRawStr.size()); + } + + if (NapiUtils::HasNamedProperty(env, resContext, "responseCode")) { + uint32_t code = + NapiUtils::GetUint32FromValue(env, NapiUtils::GetNamedProperty(env, resContext, "responseCode")); + handle->context->response.SetResponseCode(code); + NETSTACK_LOGD("Set response code: %{public}u", code); + } + + if (NapiUtils::HasNamedProperty(env, resContext, "result")) { + std::string result = + NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, resContext, "result")); + handle->context->response.SetResult(result); + NETSTACK_LOGD("Set response result, length=%{public}zu", result.size()); + } + + if (NapiUtils::HasNamedProperty(env, resContext, "cookies")) { + std::string cookies = + NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, resContext, "cookies")); + handle->context->response.SetCookies(cookies); + NETSTACK_LOGD("Set cookies: %{public}s", cookies.c_str()); + } + + handle->context->response.ParseHeaders(); + NETSTACK_LOGD("Parsed response headers"); + + handle->block(); +} + +napi_value HttpInterceptor::CreateRequestContextInitialRequestInterceptor(napi_env env, RequestContext *context) +{ + napi_value reqContext = NapiUtils::CreateObject(env); + if (reqContext == nullptr) { + NETSTACK_LOGE("Failed to create reqContext object"); + return nullptr; + } + + std::string url = context->options.GetUrl(); + NapiUtils::SetNamedProperty(env, reqContext, "url", NapiUtils::CreateStringUtf8(env, url)); + + napi_value headerObj = NapiUtils::CreateObject(env); + const auto &headers = context->options.GetHeader(); + NETSTACK_LOGD("RequestContext::SetInitialRequestInterceptor: headers count = %{public}zu", headers.size()); + for (const auto &[key, value] : headers) { + napi_value val = NapiUtils::CreateStringUtf8(env, value); + NapiUtils::SetNamedProperty(env, headerObj, key, val); + } + NapiUtils::SetNamedProperty(env, reqContext, "head", headerObj); + + std::string body = context->options.GetBody(); + NapiUtils::SetNamedProperty(env, reqContext, "body", NapiUtils::CreateStringUtf8(env, body)); + + return reqContext; +} + +napi_value HttpInterceptor::CreateResponseContextInitialRequestInterceptor(napi_env env, RequestContext *context) +{ + napi_value resContext = NapiUtils::CreateObject(env); + if (resContext == nullptr) { + NETSTACK_LOGE("Failed to create resContext object"); + return nullptr; + } + return resContext; +} + +void HttpInterceptor::HandlePromiseThenInitialRequestInterceptor( + napi_env env, InitialRequestInterceptorHandle *handle, napi_value promise) +{ + napi_value thenFunc = NapiUtils::GetNamedProperty(env, promise, "then"); + if (thenFunc == nullptr) { + NETSTACK_LOGE("Promise.then is not a function"); + delete handle; + return; + } + + napi_value onResolve = NapiUtils::CreateFunction( + env, "onResolve", + [](napi_env env, napi_callback_info info) -> napi_value { + NETSTACK_LOGD("RequestContext::onResolve: start"); + + size_t argc = 1; + napi_value args[1]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + bool result = NapiUtils::GetBooleanFromValue(env, args[0]); + + void *data; + napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); + auto *handle = static_cast(data); + + if (result) { + ApplyContinueInitialRequestInterceptor(env, handle); + } else { + ApplyBlockInitialRequestInterceptor(env, handle); + } + + // 释放 handle + delete handle; + return nullptr; + }, + handle); + + napi_value onReject = NapiUtils::CreateFunction( + env, "onReject", + [](napi_env env, napi_callback_info info) -> napi_value { + NETSTACK_LOGE("RequestContext::onReject: interceptor promise rejected"); + void *data; + napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); + auto *handle = static_cast(data); + delete handle; + return nullptr; + }, + handle); + + napi_value argv1[] = { onResolve, onReject }; + constexpr size_t thenCallArgc = 2; + NapiUtils::CallFunction(env, promise, thenFunc, thenCallArgc, argv1); +} + +void HttpInterceptor::HandlePromiseInitialRequestInterceptor(napi_env env, InitialRequestInterceptorHandle *handle) +{ + auto context = handle->context; + + napi_value interceptorRef = NapiUtils::GetReference(env, context->interceptorRefs_["INITIAL_REQUEST"]); + if (interceptorRef == nullptr) { + NETSTACK_LOGE("Interceptor reference is null"); + delete handle; + return; + } + + napi_value interceptorHandle; + napi_status status = napi_get_named_property(env, interceptorRef, "interceptorHandle", &interceptorHandle); + if (status != napi_ok) { + NETSTACK_LOGE("Invalid interceptorHandle"); + delete handle; + return; + } + + napi_value argv[] = { handle->reqContext, handle->resContext }; + napi_value promise = NapiUtils::CallFunction(env, interceptorRef, interceptorHandle, 2, argv); + if (promise == nullptr) { + NETSTACK_LOGE("Interceptor did not return a valid promise"); + delete handle; + return; + } + + HandlePromiseThenInitialRequestInterceptor(env, handle, promise); + NETSTACK_LOGD("RequestContext::SetInitialRequestInterceptor: finished setup"); +} + +void HttpInterceptor::SetInitialRequestInterceptor() +{ + initialRequestInterceptorCallback_ = [](RequestContext *context, std::function after, + std::function block) { + napi_env env = context->GetEnv(); + NETSTACK_LOGD("RequestContext::SetInitialRequestInterceptor: running"); + + napi_value reqContext = CreateRequestContextInitialRequestInterceptor(env, context); + if (reqContext == nullptr) { + NETSTACK_LOGE("Failed to create reqContext object"); + return; + } + + napi_value resContext = CreateResponseContextInitialRequestInterceptor(env, context); + if (resContext == nullptr) { + NETSTACK_LOGE("Failed to create resContext object"); + return; + } + + auto *handle = + new InitialRequestInterceptorHandle { std::move(after), context, std::move(block), reqContext, resContext }; + + HandlePromiseInitialRequestInterceptor(env, handle); + }; +} + +// RedirectionInterceptor +void HttpInterceptor::ApplyContinueRedirectionInterceptor(napi_env env, RedirectionInterceptorHandle *handle) +{ + auto handleInfo = handle->handleInfo; + auto urlValue = NapiUtils::GetNamedProperty(env, handle->reqContext, "url"); + if (urlValue != nullptr) { + auto newUrl = NapiUtils::GetStringFromValueUtf8(env, urlValue); + *handleInfo->location = newUrl; + NETSTACK_LOGD("wangz::redirectionInterceptor updated url:%{public}s success", handleInfo->location->c_str()); + } + + if (napi_value newHeadObj = NapiUtils::GetNamedProperty(env, handle->reqContext, "head")) { + for (const auto &key : NapiUtils::GetPropertyNames(env, newHeadObj)) { + std::string value = + NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, newHeadObj, key.c_str())); + handle->context->options.SetHeader(key, value); + NETSTACK_LOGD("Updated header: %{public}s = %{public}s", key.c_str(), value.c_str()); + } + } + + napi_value bodyValue = NapiUtils::GetNamedProperty(env, handle->reqContext, "body"); + if (bodyValue != nullptr) { + napi_valuetype bodyType = NapiUtils::GetValueType(env, bodyValue); + if (bodyType == napi_string) { + std::string bodyStr = NapiUtils::GetStringFromValueUtf8(env, bodyValue); + handle->context->options.SetBody(bodyStr.data(), bodyStr.size()); + NETSTACK_LOGD("updated string body, length=%{public}zu", bodyStr.size()); + } else if (NapiUtils::ValueIsArrayBuffer(env, bodyValue)) { + size_t bufferLength = 0; + void *bufferData = NapiUtils::GetInfoFromArrayBufferValue(env, bodyValue, &bufferLength); + if (bufferData != nullptr && bufferLength > 0) { + handle->context->options.SetBody(bufferData, bufferLength); + NETSTACK_LOGD("wangz::updated array buffer body, length=%{public}zu", bufferLength); + } else { + NETSTACK_LOGE("wangz::invalid array buffer data or length"); + } + } else { + NETSTACK_LOGE("wangz::unsupported body type, type=%{public}d", bodyType); + } + } else { + NETSTACK_LOGD("wangz::body is null, using empty body"); + handle->context->options.SetBody("", 0); + } + handle->handleRedirect(); +} + +void HttpInterceptor::ApplyBlockRedirectionInterceptor(napi_env env, RedirectionInterceptorHandle *handle) +{ + if (NapiUtils::HasNamedProperty(env, handle->resContext, "header")) { + napi_value headerValue = NapiUtils::GetNamedProperty(env, handle->resContext, "header"); + std::string headerRawStr = NapiUtils::GetStringFromValueUtf8(env, headerValue); + handle->context->response.SetRawHeader(headerRawStr); + NETSTACK_LOGD("wangz::Set raw header, length=%{public}zu", headerRawStr.size()); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "responseCode")) { + napi_value codeValue = NapiUtils::GetNamedProperty(env, handle->resContext, "responseCode"); + uint32_t responseCode = NapiUtils::GetUint32FromValue(env, codeValue); + handle->context->response.SetResponseCode(responseCode); + NETSTACK_LOGD("wangz::Set response code: %{public}u", responseCode); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "result")) { + napi_value resultValue = NapiUtils::GetNamedProperty(env, handle->resContext, "result"); + std::string resultStr = NapiUtils::GetStringFromValueUtf8(env, resultValue); + handle->context->response.SetResult(resultStr); + NETSTACK_LOGD("wangz::Set response result, length=%{public}zu", resultStr.size()); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "cookies")) { + napi_value cookiesValue = NapiUtils::GetNamedProperty(env, handle->resContext, "cookies"); + std::string cookiesStr = NapiUtils::GetStringFromValueUtf8(env, cookiesValue); + handle->context->response.SetCookies(cookiesStr); + NETSTACK_LOGD("wangz::Set cookies: %{public}s", cookiesStr.c_str()); + } + + handle->context->response.ParseHeaders(); + NETSTACK_LOGD("wangz::Parsed response headers"); + handle->handleCompletion(); +} + +napi_value HttpInterceptor::CreateRequestContextRedirectionInterceptor( + napi_env env, RequestContext *context, HttpOverCurl::RedirectionInterceptorInfo *handleInfo) +{ + napi_value reqContext = NapiUtils::CreateObject(env); + + napi_value urlValue = NapiUtils::CreateStringUtf8(env, *handleInfo->location); + if (urlValue != nullptr) { + NapiUtils::SetNamedProperty(env, reqContext, "url", urlValue); + } + + napi_value headerObj = NapiUtils::CreateObject(env); + const std::map &headers = context->options.GetHeader(); + NETSTACK_LOGD("wangz::redirectionInterceptorRunner_ run headers.size %{public}d", headers.size()); + for (const auto &[key, value] : headers) { + napi_value headerValue = NapiUtils::CreateStringUtf8(env, value); + if (headerValue != nullptr) { + NapiUtils::SetNamedProperty(env, headerObj, key, headerValue); + } + } + NapiUtils::SetNamedProperty(env, reqContext, "head", headerObj); + + std::string body = context->options.GetBody(); + napi_value bodyValue = NapiUtils::CreateStringUtf8(env, body); + if (bodyValue != nullptr) { + NapiUtils::SetNamedProperty(env, reqContext, "body", bodyValue); + } + return reqContext; +} + +napi_value HttpInterceptor::CreateResponseContextRedirectionInterceptor( + napi_env env, RequestContext *context, HttpOverCurl::RedirectionInterceptorInfo *handleInfo) +{ + napi_value resContext = NapiUtils::CreateObject(env); + const std::map &responseHeaders = context->response.GetHeader(); + napi_value responseHeaderObj = NapiUtils::CreateObject(env); + if (!responseHeaders.empty()) { + for (const auto &[key, value] : responseHeaders) { + napi_value val = NapiUtils::CreateStringUtf8(env, value); + NapiUtils::SetNamedProperty(env, responseHeaderObj, key, val); + } + } + NapiUtils::SetNamedProperty(env, resContext, "head", responseHeaderObj); + + std::string responseResult = context->response.GetResult(); + NapiUtils::SetNamedProperty(env, resContext, "result", NapiUtils::CreateStringUtf8(env, responseResult)); + + HttpExec::GetCurlDataFromHandle(handleInfo->message->easy_handle, context, CURLMSG_DONE, CURLE_OK); + uint32_t responseCode = context->response.GetResponseCode(); + NapiUtils::SetNamedProperty(env, resContext, "responseCode", NapiUtils::CreateUint32(env, responseCode)); + + std::string responseCookies = context->response.GetCookies(); + NapiUtils::SetNamedProperty(env, resContext, "cookies", NapiUtils::CreateStringUtf8(env, responseCookies)); + + return resContext; +} + +void HttpInterceptor::HandlePromiseThenRedirectionInterceptor( + napi_env env, RedirectionInterceptorHandle *handle, napi_value promise) +{ + napi_value thenFunc = NapiUtils::GetNamedProperty(env, promise, "then"); + if (thenFunc == nullptr) { + NETSTACK_LOGE("Promise.then is not a function"); + delete handle->handleInfo; + delete handle; + return; + } + + napi_value onResolve = NapiUtils::CreateFunction( + env, "onResolve", + [](napi_env env, napi_callback_info info) -> napi_value { + NETSTACK_LOGD("RequestContext::onResolve: start"); + + size_t argc = 1; + napi_value args[1]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + bool result = NapiUtils::GetBooleanFromValue(env, args[0]); + + void *data; + napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); + auto *handle = static_cast(data); + + if (result) { + ApplyContinueRedirectionInterceptor(env, handle); + } else { + ApplyBlockRedirectionInterceptor(env, handle); + } + delete handle->handleInfo; + delete handle; + return nullptr; + }, + handle); + + napi_value onReject = NapiUtils::CreateFunction( + env, "onReject", + [](napi_env env, napi_callback_info info) -> napi_value { + NETSTACK_LOGE("RequestContext::onReject: interceptor promise rejected"); + void *data; + napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); + auto *handle = static_cast(data); + delete handle->handleInfo; + delete handle; + return nullptr; + }, + handle); + + napi_value argv1[] = { onResolve, onReject }; + constexpr size_t thenCallArgc = 2; + NapiUtils::CallFunction(env, promise, thenFunc, thenCallArgc, argv1); +} + +void HttpInterceptor::HandlePromiseRedirectionInterceptor(napi_env env, RedirectionInterceptorHandle *handle) +{ + auto context = handle->context; + + napi_value interceptorRef = NapiUtils::GetReference(env, context->interceptorRefs_["REDIRECTION"]); + if (interceptorRef == nullptr) { + NETSTACK_LOGE("Interceptor reference is null"); + delete handle->handleInfo; + delete handle; + return; + } + + napi_value interceptorHandle; + napi_status status = napi_get_named_property(env, interceptorRef, "interceptorHandle", &interceptorHandle); + if (status != napi_ok) { + NETSTACK_LOGE("Invalid interceptorHandle"); + delete handle->handleInfo; + delete handle; + return; + } + napi_value argv[] = { handle->reqContext, handle->resContext }; + napi_value promise = NapiUtils::CallFunction(env, interceptorRef, interceptorHandle, 2, argv); + if (promise == nullptr) { + NETSTACK_LOGE("Interceptor did not return a valid promise"); + delete handle->handleInfo; + delete handle; + return; + } + HandlePromiseThenRedirectionInterceptor(env, handle, promise); + NETSTACK_LOGD("RequestContext::SetRedirectionInterceptor: finished setup"); +} + +void HttpInterceptor::SetRedirectionInterceptor() +{ + redirectionInterceptorCallback_ = [](RequestContext *context, std::function handleRedirect, + std::function handleCompletion, + HttpOverCurl::RedirectionInterceptorInfo *handleInfo) { + napi_env env = context->GetEnv(); + NETSTACK_LOGD("RequestContext::SetRedirectionInterceptor: running"); + + napi_value reqContext = CreateRequestContextRedirectionInterceptor(env, context, handleInfo); + if (reqContext == nullptr) { + NETSTACK_LOGE("Failed to create reqContext object"); + delete handleInfo; + return; + } + + napi_value resContext = CreateResponseContextRedirectionInterceptor(env, context, handleInfo); + if (resContext == nullptr) { + NETSTACK_LOGE("Failed to create resContext object"); + delete handleInfo; + return; + } + auto *handle = new RedirectionInterceptorHandle { std::move(handleRedirect), std::move(handleCompletion), + handleInfo, reqContext, resContext }; + handle->context = context; + HandlePromiseRedirectionInterceptor(env, handle); + }; +} + +// FinalResponseInterceptor +void HttpInterceptor::ApplyContinueFinalResponseInterceptor(napi_env env, FinalResponseInterceptorHandle *handle) +{ + auto newUrl = NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, handle->reqContext, "url")); + NETSTACK_LOGD("wangz::finalResponseInterceptor updated url:%{public}s", newUrl.c_str()); + handle->context->options.SetUrl(newUrl); + + napi_value newHeadObj = NapiUtils::GetNamedProperty(env, handle->reqContext, "head"); + if (newHeadObj != nullptr) { + std::vector headerKeys = NapiUtils::GetPropertyNames(env, newHeadObj); + for (const auto &key : headerKeys) { + std::string value = + NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, newHeadObj, key.c_str())); + handle->context->options.SetHeader(key, value); + NETSTACK_LOGD("Updated header: %{public}s = %{public}s", key.c_str(), value.c_str()); + } + } + + napi_value bodyValue = NapiUtils::GetNamedProperty(env, handle->reqContext, "body"); + if (bodyValue != nullptr) { + napi_valuetype bodyType = NapiUtils::GetValueType(env, bodyValue); + if (bodyType == napi_string) { + std::string bodyStr = NapiUtils::GetStringFromValueUtf8(env, bodyValue); + handle->context->options.SetBody(bodyStr.data(), bodyStr.size()); + NETSTACK_LOGD("wangz::updated string body, length=%{public}zu", bodyStr.size()); + } else if (NapiUtils::ValueIsArrayBuffer(env, bodyValue)) { + size_t bufferLength = 0; + void *bufferData = NapiUtils::GetInfoFromArrayBufferValue(env, bodyValue, &bufferLength); + if (bufferData != nullptr && bufferLength > 0) { + handle->context->options.SetBody(bufferData, bufferLength); + NETSTACK_LOGD("wangz::updated array buffer body, length=%{public}zu", bufferLength); + } else { + NETSTACK_LOGE("wangz::invalid array buffer data or length"); + } + } else { + NETSTACK_LOGE("wangz::unsupported body type, type=%{public}d", bodyType); + } + } else { + NETSTACK_LOGD("wangz::body is null, using empty body"); + handle->context->options.SetBody("", 0); + } +} + +void HttpInterceptor::ApplyBlockFinalResponseInterceptor(napi_env env, FinalResponseInterceptorHandle *handle) +{ + if (NapiUtils::HasNamedProperty(env, handle->resContext, "header")) { + napi_value headerValue = NapiUtils::GetNamedProperty(env, handle->resContext, "header"); + std::string headerRawStr = NapiUtils::GetStringFromValueUtf8(env, headerValue); + handle->context->response.SetRawHeader(headerRawStr); + NETSTACK_LOGD("wangz::Set raw header, length=%{public}zu", headerRawStr.size()); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "responseCode")) { + napi_value codeValue = NapiUtils::GetNamedProperty(env, handle->resContext, "responseCode"); + uint32_t responseCode = NapiUtils::GetUint32FromValue(env, codeValue); + handle->context->response.SetResponseCode(responseCode); + NETSTACK_LOGD("wangz::Set response code: %{public}u", responseCode); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "result")) { + napi_value resultValue = NapiUtils::GetNamedProperty(env, handle->resContext, "result"); + std::string resultStr = NapiUtils::GetStringFromValueUtf8(env, resultValue); + handle->context->response.SetResult(resultStr); + NETSTACK_LOGD("wangz::Set response result, length=%{public}zu", resultStr.size()); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "cookies")) { + napi_value cookiesValue = NapiUtils::GetNamedProperty(env, handle->resContext, "cookies"); + std::string cookiesStr = NapiUtils::GetStringFromValueUtf8(env, cookiesValue); + handle->context->response.SetCookies(cookiesStr); + NETSTACK_LOGD("wangz::Set cookies: %{public}s", cookiesStr.c_str()); + } + + handle->context->response.ParseHeaders(); + NETSTACK_LOGD("wangz::Parsed response headers done"); +} + +napi_value HttpInterceptor::CreateRequestContextFinalResponseInterceptor(napi_env env, RequestContext *context) +{ + napi_value reqContext = NapiUtils::CreateObject(env); + std::string url = context->options.GetUrl(); + NapiUtils::SetNamedProperty(env, reqContext, "url", NapiUtils::CreateStringUtf8(env, url)); + napi_value headerObj = NapiUtils::CreateObject(env); + const std::map &headers = context->options.GetHeader(); + NETSTACK_LOGD("wangz::finalResponseInterceptorRunner_ run headers.size %{public}d", headers.size()); + if (!headers.empty()) { + for (const auto &[key, value] : headers) { + napi_value val = NapiUtils::CreateStringUtf8(env, value); + NapiUtils::SetNamedProperty(env, headerObj, key.c_str(), val); + } + } + NapiUtils::SetNamedProperty(env, reqContext, "head", headerObj); + std::string body = context->options.GetBody(); + NapiUtils::SetNamedProperty(env, reqContext, "body", NapiUtils::CreateStringUtf8(env, body)); + return reqContext; +} + +napi_value HttpInterceptor::CreateResponseContextFinalResponseInterceptor(napi_env env, RequestContext *context) +{ + napi_value resContext = HttpExec::RequestCallback(context); + return resContext; +} + +void HttpInterceptor::HandlePromiseThenFinalResponseInterceptor( + napi_env env, FinalResponseInterceptorHandle *handle, napi_value promise) +{ + napi_value thenFunc = NapiUtils::GetNamedProperty(env, promise, "then"); + if (thenFunc == nullptr) { + NETSTACK_LOGE("Promise.then is not a function"); + delete handle; + return; + } + + napi_value onResolve = NapiUtils::CreateFunction( + env, "onResolve", + [](napi_env env, napi_callback_info info) -> napi_value { + NETSTACK_LOGD("RequestContext::onResolve: start"); + + size_t argc = 1; + napi_value args[1]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + bool result = NapiUtils::GetBooleanFromValue(env, args[0]); + + void *data; + napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); + auto *handle = static_cast(data); + + if (result) { + ApplyContinueFinalResponseInterceptor(env, handle); + } else { + ApplyBlockFinalResponseInterceptor(env, handle); + } + handle->after(); + delete handle; + return nullptr; + }, + handle); + + napi_value onReject = NapiUtils::CreateFunction( + env, "onReject", + [](napi_env env, napi_callback_info info) -> napi_value { + NETSTACK_LOGE("RequestContext::onReject: interceptor promise rejected"); + void *data; + napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); + auto *handle = static_cast(data); + delete handle; + return nullptr; + }, + handle); + + napi_value argv1[] = { onResolve, onReject }; + constexpr size_t thenCallArgc = 2; + NapiUtils::CallFunction(env, promise, thenFunc, thenCallArgc, argv1); +} + +void HttpInterceptor::HandlePromiseFinalResponseInterceptor(napi_env env, FinalResponseInterceptorHandle *handle) +{ + auto context = handle->context; + + napi_value interceptorRef = NapiUtils::GetReference(env, context->interceptorRefs_["FINAL_RESPONSE"]); + if (interceptorRef == nullptr) { + NETSTACK_LOGE("Interceptor reference is null"); + delete handle; + return; + } + + napi_value interceptorHandle; + napi_status status = napi_get_named_property(env, interceptorRef, "interceptorHandle", &interceptorHandle); + if (status != napi_ok) { + NETSTACK_LOGE("Invalid interceptorHandle"); + delete handle; + return; + } + napi_value argv[] = { handle->reqContext, handle->resContext }; + napi_value promise = NapiUtils::CallFunction(env, interceptorRef, interceptorHandle, 2, argv); + if (promise == nullptr) { + NETSTACK_LOGE("Interceptor did not return a valid promise"); + delete handle; + return; + } + + HandlePromiseThenFinalResponseInterceptor(env, handle, promise); + NETSTACK_LOGD("RequestContext::SetFinalResponseInterceptor: finished setup"); +} + +void HttpInterceptor::SetFinalResponseInterceptor() +{ + finalResponseInterceptorCallback_ = [](RequestContext *context, std::function after) { + napi_env env = context->GetEnv(); + NETSTACK_LOGD("RequestContext::SetFinalResponseInterceptor: running"); + + napi_value reqContext = CreateRequestContextFinalResponseInterceptor(env, context); + if (reqContext == nullptr) { + NETSTACK_LOGE("Failed to create reqContext object"); + return; + } + + napi_value resContext = CreateResponseContextFinalResponseInterceptor(env, context); + if (resContext == nullptr) { + NETSTACK_LOGE("Failed to create resContext object"); + return; + } + + auto *handle = new FinalResponseInterceptorHandle { std::move(after), context, reqContext, resContext }; + + HandlePromiseFinalResponseInterceptor(env, handle); + }; +} + +// CacheCheckedInterceptor +void HttpInterceptor::ApplyContinueCacheCheckedInterceptor(napi_env env, CacheCheckedInterceptorHandle *handle) +{ + auto newUrl = NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, handle->reqContext, "url")); + NETSTACK_LOGD("wangz::cacheCheckedInterceptor updated url:%{public}s", newUrl.c_str()); + handle->context->options.SetUrl(newUrl); + + napi_value newHeadObj = NapiUtils::GetNamedProperty(env, handle->reqContext, "head"); + if (newHeadObj != nullptr) { + std::vector headerKeys = NapiUtils::GetPropertyNames(env, newHeadObj); + for (const auto &key : headerKeys) { + std::string value = + NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, newHeadObj, key.c_str())); + handle->context->options.SetHeader(key, value); + NETSTACK_LOGD("Updated header: %{public}s = %{public}s", key.c_str(), value.c_str()); + } + } + + napi_value bodyValue = NapiUtils::GetNamedProperty(env, handle->reqContext, "body"); + if (bodyValue != nullptr) { + napi_valuetype bodyType = NapiUtils::GetValueType(env, bodyValue); + if (bodyType == napi_string) { + std::string bodyStr = NapiUtils::GetStringFromValueUtf8(env, bodyValue); + handle->context->options.SetBody(bodyStr.data(), bodyStr.size()); + NETSTACK_LOGD("wangz::updated string body, length=%{public}zu", bodyStr.size()); + } else if (NapiUtils::ValueIsArrayBuffer(env, bodyValue)) { + size_t bufferLength = 0; + void *bufferData = NapiUtils::GetInfoFromArrayBufferValue(env, bodyValue, &bufferLength); + if (bufferData != nullptr && bufferLength > 0) { + handle->context->options.SetBody(bufferData, bufferLength); + NETSTACK_LOGD("wangz::updated array buffer body, length=%{public}zu", bufferLength); + } else { + NETSTACK_LOGE("wangz::invalid array buffer data or length"); + } + } else { + NETSTACK_LOGE("wangz::unsupported body type, type=%{public}d", bodyType); + } + } else { + NETSTACK_LOGD("wangz::body is null, using empty body"); + handle->context->options.SetBody("", 0); + } + handle->after(); +} + +void HttpInterceptor::ApplyBlockCacheCheckedInterceptor(napi_env env, CacheCheckedInterceptorHandle *handle) +{ + if (NapiUtils::HasNamedProperty(env, handle->resContext, "header")) { + napi_value headerValue = NapiUtils::GetNamedProperty(env, handle->resContext, "header"); + std::string headerRawStr = NapiUtils::GetStringFromValueUtf8(env, headerValue); + handle->context->response.SetRawHeader(headerRawStr); + NETSTACK_LOGD("wangz::Set raw header, length=%{public}zu", headerRawStr.size()); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "responseCode")) { + napi_value codeValue = NapiUtils::GetNamedProperty(env, handle->resContext, "responseCode"); + uint32_t responseCode = NapiUtils::GetUint32FromValue(env, codeValue); + handle->context->response.SetResponseCode(responseCode); + NETSTACK_LOGD("wangz::Set response code: %{public}u", responseCode); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "result")) { + napi_value resultValue = NapiUtils::GetNamedProperty(env, handle->resContext, "result"); + std::string resultStr = NapiUtils::GetStringFromValueUtf8(env, resultValue); + handle->context->response.SetResult(resultStr); + NETSTACK_LOGD("wangz::Set response result, length=%{public}zu", resultStr.size()); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "cookies")) { + napi_value cookiesValue = NapiUtils::GetNamedProperty(env, handle->resContext, "cookies"); + std::string cookiesStr = NapiUtils::GetStringFromValueUtf8(env, cookiesValue); + handle->context->response.SetCookies(cookiesStr); + NETSTACK_LOGD("wangz::Set cookies: %{public}s", cookiesStr.c_str()); + } + + handle->context->response.ParseHeaders(); + NETSTACK_LOGD("wangz::Parsed response headers"); + handle->block(); +} + +napi_value HttpInterceptor::CreateRequestContextCacheCheckedInterceptor(napi_env env, RequestContext *context) +{ + napi_value reqContext = NapiUtils::CreateObject(context->GetEnv()); + std::string url = context->options.GetUrl(); + NapiUtils::SetNamedProperty( + context->GetEnv(), reqContext, "url", NapiUtils::CreateStringUtf8(context->GetEnv(), url)); + + napi_value headerObj = NapiUtils::CreateObject(context->GetEnv()); + const std::map &headers = context->options.GetHeader(); + NETSTACK_LOGD("wangz::cacheCheckedInterceptorRunner_ run headers.size %{public}d", headers.size()); + if (!headers.empty()) { + std::for_each( + headers.begin(), headers.end(), [&context, &headerObj](const std::pair &p) { + napi_value value = NapiUtils::CreateStringUtf8(context->GetEnv(), p.second); + NapiUtils::SetNamedProperty(context->GetEnv(), headerObj, p.first, value); + }); + } + NapiUtils::SetNamedProperty(context->GetEnv(), reqContext, "head", headerObj); + std::string body = context->options.GetBody(); + NapiUtils::SetNamedProperty( + context->GetEnv(), reqContext, "body", NapiUtils::CreateStringUtf8(context->GetEnv(), body)); + return reqContext; +} + +napi_value HttpInterceptor::CreateResponseContextCacheCheckedInterceptor(napi_env env, RequestContext *context) +{ + napi_value resContext = HttpExec::RequestCallback(context); + return resContext; +} + +void HttpInterceptor::HandlePromiseThenCacheCheckedInterceptor( + napi_env env, CacheCheckedInterceptorHandle *handle, napi_value promise) +{ + napi_value thenFunc = NapiUtils::GetNamedProperty(env, promise, "then"); + if (thenFunc == nullptr) { + NETSTACK_LOGE("Promise.then is not a function"); + delete handle; + return; + } + + napi_value onResolve = NapiUtils::CreateFunction( + env, "onResolve", + [](napi_env env, napi_callback_info info) -> napi_value { + NETSTACK_LOGD("RequestContext::onResolve: start"); + + size_t argc = 1; + napi_value args[1]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + bool result = NapiUtils::GetBooleanFromValue(env, args[0]); + + void *data; + napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); + auto *handle = static_cast(data); + + if (result) { + ApplyContinueCacheCheckedInterceptor(env, handle); + } else { + ApplyBlockCacheCheckedInterceptor(env, handle); + } + delete handle; + return nullptr; + }, + handle); + + napi_value onReject = NapiUtils::CreateFunction( + env, "onReject", + [](napi_env env, napi_callback_info info) -> napi_value { + NETSTACK_LOGE("RequestContext::onReject: interceptor promise rejected"); + void *data; + napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); + auto *handle = static_cast(data); + delete handle; + return nullptr; + }, + handle); + + napi_value argv1[] = { onResolve, onReject }; + constexpr size_t thenCallArgc = 2; + NapiUtils::CallFunction(env, promise, thenFunc, thenCallArgc, argv1); +} + +void HttpInterceptor::HandlePromiseCacheCheckedInterceptor(napi_env env, CacheCheckedInterceptorHandle *handle) +{ + auto context = handle->context; + + napi_value interceptorRef = NapiUtils::GetReference(env, context->interceptorRefs_["FINAL_RESPONSE"]); + if (interceptorRef == nullptr) { + NETSTACK_LOGE("Interceptor reference is null"); + delete handle; + return; + } + + napi_value interceptorHandle; + napi_status status = napi_get_named_property(env, interceptorRef, "interceptorHandle", &interceptorHandle); + if (status != napi_ok) { + NETSTACK_LOGE("Invalid interceptorHandle"); + delete handle; + return; + } + napi_value argv[] = { handle->reqContext, handle->resContext }; + napi_value promise = NapiUtils::CallFunction(env, interceptorRef, interceptorHandle, 2, argv); + if (promise == nullptr) { + NETSTACK_LOGE("Interceptor did not return a valid promise"); + delete handle; + return; + } + HandlePromiseThenCacheCheckedInterceptor(env, handle, promise); + NETSTACK_LOGD("RequestContext::SetInitialRequestInterceptor: finished setup"); +} + +void HttpInterceptor::SetCacheCheckedInterceptor() +{ + cacheCheckedInterceptorCallback_ = [](RequestContext *context, std::function after, + std::function block) { + napi_env env = context->GetEnv(); + NETSTACK_LOGD("RequestContext::SetCacheCheckedInterceptor: running"); + + napi_value reqContext = CreateRequestContextCacheCheckedInterceptor(env, context); + if (reqContext == nullptr) { + NETSTACK_LOGE("Failed to create reqContext object"); + return; + } + + napi_value resContext = CreateResponseContextCacheCheckedInterceptor(env, context); + if (resContext == nullptr) { + NETSTACK_LOGE("Failed to create resContext object"); + return; + } + + auto *handle = + new CacheCheckedInterceptorHandle { std::move(after), context, std::move(block), reqContext, resContext }; + + HandlePromiseCacheCheckedInterceptor(env, handle); + }; +} + +// ConnectNetworkInterceptor +void HttpInterceptor::ApplyContinueConnectNetworkInterceptor(napi_env env, InitialRequestInterceptorHandle *handle) +{ + CURL *easyHander = handle->context->GetCurlHandle(); + + auto newUrl = NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, handle->reqContext, "url")); + NETSTACK_LOGD("wangz::connectNetworkInterceptor updated url:%{public}s", newUrl.c_str()); + curl_easy_setopt(easyHander, CURLOPT_URL, newUrl.c_str()); + + napi_value newHeadObj = NapiUtils::GetNamedProperty(env, handle->reqContext, "head"); + if (newHeadObj != nullptr) { + std::vector headerKeys = NapiUtils::GetPropertyNames(env, newHeadObj); + curl_slist *headers = nullptr; + for (const auto &key : headerKeys) { + std::string value = + NapiUtils::GetStringFromValueUtf8(env, NapiUtils::GetNamedProperty(env, newHeadObj, key.c_str())); + std::string headerLine = key + ": " + value; + headers = curl_slist_append(headers, headerLine.c_str()); + NETSTACK_LOGD("Updated header: %{public}s = %{public}s", key.c_str(), value.c_str()); + } + curl_easy_setopt(easyHander, CURLOPT_HTTPHEADER, headers); + } + + napi_value bodyValue = NapiUtils::GetNamedProperty(env, handle->reqContext, "body"); + if (bodyValue != nullptr) { + napi_valuetype bodyType = NapiUtils::GetValueType(env, bodyValue); + if (bodyType == napi_string) { + std::string bodyStr = NapiUtils::GetStringFromValueUtf8(env, bodyValue); + curl_easy_setopt(easyHander, CURLOPT_POSTFIELDS, bodyStr.c_str()); + curl_easy_setopt(easyHander, CURLOPT_POSTFIELDSIZE, bodyStr.size()); + NETSTACK_LOGD("wangz::updated string body, length=%{public}zu", bodyStr.size()); + } else if (NapiUtils::ValueIsArrayBuffer(env, bodyValue)) { + size_t bufferLength = 0; + void *bufferData = NapiUtils::GetInfoFromArrayBufferValue(env, bodyValue, &bufferLength); + if (bufferData != nullptr && bufferLength > 0) { + curl_easy_setopt(easyHander, CURLOPT_POSTFIELDS, bufferData); + curl_easy_setopt(easyHander, CURLOPT_POSTFIELDSIZE, bufferLength); + NETSTACK_LOGD("wangz::updated array buffer body, length=%{public}zu", bufferLength); + } else { + NETSTACK_LOGE("wangz::invalid array buffer data or length"); + } + } else { + NETSTACK_LOGE("wangz::unsupported body type, type=%{public}d", bodyType); + } + } else { + NETSTACK_LOGD("wangz::body is null, using empty body"); + curl_easy_setopt(easyHander, CURLOPT_POSTFIELDS, ""); + curl_easy_setopt(easyHander, CURLOPT_POSTFIELDSIZE, 0); + } + handle->after(); +} + +void HttpInterceptor::ApplyBlockConnectNetworkInterceptor(napi_env env, InitialRequestInterceptorHandle *handle) +{ + if (NapiUtils::HasNamedProperty(env, handle->resContext, "header")) { + napi_value headerValue = NapiUtils::GetNamedProperty(env, handle->resContext, "header"); + std::string headerRawStr = NapiUtils::GetStringFromValueUtf8(env, headerValue); + handle->context->response.SetRawHeader(headerRawStr); + NETSTACK_LOGD("wangz::Set raw header, length=%{public}zu", headerRawStr.size()); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "responseCode")) { + napi_value codeValue = NapiUtils::GetNamedProperty(env, handle->resContext, "responseCode"); + uint32_t responseCode = NapiUtils::GetUint32FromValue(env, codeValue); + handle->context->response.SetResponseCode(responseCode); + NETSTACK_LOGD("wangz::Set response code: %{public}u", responseCode); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "result")) { + napi_value resultValue = NapiUtils::GetNamedProperty(env, handle->resContext, "result"); + std::string resultStr = NapiUtils::GetStringFromValueUtf8(env, resultValue); + handle->context->response.SetResult(resultStr); + NETSTACK_LOGD("wangz::Set response result, length=%{public}zu", resultStr.size()); + } + + if (NapiUtils::HasNamedProperty(env, handle->resContext, "cookies")) { + napi_value cookiesValue = NapiUtils::GetNamedProperty(env, handle->resContext, "cookies"); + std::string cookiesStr = NapiUtils::GetStringFromValueUtf8(env, cookiesValue); + handle->context->response.SetCookies(cookiesStr); + NETSTACK_LOGD("wangz::Set cookies: %{public}s", cookiesStr.c_str()); + } + + handle->context->response.ParseHeaders(); + NETSTACK_LOGD("wangz::Parsed response headers"); + + handle->block(); +} + +napi_value HttpInterceptor::CreateRequestContextConnectNetworkInterceptor(napi_env env, RequestContext *context) +{ + napi_value reqContext = NapiUtils::CreateObject(context->GetEnv()); + std::string url = context->options.GetUrl(); + NapiUtils::SetNamedProperty( + context->GetEnv(), reqContext, "url", NapiUtils::CreateStringUtf8(context->GetEnv(), url)); + + napi_value headerObj = NapiUtils::CreateObject(context->GetEnv()); + const std::map &headers = context->options.GetHeader(); + NETSTACK_LOGD("wangz::connectNetworkInterceptorRunner_ run headers.size %{public}d", headers.size()); + if (!headers.empty()) { + std::for_each( + headers.begin(), headers.end(), [&context, &headerObj](const std::pair &p) { + napi_value value = NapiUtils::CreateStringUtf8(context->GetEnv(), p.second); + NapiUtils::SetNamedProperty(context->GetEnv(), headerObj, p.first, value); + }); + } + NapiUtils::SetNamedProperty(context->GetEnv(), reqContext, "head", headerObj); + std::string body = context->options.GetBody(); + NapiUtils::SetNamedProperty( + context->GetEnv(), reqContext, "body", NapiUtils::CreateStringUtf8(context->GetEnv(), body)); + return reqContext; +} + +napi_value HttpInterceptor::CreateResponseContextConnectNetworkInterceptor(napi_env env, RequestContext *context) +{ + napi_value resContext = HttpExec::RequestCallback(context); + return resContext; +} + +void HttpInterceptor::HandlePromiseThenConnectNetworkInterceptor( + napi_env env, InitialRequestInterceptorHandle *handle, napi_value promise) +{ + napi_value thenFunc = NapiUtils::GetNamedProperty(env, promise, "then"); + if (thenFunc == nullptr) { + NETSTACK_LOGE("Promise.then is not a function"); + delete handle; + return; + } + + napi_value onResolve = NapiUtils::CreateFunction( + env, "onResolve", + [](napi_env env, napi_callback_info info) -> napi_value { + NETSTACK_LOGD("RequestContext::onResolve: start"); + + size_t argc = 1; + napi_value args[1]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + bool result = NapiUtils::GetBooleanFromValue(env, args[0]); + + void *data; + napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); + auto *handle = static_cast(data); + + if (result) { + ApplyContinueConnectNetworkInterceptor(env, handle); + } else { + ApplyBlockConnectNetworkInterceptor(env, handle); + } + delete handle; + return nullptr; + }, + handle); + + napi_value onReject = NapiUtils::CreateFunction( + env, "onReject", + [](napi_env env, napi_callback_info info) -> napi_value { + NETSTACK_LOGE("RequestContext::onReject: interceptor promise rejected"); + void *data; + napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); + auto *handle = static_cast(data); + delete handle; + return nullptr; + }, + handle); + + napi_value argv1[] = { onResolve, onReject }; + constexpr size_t thenCallArgc = 2; + NapiUtils::CallFunction(env, promise, thenFunc, thenCallArgc, argv1); +} + +void HttpInterceptor::HandlePromiseConnectNetworkInterceptor(napi_env env, InitialRequestInterceptorHandle *handle) +{ + auto context = handle->context; + + napi_value interceptorRef = NapiUtils::GetReference(env, context->interceptorRefs_["CONNECT_NETWORK"]); + if (interceptorRef == nullptr) { + NETSTACK_LOGE("Interceptor reference is null"); + delete handle; + return; + } + + napi_value interceptorHandle; + napi_status status = napi_get_named_property(env, interceptorRef, "interceptorHandle", &interceptorHandle); + if (status != napi_ok) { + NETSTACK_LOGE("Invalid interceptorHandle"); + delete handle; + return; + } + napi_value argv[] = { handle->reqContext, handle->resContext }; + napi_value promise = NapiUtils::CallFunction(env, interceptorRef, interceptorHandle, 2, argv); + if (promise == nullptr) { + NETSTACK_LOGE("Interceptor did not return a valid promise"); + delete handle; + return; + } + HandlePromiseThenConnectNetworkInterceptor(env, handle, promise); + NETSTACK_LOGD("RequestContext::SetConnectNetworkInterceptor: finished setup"); +} + +void HttpInterceptor::SetConnectNetworkInterceptor() +{ + connectNetworkInterceptorCallback_ = [](RequestContext *context, std::function after, + std::function block) { + napi_env env = context->GetEnv(); + NETSTACK_LOGD("RequestContext::SetConnectNetworkInterceptor: running"); + + napi_value reqContext = CreateRequestContextConnectNetworkInterceptor(env, context); + if (reqContext == nullptr) { + NETSTACK_LOGE("Failed to create reqContext object"); + return; + } + + napi_value resContext = CreateResponseContextConnectNetworkInterceptor(env, context); + if (resContext == nullptr) { + NETSTACK_LOGE("Failed to create resContext object"); + return; + } + + auto *handle = + new InitialRequestInterceptorHandle { std::move(after), context, std::move(block), reqContext, resContext }; + + HandlePromiseConnectNetworkInterceptor(env, handle); + }; +} + +} // namespace OHOS::NetStack::Http diff --git a/frameworks/js/napi/http/http_module/include/http_module.h b/frameworks/js/napi/http/http_module/include/http_module.h index 9ca1ae3c2fbc28263ccd6f239be9af5f5c867109..05e1f4b884e8072c5a9d78823485b481f55a08e2 100644 --- a/frameworks/js/napi/http/http_module/include/http_module.h +++ b/frameworks/js/napi/http/http_module/include/http_module.h @@ -16,6 +16,7 @@ #ifndef COMMUNICATIONNETSTACK_HTTP_MODULE_H #define COMMUNICATIONNETSTACK_HTTP_MODULE_H +#include #include "napi/native_api.h" namespace OHOS::NetStack::Http { @@ -47,6 +48,53 @@ public: static napi_value Off(napi_env env, napi_callback_info info); }; + class HttpInterceptor { + public: + napi_env napi_env_ = nullptr; + std::string interceptorType_; + napi_ref interceptorRef_ = nullptr; + + HttpInterceptor(napi_env env, const std::string &type, napi_value interceptorInstance) + : napi_env_(env), interceptorType_(type) + { + napi_create_reference(env, interceptorInstance, 1, &interceptorRef_); + } + + ~HttpInterceptor() + { + if (interceptorRef_ != nullptr) { + napi_delete_reference(napi_env_, interceptorRef_); + } + } + napi_value GetInstance(napi_env env) const + { + napi_value instance; + napi_get_reference_value(env, interceptorRef_, &instance); + return instance; + } + }; + + class HttpInterceptorChain { + public: + std::vector chain_; + napi_env env_ = nullptr; + HttpInterceptorChain(napi_env env) : env_(env) { } + ~HttpInterceptorChain() + { + for (auto *interceptor : chain_) { + delete interceptor; + } + } + + static constexpr const char *FUNCTION_GETCHAIN = "getChain"; + static constexpr const char *FUNCTION_ADDCHAIN = "addChain"; + static constexpr const char *FUNCTION_APPLY = "apply"; + + static napi_value GetChain(napi_env env, napi_callback_info info); + static napi_value AddChain(napi_env env, napi_callback_info info); + static napi_value Apply(napi_env env, napi_callback_info info); + }; + static constexpr const char *FUNCTION_CREATE_HTTP = "createHttp"; static constexpr const char *FUNCTION_CREATE_HTTP_RESPONSE_CACHE = "createHttpResponseCache"; static constexpr const char *INTERFACE_REQUEST_METHOD = "RequestMethod"; @@ -60,6 +108,7 @@ public: static constexpr const char *INTERFACE_ADDRESS_FAMILY = "AddressFamily"; static constexpr const char *INTERFACE_SSL_TYPE = "SslType"; static constexpr const char *INTERFACE_CLIENT_ENC_CERT = "CertType"; + static constexpr const char *INTERFACE_HTTP_INTERCEPTOR_CHAIN = "HttpInterceptorChain"; static napi_value InitHttpModule(napi_env env, napi_value exports); @@ -72,6 +121,8 @@ private: static void DefineHttpResponseCacheClass(napi_env env, napi_value exports); + static void DefineHttpInterceptorChainClass(napi_env env, napi_value exports); + static void InitHttpProperties(napi_env env, napi_value exports); static void InitRequestMethod(napi_env env, napi_value exports); diff --git a/frameworks/js/napi/http/http_module/src/http_module.cpp b/frameworks/js/napi/http/http_module/src/http_module.cpp index 24bebdbda340963b24b09420f4752144383b91c3..5ef2c4333b992c81a03183388699cf292e7b9e71 100644 --- a/frameworks/js/napi/http/http_module/src/http_module.cpp +++ b/frameworks/js/napi/http/http_module/src/http_module.cpp @@ -56,6 +56,7 @@ napi_value HttpModuleExports::InitHttpModule(napi_env env, napi_value exports) { DefineHttpRequestClass(env, exports); DefineHttpResponseCacheClass(env, exports); + DefineHttpInterceptorChainClass(env, exports); InitHttpProperties(env, exports); g_moduleId = NapiUtils::CreateUvHandlerQueue(env); NapiUtils::SetEnvValid(env); @@ -123,6 +124,28 @@ void HttpModuleExports::DefineHttpResponseCacheClass(napi_env env, napi_value ex ModuleTemplate::DefineClass(env, exports, properties, INTERFACE_HTTP_RESPONSE_CACHE); } +void HttpModuleExports::DefineHttpInterceptorChainClass(napi_env env, napi_value exports) +{ + std::initializer_list properties = { + DECLARE_NAPI_FUNCTION(HttpInterceptorChain::FUNCTION_GETCHAIN, HttpInterceptorChain::GetChain), + DECLARE_NAPI_FUNCTION(HttpInterceptorChain::FUNCTION_ADDCHAIN, HttpInterceptorChain::AddChain), + DECLARE_NAPI_FUNCTION(HttpInterceptorChain::FUNCTION_APPLY, HttpInterceptorChain::Apply), + }; + ModuleTemplate::DefineClassNew(env, exports, properties, INTERFACE_HTTP_INTERCEPTOR_CHAIN, + [](napi_env env, napi_callback_info info) -> napi_value { + HttpInterceptorChain *chain = new HttpInterceptorChain(env); + napi_value thisVal; + napi_get_cb_info(env, info, nullptr, nullptr, &thisVal, nullptr); + napi_wrap( + env, thisVal, reinterpret_cast(chain), + [](napi_env env, void *data, void *hint) { + delete static_cast(data); + }, + nullptr, nullptr); + return thisVal; + }); +} + void HttpModuleExports::InitHttpProperties(napi_env env, napi_value exports) { std::initializer_list properties = { @@ -372,6 +395,102 @@ napi_value HttpModuleExports::HttpResponseCache::Delete(napi_env env, napi_callb env, info, DELETE_ASYNC_WORK_NAME, nullptr, HttpAsyncWork::ExecDelete, HttpAsyncWork::DeleteCallback); } +napi_value HttpModuleExports::HttpInterceptorChain::GetChain(napi_env env, napi_callback_info info) +{ + napi_value this_arg; + napi_status status = napi_get_cb_info(env, info, nullptr, nullptr, &this_arg, nullptr); + if (status != napi_ok) { + return nullptr; + } + HttpInterceptorChain *chain = nullptr; + status = napi_unwrap(env, this_arg, reinterpret_cast(&chain)); + if (status != napi_ok || chain == nullptr) { + return NapiUtils::GetBoolean(env, false); + } + + if (chain->chain_.empty()) { + return NapiUtils::CreateArray(env, 0); + } + + napi_value result = NapiUtils::CreateArray(env, chain->chain_.size()); + for (size_t i = 0; i < chain->chain_.size(); ++i) { + HttpInterceptor *interceptor = chain->chain_[i]; + napi_value interceptorInstance = interceptor->GetInstance(env); + NapiUtils::SetArrayElement(env, result, i, interceptorInstance); + } + return result; +} + +napi_value HttpModuleExports::HttpInterceptorChain::AddChain(napi_env env, napi_callback_info info) +{ + napi_value rs = NapiUtils::GetBoolean(env, false); + size_t argc = 1; + napi_value args[1]; + napi_value this_arg; + napi_status status = napi_get_cb_info(env, info, &argc, args, &this_arg, nullptr); + if (status != napi_ok || argc != 1) { + return rs; + } + + HttpInterceptorChain *chain = nullptr; + status = napi_unwrap(env, this_arg, reinterpret_cast(&chain)); + if (status != napi_ok || chain == nullptr) { + return rs; + } + + if (!NapiUtils::IsArray(env, args[0])) { + return rs; + } + + uint32_t length = NapiUtils::GetArrayLength(env, args[0]); + for (uint32_t i = 0; i < length; ++i) { + napi_value interceptor = NapiUtils::GetArrayElement(env, args[0], i); + std::string type = NapiUtils::GetStringPropertyUtf8(env, interceptor, "interceptorType"); + if (type.empty()) { + return rs; + } + for (const auto &existing : chain->chain_) { + if (existing->interceptorType_ == type) { + return rs; + } + } + HttpInterceptor *newInterceptor = new HttpInterceptor(env, type, interceptor); + chain->chain_.push_back(newInterceptor); + } + return NapiUtils::GetBoolean(env, true); +} + +napi_value HttpModuleExports::HttpInterceptorChain::Apply(napi_env env, napi_callback_info info) +{ + napi_value rs = NapiUtils::GetBoolean(env, false); + napi_value this_arg; + napi_status status = napi_get_cb_info(env, info, nullptr, nullptr, &this_arg, nullptr); + if (status != napi_ok) { + return rs; + } + HttpInterceptorChain *chain = nullptr; + status = napi_unwrap(env, this_arg, reinterpret_cast(&chain)); + if (status != napi_ok || chain == nullptr) { + return rs; + } + + if (chain->chain_.empty()) { + return NapiUtils::GetBoolean(env, true); + } + + std::unordered_map interceptorRefs; + for (size_t i = 0; i < chain->chain_.size(); ++i) { + HttpInterceptor *interceptor = chain->chain_[i]; + napi_value interceptorInstance = interceptor->GetInstance(env); + napi_ref ref; + if (napi_create_reference(env, interceptorInstance, 1, &ref) == napi_ok) { + interceptorRefs.insert({ interceptor->interceptorType_, ref }); + } + } + ModuleTemplate::InterceptorChainApply(env, info, interceptorRefs); + return NapiUtils::GetBoolean(env, true); +} + static napi_module g_httpModule = { .nm_version = 1, .nm_flags = 0, diff --git a/utils/http_over_curl/include/epoll_multi_driver.h b/utils/http_over_curl/include/epoll_multi_driver.h index 4dabc119dc639cc71089c783f5f757905fc9659b..ae48b60387269c2927576c51be7b67369044972b 100644 --- a/utils/http_over_curl/include/epoll_multi_driver.h +++ b/utils/http_over_curl/include/epoll_multi_driver.h @@ -21,6 +21,11 @@ #include "curl/curl.h" +#if ENABLE_HTTP_INTERCEPT +#include "http_exec.h" +#endif + + #include "epoller.h" #include "thread_safe_storage.h" #include "timeout_timer.h" @@ -32,6 +37,9 @@ namespace OHOS::NetStack::HttpOverCurl { struct RequestInfo; +constexpr long HTTP_STATUS_REDIRECT_START = 300; +constexpr long HTTP_STATUS_CLIENT_ERROR_START = 400; + class EpollMultiDriver { public: EpollMultiDriver() = delete; @@ -59,10 +67,16 @@ private: void EpollSocketCallback(int fd); void CheckMultiInfo(); + void HandleCurlDoneMessage(CURLMsg *message); void Initialize(); void IncomingRequestCallback(); +#if ENABLE_HTTP_INTERCEPT + void HandleRedirect(CURL *easyHandle, std::shared_ptr location, RequestInfo *requestInfo); +#endif + void HandleCompletion(CURLMsg *message, RequestInfo *requestInfo); + std::shared_ptr> incomingQueue_; HttpOverCurl::Epoller poller_; diff --git a/utils/http_over_curl/include/request_info.h b/utils/http_over_curl/include/request_info.h index 1ee9eb164094ad6bffb908af59f614b2d53f60a9..d8cc422f543770d2cead9cd989d7beed6a2baea7 100644 --- a/utils/http_over_curl/include/request_info.h +++ b/utils/http_over_curl/include/request_info.h @@ -36,6 +36,11 @@ struct RequestInfo { void *opaqueData; }; +struct RedirectionInterceptorInfo { + CURLMsg *message; + std::shared_ptr location; +}; + } // namespace OHOS::NetStack::HttpOverCurl #endif // COMMUNICATIONNETSTACK_REQUEST_INFO_H diff --git a/utils/http_over_curl/src/epoll_multi_driver.cpp b/utils/http_over_curl/src/epoll_multi_driver.cpp index 628f2814c63ba697778d4add86236066a82abbdc..f6a5b1c9d48f5f78048624e7cf9a00c49625e4b0 100644 --- a/utils/http_over_curl/src/epoll_multi_driver.cpp +++ b/utils/http_over_curl/src/epoll_multi_driver.cpp @@ -116,20 +116,49 @@ void EpollMultiDriver::IncomingRequestCallback() auto requestsToAdd = incomingQueue_->Flush(); for (auto &request : requestsToAdd) { #ifdef HTTP_HANDOVER_FEATURE - if (netHandoverHandler_ && - netHandoverHandler_->TryFlowControl(request, HandoverRequestType::INCOMING)) { + if (netHandoverHandler_ && netHandoverHandler_->TryFlowControl(request, HandoverRequestType::INCOMING)) { continue; } #endif ongoingRequests_[request->easyHandle] = request; - auto ret = curl_multi_add_handle(multi_, request->easyHandle); - if (ret != CURLM_OK) { - NETSTACK_LOGE("curl_multi_add_handle err, ret = %{public}d %{public}s", ret, curl_multi_strerror(ret)); + + std::function addHandleCallback = std::bind( + [](CURLM *multi, RequestInfo *req) -> bool { + auto ret = curl_multi_add_handle(multi, req->easyHandle); + if (ret != CURLM_OK) { + NETSTACK_LOGE( + "curl_multi_add_handle err, ret = %{public}d %{public}s", ret, curl_multi_strerror(ret)); + return false; + } + + if (req->callbacks.startedCallback) { + req->callbacks.startedCallback(req->easyHandle, req->opaqueData); + } + return true; + }, + multi_, request); + +#if ENABLE_HTTP_INTERCEPT + auto context = reinterpret_cast(request->opaqueData); + std::function blockCallback = std::bind( + [](OHOS::NetStack::Http::RequestContext *context) { + Http::HttpExec::ProcessResponseHeadersAndEmitEvents(context); + Http::HttpExec::ProcessResponseBodyAndEmitEvents(context); + Http::HttpExec::EnqueueCallback(context); + }, + context); + auto interceptor = context->GetInterceptor(); + if (interceptor != nullptr && interceptor->IsConnectNetworkInterceptor()) { + auto interceptorCallback = interceptor->GetConnectNetworkInterceptorCallback(); + auto interceptorWork = std::bind(interceptorCallback, context, addHandleCallback, blockCallback); + NapiUtils::CreateUvQueueWorkByModuleId(context->GetEnv(), interceptorWork, context->GetModuleId()); + NETSTACK_LOGD("HttpExec: connectNetworkInterceptorCallback_ executed successfully"); continue; } +#endif - if (request->callbacks.startedCallback) { - request->callbacks.startedCallback(request->easyHandle, request->opaqueData); + if (!addHandleCallback()) { + continue; } } } @@ -162,6 +191,27 @@ void EpollMultiDriver::EpollTimerCallback() CheckMultiInfo(); } +#if ENABLE_HTTP_INTERCEPT +void EpollMultiDriver::HandleRedirect(CURL *easyHandle, std::shared_ptr location, RequestInfo *requestInfo) +{ + if (easyHandle) { + (void)curl_easy_cleanup(easyHandle); + } + auto context = reinterpret_cast(requestInfo->opaqueData); + context->options.SetUrl(location->c_str()); + delete requestInfo; + Http::HttpExec::RequestWithoutCache(context); +} +#endif + +void EpollMultiDriver::HandleCompletion(CURLMsg *message, RequestInfo *requestInfo) +{ + if (requestInfo != nullptr && requestInfo->callbacks.doneCallback) { + requestInfo->callbacks.doneCallback(message, requestInfo->opaqueData); + } + delete requestInfo; +} + __attribute__((no_sanitize("cfi"))) void EpollMultiDriver::CheckMultiInfo() { CURLMsg *message; @@ -170,27 +220,7 @@ __attribute__((no_sanitize("cfi"))) void EpollMultiDriver::CheckMultiInfo() while ((message = curl_multi_info_read(multi_, &pending))) { switch (message->msg) { case CURLMSG_DONE: { - auto easyHandle = message->easy_handle; -#if HAS_NETSTACK_CHR - ChrClient::NetStackChrClient::GetInstance().GetDfxInfoFromCurlHandleAndReport(easyHandle, - message->data.result); -#endif - if (!easyHandle) { - break; - } - curl_multi_remove_handle(multi_, easyHandle); - auto requestInfo = ongoingRequests_[easyHandle]; - ongoingRequests_.erase(easyHandle); -#ifdef HTTP_HANDOVER_FEATURE - if (netHandoverHandler_ && - netHandoverHandler_->ProcessRequestErr(ongoingRequests_, multi_, requestInfo, message)) { - break; - } -#endif - if (requestInfo != nullptr && requestInfo->callbacks.doneCallback) { - requestInfo->callbacks.doneCallback(message, requestInfo->opaqueData); - } - delete requestInfo; + HandleCurlDoneMessage(message); break; } default: @@ -200,6 +230,56 @@ __attribute__((no_sanitize("cfi"))) void EpollMultiDriver::CheckMultiInfo() } } +void EpollMultiDriver::HandleCurlDoneMessage(CURLMsg *message) +{ + auto easyHandle = message->easy_handle; +#ifdef HAS_NETSTACK_CHR +#if ENABLE_HTTP_INTERCEPT + long responseCode = 0; + curl_easy_getinfo(easyHandle, CURLINFO_RESPONSE_CODE, &responseCode); + if (responseCode < HTTP_STATUS_REDIRECT_START || responseCode >= HTTP_STATUS_CLIENT_ERROR_START) +#endif + { + ChrClient::NetStackChrClient::GetInstance().GetDfxInfoFromCurlHandleAndReport(easyHandle, message->data.result); + } +#endif + if (!easyHandle) { + return; + } + curl_multi_remove_handle(multi_, easyHandle); + auto requestInfo = ongoingRequests_[easyHandle]; + ongoingRequests_.erase(easyHandle); +#ifdef HTTP_HANDOVER_FEATURE + if (netHandoverHandler_ && netHandoverHandler_->ProcessRequestErr(ongoingRequests_, multi_, requestInfo, message)) { + return; + } +#endif + std::function handleCompletion = std::bind(&EpollMultiDriver::HandleCompletion, this, message, requestInfo); +#if ENABLE_HTTP_INTERCEPT + char *location = nullptr; + curl_easy_getinfo(easyHandle, CURLINFO_REDIRECT_URL, &location); + if (responseCode >= HTTP_STATUS_REDIRECT_START && responseCode < HTTP_STATUS_CLIENT_ERROR_START && location) { + NETSTACK_LOGD("Redirect detected: %{public}s, status=%{public}d", location, static_cast(responseCode)); + auto locationPtr = std::make_shared(location); + std::function handleRedirect = + std::bind(&EpollMultiDriver::HandleRedirect, this, easyHandle, locationPtr, requestInfo); + auto context = reinterpret_cast(requestInfo->opaqueData); + auto interceptor = context->GetInterceptor(); + if (interceptor != nullptr && interceptor->IsRedirectionInterceptor()) { + auto interceptorCallback = interceptor->GetRedirectionInterceptorCallback(); + auto handleInfo = new RedirectionInterceptorInfo { message, locationPtr }; + auto redirectCallback = + std::bind(interceptorCallback, context, handleRedirect, handleCompletion, handleInfo); + NapiUtils::CreateUvQueueWorkByModuleId(context->GetEnv(), redirectCallback, context->GetModuleId()); + return; + } + handleRedirect(); + return; + } +#endif + handleCompletion(); +} + int EpollMultiDriver::MultiSocketCallback(curl_socket_t socket, int action, CurlSocketContext *socketContext) { switch (action) { diff --git a/utils/napi_utils/include/event_manager.h b/utils/napi_utils/include/event_manager.h index fdf9e2f79a60aa2c22ffc405b2eea98ff3a2e224..0e74b6ad2c6164a137d456391efff54049a25dd7 100644 --- a/utils/napi_utils/include/event_manager.h +++ b/utils/napi_utils/include/event_manager.h @@ -224,6 +224,7 @@ public: [[maybe_unused]] struct { uint32_t magicNumber = EVENT_MANAGER_MAGIC_NUMBER; } innerMagic_; + std::unordered_map interceptorRefs_; }; struct EventManagerWrapper { diff --git a/utils/napi_utils/include/module_template.h b/utils/napi_utils/include/module_template.h index ef57c18268cd6592aa84aa75e257184cfb96ee6a..068ce10a3783a0319b5c00582b2b1dde82eba50a 100644 --- a/utils/napi_utils/include/module_template.h +++ b/utils/napi_utils/include/module_template.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -169,6 +170,9 @@ napi_value InterfaceWithOutAsyncWorkWithManagerWrapper(napi_env env, napi_callba NETSTACK_LOGE("new context is nullptr"); return NapiUtils::GetUndefined(env); } +#if ENABLE_HTTP_INTERCEPT + context->SetInterceptorRefs(wrapper->eventManager.interceptorRefs_); +#endif context->ParseParams(params, paramsCount); napi_value ret = NapiUtils::GetUndefined(env); if (NapiUtils::GetValueType(env, context->GetCallback()) != napi_function && context->IsNeedPromise()) { @@ -282,6 +286,12 @@ void CleanUpWithSharedManager(void* data); void DefineClass(napi_env env, napi_value exports, const std::initializer_list &properties, const std::string &className); +void DefineClassNew(napi_env env, napi_value exports, const std::initializer_list &properties, + const std::string &className, napi_callback constructor); + +napi_value InterceptorChainApply( + napi_env env, napi_callback_info info, std::unordered_map interceptorReferences); + napi_value NewInstanceNoManager(napi_env env, napi_callback_info info, const std::string &name, Finalizer finalizer); napi_value NewInstanceWithSharedManager(napi_env env, napi_callback_info info, const std::string &className, diff --git a/utils/napi_utils/src/module_template.cpp b/utils/napi_utils/src/module_template.cpp index 9ccbea0d8b0a2849490d265bfd66bc953c8a2970..0c1591ed784d1f315791b4babe626ba88375c8ee 100644 --- a/utils/napi_utils/src/module_template.cpp +++ b/utils/napi_utils/src/module_template.cpp @@ -333,6 +333,49 @@ void DefineClass(napi_env env, napi_value exports, const std::initializer_list &properties, + const std::string &className, napi_callback constructor) +{ + napi_value jsConstructor = nullptr; + + napi_property_descriptor descriptors[properties.size()]; + std::copy(properties.begin(), properties.end(), descriptors); + + NAPI_CALL_RETURN_VOID(env, + napi_define_class(env, className.c_str(), NAPI_AUTO_LENGTH, constructor, nullptr, properties.size(), + descriptors, &jsConstructor)); + NapiUtils::SetNamedProperty(env, exports, className, jsConstructor); +} + +napi_value InterceptorChainApply( + napi_env env, napi_callback_info info, std::unordered_map interceptorReferences) +{ + size_t argCount = 1; + napi_value args[1]; + napi_status status = napi_get_cb_info(env, info, &argCount, args, nullptr, nullptr); + if (status != napi_ok) { + NETSTACK_LOGE("wangz::InterceptorChainApply failed to get callback info, napi_status: %{public}d", status); + return NapiUtils::GetBoolean(env, false); + } + + EventManagerWrapper *wrapper = nullptr; + status = napi_unwrap(env, args[0], reinterpret_cast(&wrapper)); + if (status != napi_ok) { + NETSTACK_LOGE("wangz::InterceptorChainApply failed to unwrap wrapper, napi_status: %{public}d", status); + return NapiUtils::GetBoolean(env, false); + } + + if (wrapper == nullptr) { + NETSTACK_LOGE("wangz::InterceptorChainApply unwrap succeeded but wrapper is nullptr"); + return NapiUtils::GetBoolean(env, false); + } + + NETSTACK_LOGD( + "wangz::InterceptorChainApply interceptor reference count: %{public}zu", interceptorReferences.size()); + wrapper->eventManager.interceptorRefs_ = std::move(interceptorReferences); + return NapiUtils::GetBoolean(env, true); +} + napi_value NewInstanceWithManagerWrapper(napi_env env, napi_callback_info info, const std::string &className, Finalizer finalizer) {