介绍

本篇 Codelab 依据网络模块以及 Webview 完成一次 HTTPS 恳求,并对其进程进行抓包剖析。作用如图所示:

相关概念

Webview:供给 Web 控制才干,Web 组件供给网页显现才干。

HTTP数据恳求:网络管理模块,供给 HTTP 数据恳求才干,支撑 GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT 恳求办法。

●HTTPS:应用层协议,支撑加密传输以及身份认证,确保数据的安全传输。

●SSL:SSL(SecureSocketLayer)安全套接层是坐落传输通讯协议(TCP/IP)之上完成的一种安全协议。

●TLS:TLS(TransportLayerSecurity)是一种安全协议,旨在完成数据加密传输。

完好示例

gitee源码地址

源码下载

HTTPS恳求进程(ArkTS).zip

环境建立

咱们首要需求完成 HarmonyOS 开发环境建立,可参照如下步骤进行。

软件要求

DevEcoStudio版别:DevEcoStudio3.1Release。

HarmonyOSSDK版别:APIversion9。

硬件要求

●设备类型:华为手机或运行在 DevEcoStudio 上的华为手机设备模拟器。

●HarmonyOS 体系:3.1.0DeveloperRelease。

环境建立

1. 装置 DevEcoStudio,详情请参阅下载和装置软件

2. 设置 DevEcoStudio 开发环境,DevEcoStudio 开发环境需求依赖于网络环境,需求衔接上网络才干确保东西的正常运用,能够依据如下两种情况来装备开发环境:

●假如能够直接拜访 Internet,只需进行下载HarmonyOSSDK操作。

●假如网络不能直接拜访 Internet,需求经过署理服务器才能够拜访,请参阅装备开发环境

3. 开发者能够参阅以下链接,完成设备调试的相关装备:

运用真机进行调试

运用模拟器进行调试

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

代码结构解读

本篇 Codelab 只对核心代码进行解说,关于完好代码,咱们会在源码下载或 gitee 中供给。

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

创立 HTTPS 恳求

HTTPS 协议是坐落应用层的一种安全传输协议,与 HTTP 最大的区别是服务端与客户端之间进行数据传输都会经过 TLS/SSL 加密。该示例恳求HarmonyOS官网,并将恳求得到的内容经过 Web 容器展现出来。作用如图所示:

首要在 HttpUtil.ets 中调用 createHttp 办法创立一个恳求使命,再经过 request 办法建议网络恳求。该办法支撑三个参数:url、options 以及 callback 回调,其间 options 能够设置恳求办法、恳求头以及超时时刻等。

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

接着在进口页面中调用上述封装的 httpGet 办法恳求指定网址,将恳求得到的内容嵌入到 Web 组件中。

//WebPage.ets
importhttpfrom'@ohos.net.http';
...
@Entry
@Component
structWebPage{
  @StatewebVisibility:Visibility=Visibility.Hidden;
  ...
  build() {
    Column() {
      ...
    }
  }
asynconRequest() {
    if (this.webVisibility===Visibility.Hidden) {
      this.webVisibility=Visibility.Visible;
      try {
letresult=awaithttpGet(this.webSrc);
        if (result&&result.responseCode===http.ResponseCode.OK) {
          this.controller.clearHistory();
          this.controller.loadUrl(this.webSrc);
        } 
      } catch (error) {
promptAction.showToast({
message: $r('app.string.http_response_error')
        })
      }
    } else {
      this.webVisibility=Visibility.Hidden;
    }
  }
}

剖析模块源码可知,经过 request 办法建立恳求后,模块底层首要会调用三方库libcurl中的 curl_easy_init 初始化一个简略会话。初始化完成后,接着调用 curl_easy_setopt 办法设置传输选项。其间 CURLOPT_URL 用于设置恳求的 URL 地址,对应 request 中的 url 参数;CURLOPT_WRITEFUNCTION 能够设置一个回调,保存接纳的数据;CURLOPT_HEADERDATA 支撑设置回调,在回调中保存呼应头数据。

