QtWebApp开发https服务器,完成客户端与服务器基于ssl的双向认证,纯代码操作

引言:所谓http协议,本质上也是基于TCP/IP上服务器与客户端请求和应答的标准,web开发中常用的http server有apache和nginx。Qt程序作为http client可以使用QNetworkAccessManager很方便的进行http相关的操作。Qt本身并没有http server相关的库,也许是因为很少有这种需求吧。但是实际开发中也会有做简单的http server的需求。实际上QT6.4里面引入了基于http的服务器,但是看了下,也是非常的鸡肋。

大概有以下几个基于QT/C++的http服务器的库。

一、qthttpserver

前面说到,Qt官方在发行版中并没有提供相应的类来实现HTTP 服务端,但是Qt-lab中提供了一个qthttpserver,​ ​Github地址在这里​​,不知道基于什么原因Qt官方没有将其纳入正式的Qt发行版中,如果要使用qthttpserver的话,就得自己下载源码进行编译了,不过编译可没那么容易,会遇到各种错误,而且还没有提供说明文档,这就不太友好了。
关于编译或简单使用参考以下文章:

Qt开发的轻量级http服务器-QtHttpServer编译
关于Qt HttpServer的一些测试(Qt6.4.0rc)
QtHttpServer编译使用,亲测可用
QT基于qhttp-server搭建http服务器

二、cpp-httplib

这是Github上一个开源的C++实现http server的项目,使用方法非常简单,就只有一个头文件,引入到自己的项目中就可以使用了,不过这是一个多线程“阻塞”HTTP 库,如果您正在寻找“非阻塞”库,那就要换别的了。具体使用可以参考Github里面的文档介绍,很详细。​ ​

三、 QtWebApp

QtWepApp 是 C++ 中的 HTTP 服务器库,适用于 Linux、Windows、Mac OS 和Qt 框架支持的许多其他操作系统。QtWebApp要求Qt版本至少是 Qt5.15 及其以上
这个库使用也非常简单,并且很明显它是用QT写的,所以与Qt的项目可以无缝衔接。
但并不是QT官方写的,不过它的名气比Qt官方qthhttpserver库要大。

有趣的是,Qt制造商多年来一直在开发标准HTTP服务器,但到2022年,它仍然不包括在Qt库中。这也许可以解释为什么很多人使用的库。
实际上QT6.4里面引入了基于http的服务器,但是看了下,也是非常的鸡肋。

本次选择更为实用的QtWebApp来作为http服务器。

源码下载:http://www.stefanfrings.de/qtwebapp/index-en.html
Project homepage:http://stefanfrings.de/qtwebapp/index-en.html
Tutorial:http://stefanfrings.de/qtwebapp/tutorial/index.html
API doc:http://stefanfrings.de/qtwebapp/api/index.html

在这里插入图片描述

关于QtWebApp的使用,可以直接阅读源码提供的demo1,结合日志可以很快的理解。
更好的是直接参考QtWebApp的使用网站:Tutorial,真的是非常详细,,不过是英文版的,需要具备一定的英语阅读能力。网站如下图所示。

在这里插入图片描述
可以直接把源码模块直接引入到工程里面使用,源码里面就3个模块{httpserver、logging、templateengine},按需引入即可,,,httpserver模块是最核心的,肯定是要引入的。。。
也可以将源码编译成库,,然后再通过头文件+库的形式去倒腾,,,,
推荐直接引入源码,首先QtWebApp的模块并不大,,而且可以随时调试源码或修改源码,如下文陈述,就涉及到了修改源码。

四、双向认证

之前,使用nginx作为https服务器,与客户端进行了双向认证和单向认证。
参考以下两篇文章,本篇是在以下两篇文章的基础上继续开发的,,证书、私钥等的制作及流程验证在以下文章中均作了详细的说明。所以,阅读此篇文章前务必首先阅读以下两篇文章。

ssl单向证书和双向证书校验测试及搭建流程
QT充当客户端模拟浏览器等第三方客户端对https进行双向验证

此处,采用QtWebApp来手动开发https服务器,来完成与客户端之间基于ssl的双向认证
客户端:win10 + Qt5.9.9,192.168.64.1
服务端:win10 + Qt6.4, 192.168.64.176

此时直接将QtWebApp的demo1拷过来,并在配置文件中指定证书及秘钥,证书和秘钥在之前的文章中都倒腾过,并且直接使用之前做好的,可以参考ssl单向证书和双向证书校验测试及搭建流程一文。
并将配置文件的端口改为443,这是https的默认端口,,(当然也可以设置为其他端口,写为443时在客户端向服务器发起请求时无需手动指定端口号,否则必须手动指定端口号)

