背景
HarmonyOS Next版中国移动app在开发接入小程序时,发现有一个小程序中的登录信息校验的网络请求一直在报错。排查发现这个接口的入参不正确,通过反编译小程序的源码发现该接口的入参是依赖另一个接口响应头中set-cookie字段的值。打断点对比HarmonyOS Next系统和安卓系统的set-cookie字段值,HarmonyOS Next上值是array类型,安卓上是string类型。
而且抓包看到的服务的响应值也只是一个单纯的文本。
所以,不得不怀疑是鸿蒙系统的网络库把HttpResponse的header中set-cookie值封装成了array类型。
分析源码
http请求是调用OpenHarmony的ohos.net.http的api,所以找到communication_netstack源码库。
HttpResponse的header中除了set-cookie其它字段的值都没问题,所以直接在代码库中搜索"set-cookie"。从定义的常量名就能看出来是和处理http响应有关的。
arduino
##frameworks\js\napi\http\constant\src\constant.cpp
const char *const HttpConstant::RESPONSE_KEY_SET_COOKIE = "set-cookie";
下面我们就梳理下响应头的处理过程。
scss
##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;
}
DD一下: 欢迎大家关注公众号<程序猿百晓生>,可以了解到以下知识点。
erlang
`欢迎大家关注公众号<程序猿百晓生>,可以了解到以下知识点。`
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案)
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......
先跳到HttpResponse处理header的代码:
css
##frameworks\js\napi\http\options\src\http_response.cpp
void HttpResponse:[图片上传失败...(image-ac05dd-1729756405605)]
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中响应头处理的流程:
arduino
##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属性特殊处理。