//http_exec.cpp
boolHttpExec::RequestWithoutCache(RequestContext*context)
{
    if (!staticVariable_.initialized) {
        NETSTACK_LOGE("curlnotinit");
        return false;
    }
autohandle= curl_easy_init();
    ...
    if (!SetOption(handle,context,context->GetCurlHeaderList())) {
        NETSTACK_LOGE("setoptionfailed");
        return false;
    }
    ...
    return true;
}
...
boolHttpExec::SetOption(CURL*curl,RequestContext*context,structcurl_slist*requestHeader)
{
    conststd::string&method=context->options.GetMethod();
    if (!MethodForGet(method) && !MethodForPost(method)) {
        NETSTACK_LOGE("method%{public}snotsupported",method.c_str());
        return false;
    }
    if (context->options.GetMethod() ==HttpConstant::HTTP_METHOD_HEAD) {
        NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_NOBODY, 1L,context);
    }
    //设置恳求URL
    NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_URL,context->options.GetUrl().c_str(),context);
    ...
    //设置CURLOPT_WRITEFUNCTION传输选项,OnWritingMemoryBody为回调函数
    NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_WRITEFUNCTION,OnWritingMemoryBody,context);
    NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_WRITEDATA,context,context);
    //在OnWritingMemoryHeader写入呼应头数据
    NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_HEADERFUNCTION,OnWritingMemoryHeader,context);
    NETSTACK_CURL_EASY_SET_OPTION(curl,CURLOPT_HEADERDATA,context,context);
    ...
    return true;
}
...
#defineNETSTACK_CURL_EASY_SET_OPTION(handle,opt,data,asyncContext)
    do {            
CURLcoderesult= curl_easy_setopt(handle,opt,data);
        if (result!=CURLE_OK) {
            const char *err= curl_easy_strerror(result);
            NETSTACK_LOGE("Failedtosetoption:%{public}s,%{public}s%{public}d",#opt,err,result);
            (asyncContext)->SetErrorCode(result);
            return false;
        }

传输选项设置成功后,调用 curl_multi_perform 执行传输恳求,并经过 curl_multi_info_read 查询处理句柄是否有音讯回来,终究进入 HandleCurlData 办法处理回来数据。

//http_exec.cpp
voidHttpExec::SendRequest()
{
    ...
    do {
        ...
autoret= curl_multi_perform(staticVariable_.curlMulti, &runningHandle);
        ...
    } while (runningHandle> 0);
}
...
voidHttpExec::ReadResponse()
{
CURLMsg*msg=nullptr; /*NOLINT*/
    do {
        ...
msg= curl_multi_info_read(staticVariable_.curlMulti, &leftMsg);
        if (msg) {
            if (msg->msg==CURLMSG_DONE) {
                HandleCurlData(msg);
            }
        }
    } while (msg);
}

在 HandleCurlData 函数中调用 ParseHeaders 函数将上面回调写入的呼应头解析出来,其间呼应头中会携带客户端和服务端支撑的最高网络协议,假如是 HTTP/2 表示支撑 HTTPS 加密传输。

//http_exec.cpp
boolHttpExec::GetCurlDataFromHandle(CURL*handle,RequestContext*context,CURLMSGcurlMsg,CURLcoderesult)
{
    ...
context->response.ParseHeaders();
    return true;
}
//http_response.cpp
voidHttpResponse::ParseHeaders()
{
std::vector<std::string>vec=CommonUtils::Split(rawHeader_,HttpConstant::HTTP_LINE_SEPARATOR);
    for (constauto&header:vec) {
        if (CommonUtils::Strip(header).empty()) {
            continue;
        }
autoindex=header.find(HttpConstant::HTTP_HEADER_SEPARATOR);
        if (index==std::string::npos) {
header_[CommonUtils::Strip(header)] = "";
            NETSTACK_LOGI("HEAD:%{public}s",CommonUtils::Strip(header).c_str());
            continue;
        }
header_[CommonUtils::ToLower(CommonUtils::Strip(header.substr(0,index)))] =
CommonUtils::Strip(header.substr(index  1));
    }
}

将本篇 Codelab 中的网址协议头更改为 http 时,在 DevEcoStudio 的日志中看到服务端会回来 301 状况码永久重定向到 https,因此终究通讯依旧会经历 TLS 加密传输。

模块源码能够在 Gitee 开源库房 communication_netstack 中获取,本篇 Codelab 引证源码部分坐落 http_exec 文件中。

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

TLS/SSL 握手进程

本章节首要经过抓包数据剖析 TLS 协议的握手进程,其间包括交流参数、证书验证、密钥计算以及验证密钥等,抓包内容如图所示:

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

握手进程如图所示:

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

5.1第一次握手

依据上图中能够看到,客户端首要会进行第一次握手衔接,发送“ClientHello”音讯给服务端敞开一个新的会话衔接。剖析数据包得到,客户端在第一次握手时会向服务端传递协议版别号(TLS1.2)、随机数(ClientRandom,用于后续生成“会话密钥”)、SessionID 以及 CipherSuites(客户端支撑的暗码套件)。数据内容如图所示:

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

5.2第2次握手

服务端接纳到客户端数据后,将呼应数据经过“SeverHello”传递给客户端,包括随机数(SeverRandom,用于后续生成“会话密钥”)、协议版别号(TLS1.2)以及 CipherSuite(恣意选择一个客户端支撑的暗码套件),数据内容如图所示:

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

服务端传递“SeverHello”后,紧跟着会将 Certificate(证书)、“SeverKeyExchange”音讯以及“ServerHelloDone”音讯传递给客户端。此处侧重剖析“SeverKeyExchange”,数据内容如图所示:

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

5.3第三次握手

客户端收到“ServerHelloDone”音讯后,会将 ClientParams 数据传递给服务端,其间包括本身生成的椭圆曲线公钥(Pubkey),数据内容如图所示:

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

经过上述进程,客户端持有 ClientRandom、ServerRandom 以及 ServerParams,将 ServerParams 运用服务端公钥解密后得到“ServerKeyExchange”音讯中的暂时公钥,客户端运用 x25519 算法计算出预主密钥(PremasterSecret),然后再结合客户端随机数、服务端随机数以及预主密钥生成主密钥,终究构建“会话密钥”。“ChangeCipherSpec”音讯表示客户端已经生成密钥,并切换到加密模式。终究将之前所有的握手数据做一个摘要,再利用两边洽谈好的对称密钥进行加密,经过“EncryptedHandshakeMessage”音讯将加密数据传递给服务端做校验。数据内容如图所示:

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

5.4第四次握手

服务端利用 ClientRandom、ServerRandom 以及 ClientParams 计算得出“会话密钥”,向客户端传递“ChangeCipherSpec”和“EncryptedHandshakeMessage”音讯供客户端校验。当两边校验经往后,真实的数据才开始传输。

依据HarmonyOS的HTTPS恳求进程开发示例(ArkTS)

总结

您已经完成了本次 Codelab 的学习,并了解到以下知识点:

1. 运用 @ohos.net.http 建立一次 https 恳求。

2. 经过剖析 TLS/SSL 握手进程中的传输数据包来了解数据安全传输。