基于 HarmonyOS 的 HTTPS 请求过程开发示例(ArkTS)

介绍

本篇 Codelab 基于网络模块以及 Webview 实现一次 HTTPS 请求,并对其过程进行抓包分析。效果如图所示:

相关概念

● Webview:提供 Web 控制能力,Web 组件提供网页显示能力。

● HTTP数据请求:网络管理模块,提供 HTTP 数据请求能力,支持 GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT 请求方法。

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

● SSL:SSL(Secure Socket Layer)安全套接层是位于传输通信协议(TCP/IP)之上实现的一种安全协议。

● TLS:TLS(Transport Layer Security)是一种安全协议,旨在实现数据加密传输。

完整示例

gitee源码地址

源码下载

HTTPS请求过程(ArkTS).zip

环境搭建

我们首先需要完成 HarmonyOS 开发环境搭建,可参照如下步骤进行。

软件要求

● DevEco Studio版本:DevEco Studio 3.1 Release。 

● HarmonyOS SDK版本:API version 9。

硬件要求

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

● HarmonyOS 系统:3.1.0 Developer Release。

环境搭建

1.  安装 DevEco Studio,详情请参考下载和安装软件。

2.  设置 DevEco Studio 开发环境,DevEco Studio 开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:

● 如果可以直接访问 Internet,只需进行下载HarmonyOS SDK操作。

● 如果网络不能直接访问 Internet,需要通过代理服务器才可以访问,请参考配置开发环境。

3.  开发者可以参考以下链接,完成设备调试的相关配置:

● 使用真机进行调试

● 使用模拟器进行调试

代码结构解读

本篇 Codelab 只对核心代码进行讲解,对于完整代码,我们会在源码下载或 gitee 中提供。

├──entry/src/main/ets                // 代码区│  ├──common│  │  ├──constants│  │  │  ├──StyleConstants.ets       // 样式常量类 │  │  │  └──CommonConstants.ets      // 常量类│  │  └──utils│  │     ├──HttpUtil.ets             // 网络请求方法│  │     └──Logger.ets               // 日志打印工具类│  ├──entryability│  │  └──EntryAbility.ts             // 程序入口类│  └──pages│     └──WebPage.ets                 // 页面入口└──entry/src/main/resources          // 资源文件目录

创建 HTTPS 请求

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

首先在 HttpUtil.ets 中调用 createHttp 方法创建一个请求任务,再通过 request 方法发起网络请求。该方法支持三个参数:url、options 以及 callback 回调,其中 options 可以设置请求方法、请求头以及超时时间等。

// HttpUtil.etsimport http from '@ohos.net.http';export default async function httpGet(url: string) {  if (!url) {    return undefined;  }  let request = http.createHttp();  let options = {    method: http.RequestMethod.GET,    header: { 'Content-Type': 'application/json' },    readTimeout: CommonConstant.READ_TIMEOUT,    connectTimeout: CommonConstant.CONNECT_TIMEOUT  } as http.HttpRequestOptions;  let result = await request.request(url, options);  return result;}

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

