积分6 / 贡献0

提问0答案被采纳0文章3

作者动态

[经验分享] ohos.net.http请求HttpResponse header中set-ccokie值被转成array类型

中移-zhangzhou 显示全部楼层 发表于 2024-9-29 17:42:05
# 背景

HarmonyOS Next版中国移动app在开发接入小程序时,发现有一个小程序中的登录信息校验的网络请求一直在报错。排查发现这个接口的入参不正确,通过反编译小程序的源码发现该接口的入参是依赖另一个接口响应头中set-cookie字段的值。打断点对比HarmonyOS Next系统和安卓系统的set-cookie字段值,HarmonyOS Next上值是array类型,安卓上是string类型。

![文档1.jpg]( "文档1.jpg")

而且抓包看到的服务的响应值也只是一个单纯的文本。

![文档2.jpg]( "文档2.jpg")

所以,不得不怀疑是鸿蒙系统的网络库把HttpResponse的header中set-cookie值封装成了array类型。

# 分析源码

http请求是调用OpenHarmony的ohos.net.http的api,所以找到communication\_netstack源码库。

HttpResponse的header中除了set-cookie其它字段的值都没问题,所以直接在代码库中搜索“set-cookie”。从定义的常量名就能看出来是和处理http响应有关的。

```
##frameworks\js\napi\http\constant\src\constant.cpp

const char *const HttpConstant::RESPONSE_KEY_SET_COOKIE = "set-cookie";
```

下面我们就梳理下响应头的处理过程。

```
##frameworks\js\napi\http\http_exec\src\http_exec.cpp

#define NETSTACK_CURL_EASY_SET_OPTION(handle, opt, data, asyncContext)                                   \
    do {                                                                                                 \
        CURLcode result = curl_easy_setopt(handle, opt, data);                                           \
        if (result != CURLE_OK) {                                                                        \
            const char *err = curl_easy_strerror(result);                                                \
            NETSTACK_LOGE("Failed to set option: %{public}s, %{public}s %{public}d", #opt, err, result); \
            (asyncContext)->SetErrorCode(result);                                                        \
            return false;                                                                                \
        }                                                                                                \
    } while (0)

//注册接收header信息,执行回调函数 OnWritingMemoryHeader   
NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HEADERFUNCTION, OnWritingMemoryHeader, context);

//header回调
size_t HttpExec::OnWritingMemoryHeader(const void *data, size_t size, size_t memBytes, void *userData)
{
    auto context = static_cast<RequestContext *>(userData);
    if (context == nullptr) {
        return 0;
    }
    context->GetTrace().Tracepoint(TraceEvents::RECEIVING);
    if (context->GetSharedManager()->IsEventDestroy()) {
        context->StopAndCacheNapiPerformanceTiming(HttpConstant::RESPONSE_HEADER_TIMING);
        return 0;
    }
    context->response.AppendRawHeader(data, size * memBytes);
    if (CommonUtils::EndsWith(context->response.GetRawHeader(), HttpConstant::HTTP_RESPONSE_HEADER_SEPARATOR)) {
        //1、HttpResponse解析header信息
        context->response.ParseHeaders();
        if (context->GetSharedManager()) {
        //2、针对set-cookie处理header
            auto headerMap = new std::map<std::string, std::string>(MakeHeaderWithSetCookie(context));
            //3、提交任务回调ResponseHeaderCallback
    context->GetSharedManager()->EmitByUvWithoutCheck(ON_HEADER_RECEIVE, headerMap, ResponseHeaderCallback);
            auto headersMap = new std::map<std::string, std::string>(MakeHeaderWithSetCookie(context));
            context->GetSharedManager()->EmitByUvWithoutCheck(ON_HEADERS_RECEIVE, headersMap, ResponseHeaderCallback);
        }
    }
    context->StopAndCacheNapiPerformanceTiming(HttpConstant::RESPONSE_HEADER_TIMING);
    return size * memBytes;
}

```

先跳到HttpResponse处理header的代码:

```
##frameworks\js\napi\http\options\src\http_response.cpp

void HttpResponse:arseHeaders()
{
    std::vector<std::string> vec = CommonUtils::Split(rawHeader_, HttpConstant::HTTP_LINE_SEPARATOR);
    for (const auto &header : vec) {
        if (CommonUtils::Strip(header).empty()) {
            continue;
        }
        auto index = header.find(HttpConstant::HTTP_HEADER_SEPARATOR);
        if (index == std::string::npos) {
            header_[CommonUtils::Strip(header)] = "";
            NETSTACK_LOGD("HEAD: %{public}s", CommonUtils::Strip(header).c_str());
            continue;
        }
        //针对set-cookie,将值存到setCookie_数组
        if (CommonUtils::ToLower(CommonUtils::Strip(header.substr(0, index))) ==
            HttpConstant::RESPONSE_KEY_SET_COOKIE) {
            setCookie_.push_back(CommonUtils::Strip(header.substr(index + 1)));
            continue;
        }
        header_[CommonUtils::ToLower(CommonUtils::Strip(header.substr(0, index)))] =
            CommonUtils::Strip(header.substr(index + 1));
    }
}

```

