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.mzph.cn/news/26133.shtml

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

相关文章

深度学习——全维度动态卷积ODConv

ODConv(OMNI-DIMENSIONAL DYNAMIC CONVOLUTION)是一种关注了空域、输入通道、输出通道等维度上的动态性的卷积方法&#xff0c;因此被称为全维度动态卷积。 part1. 什么是动态卷积 动态卷积就是对卷积核进行线性加权 第一篇提出动态卷积的文章也是在SE之后&#xff0c;他提出…

快速排序【Java算法】

文章目录 1. 概念2. 思路3. 代码实现 1. 概念 快速排序是一种比较高效的排序算法&#xff0c;采用 “分而治之” 的思想&#xff0c;通过多次比较和交换来实现排序&#xff0c;在一趟排序中把将要排序的数据分成两个独立的部分&#xff0c;对这两部分进行排序使得其中一部分所有…

Flask项目打包为exe(附带项目资源,静态文件)

1.在项目根目录创建my_app.spec文件&#xff0c;内容如下&#xff1a; # -*- mode: python ; coding: utf-8 -*-block_cipher Nonea Analysis([server.py], # flask入口pathex[],binaries[], datas[("E:/**/templates","/templates"),("E:/**/s…

绝对领跑!清华最新报告评估,文心大模型3.5稳坐国内第一

近日&#xff0c;清华大学新闻与传播学院沈阳团队发布《大语言模型综合性能评估报告》&#xff08;下文简称“报告”&#xff09;&#xff0c;报告显示百度文心一言在三大维度20项指标中综合评分国内第一&#xff0c;超越ChatGPT&#xff0c;其中中文语义理解排名第一&#xff…

无涯教程-Perl - each函数

描述 在列表context中调用此函数时,将返回一个由2个元素组成的列表,该列表由哈希的下一个元素的键和值组成,以便您可以对其进行迭代。在标量context中调用时,仅返回哈希中下一个元素的键。 语法 以下是此函数的简单语法- each HASH返回值 在列表context中调用此函数时,将返…

(文章复现)建筑集成光储系统规划运行综合优化方法matlab代码

参考文献&#xff1a; [1]陈柯蒙,肖曦,田培根等.一种建筑集成光储系统规划运行综合优化方法[J].中国电机工程学报,2023,43(13):5001-5012. 1.基本原理 本文建立的双层耦合模型内、外层分别对应求解容量配置与能量调度问题。外层模型设置光伏与储能容量备选集并将容量配置组合…

实战项目——多功能电子时钟

一&#xff0c;项目要求 二&#xff0c;理论原理 通过按键来控制状态机的状态&#xff0c;在将状态值传送到各个模块进行驱动&#xff0c;在空闲状态下&#xff0c;数码管显示基础时钟&#xff0c;基础时钟是由7个计数器组合而成&#xff0c;当在ADJUST状态下可以调整时间&…

17款奔驰S400升级原厂前排座椅通风系统,夏天必备的功能

通风座椅的主动通风功能可以迅速将座椅表面温度降至适宜程度&#xff0c;从而确保最佳座椅舒适性。该功能启用后&#xff0c;车内空气透过打孔皮饰座套被吸入座椅内部&#xff0c;持续时间为 8 分钟。然后&#xff0c;风扇会自动改变旋转方向&#xff0c;将更凉爽的环境空气从座…

拦截器——Interceptor及与过滤器区别

目录 spring中拦截器 过滤器跟拦截器的区别 HandlerInterceptor拦截器 拦截器工作原理 拦截器使用场景 定义拦截器 LoginInterceptor 注册拦截器 MethodInterceptor拦截器 方式一&#xff1a;继承 MethodInterceptor 方式二&#xff1a;基于注解的AspectJ方…

用于大型图像模型的 CNN 内核的最新内容

一、说明 由于OpenAI的ChatGPT的巨大成功引发了大语言模型的繁荣&#xff0c;许多人预见到大图像模型的下一个突破。在这个领域&#xff0c;可以提示视觉模型分析甚至生成图像和视频&#xff0c;其方式类似于我们目前提示 ChatGPT 的方式。 用于大型图像模型的最新深度学习方法…

