鸿蒙4.0网络库实现分析

671 阅读7分钟

网络请求

官方http使用文档

使用该功能需要申请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,
    });

源码分析

网络模块

gitee.com/openharmony…

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, &paramsCount, 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);