再回到http\_exec.cpp中响应头处理的流程:

```
##frameworks\js\napi\http\http_exec\src\http_exec.cpp

//解析header信息后,处理set-cookie数据,返回header数据
static std::map<std::string, std::string> MakeHeaderWithSetCookie(RequestContext * context)
{
    std::map<std::string, std::string> tempMap = context->response.GetHeader();
    std::string setCookies;
    size_t loop = 0;
    // 从HttpResponse的setCookie_数组取值,如果有多个值,数据拼接
    for (const auto &setCookie : context->response.GetsetCookie()) {
        setCookies += setCookie;
        if (loop + 1 < context->response.GetsetCookie().size()) {
            setCookies += HttpConstant::RESPONSE_KEY_SET_COOKIE_SEPARATOR;
        }
        ++loop;
    }
    tempMap[HttpConstant::RESPONSE_KEY_SET_COOKIE] = setCookies;
    return tempMap;
}

//创建uv任务回调
static void ResponseHeaderCallback(uv_work_t *work, int status)
{
    (void)status;

    auto workWrapper = static_cast<UvWorkWrapper *>(work->data);
    napi_env env = workWrapper->env;
    //headr数据
    auto headerMap = static_cast<std::map<std::string, std::string> *>(workWrapper->data);
    auto closeScope = [env](napi_handle_scope scope) { NapiUtils::CloseScope(env, scope); };
    std::unique_ptr<napi_handle_scope__, decltype(closeScope)> scope(NapiUtils::OpenScope(env), closeScope);
    //napi创建header object
    napi_value header = NapiUtils::CreateObject(env);
    if (NapiUtils::GetValueType(env, header) == napi_object) {
        //针对header数据,set-cookie值转成array类型
        MakeHeaderWithSetCookieArray(env, header, headerMap);
    }
    std::pair<napi_value, napi_value> arg = {NapiUtils::GetUndefined(env), header};
    //ts层回调
    workWrapper->manager->Emit(workWrapper->type, arg);
    delete headerMap;
    headerMap = nullptr;
    delete workWrapper;
    workWrapper = nullptr;
    delete work;
    work = nullptr;
}

static void MakeHeaderWithSetCookieArray(napi_env env, napi_value header, std::map<std::string, std::string> *headerMap)
{
    for (const auto &it : *headerMap) {
        if (!it.first.empty() && !it.second.empty()) {
            if (it.first == HttpConstant::RESPONSE_KEY_SET_COOKIE) {
                //header信息中的set-cookie转成array
                MakeSetCookieArray(env, header, it);
                continue;
            }
            NapiUtils::SetStringPropertyUtf8(env, header, it.first, it.second);
        }
    }
}

static void MakeSetCookieArray(napi_env env, napi_value header,
                               const std::pair<const std::basic_string<char>, std::basic_string<char>> &headerElement)
{
//先前处理set-cookie数据转换成数组
    std::vector<std::string> cookieVec =
        CommonUtils::Split(headerElement.second, HttpConstant::RESPONSE_KEY_SET_COOKIE_SEPARATOR);
    uint32_t index = 0;
    auto len = cookieVec.size();
    auto array = NapiUtils::CreateArray(env, len);
    //napi创建成ts的array
    //不论set-cookie数据是一个还是多个,都会转换成array
    for (const auto &setCookie : cookieVec) {
        auto str = NapiUtils::CreateStringUtf8(env, setCookie);
        NapiUtils::SetArrayElement(env, array, index, str);
        ++index;
    }
    NapiUtils::SetArrayProperty(env, header, HttpConstant::RESPONSE_KEY_SET_COOKIE, array);
}

```

响应头处理的整体流程还是很简单的:1、http获取响应头后执行HttpExec::OnWritingMemoryHeader;2、HttpResponse:arseHeaders解析header数据后,MakeHeaderWithSetCookie处理set-cookie数据,如果有多个set-cookie数据,将数据拼接;3、创建uv任务回调ResponseHeaderCallback,创建ts层header对象,针对set-cookie处理,将set-cookie数据创建成ts层的array对象。

# 总结

通过上面的源码分析,使用ohos.net.http的api发送http请求,ohos.net.http的实现中将响应头的set-cookie字段数据做了针对处理,创建成了array类型回调到ts层。

后来咨询了华为,得到的回复:响应头中set-cookie可能有多个值,但header对象中属性名不能重复,所以将set-cookie解析成数组。不过,确实是挖了个坑。我们也只能在使用http.request的响应回调中再对header对象的set-cookie属性特殊处理。

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部