diff --git a/frameworks/js/napi/http/BUILD.gn b/frameworks/js/napi/http/BUILD.gn index dc9622fdd2ca8c0004eddc94496b77817f0cfcee..fac56a4ae2af878c00b8580b78aa52ef9ae77e24 100644 --- a/frameworks/js/napi/http/BUILD.gn +++ b/frameworks/js/napi/http/BUILD.gn @@ -171,7 +171,9 @@ ohos_shared_library("http") { "$NETSTACK_DIR/utils/netstack_chr_client/src/netstack_chr_report.cpp", "$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", ] + defines += [ "HTTP_HANDOVER_FEATURE" ] } else { defines = [ "HAS_NETMANAGER_BASE=0", 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 dc7a92c9e701d7888bf5fe6c501537325c5c73af..27d5dc5c9df658d9b821b6648bd4a118db7f2853 100644 --- a/frameworks/js/napi/http/async_context/include/request_context.h +++ b/frameworks/js/napi/http/async_context/include/request_context.h @@ -50,6 +50,17 @@ struct CertsPath { std::string certFile; }; +#ifdef HTTP_HANDOVER_FEATURE +struct RequestHandOverInfo { + RequestHandOverInfo() = default; + ~RequestHandOverInfo() = default; + int32_t handOverNum; + int32_t handOverReason; + double flowControlTime; + bool isRead; +}; +#endif + class RequestContext final : public BaseContext { public: friend class HttpExec; @@ -140,6 +151,12 @@ public: [[nodiscard]] std::string GetBundleName() const; +#ifdef HTTP_HANDOVER_FEATURE + void SetTraceName(const std::string &traceName); + + [[nodiscard]] std::string GetTraceName() const; +#endif + void SetCurlHandle(CURL *handle); void SendNetworkProfiler(); @@ -157,6 +174,11 @@ public: void SetPinnedPubkey(std::string &pubkey); std::string GetPinnedPubkey() const; +#ifdef HTTP_HANDOVER_FEATURE + void SetRequestHandOverInfo(int32_t handOverNum, int32_t handOverReason, double flowControlTime, bool isRead); + + const RequestHandOverInfo &GetRequestHandOverInfo(); +#endif private: uint32_t magicNumber_ = MAGIC_NUMBER; int32_t taskId_ = -1; @@ -178,6 +200,9 @@ private: curl_slist *curlHostList_ = nullptr; bool isAtomicService_ = false; std::string bundleName_; +#ifdef HTTP_HANDOVER_FEATURE + std::string traceName_; +#endif bool isRootCaVerified_ = false; bool isRootCaVerifiedOk_ = false; std::string pinnedPubkey_; @@ -185,6 +210,9 @@ private: std::unique_ptr networkProfilerUtils_; #endif CURL *curlHandle_; +#ifdef HTTP_HANDOVER_FEATURE + RequestHandOverInfo requestHandOverInfo_; +#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 38b5224bf82c15757e36392120b6064a0cbf876a..6b8e21c75ae2cba075c35ee84b16076b4cf79d22 100755 --- a/frameworks/js/napi/http/async_context/src/request_context.cpp +++ b/frameworks/js/napi/http/async_context/src/request_context.cpp @@ -92,6 +92,11 @@ RequestContext::RequestContext(napi_env env, const std::shared_ptr bundleName_(""), trace_("HttpRequest_" + std::to_string(taskId_)) { +#ifdef HTTP_HANDOVER_FEATURE + taskId_ = g_currentTaskId++; + isAtomicService_ = false; + bundleName_ = ""; +#endif StartTiming(); #if HAS_NETMANAGER_BASE networkProfilerUtils_ = std::make_unique(); @@ -982,6 +987,18 @@ std::string RequestContext::GetBundleName() const return bundleName_; } +#ifdef HTTP_HANDOVER_FEATURE +void RequestContext::SetTraceName(const std::string &traceName) +{ + traceName_ = traceName; +} + +std::string RequestContext::GetTraceName() const +{ + return traceName_; +} +#endif + void RequestContext::SetCurlHandle(CURL *handle) { curlHandle_ = handle; @@ -1030,6 +1047,22 @@ std::string RequestContext::GetPinnedPubkey() const return pinnedPubkey_; } +#ifdef HTTP_HANDOVER_FEATURE +void RequestContext::SetRequestHandOverInfo(int32_t handOverNum, int32_t handOverReason, double flowControlTime, + bool isRead) +{ + requestHandOverInfo_.handOverNum = handOverNum; + requestHandOverInfo_.handOverReason = handOverReason; + requestHandOverInfo_.flowControlTime = flowControlTime; + requestHandOverInfo_.isRead = isRead; +} + +const RequestHandOverInfo &RequestContext::GetRequestHandOverInfo() +{ + return requestHandOverInfo_; +} +#endif + void RequestContext::ParseAddressFamily(napi_value optionsValue) { std::string addressFamily = NapiUtils::GetStringPropertyUtf8(GetEnv(), optionsValue, 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 f0e96693720af455240b090158befd76b750571f..addd2fa3ef0c34de065fbc32dc92c2eb4bcc6dca 100755 --- a/frameworks/js/napi/http/http_exec/src/http_exec.cpp +++ b/frameworks/js/napi/http/http_exec/src/http_exec.cpp @@ -118,6 +118,10 @@ static constexpr const int SSL_CTX_EX_DATA_REQUEST_CONTEXT_INDEX = 1; static constexpr const char *HTTP_AF_ONLYV4 = "ONLY_V4"; static constexpr const char *HTTP_AF_ONLYV6 = "ONLY_V6"; +#ifdef HTTP_HANDOVER_FEATURE +static bool IsHandOverFeature = false; +static constexpr const int32_t SLEEP_TIMEOUT_MS = 5000; +#endif static void RequestContextDeleter(RequestContext *context) { @@ -280,6 +284,37 @@ bool HttpExec::AddCurlHandle(CURL *handle, RequestContext *context) #if HAS_NETMANAGER_BASE std::stringstream name; name << HTTP_REQ_TRACE_NAME << "_" << std::this_thread::get_id(); +#ifdef HTTP_HANDOVER_FEATURE + context->SetTraceName(name.str()); + StartAsyncTrace(HITRACE_TAG_NET, context->GetTraceName(), context->GetTaskId()); + SetServerSSLCertOption(handle, context); + + static HttpOverCurl::EpollRequestHandler requestHandler(SLEEP_TIMEOUT_MS, true); + + static auto startedCallback = +[](CURL *easyHandle, void *opaqueData) { + char *url = nullptr; + curl_easy_getinfo(easyHandle, CURLINFO_EFFECTIVE_URL, &url); + }; + + static auto responseCallback = +[](CURLMsg *curlMessage, void *opaqueData) { + auto context = static_cast(opaqueData); + HttpExec::HandleCurlData(curlMessage, context); + }; + + if (requestHandler.GetRequestHandOver()) { + IsHandOverFeature = true; + static auto handOverCallback = + +[](void *opaqueData, int32_t handOverNum, int32_t handOverReason, double flowControlTime, int32_t isRead) { + auto context = static_cast(opaqueData); + context->SetRequestHandOverInfo(handOverNum, handOverReason, flowControlTime, isRead); + }; + requestHandler.Process(handle, startedCallback, responseCallback, handOverCallback, context); + } else { + static auto defaultHandOverCallback = +[](void *opaqueData, int32_t handOverNum, int32_t handOverReason, + double flowControlTime, int32_t isRead) {}; + requestHandler.Process(handle, startedCallback, responseCallback, defaultHandOverCallback, context); + } +#else SetTraceOptions(handle, context); SetServerSSLCertOption(handle, context); @@ -299,6 +334,7 @@ bool HttpExec::AddCurlHandle(CURL *handle, RequestContext *context) }; requestHandler.Process(handle, startedCallback, responseCallback, context); +#endif return true; #else std::thread([context, handle] { @@ -487,6 +523,27 @@ void HttpExec::CacheCurlPerformanceTiming(CURL *handle, RequestContext *context) char *ip = nullptr; curl_easy_getinfo(handle, CURLINFO_PRIMARY_IP, &ip); int32_t errCode = context->IsExecOK() ? 0 : context->GetErrorCode(); +#ifdef HTTP_HANDOVER_FEATURE + auto requestHandOverInfo = context->GetRequestHandOverInfo(); + if (IsHandOverFeature && requestHandOverInfo.handOverNum > 0) { + NETSTACK_LOGI("taskid=%{public}d" + ", size:%{public}" CURL_FORMAT_CURL_OFF_T + ", dns:%{public}.3f, connect:%{public}.3f, tls:%{public}.3f, firstSend:%{public}.3f" + ", firstRecv:%{public}.3f, total:%{public}.3f, redirect:%{public}.3f" + ", errCode:%{public}d, RespCode:%{public}s, httpVer:%{public}s, method:%{public}s, osErr:%{public}ld" + ", handOverNum:%{public}d, handOverReason:%{public}s, %{public}s:%{public}.3f, isRead:%{public}s, " + "isStream:%{public}s", + context->GetTaskId(), size, dnsTime, connectTime == 0 ? 0 : connectTime - dnsTime, + tlsTime == 0 ? 0 : tlsTime - connectTime, + firstSendTime == 0 ? 0 : firstSendTime - std::max({dnsTime, connectTime, tlsTime}), + firstRecvTime == 0 ? 0 : firstRecvTime - firstSendTime, totalTime, redirectTime, errCode, + std::to_string(responseCode).c_str(), std::to_string(httpVer).c_str(), context->options.GetMethod().c_str(), + osErr, requestHandOverInfo.handOverNum, requestHandOverInfo.handOverReason ? "flowControl" : "retrans", + requestHandOverInfo.handOverReason ? "flowControlTime" : "retransTime", requestHandOverInfo.flowControlTime, + requestHandOverInfo.isRead == 1 ? "true" : requestHandOverInfo.isRead == 0 ? "false" : "unknown", + context->IsRequestStream() ? "true" : "false"); + } +#else NETSTACK_LOGI( "taskid=%{public}d" ", size:%{public}" CURL_FORMAT_CURL_OFF_T @@ -499,6 +556,7 @@ void HttpExec::CacheCurlPerformanceTiming(CURL *handle, RequestContext *context) firstRecvTime == 0 ? 0 : firstRecvTime - firstSendTime, totalTime, redirectTime, errCode, std::to_string(responseCode).c_str(), std::to_string(httpVer).c_str(), context->options.GetMethod().c_str(), osErr); +#endif #if HAS_NETMANAGER_BASE if (EventReport::GetInstance().IsValid()) { HttpPerfInfo httpPerfInfo; diff --git a/utils/http_over_curl/include/epoll_multi_driver.h b/utils/http_over_curl/include/epoll_multi_driver.h index b85ccd5404c4afeae0a5bb6635a5f650485df6b7..e824a54ad319eb8f19726b5197f2f76093c72434 100644 --- a/utils/http_over_curl/include/epoll_multi_driver.h +++ b/utils/http_over_curl/include/epoll_multi_driver.h @@ -24,6 +24,9 @@ #include "epoller.h" #include "thread_safe_storage.h" #include "timeout_timer.h" +#ifdef HTTP_HANDOVER_FEATURE +#include "http_handover_handler.h" +#endif namespace OHOS::NetStack::HttpOverCurl { @@ -33,6 +36,10 @@ class EpollMultiDriver { public: EpollMultiDriver() = delete; explicit EpollMultiDriver(const std::shared_ptr> &incomingQueue); +#ifdef HTTP_HANDOVER_FEATURE + EpollMultiDriver(const std::shared_ptr> &incomingQueue, + std::shared_ptr &netHandoverHandler); +#endif ~EpollMultiDriver(); void Step(int waitEventsTimeoutMs); @@ -59,13 +66,18 @@ private: void Initialize(); void IncomingRequestCallback(); - +#ifdef HTTP_HANDOVER_FEATURE + void HandOverRequestCallback(); +#endif std::shared_ptr> incomingQueue_; HttpOverCurl::Epoller poller_; HttpOverCurl::TimeoutTimer timeoutTimer_; CURLM *multi_ = nullptr; +#ifdef HTTP_HANDOVER_FEATURE + std::shared_ptr netHandoverHandler_; +#endif // Number of running handles int stillRunning = 0; diff --git a/utils/http_over_curl/include/epoll_request_handler.h b/utils/http_over_curl/include/epoll_request_handler.h index 94d40f342dad03a872756c5d8aad5047027d3179..01a4cb95e3d31f37dd201020ec296d51e59c66df 100644 --- a/utils/http_over_curl/include/epoll_request_handler.h +++ b/utils/http_over_curl/include/epoll_request_handler.h @@ -38,11 +38,24 @@ public: void Process(CURL *easyHandle, const TransferStartedCallback &startedCallback, const TransferDoneCallback &responseCallback, void *opaqueData = nullptr); +#ifdef HTTP_HANDOVER_FEATURE + explicit EpollRequestHandler(int sleepTimeoutMs = 5000, bool netHandover = false); + + void Process(CURL *easyHandle, const TransferStartedCallback &startedCallback, + const TransferDoneCallback &responseCallback, const TransferHandOverCallback &handOverCallback, + void *opaqueData = nullptr); + + bool GetRequestHandOver(); +#endif + private: void WorkingThread(); std::atomic_bool stop_ = false; std::once_flag init_; int sleepTimeoutMs_; +#ifdef HTTP_HANDOVER_FEATURE + bool netHandover_; +#endif std::thread workThread_; std::shared_ptr> incomingQueue_; diff --git a/utils/http_over_curl/include/http_handover_handler.h b/utils/http_over_curl/include/http_handover_handler.h new file mode 100644 index 0000000000000000000000000000000000000000..69a5fbd5144f9ad0ef785d0f2a5037d5c1d23908 --- /dev/null +++ b/utils/http_over_curl/include/http_handover_handler.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 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_HANDOVER_HANDLER_H +#define COMMUNICATIONNETSTACK_HTTP_HANDOVER_HANDLER_H + +#include +#include +#include +#include + +#include "curl/curl.h" + +#include "epoller.h" +#include "thread_safe_storage.h" +#include "timeout_timer.h" +#include "manual_reset_event.h" +#include "epoll_request_handler.h" +#include "request_info.h" +#include "request_context.h" + +namespace OHOS::NetStack::HttpOverCurl { + +struct RequestInfo; + +class HttpHandoverHandler { +public: + enum { INIT, START, CONTINUE, END, FATAL }; + explicit HttpHandoverHandler(); + ~HttpHandoverHandler(); + + bool InitSuccess(); + void HandOverQuery(int32_t &status, int32_t &netId); + bool CheckSocketOpentimeLessThanEndTime(curl_socket_t fd); + void SetSocketOpenTime(curl_socket_t fd); + void EraseFd(curl_socket_t fd); + void SetEndTime(); + bool RetransRequest(std::map &ongoingRequests, CURLM *multi, RequestInfo *request); + bool CheckRequestCanRetrans(RequestInfo *request); + bool TryFlowControl(RequestInfo* requestInfo); + void RetransFailedHandle(std::map &ongoingRequests, std::queue &retransfailed); + void HandOverRequestCallback(std::map &ongoingRequests, CURLM *multi); + + void RegisterForPolling(Epoller &poller) const; + bool IsItYours(FileDescriptor descriptor) const; + void Set(); + int32_t IsRequestRead(CURL *easyHandle); + int32_t IsRequestRead(CURL *easyHandle, time_t &recvtime, time_t &sendtime); + bool ProcessRequestErr(CURL *easyHandle, std::map &ongoingRequests, CURLM *multi, + RequestInfo *requestInfo, CURLMsg *msg); + void SetHandOverInfo(CURL *easyHandle, RequestInfo *requestInfo); + bool CheckRequestNetError(std::map &ongoingRequests, CURLM *multi, + RequestInfo *requestInfo, CURLMsg *msg); + void AddRequest(void *userp, bool isHandOver, int32_t isRead); + void DelRequest(void *userp); + int32_t QueryRequest(void *userp, int32_t &handOverReason, double &flowControlTime, int32_t &isRead); + +private: + bool Initialize(); + void SetCallback(RequestInfo *request); + void *netHandoverHandler_ = nullptr; + void *httpHandOverManager_ = nullptr; + std::unique_ptr handOverEvent_; + typedef void *(*HTTP_HAND_OVER_INIT)(void *user, void (*HMS_NetworkBoost_HandoverEventCallback)(void *)); + typedef int32_t (*HTTP_HAND_OVER_UNINIT)(void *handle); + typedef void (*HTTP_HAND_OVER_QUERY)(void *handle, int32_t &status, int32_t &netId); + typedef void (*HTTP_HAND_OVER_ADD)(void *handle, void *userp, bool isHandOver, int32_t isRead); + typedef void (*HTTP_HAND_OVER_DEL)(void *handle, void *userp, bool isSuccess); + typedef int32_t (*HTTP_HAND_OVER_QUERY_REQUEST)(void *handle, void *userp, int32_t *handOverReason, + double *flowControlTime, int32_t *isRead); + HTTP_HAND_OVER_INIT httpHandOverInit_ = nullptr; + HTTP_HAND_OVER_UNINIT httpHandOverUninit_ = nullptr; + HTTP_HAND_OVER_QUERY httpHandOverQuery_ = nullptr; + HTTP_HAND_OVER_ADD httpHandOverAddRequest_ = nullptr; + HTTP_HAND_OVER_DEL httpHandOverDelRequest_ = nullptr; + HTTP_HAND_OVER_QUERY_REQUEST httpHandOverQueryRequest_ = nullptr; + std::set handoverQueue_; + std::map socketopentime_; + std::map requestEndtime_; + bool initsuccess_; + int endTime_ = 0; + int retrans_ = 0; +}; + +} // namespace OHOS::NetStack::HttpOverCurl + +#endif // COMMUNICATIONNETSTACK_HTTP_HANOVER_HANDLER_H \ No newline at end of file diff --git a/utils/http_over_curl/include/manual_reset_event.h b/utils/http_over_curl/include/manual_reset_event.h index 9c60dbfc50f5e1ef2a014a415f4219672928fc26..be59c62bf75785f00365b3bb1d89257fc39c0280 100644 --- a/utils/http_over_curl/include/manual_reset_event.h +++ b/utils/http_over_curl/include/manual_reset_event.h @@ -31,6 +31,15 @@ struct ManualResetEvent { underlying_ = eventfd(0, 0); } + explicit ManualResetEvent(bool isSemaphore) + { + if (isSemaphore) { + underlying_ = eventfd(0, EFD_SEMAPHORE); + } else { + underlying_ = eventfd(0, 0); + } + } + ~ManualResetEvent() { close(underlying_); diff --git a/utils/http_over_curl/include/request_info.h b/utils/http_over_curl/include/request_info.h index 8025146cc7bfe4d702a30cc286cab5995ca09517..a6db40b216542c49dd86a6d11c137e10c0bb3f1c 100644 --- a/utils/http_over_curl/include/request_info.h +++ b/utils/http_over_curl/include/request_info.h @@ -26,6 +26,9 @@ struct RequestInfo { CURL *easyHandle; TransferStartedCallback startedCallback; TransferDoneCallback doneCallback; +#ifdef HTTP_HANDOVER_FEATURE + TransferHandOverCallback HandOverCallback; +#endif void *opaqueData; }; diff --git a/utils/http_over_curl/include/transfer_callbacks.h b/utils/http_over_curl/include/transfer_callbacks.h index f484b1c62e8d7326af7dbc005690d40c21766ede..0ee990f9a2de57c021a97d5cae0e8a9153fe0689 100644 --- a/utils/http_over_curl/include/transfer_callbacks.h +++ b/utils/http_over_curl/include/transfer_callbacks.h @@ -24,6 +24,10 @@ namespace OHOS::NetStack::HttpOverCurl { using TransferDoneCallback = std::function; using TransferStartedCallback = std::function; +#ifdef HTTP_HANDOVER_FEATURE +using TransferHandOverCallback = + std::function; +#endif } // namespace OHOS::NetStack::HttpOverCurl diff --git a/utils/http_over_curl/src/epoll_multi_driver.cpp b/utils/http_over_curl/src/epoll_multi_driver.cpp index 322e70f29872a71c2b06882142e102afd202fa82..3465a8c0c364fbf517a964b15a659b771b6a5ca1 100644 --- a/utils/http_over_curl/src/epoll_multi_driver.cpp +++ b/utils/http_over_curl/src/epoll_multi_driver.cpp @@ -20,6 +20,10 @@ #if HAS_NETSTACK_CHR #include "netstack_chr_client.h" #endif +#ifdef HTTP_HANDOVER_FEATURE +#include "http_handover_handler.h" +#include "request_context.h" +#endif namespace OHOS::NetStack::HttpOverCurl { @@ -31,8 +35,22 @@ EpollMultiDriver::EpollMultiDriver(const std::shared_ptr> &incomingQueue, + std::shared_ptr &netHandoverHandler) + : incomingQueue_(incomingQueue), netHandoverHandler_(netHandoverHandler) +{ + Initialize(); +} +#endif + void EpollMultiDriver::Initialize() { +#ifdef HTTP_HANDOVER_FEATURE + if (netHandoverHandler_) { + netHandoverHandler_->RegisterForPolling(poller_); + } +#endif timeoutTimer_.RegisterForPolling(poller_); incomingQueue_->GetSyncEvent().RegisterForPolling(poller_); multi_ = curl_multi_init(); @@ -88,6 +106,10 @@ void EpollMultiDriver::Step(int waitEventsTimeoutMs) IncomingRequestCallback(); } else if (timeoutTimer_.IsItYours(events[idx].data.fd)) { EpollTimerCallback(); +#ifdef HTTP_HANDOVER_FEATURE + } else if (netHandoverHandler_ && netHandoverHandler_->IsItYours(events[idx].data.fd)) { + HandOverRequestCallback(); +#endif } else { // curl socket event EpollSocketCallback(events[idx].data.fd); } @@ -98,6 +120,12 @@ void EpollMultiDriver::IncomingRequestCallback() { auto requestsToAdd = incomingQueue_->Flush(); for (auto &request : requestsToAdd) { +#ifdef HTTP_HANDOVER_FEATURE + if (netHandoverHandler_ && netHandoverHandler_->TryFlowControl(request)) { + NETSTACK_LOGI("incoming request"); + continue; + } +#endif ongoingRequests_[request->easyHandle] = request; auto ret = curl_multi_add_handle(multi_, request->easyHandle); if (ret != CURLM_OK) { @@ -155,6 +183,14 @@ __attribute__((no_sanitize("cfi"))) void EpollMultiDriver::CheckMultiInfo() curl_multi_remove_handle(multi_, easyHandle); auto requestInfo = ongoingRequests_[easyHandle]; ongoingRequests_.erase(easyHandle); +#ifdef HTTP_HANDOVER_FEATURE + if (netHandoverHandler_) { + if (netHandoverHandler_->ProcessRequestErr(easyHandle, ongoingRequests_, multi_, requestInfo, + message)) { + break; + } + } +#endif if (requestInfo != nullptr && requestInfo->doneCallback) { requestInfo->doneCallback(message, requestInfo->opaqueData); } diff --git a/utils/http_over_curl/src/epoll_request_handler.cpp b/utils/http_over_curl/src/epoll_request_handler.cpp index 71108813ca09df2536ec05a6e87e4032942d5476..4242a1245d5fe1689762608f8197476e97e13859 100644 --- a/utils/http_over_curl/src/epoll_request_handler.cpp +++ b/utils/http_over_curl/src/epoll_request_handler.cpp @@ -20,6 +20,9 @@ #include "epoll_multi_driver.h" #include "netstack_log.h" #include "request_info.h" +#ifdef HTTP_HANDOVER_FEATURE +#include "http_handover_handler.h" +#endif namespace OHOS::NetStack::HttpOverCurl { static constexpr const char *HTTP_WORK_THREAD = "OS_NET_HttpWork"; @@ -30,6 +33,14 @@ EpollRequestHandler::EpollRequestHandler(int sleepTimeoutMs) { } +#ifdef HTTP_HANDOVER_FEATURE +EpollRequestHandler::EpollRequestHandler(int sleepTimeoutMs, bool netHandover) + : sleepTimeoutMs_(sleepTimeoutMs), netHandover_(netHandover), + incomingQueue_(std::make_shared>()) +{ +} +#endif + EpollRequestHandler::~EpollRequestHandler() { stop_ = true; @@ -38,6 +49,29 @@ EpollRequestHandler::~EpollRequestHandler() } } +#ifdef HTTP_HANDOVER_FEATURE +void EpollRequestHandler::Process(CURL *easyHandle, const TransferStartedCallback &startedCallback, + const TransferDoneCallback &responseCallback, const TransferHandOverCallback &handOverCallback, void *opaqueData) +{ + auto requestInfo = new RequestInfo{easyHandle, startedCallback, responseCallback, handOverCallback, opaqueData}; + incomingQueue_->Push(requestInfo); + + auto start = [this]() { + auto f = [this]() { +#if defined(MAC_PLATFORM) || defined(IOS_PLATFORM) + pthread_setname_np(HTTP_WORK_THREAD); +#else + pthread_setname_np(pthread_self(), HTTP_WORK_THREAD); +#endif + WorkingThread(); + }; + workThread_ = std::thread(f); + workThread_.detach(); + }; + + std::call_once(init_, start); +} +#else void EpollRequestHandler::Process(CURL *easyHandle, const TransferStartedCallback &startedCallback, const TransferDoneCallback &responseCallback, void *opaqueData) { @@ -59,10 +93,29 @@ void EpollRequestHandler::Process(CURL *easyHandle, const TransferStartedCallbac std::call_once(init_, start); } +#endif + +#ifdef HTTP_HANDOVER_FEATURE +bool EpollRequestHandler::GetRequestHandOver() +{ + return netHandover_; +} +#endif void EpollRequestHandler::WorkingThread() { +#ifdef HTTP_HANDOVER_FEATURE + std::shared_ptr netHandoverHandler = std::make_shared(); + if (netHandover_ && netHandoverHandler->InitSuccess()) { + NETSTACK_LOGI("NetHandover enabled"); + } else { + netHandoverHandler = nullptr; + NETSTACK_LOGI("NetHandover disabled!!!"); + } + EpollMultiDriver requestHandler(incomingQueue_, netHandoverHandler); +#else EpollMultiDriver requestHandler(incomingQueue_); +#endif while (!stop_) { requestHandler.Step(sleepTimeoutMs_); diff --git a/utils/http_over_curl/src/http_handover_handler.cpp b/utils/http_over_curl/src/http_handover_handler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6090011f4abd80b3f6e1f5fdfb1bb69ce3159d6f --- /dev/null +++ b/utils/http_over_curl/src/http_handover_handler.cpp @@ -0,0 +1,423 @@ +/* + * Copyright (c) 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_handover_handler.h" +#include +#include "netstack_log.h" +#include "request_info.h" +#include "request_context.h" + +namespace OHOS::NetStack::HttpOverCurl { + +const char *const METHOD_GET = "GET"; +const char *const METHOD_HEAD = "HEAD"; +const char *const METHOD_OPTIONS = "OPTIONS"; +const char *const METHOD_TRACE = "TRACE"; + +HttpHandoverHandler::HttpHandoverHandler() + : handOverEvent_(std::make_unique(true)) +{ + NETSTACK_LOGI("HttpHandoverHandler init"); + initsuccess_ = Initialize(); +} + +void HandOverCallback(void *user) +{ + NETSTACK_LOGI("HandOverCallback enter"); + if (user == nullptr) { + NETSTACK_LOGE("user is nullptr"); + return; + } + + HttpHandoverHandler* const handoverhandler = reinterpret_cast(user); + handoverhandler->Set(); +} + +bool HttpHandoverHandler::InitSuccess() +{ + return initsuccess_; +} + +bool HttpHandoverHandler::Initialize() +{ + const std::string HTTP_HANDOVER_WRAPPER_PATH = "/system/lib64/libhttp_handover.z.so"; + if (netHandoverHandler_ == nullptr) { + netHandoverHandler_ = dlopen(HTTP_HANDOVER_WRAPPER_PATH.c_str(), RTLD_NOW); + if (netHandoverHandler_ == nullptr) { + NETSTACK_LOGE("libhttp_handover.z.so was not loaded, error: %{public}s", dlerror()); + return false; + } + } + httpHandOverInit_ = (HTTP_HAND_OVER_INIT)dlsym(netHandoverHandler_, "HMS_NetworkBoost_HttpHandoverManagerInit"); + httpHandOverUninit_ = + (HTTP_HAND_OVER_UNINIT)dlsym(netHandoverHandler_, "HMS_NetworkBoost_HttpHandoverManagerUninit"); + httpHandOverQuery_ = + (HTTP_HAND_OVER_QUERY)dlsym(netHandoverHandler_, "HMS_NetworkBoost_HttpHandoverManagerQuery"); + httpHandOverAddRequest_ = + (HTTP_HAND_OVER_ADD)dlsym(netHandoverHandler_, "HMS_NetworkBoost_HttpHandoverManagerAddRequest"); + httpHandOverDelRequest_ = + (HTTP_HAND_OVER_DEL)dlsym(netHandoverHandler_, "HMS_NetworkBoost_HttpHandoverManagerDelRequest"); + httpHandOverQueryRequest_ = + (HTTP_HAND_OVER_QUERY_REQUEST)dlsym(netHandoverHandler_, "HMS_NetworkBoost_HttpHandoverManagerQueryRequest"); + bool hasFuncNull = (httpHandOverInit_ == nullptr || httpHandOverUninit_ == nullptr || + httpHandOverQuery_ == nullptr || httpHandOverAddRequest_ == nullptr || httpHandOverDelRequest_ == nullptr || + httpHandOverQueryRequest_ == nullptr); + if (hasFuncNull) { + NETSTACK_LOGE("[HTTP HANDOVER] http handover wrapper symbol failed, error: %{public}s", dlerror()); + return false; + } + NETSTACK_LOGI("NetHandover enabled"); + + if (netHandoverHandler_ != nullptr) { + httpHandOverManager_ = httpHandOverInit_(this, HandOverCallback); + } + return true; +} + +HttpHandoverHandler::~HttpHandoverHandler() +{ + if (httpHandOverManager_ != nullptr) { + NETSTACK_LOGD("start httpHandOverUninit_"); + httpHandOverUninit_(httpHandOverManager_); + } + + if (netHandoverHandler_ != nullptr) { + dlclose(netHandoverHandler_); + netHandoverHandler_ = nullptr; + } + httpHandOverInit_ = nullptr; + httpHandOverUninit_ = nullptr; +} + +void HttpHandoverHandler::RegisterForPolling(Epoller &poller) const +{ + handOverEvent_->RegisterForPolling(poller); +} + +bool HttpHandoverHandler::IsItYours(FileDescriptor descriptor) const +{ + return handOverEvent_->IsItYours(descriptor); +} + +void HttpHandoverHandler::Set() +{ + handOverEvent_->Set(); +} + +void HttpHandoverHandler::HandOverQuery(int32_t &status, int32_t &netId) +{ + if (httpHandOverQuery_ == nullptr) { + NETSTACK_LOGD("nullptr param error"); + return; + } + return httpHandOverQuery_(httpHandOverManager_, status, netId); +} + +bool HttpHandoverHandler::CheckSocketOpentimeLessThanEndTime(curl_socket_t fd) +{ + if (socketopentime_.count(fd) == 0) { + return false; + } + bool ret = socketopentime_[fd] < endTime_; + if (ret) { + NETSTACK_LOGI("Old fd:%{public}d fdtime:%{public}d endTime:%{public}d", (int)fd, socketopentime_[fd], endTime_); + } + return ret; +} + +void HttpHandoverHandler::SetSocketOpenTime(curl_socket_t fd) +{ + socketopentime_[fd] = endTime_; +} + +void HttpHandoverHandler::EraseFd(curl_socket_t fd) +{ + if (socketopentime_.count(fd) == 0) { + return ; + } + socketopentime_.erase(fd); +} + +void HttpHandoverHandler::SetCallback(RequestInfo *request) +{ + static auto checksockettime = +[](void *user, curl_socket_t fd) -> bool { + auto handover = static_cast(user); + if (handover && handover->CheckSocketOpentimeLessThanEndTime(fd)) { + return false; + } + return true; + }; + + curl_easy_setopt(request->easyHandle, CURLOPT_CONNREUSEDATA, this); + curl_easy_setopt(request->easyHandle, CURLOPT_CONNREUSEFUNCTION, checksockettime); + + static auto opensocket = +[](void *user, curlsocktype purpose, struct curl_sockaddr *addr) -> curl_socket_t { + curl_socket_t sockfd = socket(addr->family, addr->socktype, addr->protocol); + if (sockfd < 0) { + NETSTACK_LOGI("Failled to open socket: %{public}d, error: %{public}d", sockfd, errno); + return -1; + } + auto handover = static_cast(user); + if (handover) { + handover->SetSocketOpenTime(sockfd); + } + return sockfd; + }; + + curl_easy_setopt(request->easyHandle, CURLOPT_CONNREUSEDATA, this); + curl_easy_setopt(request->easyHandle, CURLOPT_CONNREUSEFUNCTION, opensocket); + + static auto closeSocketCallback = +[](void *user, curl_socket_t fd) -> int { + auto handover = static_cast(user); + if (handover) { + handover->EraseFd(fd); + } + if (close(fd) < 0) { + NETSTACK_LOGI("Failed to close socket: %{publlic}d, errno: %{public}d", fd, errno); + return -1; + } + return 0; + }; + + curl_easy_setopt(request->easyHandle, CURLOPT_CONNREUSEDATA, this); + curl_easy_setopt(request->easyHandle, CURLOPT_CONNREUSEFUNCTION, closeSocketCallback); +} + +bool HttpHandoverHandler::TryFlowControl(RequestInfo *requestInfo) +{ + int32_t status = -1; + int32_t netId = -1; + HandOverQuery(status, netId); + SetCallback(requestInfo); + if (status == HttpHandoverHandler::START) { + handoverQueue_.insert(requestInfo); + auto context = static_cast(requestInfo->opaqueData); + NETSTACK_LOGI("taskid=%{public}d, FlowControl", context->GetTaskId()); + AddRequest(requestInfo->opaqueData, true, IsRequestRead(requestInfo->easyHandle)); + return true; + } + AddRequest(requestInfo->opaqueData, false, IsRequestRead(requestInfo->easyHandle)); + return false; +} + +void HttpHandoverHandler::SetEndTime() +{ + ++endTime_; + NETSTACK_LOGI("endTime: %{public}d, flowcontrol queue size %{public}d", endTime_, (int)handoverQueue_.size()); +} + +bool HttpHandoverHandler::RetransRequest(std::map &ongoingRequests, CURLM *multi, + RequestInfo *request); +{ + auto ret = curl_multi_add_handle(multi, request->easyHandle); + if (ret != CURLM_OK) { + NETSTACK_LOGI("curl_multi_add_handle err, ret = %{public}d %{public}s", ret, curl_multi_strerror(ret)); + return false; + } + ongoingRequests[request->easyHandle] = request; + return true; +} + +bool HttpHandoverHandler::CheckRequestCanRetrans(RequestInfo *request) +{ + time_t recvtime = 0; + time_t sendtime = 0; + int32_t isRead = IsRequestRead(request->easyHandle, recvtime, sendtime); + if (isRead == -1) { + return false; + } + auto context = static_cast(request->opaqueData); + auto method = context->options.GetMethod(); + int isInstream = context->IsRequestInStream(); + uint32_t readTimeout = context->options.GetReadTimeout(); + uint32_t connecttimeout = context->options.GetConnectTimeout(); + NETSTACK_LOGI( + "taskid=%{public}d," + "method:%{public}s Instream:%{public}d, recvtime:%{public}d, sendtime:%{public}d, readTimeout:%{public}u" + "connecttimeout:%{public}u, url:%{public}s ", context->GetTaskId(), method.c_str(), + isInstream, (int)recvtime, (int)sendtime, readTimeout, connecttimeout, context->options.GetUrl().c_str()); + + if (sendtime == 0) { + return true; + } + bool isSafe = (method == METHOD_GET || method == METHOD_HEAD || method == METHOD_OPTIONS || method == METHOD_TRACE); + if (!isSafe) { + return false; + } + if (isRead == 0 || !context->IsRequestInStream()) { + return true; + } + return false; +} + +void HttpHandoverHandler::RetransFailedHandle(std::map &ongoingRequests, + std::queue &retransfailed) +{ + while (!retransfailed.empty()) { + auto &handle = retransfailed.front(); + retransfailed.pop(); + if (!ongoingRequests.count(handle)) { + continue; + } + auto request = ongoingRequests[handle]; + if (request != nullptr && request->doneCallback) { + CURLMsg message; + message.msg = CURLMSG_DONE; + message.data.result = CURLE_SEND_ERROR; + request->doneCallback(&message, request->opaqueData); + } + ongoingRequests.erase(handle); + } +} + +void HttpHandoverHandler::HandOverRequestCallback(std::map &ongoingRequests, CURLM *multi) +{ + handOverEvent_ ->Reset(); + int32_t status = -1; + int32_t netId = -1; + HandOverQuery(status, netId); + NETSTACK_LOGI("Enter HandoverRequestCallback status %{public}d", status); + if (status == HttpHandoverHandler::START) { + NETSTACK_LOGI("ongoingRequests:%{public}d", (int)ongoingRequests.size()); + for (auto &request : ongoingRequests) { + if (requestEndtime_.count(request.second) == 0) { + requestEndtime_[request.second] = endTime; + } + (void)CheckRequestCanRetrans(request.second); + } + } else if (status == HttpHandoverHandler::END || status == HttpHandoverHandler::FATAL) { + if (status == HttpHandoverHandler::END) { + NETSTACK_LOGI("ongoingRequests:%{public}d, retrans:%{public}d", (int)ongoingRequests.size(), retrans_); + SetEndTime(); + srd::queue retransfailed; + for (auto &request : ongoingRequests) { + if (CheckRequestCanRetrans(request.second)) { + curl_multi_remove_handle(multi, request.first); + if (RetransRequest(ongoingRequests, multi, request.second)) { + ++retrans_; + } else { + retransfailed.push(request.first); + } + } + } + RetransFailedHandle(ongoingRequests, retransfailed); + } + NETSTACK_LOGI("handoverQueue_:%{public}d, retrans:%{public}d", (int)handoverQueue_.size(), retrans_); + for (auto &request : handoverQueue_) { + (void) RetransRequest(ongoingRequests, multi, request); + } + handoverQueue_.clead(); + retrans_ = 0; + } +} + +int32_t HttpHandoverHandler::IsRequestRead(CURL *easyHandle) +{ + time_t recvtime = 0; + time_t sendtime = 0; + return IsRequestRead(easyHandle, recvtime, sendtime); +} + +int32_t HttpHandoverHandler::IsRequestRead(CURL *easyHandle, time_t &recvtime, time_t &sendtime) +{ + CURLcode result = curl_easy_getinfo(easyHandle, CURLINFO_STARTTRANSFER_TIME_T, &recvtime); + if (result != CURL_OK) { + NETSTACK_LOGI("get recv time failed:%{public}s", curl_easy_strerror(result)); + return -1; + } + result = curl_easy_getinfo(easyHandle, CURLINFO_PRETRANSFER_TIME_T, &sendtime); + if (result != CURL_OK) { + NETSTACK_LOGI("get send time failed:%{public}s", curl_easy_strerror(result)); + return -1; + } + return (recvtime == 0 || sendtime == recvtime) ? 0 : 1; +} + +bool HttpHandoverHandler::ProcessRequestErr(CURL *easyHandle, std::map &ongoingRequests, + CURLM *multi, RequestInfo *requestInfo, CURLMsg *msg) +{ + if (CheckRequestNetError(ongoingRequests, multi, requestInfo, msg)) { + return true; + } + SetHandOverInfo(easyHandle, RequestInfo); + return false; +} + +void HttpHandoverHandler::SetHandOverInfo(CURL *easyHandle, RequestInfo *requestInfo) +{ + if (requestInfo != nullptr) { + int32_t handOverReason = 0; + double flowControlTime = 0; + int32_t isRead = 0; + int32_t handOverNum = QueryRequest(requestInfo->opaqueData, handOverReason, flowControlTime, isRead); + if (requestInfo->HandOverCallback) { + requestInfo->HandOverCallback(requestInfo->opaqueData, handOverNum, handOverReason, flowControlTime, + isRead); + } + DelRequest(requestInfo->opaqueData); + } + return; +} + +bool HttpHandoverHandler::CheckRequestNetError(std::map &ongoingRequests, CURLM multi, + RequestInfo *requestInfo, CURLMsg *msg) +{ + if (!RequestInfo || requestEndtime_.count(RequestInfo) == 0) { + return false; + } + int endTime = requestEndtime_[requestInfo]; + requestEndtime_.erase(requestInfo); + if (!msg || (msg->data.result != CURL_SEND_ERROR && msg->data.result != CURLE_RECV_ERROR)) { + return false; + } + if (!CheckRequestCanRetrans(requestInfo)) { + return false; + } + if (TryFlowControl(requestInfo)) { + ++retrans; + return true; + } + if (endTime == endTime_ - 1) { + NETSTACK_LOGI("networkerror afteer end status"); + AddRequest(requestInfo->opaqueData, true, IsRequestRead(requestInfo->easyHandle)); + return RetransRequest(ongoingRequests, multi, requestInfo); + } + return false; +} + +void HttpHandoverHandler::AddRequest(void *userp, bool isHandOver, int32_t isRead) +{ + if (isHandOver) { + NETSTACK_LOGI("HttpHandoverHandler AddRequest"); + } + httoHandOverAddRequest_(httpHandOverManager_, userp, isHandOver, isRead); +} + +void HttpHandoverHandler::DelRequest(void *userp) +{ + auto context = static_cast(userp); + if (context->IsParseOK() && context->IsExecOK()) { + httpHandOverDelRequest_(httpHandOverManager_, userp, true); + return; + } + httphandOverQueryRequest_(httpHandOverManager_, userp, false); +} + +int32_t HttpHandoverHandler::QueryRequest(void *userp, int32_t &handOverReason, double &flowControlTime, + int32_t &isRead) +{ + return httphandOverQueryRequest_(httpHandOverManager_, userp, &handOverReason, double &flowControlTime, &isRead); +} +} \ No newline at end of file