网络请求
使用该功能需要申请ohos.permission.INTERNET权限。
使用例子
let httpRequest = http.createHttp();
httpRequest.request(
url,
{
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json'
},
extraData: {
"userId": userId,
},
expectDataType: http.HttpDataType.STRING,
});
源码分析
网络模块
Http 模块注册
import http from '@ohos.net.http';
napi_value HttpModuleExports::InitHttpModule(napi_env env, napi_value exports)
{
DefineHttpRequestClass(env, exports);
DefineHttpResponseCacheClass(env, exports);
InitHttpProperties(env, exports);
return exports;
}
static napi_module g_httpModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = HttpModuleExports::InitHttpModule,
.nm_modname = HTTP_MODULE_NAME,
.nm_priv = nullptr,
.reserved = {nullptr},
};
extern "C" __attribute__((constructor)) void RegisterHttpModule(void)
{
napi_module_register(&g_httpModule);
}
}
http js对象导出
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";
static constexpr const char *INTERFACE_RESPONSE_CODE = "ResponseCode";
static constexpr const char *INTERFACE_HTTP_REQUEST = "HttpRequest";
static constexpr const char *INTERFACE_HTTP_PROTOCOL = "HttpProtocol";
static constexpr const char *INTERFACE_HTTP_RESPONSE_CACHE = "HttpResponseCache";
static constexpr const char *INTERFACE_HTTP_DATA_TYPE = "HttpDataType";
void HttpModuleExports::DefineHttpRequestClass(napi_env env, napi_value exports)
{
std::initializer_list<napi_property_descriptor> properties = {
DECLARE_NAPI_FUNCTION(HttpRequest::FUNCTION_REQUEST, HttpRequest::Request),
DECLARE_NAPI_FUNCTION(HttpRequest::FUNCTION_REQUEST_IN_STREAM, HttpRequest::RequestInStream),
DECLARE_NAPI_FUNCTION(HttpRequest::FUNCTION_DESTROY, HttpRequest::Destroy),
DECLARE_NAPI_FUNCTION(HttpRequest::FUNCTION_ON, HttpRequest::On),
DECLARE_NAPI_FUNCTION(HttpRequest::FUNCTION_ONCE, HttpRequest::Once),
DECLARE_NAPI_FUNCTION(HttpRequest::FUNCTION_OFF, HttpRequest::Off),
};
ModuleTemplate::DefineClass(env, exports, properties, INTERFACE_HTTP_REQUEST);
}
void HttpModuleExports::DefineHttpResponseCacheClass(napi_env env, napi_value exports)
{
std::initializer_list<napi_property_descriptor> properties = {
DECLARE_NAPI_FUNCTION(HttpResponseCache::FUNCTION_FLUSH, HttpResponseCache::Flush),
DECLARE_NAPI_FUNCTION(HttpResponseCache::FUNCTION_DELETE, HttpResponseCache::Delete),
};
ModuleTemplate::DefineClass(env, exports, properties, INTERFACE_HTTP_RESPONSE_CACHE);
}
void HttpModuleExports::InitHttpProperties(napi_env env, napi_value exports)
{
std::initializer_list<napi_property_descriptor> properties = {
DECLARE_NAPI_FUNCTION(FUNCTION_CREATE_HTTP, CreateHttp),
DECLARE_NAPI_FUNCTION(FUNCTION_CREATE_HTTP_RESPONSE_CACHE, CreateHttpResponseCache),
};
NapiUtils::DefineProperties(env, exports, properties);
InitRequestMethod(env, exports);
InitResponseCode(env, exports);
InitHttpProtocol(env, exports);
InitHttpDataType(env, exports);
}
http.createHttp
js调用代码
let httpRequest = http.createHttp();
调用native绑定方法
static constexpr const char *INTERFACE_HTTP_REQUEST = "HttpRequest";
napi_value HttpModuleExports::CreateHttp(napi_env env, napi_callback_info info)
{
return ModuleTemplate::NewInstance(env, info, INTERFACE_HTTP_REQUEST, [](napi_env, void *data, void *) {
NETSTACK_LOGD("http request handle is finalized");
auto manager = reinterpret_cast<EventManager *>(data);
if (manager != nullptr) {
EventManager::SetInvalid(manager);
}
});
}
返回一个HttpRequest js对象
napi_value NewInstance(napi_env env, napi_callback_info info, const std::string &className, Finalizer finalizer)
{
napi_value thisVal = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, nullptr, nullptr, &thisVal, nullptr));
napi_value jsConstructor = NapiUtils::GetNamedProperty(env, thisVal, className);
if (NapiUtils::GetValueType(env, jsConstructor) == napi_undefined) {
return nullptr;
}
napi_value result = nullptr;
NAPI_CALL(env, napi_new_instance(env, jsConstructor, 0, nullptr, &result));
auto manager = new EventManager();
EventManager::SetValid(manager);
if (className == Http::HttpModuleExports::INTERFACE_HTTP_REQUEST) {
manager->CreateEventReference(env, thisVal);
}
napi_wrap(env, result, reinterpret_cast<void *>(manager), finalizer, nullptr, nullptr);
return result;
}
httpRequest.request
js调用代码
httpRequest.request(
url,
{
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json'
},
extraData: {
"userId": userId,
},
expectDataType: http.HttpDataType.STRING,
});
调用native绑定方法,注意RequestContext这个类贯穿整个请求周期。
napi_value HttpModuleExports::HttpRequest::Request(napi_env env, napi_callback_info info)
{
return ModuleTemplate::InterfaceWithOutAsyncWork<RequestContext>(
env, info,
[](napi_env, napi_value, RequestContext *context) -> bool {
if (!HttpExec::Initialize()) {
return false;
}
HttpExec::AsyncRunRequest(context);
return context->IsExecOK();
},
"Request", HttpAsyncWork::ExecRequest, HttpAsyncWork::RequestCallback);
}
template <class Context>
napi_value InterfaceWithOutAsyncWork(napi_env env, napi_callback_info info,
bool (*Work)(napi_env, napi_value, Context *), const std::string &asyncWorkName,
AsyncWorkExecutor executor, AsyncWorkCallback callback)
{
static_assert(std::is_base_of<BaseContext, Context>::value);
napi_value thisVal = nullptr;
size_t paramsCount = MAX_PARAM_NUM;
napi_value params[MAX_PARAM_NUM] = {nullptr};
NAPI_CALL(env, napi_get_cb_info(env, info, ¶msCount, params, &thisVal, nullptr));
EventManager *manager = nullptr;
auto napi_ret = napi_unwrap(env, thisVal, reinterpret_cast<void **>(&manager));
if (napi_ret != napi_ok) {
NETSTACK_LOGE("get event manager in napi_unwrap failed, napi_ret is %{public}d", napi_ret);
return NapiUtils::GetUndefined(env);
}
auto context = new Context(env, manager);
context->ParseParams(params, paramsCount);
napi_value ret = NapiUtils::GetUndefined(env);
if (NapiUtils::GetValueType(env, context->GetCallback()) != napi_function && context->IsNeedPromise()) {
NETSTACK_LOGI("%{public}s create promise", asyncWorkName.c_str());
ret = context->CreatePromise();
}
context->CreateReference(thisVal);
if (Work != nullptr) {
if (!Work(env, thisVal, context)) {
NETSTACK_LOGE("work failed error code = %{public}d", context->GetErrorCode());
}
}
if (!context->IsParseOK() || context->IsPermissionDenied() || context->GetManager()->IsEventDestroy()) {
context->CreateAsyncWork(asyncWorkName, executor, callback);
}
return ret;
}
请求参数解析
解析js层传入的请求参数到RequestContext
void RequestContext::ParseParams(napi_value *params, size_t paramsCount)
{
bool valid = CheckParamsType(params, paramsCount);
...
if (paramsCount == PARAM_JUST_URL) {
options.SetUrl(NapiUtils::GetStringFromValueUtf8(GetEnv(), params[0]));
SetParseOK(true);
return;
}
if (paramsCount == PARAM_URL_AND_OPTIONS_OR_CALLBACK) {
napi_valuetype type = NapiUtils::GetValueType(GetEnv(), params[1]);
if (type == napi_function) {
options.SetUrl(NapiUtils::GetStringFromValueUtf8(GetEnv(), params[0]));
SetParseOK(SetCallback(params[1]) == napi_ok);
return;
}
if (type == napi_object) {
UrlAndOptions(params[0], params[1]);
return;
}
return;
}
if (paramsCount == PARAM_URL_AND_OPTIONS_AND_CALLBACK) {
if (SetCallback(params[PARAM_URL_AND_OPTIONS_AND_CALLBACK - 1]) != napi_ok) {
return;
}
UrlAndOptions(params[0], params[1]);
}
}
void。RequestContext::UrlAndOptions(napi_value urlValue, napi_value optionsValue)
{
options.SetUrl(NapiUtils::GetStringFromValueUtf8(GetEnv(), urlValue));
std::string method = NapiUtils::GetStringPropertyUtf8(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_METHOD);
if (method.empty()) {
method = HttpConstant::HTTP_METHOD_GET;
}
options.SetMethod(method);
ParseNumberOptions(optionsValue);
ParseUsingHttpProxy(optionsValue);
/* parse extra data here to recover header */
if (!ParseExtraData(optionsValue)) {
return;
}
ParseHeader(optionsValue);
ParseCaPath(optionsValue);
SetParseOK(true);
}
构建Promise对象
deferred_ 也会存在RequestContext中,供后面回调
napi_value BaseContext::CreatePromise()
{
napi_value result = nullptr;
NAPI_CALL(env_, napi_create_promise(env_, &deferred_, &result));
return result;
}
初始化curl模块,构建任务消费线程池
{
if (!HttpExec::Initialize()) {
return false;
}
HttpExec::AsyncRunRequest(context);
return context->IsExecOK();
}
bool HttpExec::Initialize()
{
std::lock_guard<std::mutex> lock(staticVariable_.mutexForInitialize);
if (staticVariable_.initialized) {
return true;
}
NETSTACK_LOGI("call curl_global_init");
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
NETSTACK_LOGE("Failed to initialize 'curl'");
return false;
}
staticVariable_.curlMulti = curl_multi_init();
if (staticVariable_.curlMulti == nullptr) {
NETSTACK_LOGE("Failed to initialize 'curl_multi'");
return false;
}
staticVariable_.workThread = std::thread(RunThread);
staticVariable_.initialized = true;
return staticVariable_.initialized;
}
void HttpExec::RunThread()
{
while (staticVariable_.runThread && staticVariable_.curlMulti != nullptr) {
AddRequestInfo();
SendRequest();
ReadResponse();
std::this_thread::sleep_for(std::chrono::milliseconds(CURL_TIMEOUT_MS));
std::unique_lock l(staticVariable_.curlMultiMutex);
staticVariable_.conditionVariable.wait_for(l, std::chrono::seconds(CONDITION_TIMEOUT_S), [] {
return !staticVariable_.infoQueue.empty() || !staticVariable_.contextMap.empty();
});
}
}
构建curl请求参数
bool HttpExec::ExecRequest(RequestContext *context)
{
if (!CommonUtils::HasInternetPermission()) {
context->SetPermissionDenied(true);
return false;
}
if (context->GetManager()->IsEventDestroy()) {
return false;
}
context->options.SetRequestTime(HttpTime::GetNowTimeGMT());
CacheProxy proxy(context->options);
if (context->IsUsingCache() && proxy.ReadResponseFromCache(context)) {
return true;
}
if (!RequestWithoutCache(context)) {
context->SetErrorCode(NapiUtils::NETSTACK_NAPI_INTERNAL_ERROR);
if (EventManager::IsManagerValid(context->GetManager())) {
if (context->IsRequestInStream()) {
NapiUtils::CreateUvQueueWorkEnhanced(context->GetEnv(), context,
HttpAsyncWork::RequestInStreamCallback);
} else {
NapiUtils::CreateUvQueueWorkEnhanced(context->GetEnv(), context, HttpAsyncWork::RequestCallback);
}
}
return false;
}
return true;
}
bool HttpExec::RequestWithoutCache(RequestContext *context)
{
if (!staticVariable_.initialized) {
NETSTACK_LOGE("curl not init");
return false;
}
auto handle = curl_easy_init();
if (!handle) {
NETSTACK_LOGE("Failed to create fetch task");
return false;
}
std::vector<std::string> vec;
std::for_each(context->options.GetHeader().begin(), context->options.GetHeader().end(),
[&vec](const std::pair<std::string, std::string> &p) {
vec.emplace_back(p.first + HttpConstant::HTTP_HEADER_SEPARATOR + p.second);
});
context->SetCurlHeaderList(MakeHeaders(vec));
if (!SetOption(handle, context, context->GetCurlHeaderList())) {
NETSTACK_LOGE("set option failed");
return false;
}
context->response.SetRequestTime(HttpTime::GetNowTimeGMT());
if (!AddCurlHandle(handle, context)) {
NETSTACK_LOGE("add handle failed");
return false;
}
return true;
}
bool HttpExec::SetOption(CURL *curl, RequestContext *context, struct curl_slist *requestHeader)
{
const std::string &method = context->options.GetMethod();
if (!MethodForGet(method) && !MethodForPost(method)) {
NETSTACK_LOGE("method %{public}s not supported", method.c_str());
return false;
}
if (context->options.GetMethod() == HttpConstant::HTTP_METHOD_HEAD) {
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOBODY, 1L, context);
}
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, context->options.GetUrl().c_str(), context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CUSTOMREQUEST, method.c_str(), context);
if (MethodForPost(method) && !context->options.GetBody().empty()) {
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POST, 1L, context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDS, context->options.GetBody().c_str(), context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDSIZE, context->options.GetBody().size(), context);
}
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback, context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_XFERINFODATA, context, context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOPROGRESS, 0L, context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_WRITEFUNCTION, OnWritingMemoryBody, context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_WRITEDATA, context, context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HEADERFUNCTION, OnWritingMemoryHeader, context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HEADERDATA, context, context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HTTPHEADER, requestHeader, context);
// Some servers don't like requests that are made without a user-agent field, so we provide one
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_USERAGENT, HttpConstant::HTTP_DEFAULT_USER_AGENT, context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_FOLLOWLOCATION, 1L, context);
/* first #undef CURL_DISABLE_COOKIES in curl config */
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_COOKIEFILE, "", context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_NOSIGNAL, 1L, context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_TIMEOUT_MS, context->options.GetReadTimeout(), context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CONNECTTIMEOUT_MS, context->options.GetConnectTimeout(), context);
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HTTP_VERSION, context->options.GetHttpVersion(), context);
if (!SetOtherOption(curl, context)) {
return false;
}
return true;
}
将curl对象提交到消费队列
bool HttpExec::AddCurlHandle(CURL *handle, RequestContext *context)
{
if (handle == nullptr || staticVariable_.curlMulti == nullptr) {
NETSTACK_LOGE("handle nullptr");
return false;
}
std::thread emplaceInfoThread([context, handle] {
std::lock_guard guard(staticVariable_.curlMultiMutex);
staticVariable_.infoQueue.emplace(context, handle);
staticVariable_.conditionVariable.notify_all();
});
emplaceInfoThread.detach();
return true;
}
消费队列请求任务
void HttpExec::AddRequestInfo()
{
std::lock_guard guard(staticVariable_.curlMultiMutex);
int num = 0;
while (!staticVariable_.infoQueue.empty()) {
if (!staticVariable_.runThread || staticVariable_.curlMulti == nullptr) {
break;
}
auto info = staticVariable_.infoQueue.top();
staticVariable_.infoQueue.pop();
auto ret = curl_multi_add_handle(staticVariable_.curlMulti, info.handle);
if (ret == CURLM_OK) {
staticVariable_.contextMap[info.handle] = info.context;
}
++num;
if (num >= CURL_HANDLE_NUM) {
break;
}
}
}
curl发起请求
void HttpExec::SendRequest()
{
std::lock_guard guard(staticVariable_.curlMultiMutex);
int runningHandle = 0;
int num = 0;
do {
if (!staticVariable_.runThread || staticVariable_.curlMulti == nullptr) {
break;
}
auto ret = curl_multi_perform(staticVariable_.curlMulti, &runningHandle);
if (runningHandle > 0) {
ret = curl_multi_poll(staticVariable_.curlMulti, nullptr, 0, CURL_MAX_WAIT_MSECS, nullptr);
}
if (ret != CURLM_OK) {
return;
}
++num;
if (num >= CURL_HANDLE_NUM) {
break;
}
} while (runningHandle > 0);
}
curl接受响应数据
void HttpExec::ReadResponse()
{
std::lock_guard guard(staticVariable_.curlMultiMutex);
CURLMsg *msg = nullptr; /* NOLINT */
do {
if (!staticVariable_.runThread || staticVariable_.curlMulti == nullptr) {
break;
}
int leftMsg;
msg = curl_multi_info_read(staticVariable_.curlMulti, &leftMsg);
if (msg) {
if (msg->msg == CURLMSG_DONE) {
HandleCurlData(msg);
}
if (msg->easy_handle) {
(void)curl_multi_remove_handle(staticVariable_.curlMulti, msg->easy_handle);
(void)curl_easy_cleanup(msg->easy_handle);
}
}
} while (msg);
}
curl响应数据转换
取出map中对应的RequestContext 操作
void HttpExec::HandleCurlData(CURLMsg *msg)
{
if (msg == nullptr) {
return;
}
auto handle = msg->easy_handle;
if (handle == nullptr) {
return;
}
auto it = staticVariable_.contextMap.find(handle);
if (it == staticVariable_.contextMap.end()) {
NETSTACK_LOGE("can not find context");
return;
}
auto context = it->second;
staticVariable_.contextMap.erase(it);
if (context == nullptr) {
NETSTACK_LOGE("can not find context");
return;
}
NETSTACK_LOGI("priority = %{public}d", context->options.GetPriority());
context->SetExecOK(GetCurlDataFromHandle(handle, context, msg->msg, msg->data.result));
if (context->IsExecOK()) {
CacheProxy proxy(context->options);
proxy.WriteResponseToCache(context->response);
}
if (context->GetManager() == nullptr) {
NETSTACK_LOGE("can not find context manager");
return;
}
#ifdef ENABLE_EVENT_HANDLER
HttpEventHandlerCallback(context);
#endif
}
数据回调到js promise对象。
void HttpExec::HttpEventHandlerCallback(RequestContext *context)
{
std::mutex lock;
if (EventManager::IsManagerValid(context->GetManager())) {
if (context->IsRequestInStream()) {
...
} else {
NapiUtils::CreateUvQueueWorkEnhanced(context->GetEnv(), context, HttpAsyncWork::RequestCallback);
}
}
}
#endif
static void AsyncWorkCallback(napi_env env, napi_status status, void *data)
{
static_assert(std::is_base_of<BaseContext, Context>::value);
if (status != napi_ok) {
return;
}
char buffer[BUFFER_SIZE] = {0};
if (memset_s(buffer, BUFFER_SIZE, ASCII_ZERO, BUFFER_SIZE - 1) != EOK) {
NETSTACK_LOGE("memory operation fail");
}
auto deleter = [](Context *context) {
context->DeleteReference();
delete context;
};
std::unique_ptr<Context, decltype(deleter)> context(static_cast<Context *>(data), deleter);
size_t argc = 2;
napi_value argv[2] = {nullptr};
if (context->IsParseOK() && context->IsExecOK()) {
argv[0] = NapiUtils::GetUndefined(env);
if (Callback != nullptr) {
argv[1] = Callback(context.get());
} else {
argv[1] = NapiUtils::GetUndefined(env);
}
if (argv[1] == nullptr) {
return;
}
} else {
argv[0] = NapiUtils::CreateErrorMessage(env, context->GetErrorCode(), context->GetErrorMessage());
if (argv[0] == nullptr) {
NETSTACK_LOGE("AsyncWorkName %{public}s createErrorMessage fail", context->GetAsyncWorkName().c_str());
return;
}
argv[1] = NapiUtils::GetUndefined(env);
}
if (context->GetDeferred() != nullptr) {
if (context->IsExecOK()) {
napi_resolve_deferred(env, context->GetDeferred(), argv[1]);
} else {
napi_reject_deferred(env, context->GetDeferred(), argv[0]);
}
return;
}
napi_value func = context->GetCallback();
if (NapiUtils::GetValueType(env, func) == napi_function) {
napi_value undefined = NapiUtils::GetUndefined(env);
(void)NapiUtils::CallFunction(env, undefined, func, argc, argv);
}
so信息关系
/system/lib64/module/net/libhttp.z.so 依赖 /system/lib64/libcurl_shared.z.so
总结
鸿蒙的网络请求内部使用curl实现。
思考
libhttp.z.so封装了curl的调用 和与js api的交互。实现并不复杂,如过我们想做接口性能监控比如dns解析耗时,建连耗时的 可以利用curl提供的能力去做。
curl_easy_getinfo(curl, CURLINFO_NAMELOOKUP_TIME_T, &time_namelookup);
curl_easy_getinfo(curl, CURLINFO_CONNECT_TIME_T, &time_connect);
curl_easy_getinfo(curl, CURLINFO_PRETRANSFER_TIME_T,
&time_pretransfer);
curl_easy_getinfo(curl, CURLINFO_STARTTRANSFER_TIME_T,
&time_starttransfer);
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME_T, &time_total);