// WebPage.etsimport http from '@ohos.net.http';...@Entry@Componentstruct WebPage {  @State webVisibility: Visibility = Visibility.Hidden;  ...  build() {    Column() {      ...    }  }
  async onRequest() {    if (this.webVisibility === Visibility.Hidden) {      this.webVisibility = Visibility.Visible;      try {        let result = await httpGet(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
bool HttpExec::RequestWithoutCache(RequestContext *context)
{
    if (!staticVariable_.initialized) {
        NETSTACK_LOGE("curl not init");
        return false;
    }
    auto handle = curl_easy_init();
    ...
    if (!SetOption(handle, context, context->GetCurlHeaderList())) {
        NETSTACK_LOGE("set option 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);
    }
    // 设置请求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;
}
...
#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;                                                                                \
        }                                                                                                \

传输选项设置成功后,调用 curl_multi_perform 执行传输请求,并通过 curl_multi_info_read 查询处理句柄是否有消息返回,最后进入 HandleCurlData 方法处理返回数据。

// http_exec.cppvoid HttpExec::SendRequest(){    ...    do {        ...        auto ret = curl_multi_perform(staticVariable_.curlMulti, &runningHandle);        ...    } while (runningHandle > 0);}...void HttpExec::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
bool HttpExec::GetCurlDataFromHandle(CURL *handle, RequestContext *context, CURLMSG curlMsg, CURLcode result)
{
    ...
    context->response.ParseHeaders();
    return true;
}
// http_response.cpp
void HttpResponse::ParseHeaders()
{
    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_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 时,在 DevEco Studio 的日志中看到服务端会返回 301 状态码永久重定向到 https,因此最终通信依旧会经历 TLS 加密传输。

模块源码可以在 Gitee 开源仓库 communication_netstack 中获取,本篇 Codelab 引用源码部分位于 http_exec 文件中。

TLS/SSL 握手过程

本章节主要通过抓包数据分析 TLS 协议的握手过程,其中包括交换参数、证书验证、密钥计算以及验证密钥等,抓包内容如图所示:

握手过程如图所示:

5.1 第一次握手

根据上图中可以看到,客户端首先会进行第一次握手连接,发送“Client Hello”消息给服务端开启一个新的会话连接。分析数据包得到,客户端在第一次握手时会向服务端传递协议版本号(TLS1.2)、随机数(Client Random,用于后续生成“会话密钥”)、Session ID 以及 Cipher Suites(客户端支持的密码套件)。数据内容如图所示:

5.2 第二次握手

服务端接收到客户端数据后,将响应数据通过“Sever Hello”传递给客户端,包括随机数(Sever Random,用于后续生成“会话密钥”)、协议版本号(TLS1.2)以及 Cipher Suite(任意选择一个客户端支持的密码套件),数据内容如图所示:

服务端传递“Sever Hello”后,紧跟着会将 Certificate(证书)、“Sever Key Exchange”消息以及“Server Hello Done”消息传递给客户端。此处着重分析“Sever Key Exchange”,数据内容如图所示:

5.3 第三次握手

客户端收到“Server Hello Done”消息后,会将 Client Params 数据传递给服务端,其中包含自身生成的椭圆曲线公钥(Pubkey),数据内容如图所示:

经过上述过程,客户端持有 Client Random、Server Random 以及 Server Params,将 Server Params 使用服务端公钥解密后得到“Server Key Exchange”消息中的临时公钥,客户端使用 x25519 算法计算出预主密钥(Premaster Secret),然后再结合客户端随机数、服务端随机数以及预主密钥生成主密钥,最终构建“会话密钥”。“Change Cipher Spec”消息表示客户端已经生成密钥,并切换到加密模式。最后将之前所有的握手数据做一个摘要,再利用双方协商好的对称密钥进行加密, 通过“Encrypted Handshake Message”消息将加密数据传递给服务端做校验。数据内容如图所示:

5.4 第四次握手

服务端利用 Client Random、Server Random 以及 Client Params 计算得出“会话密钥”,向客户端传递“Change Cipher Spec”和“Encrypted Handshake Message”消息供客户端校验。当双方校验通过后,真正的数据才开始传输。

总结

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

1.  使用 @ohos.net.http 建立一次 https 请求。

2.  通过分析 TLS/SSL 握手过程中的传输数据包来理解数据安全传输。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/126077.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

开发知识点-Django

Django 1 了解简介2 Django项目结构3 url 地址 和视图函数4 路由配置5 请求及响应6 GET请求和POST请求查询字符串 7 Django设计模式及模板层8 模板层-变量和标签9 模板层-过滤器和继承继承 重写 10 url反向解析11 静态文件12 Django 应用及分布式路由创建之后 注册 一下 13 模型…

力扣每日一题 ---- 2905. 找出满足差值条件的下标 II

这道题带有绝对值差的题&#xff0c;一看就是双指针的题&#xff0c;并且还带有两个限制&#xff0c;那么我们的做法就是 固定一个条件&#xff0c;维护一个条件 本题还用到了一个贪心思路&#xff0c;会介绍到 那我们怎么固定一个条件&#xff0c;维护一个条件&#xff1f; …

基于ssm的大学生社团管理系统

基于ssm的大学生社团管理系统 摘要 基于SSM的大学生社团管理系统是一个全面、高效的社团管理平台&#xff0c;旨在帮助大学生和社团管理员更方便、更快捷地进行社团活动的组织和管理。该系统基于Spring、SpringMVC和MyBatis&#xff08;简称SSM&#xff09;开发&#xff0c;这三…

CentOS Linux 系统镜像

CentOS Linux具有以下特点&#xff1a; 稳定性&#xff1a;CentOS Linux旨在提供一个稳定、可靠的服务器环境&#xff0c;适合用于关键业务应用和生产环境。高效性&#xff1a;CentOS Linux经过优化和调整&#xff0c;可以充分发挥硬件的性能&#xff0c;提高系统的整体效率。…

selenium自动化测试入门 —— 键盘鼠标事件ActionChains

在使用 Selenium WebDriver 做自动化测试的时候&#xff0c;会经常模拟鼠标和键盘的一些行为。比如使用鼠标单击、双击、右击、拖拽等动作&#xff1b;或者键盘输入、快捷键使用、组合键使用等模拟键盘的操作。在 WebDeriver 中&#xff0c;有一个专门的类来负责实现这些测试场…

DevChat:提升编程效率的AI编程助手

一、DevChat是什么&#xff1f; DevChat是一个集成了多种主流大模型的AI编程工具&#xff0c;专注于提升程序员的编程效率。它整合了ChatGPT、Codex等热门AI大模型&#xff0c;支持自然语言编程、代码编写、代码生成、代码补全等功能。DevChat最大的优势是一站式服务&#xff…

掌握未来技术趋势:深度学习与量子计算的融合

掌握未来技术趋势&#xff1a;深度学习与量子计算的融合 摘要&#xff1a;本博客将探讨深度学习与量子计算融合的未来趋势&#xff0c;分析这两大技术领域结合带来的潜力和挑战。通过具体案例和技术细节&#xff0c;我们将一睹这两大技术在人工智能、药物研发和金融科技等领域…

【HarmonyOS】HarmonyOS Test测试用例中一些断言API的使用

【关键词】 单元测试框架、HarmonyOS Test、assertThrowError、assertFail、assertEqual 【测试代码及测试结果展示】 这里以新建API9工程自动生成的ohosTest来编写单元测试代码。 1、 测试代码&#xff1a; import { describe, it, expect } from ohos/hypium import abil…

Win10 180天后怎么才能继续体验,自动保持续期,无需手动JH

环境: Win10 专业版 自制小程序 问题描述: Win10 180天后怎么才能继续体验,自动保持续期,无需手动JH 解决方案: 在执行本程序前需要以管理员身份运行!关闭杀毒软件,否则会失败,本方案只能在个人电脑测试体验, 只能用于学习测试体验 ,勿用与商业行为 1.先完全JH…

Hadoop 视频分析系统

视频分析系统 业务流程 原始数据 vedio.json {"rank":1,"title":"《逃出大英博物馆》第二集","dzl":"77.8","bfl":"523.9","zfl":"39000","type":"影视",&quo…

强化学习中广义策略迭代

一、广义策略迭代 策略迭代包括两个同时进行的交互过程&#xff0c;一个使价值函数与当前策略保持一致&#xff08;策略评估&#xff09;&#xff0c;另一个使策略在当前价值函数下变得贪婪&#xff08;策略改进&#xff09;。在策略迭代中&#xff0c;这两个过程交替进行&…

汽车之家车型_车系_配置参数数据抓取

// 导入所需的库 #include <iostream> #include <fstream> #include <string> #include <curl/curl.h> #include <regex>// 声明全局变量 std::string htmlContent; std::regex carModelRegex("\\d{4}-\\d{2}-\\d{2}"); std::regex ca…

matlab 点云最小二乘拟合平面(PCA法)

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。爬虫网站自重。 一、算法原理 见:matlab 点云最小二乘拟合平面(PCA法详细过程版)。 二、代码实现 clc;clear; %% --------

Qt/C++开发经验小技巧286-290

国内站点&#xff1a;https://gitee.com/feiyangqingyun 国际站点&#xff1a;https://github.com/feiyangqingyun 很多时候项目越写越大&#xff0c;然后就可能遇到&#xff0c;明明之前很简单的一段代码&#xff0c;运行的好好的&#xff0c;就那么几行几十行&#xff0c;为何…

TCP发送窗口、接收窗口以及其工作原理

1*KvfIrP_Iwq40uVdRZYGnQg.png 上面的图表是从发送方的角度拍摄的快照。我们可以将数据分为4组&#xff1a; 1.已发送并已确认的字节&#xff08;蓝色&#xff09;2.已发送但尚未确认的字节&#xff08;黄色&#xff09;3.未发送但接收方准备好接收的字节&#xff08;绿色&…

spring boot 中@Value读取中文配置时乱码

1.spring boot 读取application.properties 该文件是iso8859编码 如果是直接写中文 读取时会乱码 显示成?? 必须得转ascii码才能正常显示 其他方法测试也不行 Value("${apig.order.tiaokong.qianzi}") private String apigOrderTiaokongQianzi;

DTC商业模式研报 | 创新DTC策略利于提升业务灵活性和数字化体验

报告摘要 本报告由ADYEN制作。ADYEN是一家直营业务为支付的荷兰公司&#xff0c;通过整合包括银行卡、支付宝、Paypal在内的多种流行支付方式&#xff0c;它允许企业接受电子商务&#xff0c;移动和销售点付款&#xff0c;极大地简化了消费者支付的流程&#xff0c;目前该机构…

警惕!计算机服务器中了malox勒索病毒怎么办?勒索病毒解密数据恢复

警惕&#xff01;警惕&#xff01;企业老板们请注意&#xff0c;假的malox勒索病毒出现了&#xff0c;不要被malox勒索病毒骗了&#xff0c;能减少更多的经济损失。近期&#xff0c;云天数据恢复中心陆续接到很多企业的求助&#xff0c;企业的计算机服务器遭到了malox勒索病毒攻…

华为L410上制作内网镜像模板01

原文链接&#xff1a;华为L410上制作离线安装软件模板01 hello&#xff0c;大家好啊&#xff0c;今天给大家带来一篇在内网搭建Apache服务器&#xff0c;用于安装完内网操作系统后&#xff0c;在第一次开机时候&#xff0c;为系统安装软件&#xff0c;今天给大家用WeChat举例&a…

java中的对象克隆(浅克隆和深克隆)

在实际项目中&#xff0c;一个模型类需要为不同的层提供不同的模型。VO DO DTO 需要将一个对象中的数据克隆到其他对象中。 误区&#xff1a;这种形式的代码复制的是引用&#xff0c;即对象在内存中的地址&#xff0c;stu1和stu2两个引用指向的是同一个对象 Student stu1 new…