简介:面向Qt开发者的HTTP/HTTPS服务端学习与集成素材,围绕QtHttpServer库提供可直接查阅和编译的源码、示例测试用例及HTTPS所需SSL证书与密钥文件,帮助需要在Qt应用中嵌入API接口、静态资源服务或安全Web服务的开发者降低选型与上手成本。压缩包共264个文件、约6.75MB,以h/cpp源码、pro/pri工程配置、cmake与makefile构建脚本、dll/lib运行库及txt说明为主,另含证书、密钥和少量示例页面,基本覆盖从源码阅读、工程编译到运行验证的完整链路。已有560人学习,说明其对Qt网络编程与HTTPS实践具有参考价值。读者可借助测试用例理解请求处理流程,结合SSL配置掌握证书加载、握手与加密通信要点,并参考目录中的构建脚本将库集成进现有项目,适合具备一定Qt与网络协议基础、希望快速搭建安全HTTP服务的开发者查漏补缺。
1. 一次拷库文件的翻车现场
把 QtHttpServer 的库文件、测试用例和一套 SSL 文件一次性拷进项目,include不报错,CMake 却停在Could not find a package configuration file provided by Qt6HttpServer;换台机器编译过了,sslSetup一调用又崩在QSslKey构造上。这类翻车九成不怪代码,怪环境基线没对齐:QtHttpServer 到 Qt 6.4 才正式进主干,Qt 版本低于这条线,库文件放哪儿都白搭,SSL 文件生成得再规范也挂不上去。这篇按我实际装这套东西的顺序记录——先定版本和 SSL 后端,再装库文件,然后生成并挂载 SSL 文件,接着把测试用例接到 ctest 上,最后跑三级验收。适合准备用 Qt 起一个内嵌 HTTP/HTTPS 服务、又不想被 CMake 和证书折腾一整天的开发者。
2. 环境清单与版本基线:Qt 6.4 是 QtHttpServer 的分水岭
先把账算清楚,再动手。QtHttpServer 不是一个单独下载就能用的压缩包,它是 Qt 的一个附加模块,库文件(头文件 +.so/.dll/.a+ CMake 配置文件)必须和主 Qt 版本严格对应。同一台机器上装两个 Qt 版本、模块只装在其中一个是常见情况,后面 CMake 找不到组件,八成就是这个原因。
2.1 三张版本对照表:Qt、OpenSSL、CMake
不同 Qt 版本里 QtHttpServer 的成熟度差别很大,尤其是 SSL 相关接口。
| Qt 版本 | QtHttpServer 状态 | CMake 组件名 | 建议 OpenSSL |
|---|---|---|---|
| 6.2 及更早 | 未进主干,只能自己拉分支试 | 无 | 1.1.1 |
| 6.4 | 首次随 Qt 发布,接口仍在收敛 | Qt6::HttpServer | 1.1.1 或 3.0.x |
| 6.5 ~ 6.6 | 路由与 SSL 接口稳定,适合落地 | Qt6::HttpServer | 3.0.x |
| 6.7 及以上 | 推荐,测试用例同步更新 | Qt6::HttpServer | 3.0.x |
除了 Qt 本身,还有三样东西要卡版本:编译器至少支持 C++17;CMake 不低于 3.16(Qt 6 系列的硬性要求);OpenSSL 建议 3.0.x,因为 Qt 6.5 之后对 OpenSSL 3 的支持才真正稳。具体版本的细微行为差异,以你手里那份 Qt 文档为准,别拿 6.2 的教程套 6.6 的接口。
2.2 用三条命令确认 Qt 的 SSL 后端
装之前先做体检,比装完再排查省事得多。
# 1. 确认 Qt 版本与安装前缀,后面 CMAKE_PREFIX_PATH 要填这个路径 qtpaths6 --qt-query QT_VERSION qtpaths6 --qt-query QT_INSTALL_PREFIX # 2. 确认 HttpServer 的 CMake 配置文件是否已经落地 find "$(qtpaths6 --qt-query QT_INSTALL_LIBS)/cmake" -maxdepth 1 -iname "Qt6HttpServer*" # 3. 看系统 OpenSSL 版本 openssl version -a | head -3第二条命令若没有任何输出,说明模块压根没装,先别往下走。第三条只是看系统版本,真正要确认的是 Qt 运行时链接的是哪一个 OpenSSL,这一步得写个小程序探。
// ssl_probe.cpp:编译期和运行期的 OpenSSL 大版本必须一致 #include <QtNetwork/QSslSocket> #include <QtCore/QDebug> int main() { qInfo() << "build :" << QSslSocket::sslLibraryBuildVersionString(); qInfo() << "runtime:" << QSslSocket::sslLibraryVersionString(); qInfo() << "supportsSsl:" << QSslSocket::supportsSsl(); return QSslSocket::supportsSsl() ? 0 : 1; }sslLibraryBuildVersionString()是编译 Qt 时链接的版本,sslLibraryVersionString()是运行时实际加载的版本。两者大版本不一致时(比如 build 是 1.1.1、runtime 是 3.0.x),supportsSsl()会直接返回 false,表现就是 HTTPS 一条都通不了,但 HTTP 全正常,特别容易误判成证书问题。编译命令:
g++ ssl_probe.cpp -o ssl_probe \ $(qtpaths6 --qt-query QT_INSTALL_HEADERS | sed 's|^|-I|') \ -fPIC -lQt6Network -lQt6Core-fPIC不能省,Qt 6 的库默认按位置无关代码编译;头文件路径用qtpaths6反查,避免手写路径写错版本。
2.3 目录与安装前缀的约定
工程目录我一般固定成下面这个结构,证书和测试用例各归各位,后面 CMake 里引用路径不容易乱。
project/ ├── CMakeLists.txt ├── src/ │ └── main.cpp ├── certs/ # 本地生成的自签名证书,必须进 .gitignore │ ├── server.crt │ └── server.key └── tests/ ├── CMakeLists.txt └── tst_httpserver.cppcerts/目录单独放,是为了让测试用例和服务端代码用同一份证书,避免两边各生成一份导致指纹对不上。私钥不进版本库是底线,测试证书用脚本生成,谁跑测试谁生成,这一点后面会给出脚本。
3. 安装 QtHttpServer 库文件:包管理器、在线安装器、源码编译三条路
三条路选一条走通就行,不要混着来。混着装最典型的后果是同一台机器上出现两份Qt6HttpServerConfig.cmake,CMake 挑了错的那份,编译过了但链接的库 ABI 不匹配。
3.1 Debian 系发行版仓库直装
如果发行版仓库里的 Qt 版本已经到 6.4 以上,这是最省事的一条路。
sudo apt update sudo apt install -y \ qt6-base-dev \ qt6-httpserver-dev \ libssl-dev \ cmake ninja-build g++ # 装完确认配置文件位置,正常会落在 /usr/lib/x86_64-linux-gnu/cmake/Qt6HttpServer/ dpkg -L qt6-httpserver-dev | grep -i cmakeqt6-base-dev提供Qt6::Core和Qt6::Network,qt6-httpserver-dev才提供Qt6::HttpServer,两个包缺一不可。libssl-dev在源码编译和链接期都要用,即便你打算动态加载 OpenSSL,头文件也得有。仓库里如果没有qt6-httpserver-dev这个包名,说明发行版的 Qt 版本偏低,直接跳到 3.2。
RHEL 系对应的是qt6-qtbase-devel、qt6-qthttpserver-devel、openssl-devel,包名各发行版有出入,装之前先dnf search httpserver确认一下,别照着博客硬抄。
3.2 官方安装器补装与源码 CMake 编译
用在线安装器管理 Qt 的,走维护工具 → 添加或移除组件 → 展开目标 Qt 版本 → Additional Libraries → 勾选 Qt HTTP Server。这里最容易踩的坑是版本错位:kit 用的是 6.6.3,模块却勾了 6.7 的,安装器不报错,CMake 一样找不到。勾选前把版本号对一遍。
仓库里没有现成包、又不想换 Qt 版本的,就从源码编。把 Qt 源码包里的qthttpserver子目录单独拿出来编,安装前缀必须指向主 Qt 的同一个前缀。
cmake -S qthttpserver -B build-httpserver -G Ninja \ -DCMAKE_PREFIX_PATH=/opt/Qt/6.6.3/gcc_64 \ -DCMAKE_INSTALL_PREFIX=/opt/Qt/6.6.3/gcc_64 \ -DCMAKE_BUILD_TYPE=Release \ -DQT_BUILD_TESTS=ON cmake --build build-httpserver --parallel cmake --install build-httpserver几个参数值得单独说:
CMAKE_PREFIX_PATH指向已安装的 Qt,模块编译时要靠它找Qt6Core、Qt6Network,写错就是一堆Could not find错误。CMAKE_INSTALL_PREFIX必须和主 Qt 前缀一致,否则装完 CMake 依然找不到,等于白编。QT_BUILD_TESTS=ON会额外编出模块自带的测试目标,但这条依赖 Qt 内部构建体系,部分版本编不过。编不过别硬刚,用第 4 章自建的最小用例替代,功能覆盖更可控。
3.3 用一个最小 CMakeLists 验证库文件被找到
装完别急着写业务,先做一个只验证「能不能链接」的空壳工程。
cmake_minimum_required(VERSION 3.16) project(httpserver_smoke LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) # 6.4 是硬门槛,低于这个版本这里会直接报错,比后面链接失败好排查 find_package(Qt6 6.4 REQUIRED COMPONENTS Core Network HttpServer) qt_standard_project_setup() qt_add_executable(httpserver_smoke src/main.cpp) target_link_libraries(httpserver_smoke PRIVATE Qt6::Core Qt6::Network Qt6::HttpServer )find_package里的版本号不是装饰,写上 6.4 之后,版本不够会在配置阶段就失败,报错信息明确指向 Qt 版本,而不是等到链接期给你一串找不到符号。
// src/main.cpp:最小可跑的路由,先不挂 SSL #include <QHttpServer> #include <QHttpServerResponse> #include <QCoreApplication> #include <QHostAddress> #include <QDebug> int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QHttpServer server; // 返回纯文本,验证路由与响应链路 server.route("/ping", []() { return QHttpServerResponse("pong"); }); const quint16 port = server.listen(QHostAddress::LocalHost, 8080); if (port == 0) { qCritical() << "listen failed"; return 1; } qInfo() << "listening on" << port; return app.exec(); }listen()返回实际绑定的端口,返回 0 表示失败,一定要判。传 0 作端口时系统会随机分配一个空闲端口,这个特性在测试用例里非常有用,后面会用到。
cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH=/opt/Qt/6.6.3/gcc_64 cmake --build build ./build/httpserver_smoke & curl -s http://127.0.0.1:8080/ping # 期望输出 pongcurl打印出pong,说明库文件、CMake 配置、路由链路全通了。这一关不过,别往下看 SSL。
4. SSL 文件生成与挂载:让 HTTPS 从握手失败到 200
HTTPS 这条链路上,证书文件和代码是两半,任何一半出问题表现都一样:握手失败。所以先生成一份规范的证书,再写挂载代码,最后用三种客户端各验一遍。
4.1 用 openssl 生成自签名证书与私钥
测试环境用自签名就够,关键是 SAN 必须写对,现代客户端基本不看 CN 了。
#!/usr/bin/env bash # gen_cert.sh:生成测试用自签名证书,输出到 certs/ set -euo pipefail mkdir -p certs openssl req -x509 -newkey rsa:2048 -sha256 -days 825 -nodes \ -keyout certs/server.key \ -out certs/server.crt \ -subj "/C=CN/O=LocalTest/CN=localhost" \ -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" chmod 600 certs/server.key # 立刻回读校验,确认扩展真的写进去了 openssl x509 -in certs/server.crt -noout -subject -dates -ext subjectAltName-addext subjectAltName是这份脚本的核心:DNS:localhost保证用域名访问时校验通过,IP:127.0.0.1保证直接用回环地址访问时不报IP address mismatch。两个都写上,能省掉后面一半的排查时间。
-days 825不是随便写的,主流客户端对服务端证书有效期有上限要求,超过会被直接拒绝,测试证书也没必要设太长。-nodes表示私钥不加密,测试环境免去每次输入密码;生产环境记得去掉这个参数并加密码保护。chmod 600是防止私钥被同机其他用户读走,习惯动作。
4.2 sslSetup 的调用顺序与证书链处理
证书就绪后,服务端改成 HTTPS,核心就一行sslSetup。
// src/main.cpp:HTTPS 版本 #include <QHttpServer> #include <QHttpServerResponse> #include <QCoreApplication> #include <QHostAddress> #include <QSslCertificate> #include <QSslKey> #include <QFile> #include <QDebug> int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); // 1. 读证书与私钥,格式统一用 PEM const auto certs = QSslCertificate::fromPath("certs/server.crt", QSsl::Pem); QFile keyFile("certs/server.key"); if (certs.isEmpty() || !keyFile.open(QIODevice::ReadOnly)) { qCritical() << "load cert/key failed"; return 1; } const QSslKey key(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); QHttpServer server; // 2. sslSetup 必须在 listen 之前调用,顺序反了不会报错,只是仍旧跑明文 server.sslSetup(certs.first(), key); server.route("/ping", []() { return QHttpServerResponse("pong-over-tls"); }); const quint16 port = server.listen(QHostAddress::LocalHost, 8443); if (port == 0) { qCritical() << "listen failed, check port or ssl config"; return 1; } qInfo() << "https listening on" << port; return app.exec(); }几个要害点:
QSslCertificate::fromPath返回的是列表,因为 PEM 文件里可以有多张证书。链式证书时顺序很重要——叶子证书放最前,中间 CA 依次往后,根 CA 一般不随服务端下发。
QSslKey构造时显式传QSsl::Rsa和QSsl::PrivateKey。用 EC 密钥的话算法要改成QSsl::Ec,传错算法不会崩,但构造出来的 key 是 null,sslSetup静默失败,表现就是握手一直不成功,这种玄学问题最难查。
sslSetup的调用位置在listen之前,这是血泪经验。反过来写编译没问题,运行时服务端仍按明文处理,客户端一连就报wrong version number,很容易误判成端口配错。
4.3 三种客户端校验姿势与握手排错
服务端起来后,别急着改业务代码,先用手边的客户端验证一遍。
# 姿势一:跳过校验,只验证 TLS 链路是否建立 curl -k -s -o /dev/null -w "%{http_code}\n" https://127.0.0.1:8443/ping # 姿势二:带上自签名证书做完整校验,能过说明 SAN 与证书链都没问题 curl --cacert certs/server.crt -s https://localhost:8443/ping # 姿势三:看握手细节,排错时最有用 openssl s_client -connect 127.0.0.1:8443 \ -servername localhost -CAfile certs/server.crt </dev/null \ 2>/dev/null | grep -E "Verify return code|Protocol|Cipher"姿势一返回 200 只能说明 TLS 建立成功,证书有问题时-k照样能过,所以它只能证明「链路通」。姿势二才是真正的校验,--cacert指定信任我们自签的这张证书,能返回内容说明证书链和 SAN 都正确。姿势三看Verify return code: 0 (ok),同时能看到协商出的协议版本和加密套件,用来确认走的是 TLS 1.2 还是 1.3。
如果姿势二失败而姿势一成功,问题一定在证书本身,按这个顺序查:SAN 里有没有你访问用的那个名字、证书是否过期、私钥和证书是否配对(用openssl x509 -noout -modulus和openssl rsa -noout -modulus比对模数,一致才算配对)。
5. 测试用例接入 ctest:从编译到全绿
测试用例的价值在于把上面这些手工验证固化下来。QtHttpServer 自带的用例偏内部构建体系,自己写一个最小用例接进 ctest 更实际,也更贴合项目。
5.1 测试工程的 CMake 骨架
# tests/CMakeLists.txt find_package(Qt6 6.4 REQUIRED COMPONENTS Core Network HttpServer Test) qt_add_executable(tst_httpserver tst_httpserver.cpp) target_link_libraries(tst_httpserver PRIVATE Qt6::Core Qt6::Network Qt6::HttpServer Qt6::Test ) # 证书路径通过编译定义传进去,避免用例里写死相对路径 target_compile_definitions(tst_httpserver PRIVATE TEST_CERT_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../certs" ) add_test(NAME tst_httpserver COMMAND tst_httpserver) set_tests_properties(tst_httpserver PROPERTIES TIMEOUT 30)顶层CMakeLists.txt里记得开enable_testing()并add_subdirectory(tests),否则ctest找不到任何用例。TIMEOUT 30是给测试兜底,用例里如果listen挂了没退出事件循环,ctest 不会无限期卡住——这一点比本地手跑重要得多,CI 上卡死一次能耗掉半小时。
5.2 用例里 HTTPS 相关的断言怎么写
// tests/tst_httpserver.cpp #include <QtTest> #include <QHttpServer> #include <QHttpServerResponse> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QSslConfiguration> #include <QSslCertificate> class tst_HttpServer : public QObject { Q_OBJECT private slots: void initTestCase(); void plainRoute(); void httpsRoute(); private: QHttpServer m_server; QNetworkAccessManager m_nam; quint16 m_port = 0; }; void tst_HttpServer::initTestCase() { const auto certs = QSslCertificate::fromPath( QStringLiteral(TEST_CERT_DIR "/server.crt"), QSsl::Pem); QFile keyFile(QStringLiteral(TEST_CERT_DIR "/server.key")); QVERIFY2(!certs.isEmpty(), "server.crt 加载失败"); QVERIFY2(keyFile.open(QIODevice::ReadOnly), "server.key 打不开"); const QSslKey key(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); QVERIFY2(!key.isNull(), "私钥解析失败,检查算法或密码"); m_server.sslSetup(certs.first(), key); m_server.route("/ping", []() { return QHttpServerResponse("pong"); }); // 端口传 0 由系统分配,规避并发跑用例时的端口冲突 m_port = m_server.listen(QHostAddress::LocalHost, 0); QVERIFY2(m_port != 0, "listen 失败"); } void tst_HttpServer::httpsRoute() { QNetworkRequest req(QUrl(QStringLiteral("https://localhost:%1/ping").arg(m_port))); // 把自签名证书塞进这次请求的信任链,才能走完整校验 QSslConfiguration conf = req.sslConfiguration(); conf.setCaCertificates({QSslCertificate( QStringLiteral(TEST_CERT_DIR "/server.crt"), QSsl::Pem)}); conf.setPeerVerifyMode(QSslSocket::VerifyPeer); req.setSslConfiguration(conf); QNetworkReply *reply = m_nam.get(req); QTRY_COMPARE_WITH_TIMEOUT(reply->error(), QNetworkReply::NoError, 5000); QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); reply->deleteLater(); } QTEST_GUILESS_MAIN(tst_HttpServer) #include "tst_httpserver.moc"QTEST_GUILESS_MAIN而不是QTEST_MAIN,因为服务端不需要 GUI,用无界面版本在 CI 上更稳。setCaCertificates是关键——默认配置下QNetworkAccessManager不信任自签名证书,不塞这一行,用例必红,而且报错信息是SSL handshake failed,很容易往服务端查,其实问题在客户端。
QTRY_COMPARE_WITH_TIMEOUT而不是直接QCOMPARE,是因为网络请求是异步的,回答还没到就断言必然失败。超时设 5000 毫秒,本机回环足够。
5.3 ctest 输出怎么读
cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH=/opt/Qt/6.6.3/gcc_64 cmake --build build --parallel ctest --test-dir build --output-on-failure -V--output-on-failure只在失败时打印输出,-V是全程详细输出。排查阶段用-V,日常跑用前者。正常结果是100% tests passed, 0 tests failed out of 2。
失败的定位有个小技巧:先看是initTestCase挂还是具体用例挂。initTestCase挂基本是证书加载问题,报错会明确指向server.crt或私钥;具体用例挂则多半是客户端信任链没配对。ctest -R httpsRoute可以只跑单条用例,缩短改一次跑一次的循环。
6. 避坑排查:五条把环境装坏了的现场记录
6.1 CMake 报找不到 Qt6HttpServer 的配置文件
现象:find_package(Qt6 REQUIRED COMPONENTS HttpServer)失败,提示Could not find a package configuration file provided by "Qt6HttpServer",但 Qt 明明装了。
原因:三种可能——Qt 版本低于 6.4,模块根本没发布;在线安装器里没勾选 Qt HTTP Server 组件;CMAKE_PREFIX_PATH指向了另一个 Qt 安装目录。
解决:先qtpaths6 --qt-query QT_VERSION确认版本,再按 2.2 的find命令确认Qt6HttpServerConfig.cmake是否存在,最后把CMAKE_PREFIX_PATH打出来核对。三条都过了还报错,就是机器上有两份 Qt,用-DQt6_DIR=显式指定。
6.2 服务起来了,但客户端连上去还是明文
现象:sslSetup写进去了,listen也返回了非 0 端口,curl -k https://...却报wrong version number或直接断开。
原因:sslSetup写在listen之后。这个顺序错误编译器不管,运行时不报错,服务端继续按明文处理。
解决:把sslSetup提到listen之前。顺手加一行qInfo() << certs.first().subjectInfo(QSslCertificate::CommonName);,确认读到的确实是你以为的那张证书,而不是certs/目录里的旧文件。
6.3 curl 带证书校验仍然失败
现象:curl --cacert certs/server.crt https://127.0.0.1:8443/ping报SSL certificate problem: unable to get local issuer certificate或IP address mismatch。
原因:证书生成时没写 SAN,或者只写了DNS:localhost却用 IP 访问。现代客户端优先校验 SAN,CN 已经不作数了。
解决:重生成证书,-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"两个都写;访问时域名和 IP 二选一,和 SAN 对上。临时应急可以-k,但别把-k写进测试脚本,那就等于把校验关掉了。
6.4 测试用例里 HTTPS 请求报 handshake failed
现象:plainRoute通过,httpsRoute失败,reply->error()是QNetworkReply::SslHandshakeFailedError,服务端日志干干净净。
原因:QNetworkAccessManager默认不信任自签名证书,请求没带 CA 列表。
解决:按 5.2 的写法,QSslConfiguration conf = req.sslConfiguration();再setCaCertificates,然后req.setSslConfiguration(conf)。注意必须从req.sslConfiguration()取一份再改,直接构造一个空的QSslConfiguration会把协议版本等默认值一起覆盖掉。
6.5 Windows 上能编译,运行时 SSL 直接不支持
现象:QSslSocket::supportsSsl()返回 false,所有 HTTPS 请求失败,HTTP 一切正常。
原因:Qt 在 Windows 上动态加载 OpenSSL,libcrypto-3-x64.dll和libssl-3-x64.dll不在可执行文件目录或 PATH 里,或者 DLL 版本与编译 Qt 时用的版本大版本不一致。
解决:把两个 DLL 拷到可执行文件同目录(比改 PATH 干净),再用 2.2 的探针程序对比 build 和 runtime 版本字符串。版本对不上就换 DLL,别指望「差不多能跑」。
7. 装完怎么验证:三级验收,从 ctest 到握手细节
环境装完,我固定跑三级验收,任何一级不过都不算装完。第一级看编译链路,第二级看明文协议,第三级看 TLS 握手——三级分开跑,出问题时能立刻定位到是哪一层。
| 验收项 | 命令 | 期望结果 |
|---|---|---|
| 库文件被 CMake 找到 | cmake -S . -B build -G Ninja | 配置阶段无Qt6HttpServer报错 |
| 用例全绿 | ctest --test-dir build --output-on-failure | 100% tests passed |
| 明文路由 | curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/ping | 200 |
| HTTPS 完整校验 | curl --cacert certs/server.crt -s https://localhost:8443/ping | 返回pong-over-tls |
| 握手细节 | openssl s_client -connect 127.0.0.1:8443 -servername localhost -CAfile certs/server.crt </dev/null | Verify return code: 0 (ok) |
第三级是最容易被跳过、也最能救命的一步。curl -k通过只证明 TCP 和 TLS 起来了,证书链、SAN、有效期全都没验。用s_client看Verify return code,同时瞄一眼Protocol字段确认协商的版本,比反复改代码猜快得多。
还有一个容易忽略的动作:验收时把服务的监听端口换一个再跑一遍。监听端口写死在同一台机器上,前一次进程没退干净,下一次listen直接返回 0,日志里只有一行listen failed,很多人会误判成 SSL 配置错了。用例里用listen(..., 0)让系统分配端口,就是被这个坑教育出来的。
从那以后我每次装 QtHttpServer,都强制先跑一遍openssl s_client、再跑ctest,两个都绿了才动业务代码;证书一律用脚本现生成,绝不手工拷来拷去。这套三件套——库文件、测试用例、SSL 文件——看起来是三件事,实际是一条链,任何一环放错位置,表现出来的都是同一个握手失败。希望帮到你。
本文还有配套的精品资源,点击获取