在这里插入图片描述

此时运行https服务,发现报错如下,,,,,,,说白了,要想支持https,Qt的版本至少是5.15。。
在这里插入图片描述

好,此时去虚拟机中安装Qt6.4。。
安装完毕后,,运行程序,此时报错缺少 core5compat模块。。。

在这里插入图片描述

于是,去Qt里添加 Qt 5 Compatibility Module组件,,
在这里插入图片描述

倒腾完成后,此时运行,一切OK。。接下来首先确保主机和虚拟机互ping通(注意关闭防火墙)。
结果访问 https://192.168.64.176 时,无任何响应。。
wireshark抓包观察,结果可以明显看出ssl并没有建立成功,client发出第一次握手clienthello时,服务端压根没有任何回应。。。。服务端连最基本的serverhello都没有回应。。

在这里插入图片描述
好,再通过curl工具(可以看作是客户端)看看,,可以看出curl访问时是一直在那儿等着服务端的回应。。
但服务端那边迟迟无响应,,最终握手失败。

curl -k --cert ./client.crt --key ./client.key --show-error  https://192.168.64.176 -v

请添加图片描述
在这里插入图片描述

ssl握手失败的情况比较多,,可以参考下两篇文章:https://zhuanlan.zhihu.com/p/572516140
https://www.jiyik.com/tm/xwzj/network_287.html ,说实话,此时你压根就不知道是哪个环节出了问题。。这两篇文章只能是大概看看,有点印象罢了。。。

好,我们来看看服务端的日志,,,

在这里插入图片描述

可以发现,实际上没有任何有用的信息。。
此时只能去阅读QtWebApp的源码。。= > 柳暗花明又一村。。。

在这里插入图片描述

因为https服务器的创建肯定是要加载服务端证书和私钥的,,那就只好去看看它是怎么加载的,,在httpconnectionhandlerpool.cpp 源码的140行处是加载服务端私钥的地方。因为之前在通过openssl创建服务端的私钥时指定了私钥的密码123456,而源码中通过QSSLKey创建秘钥时却没有指定密码,,,所以导致出现了客户端发起握手请求clienthello时,服务端无动于衷,,,实际上就是因为服务器加载私钥有误导致的。。。这里吐槽下,QtWebApp这地方处理的不好,,这种错误应该是要以日志的形式呈现给调试者的,,,,,最终导致握手失败。。。

修改完源码后,此时重新启动服务端程序,,,先用curl先测试一把,,
在这里插入图片描述
果然,此时握手已经成功了,客户端也接受了来自服务端的响应。
从此处我们也可以看出,,之前我们一开始通过curl工具握手失败时,分析查看了curl的相关提示,,,以及服务端QtWebApp的日志,都没有看出有价值的提示,,,所以这种情况下,还是需要去通过分析以及修改源码大法来倒腾的,,,

【注意】此处使用curl命令之前需要首先导入客户端p12格式的证书,可阅读此篇文章:ssl单向证书和双向证书校验测试及搭建流程 中的双向证书校验小节内容,此处不再赘述。。。
【值得注意的是】:在 [ssl单向证书和双向证书校验测试及搭建流程] 一文中使用curl工具验证时,并没有事先导入客户端p12格式的证书就直接验证了,也是可以的,当然了,那时的https服务器是基于linux下的nginx的此处验证时发现必须首先导入客户端p12格式的证书,否则直接响应连接失败:schannel: failed to receive handshake, SSL/TLS connection failed具体原因未知,,待后倒腾。。
在这里插入图片描述

好,抓包看看,客户端和服务器之间是否向彼此发送了证书。
在这里插入图片描述
答案是肯定的,,因为只有双向认证的情况下,客户端才会向服务端发送证书。

书归正传,此时,我们已经通过QtWebApp以代码的形式创建了https服务器,,
接下来,我们的客户端就要通过我们自己编写的代码去替代刚才的curl工具或浏览器这种集成的第三方客户端去向服务器发起https请求,,,