基于自组织竞争网络的患者癌症发病预测(matlab代码)

1.案例背景 1.1自组织竞争网络概述 前面案例中讲述的都是在训练过程中采用有导师监督学习方式的神经网络模型。这种学习方式在训练过程中,需要预先给网络提供期望输出,根据期望输出来调整网络的权重,使得实际输出和期望输出尽可能地接近。但是在很多情况下,在人们认知的过程中…

第八篇: K8S Prometheus Operator实现Ceph集群企业微信机器人告警

Prometheus Operator实现Ceph集群企业微信告警 实现方案 我们的k8s集群与ceph集群是部署在不同的服务器上&#xff0c;因此实现方案如下&#xff1a; (1) ceph集群开启mgr内置的exporter服务&#xff0c;用于获取ceph集群的metrics (2) k8s集群通过 Service Endponit Ser…

【VALSE2023】0610 胡瀚《视觉自监督学习年度进展评述》

from&#xff1a; https://www.bilibili.com/video/BV1J44y1w79r 文章目录 自监督学习年度进展技术进展趋势一&#xff1a;掩码图像建模的改进技术进展二&#xff1a;发现掩码图像建模对**大模型**比较友好技术进展三&#xff1a;针对**小模型**的掩码图像建模训练技术进展四&a…

Unity Shader编辑器工具类ShaderUtil 常用函数和用法

Unity Shader编辑器工具类ShaderUtil 常用函数和用法 Unity的Shader编辑器工具类ShaderUtil提供了一系列函数&#xff0c;用于编译、导入和管理着色器。本文将介绍ShaderUtil类中的常用函数和用法。 编译和导入函数 CompileShader 函数签名&#xff1a;public static bool C…

web前端转正工作总结范文5篇

web前端转正工作总结&#xff08;篇1&#xff09; 来到__有限公司已经三个月了&#xff0c;目前的工作是前端开发&#xff0c;我是一名应届毕业生&#xff0c;之前没有过工作经验&#xff0c;在刚来到__这个大家庭的时候&#xff0c;我就被这里的工作气氛深深地吸引&#xff0…

Elastic的下载

文章目录 ElasticSearch的下载扩展1&#xff08;ElasticSearch 与 JDK 版本 适配&#xff09;扩展2&#xff08;访问 http://192.168.1.200:9200 没有显示信息&#xff09;扩展3&#xff08;免密登录&#xff09; ElasticSearch的下载 官方下载网址&#xff1a;https://www.el…

在 Ubuntu 上安装 Docker 桌面

Ubuntu 22.04 (LTS) 安装 Docker 桌面 要成功安装 Docker Desktop&#xff0c;您必须&#xff1a; 满足系统要求拥有 64 位版本的 Ubuntu Jammy Jellyfish 22.04 (LTS) 或 Ubuntu Impish Indri 21.10。对于非 Gnome 桌面环境&#xff0c;必须安装 gnome-terminal&#xff1a;…

数字孪生技术的实用价值体现在哪?

随着科技的不断进步&#xff0c;数字孪生技术已成为引领未来发展的重要驱动力。数字孪生是将现实世界与数字世界紧密结合的技术&#xff0c;通过创建虚拟的物理模型&#xff0c;实时模拟和分析真实世界中的物体和过程&#xff0c;让数字孪生在各个领域都展现出了巨大的潜力&…

nvm下载node导致npm报错无法使用

有个依赖库需要更新下node&#xff0c;用nvm下载后项目跑不起来了&#xff0c;npm -v 还报错 其实一开始是npm下载不来&#xff0c;然后换了淘宝镜像后还是报错 然后就只能手动下载下了 进入node.js官网 https://nodejs.org/en/download 下载后注意要安装在你nvm目录中&#x…

Dockerfile构建LNMP镜像

建立工作目录 [rootlocalhost ~]# mkdir lnmp [rootlocalhost ~]# cd lnmp/ 编写Dockerfile文件 [rootlocalhost lnmp]# vim Dockerfile [rootlocalhost lnmp]# ll 总用量 4 -rw-r--r--. 1 root root 774 8月 3 14:54 Dockerfile [rootlocalhost lnmp]# vim Dockerfile #基础…