void MainWindow::testSsl()
{// 加载客户端证书QFile crtFile("C:\\Users\\XingWei\\Documents\\untitled19\\client.crt");crtFile.open(QIODevice::ReadOnly);QSslCertificate certificate(&crtFile, QSsl::Pem);crtFile.close();// 加载客户端私钥QByteArray passPhrase("123456");  // 私钥的密码QFile keyFile("C:\\Users\\XingWei\\Documents\\untitled19\\client.key");keyFile.open(QIODevice::ReadOnly);QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, passPhrase);keyFile.close();// ssl配置QSslConfiguration conf;conf.setPeerVerifyMode(QSslSocket::QueryPeer);conf.setProtocol(QSsl::TlsV1SslV3);conf.setPrivateKey(privateKey);conf.setLocalCertificate(certificate);QNetworkRequest request;request.setUrl(QUrl("https://192.168.64.176"));request.setSslConfiguration(conf);QEventLoop loop;QNetworkReply* reply = m_manager.post(request, QByteArray("AAAAAAAAAAAAAAAAAAAAA"));connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);connect(reply, &QNetworkReply::finished, this, [=] {qDebug() << "####################### get data finished...";QByteArray data = reply->readAll();qDebug() << data;});loop.exec();qDebug() << "#############> " << reply->errorString() << reply->error();
}

运行效果及抓包如下所示:
请添加图片描述

客户端和服务端建立起ssl通道后就可以将数据加密交互了。。

好,接下来,我们在https服务器中拦截来自客户端的不同请求路径,进而做出相应的回应。
好,在客户端通过向服务器发送不同的请求路径,看看响应效果如何。。。
因为demo1中提供了几个请求路径,此处就以demo1提供的请求路径为基点来测试。。。
并在服务端新增一个/hello的请求路径,,该路径向客户端响应给以HELLO,SSL.字符串。

修改上述的客户端代码如下:

// ...
QNetworkRequest request;
request.setUrl(QUrl("https://192.168.64.176/template"));
//    request.setUrl(QUrl("https://192.168.64.176/dump"));
//    request.setUrl(QUrl("https://192.168.64.176/hello"));
request.setSslConfiguration(conf);QEventLoop loop;
QNetworkReply* reply = m_manager.get(request);
// ...

服务端新增HelloController请求处理类相关代码:

在这里插入图片描述

void HelloController::service(HttpRequest &request, HttpResponse &response)
{response.setHeader("Content-Type", "text/html; charset=UTF-8");response.write("<html><body>");response.write("<h1>");response.write("HELLO, SSL.");response.write("</h1>");response.write("</body></html>",true);
}

在这里插入图片描述

访问/template路径时https服务器的响应:

"<html><body>\r\n\r\nHello,<br>\r\nyou requested the path: /template\r\n<p>\r\nAnd your web browser provided the following headers:\r\n<p>\r\n\r\n  <b>accept-encoding:</b> gzip, deflate<br>\r\n\r\n  <b>accept-language:</b> zh-CN,en,*<br>\r\n\r\n  <b>connection:</b> Keep-Alive<br>\r\n\r\n  <b>host:</b> 192.168.64.176<br>\r\n\r\n  <b>user-agent:</b> Mozilla/5.0<br>\r\n\r\n\r\n</html></body>\r\n"

访问/dump时的响应:

"<html><body><b>Request:</b><br>Method: GET<br>Path: /dump<br>Version: HTTP/1.1<p><b>Headers:</b><br>accept-encoding=gzip, deflate<br>accept-language=zh-CN,en,*<br>connection=Keep-Alive<br>host=192.168.64.176<br>user-agent=Mozilla/5.0<p><b>Parameters:</b><p><b>Cookies:</b><p><b>Body:</b><br></body></html>"

访问/hello时的响应:

"<html><body><h1>HELLO, SSL.</h1></body></html>"

用浏览器来验证一下我们的请求,更加直观,如下所示:
【注意】:此处使用浏览器验证时,必须首先导入客户端p12格式的证书,否则是无法访问的。
关于客户端证书的导入,阅读此篇文章:ssl单向证书和双向证书校验测试及搭建流程中的双向证书校验小节内容,此处不再赘述。。。
请添加图片描述

由此可见,一切正常。
当https建立后,之前基于http写的请求路径以及对应的处理函数都不用作任何改变,直接向访问http那样访问就可以了,只不过此时需要变成https了。。
由此可见,https只不过是在http的请求基础上加了一层加密。

关于http的明文传输和https的加密传输,可以通过抓包来看。
可进一步阅读 QtWebApp同时开启http服务和https服务,接受来自客户端的不同请求并进行相应的处理 一文中 测试效果小节 所对应的内容。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023上半年手机及数码行业分析报告(京东销售数据分析)

2023年上半年&#xff0c;手机市场迎来复苏&#xff0c;同环比来看&#xff0c;销量销额纷纷上涨。 而数码市场中&#xff0c;各个热门品类表现不一。微单相机及智能手表同比去年呈现增长态势&#xff0c;而笔记本电脑市场则出现下滑。 基于此现状&#xff0c;鲸参谋发布了20…

Cesium 加载ArcGIS Server切片服务错级问题

1.首先上官方api说明 ArcGisMapServerImageryProvider - Cesium Documentation 里面没有 zoomoffset参数!!! 2.如果按照互联网栅格切片规则 3857、4326、4490常用切片层级参数,则直接加载显示地图 viewer.imageryLayers.addImageryProvider(new Cesium.ArcGisMapServerI…

数据结构——AVL树

文章目录 一.AVL树的定义二.AVL树的插入三.插入后更新平衡因子四.AVL树的旋转1.左单旋2.右单旋3.先左单旋再右单旋4.先右单旋再左单旋 五.AVL树的性能分析六.检查是否满足AVL树七.源码 一.AVL树的定义 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉…

Python识别抖音Tiktok、巨量引擎滑块验证码识别

由于最近比较忙&#xff0c;所以本周搞了一个相对简单的验证码&#xff0c;就是抖音Tiktok的滑块验证码&#xff0c;这也是接到客户的一个需求。这种验证码通常在电脑端登录抖音、巨量引擎的的时候出现。 首先看一下最终的效果&#xff1a; 验证码识别过程 1、利用爬虫采集图…

mysql再docker中运行,直接在实体机上运行mysql命令初始化数据库数据

背景 项目上我们使用docker安装mysql&#xff0c;项目启动的时候需要利用sql语句初始化数据。 直接在实体上是识别不到mysql命令的。 实现方式 实现方式1&#xff1a;在docker容器内部执行sql语句 1. 将sql文件上传到容器内 docker cp /root/1.sql d5:/home/ 说明&#…

【数据结构OJ题】移除元素

原题链接&#xff1a;https://leetcode.cn/problems/remove-element/ 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 方法一&#xff1a;暴力删除&#xff0c;挪动数据覆盖。即遍历整个nums[ ]数组&#xff0c;遇到值等于val的元素&#xff0c;就将整…

Jmeter入门之digest函数 jmeter字符串连接与登录串加密应用

登录请求中加密串是由多个子串连接&#xff0c;再加密之后传输。 参数连接&#xff1a;${var1}${var2}${var3} 加密函数&#xff1a;__digest &#xff08;函数助手里如果没有该函数&#xff0c;请下载最新版本的jmeter5.0&#xff09; 函数助手&#xff1a;Options > …

Redis 安装以及配置隧道连接

目录 1.CentOS 1. 安装Redis 2. Redis 启动和停⽌ 3. 操作Redis 2.Ubuntu 1. 安装Redis 2. Redis 启动/停⽌ 3. 操作 Redis 3.开启隧道 3.1 Xshell 配置隧道 3.2 windTerm 配置隧道 3.3 FinalShell配置隧道 4.可视化客户端连接 Another Redis Desktop Manager 1.Cen…

40.利用欧拉法求解微分方程组(matlab程序)

1.简述 求解微分方程的时候&#xff0c;如果不能将求出结果的表达式&#xff0c;则可以对利用数值积分对微分方程求解&#xff0c;获取数值解。欧拉方法是最简单的一种数值解法。前面介绍过MATLAB实例讲解欧拉法求解微分方程&#xff0c;今天实例讲解欧拉法求解一阶微分方程组。…

前端Vue入门-day08-vant组件库

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 vant 组件库 安装 导入 全部导入 按需导入 浏览器配饰 Viewport 布局 Rem 布局适配 vant 组件库 …

【深度学习】采用自动编码器生成新图像

一、说明 你知道什么会很酷吗&#xff1f;如果我们不需要所有这些标记的数据来训练 我们的模型。我的意思是标记和分类数据需要太多的工作。 不幸的是&#xff0c;大多数现有模型从支持向量机到卷积神经网&#xff0c;没有它们&#xff0c;卷积神经网络就无法训练。无监督学习不…

opencv基础-38 形态学操作-闭运算(先膨胀,后腐蚀)cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

闭运算是先膨胀、后腐蚀的运算&#xff0c;它有助于关闭前景物体内部的小孔&#xff0c;或去除物体上的小黑点&#xff0c;还可以将不同的前景图像进行连接。 例如&#xff0c;在图 8-17 中&#xff0c;通过先膨胀后腐蚀的闭运算去除了原始图像内部的小孔&#xff08;内部闭合的…