Qt中使用HTTPS
一、HTTPS和HTTP区别
1. 从定义上看
HTTP: HyperText Transfer Protocol
HTTPS: HyperText Transfer Protocol over Secure Socket Layer
2. 从分层上看
HTTP: HTTP -> Socket API -> TCP/QUIC①
HTTPS: HTTP -> 安全层 -> Socket API -> TCP/QUIC
3. 安全层
显而易见,HTTPS比HTTP多了一个“安全层②”。
所谓“安全层”,无非是为了保证数据安全。其中涉及的技术,简单来说有三个:
- 数据加密:防止数据被第三方窥探到。通过AES、DES、RSA等加解密算法实现
- 数据完整性:防止数据被破坏。通过各种散列函数算法实现,如MD4/MD5,SHA-1/SHA-256等
- 通信双方认证:防止冒充。通过证书技术实现
4. 安全层实现
安全层实现主流且常见的有OpenSSL、Mbed TLS,双方区别主要应用场景不同:
- OpenSSL庞大,主要应用于PC、高端CPU上,如支持Linux的CPU
- Mbed TLS更加轻量,可以在一些低端CPU,如Arm的Cortex-M系列上运行。在IoT领域,Mbed TLS大放异彩
二、Qt HTTPS环境配置
Qt的安全层使用的是OpenSSL,支持HTTPS请求需要配置OpenSSL环境。
不过,无需自己编译OpenSSL或者满世界找编译好的库。Qt的安装路径下已经有现成的dll库。

以Mingw编译环境为例,这两个dll位于:C:\Qt\Qt5.9.1\Tools\mingw530_32\opt\bin。
把libeay32.dll 和 ssleay32.dll拷贝到程序生成目录下(即生成exe的同级目录)或者加入到系统环境变量里都可以。
三、HTTPS代码示例
1. 准备工作
- HTTPS服务器:https://httpbin.org
- 调试工具:curl
2.示例功能
- 向HTTPS服务器发送POST方法,获取Json格式数据,显示在一个QPlainText控件里
- 向HTTPS服务器发送GET方法,获取一个PNG格式图片,显示在一个QLabel控件里

3.UI和数据分离
HttpClient负责HTTPS网络连接,数据获取;获取数据后发送Signal给HttpClientView,HttpClientView负责数据结果呈现。
4.示例代码
4.1 HttpClient
---HttpClient.h
#ifndef HTTPSCLIENT_H
#define HTTPSCLIENT_H
#include <QString>
#include <QByteArray>
#include <QNetworkAccessManager>
#include <QNetworkReply>
class HttpClient : public QObject
{
Q_OBJECT
public:
enum HttpMethod{
POST = 0,
GET, DELETE, PUT
};
Q_ENUM(HttpMethod)
HttpClient();
~HttpClient();
void doRequest(HttpClient::HttpMethod method, QString& url);
private slots:
void postFinishSlot();
void httpErrorSlot(QNetworkReply::NetworkError err);
signals:
void postFinish(int statusCode, QByteArray& response);
void httpError(QNetworkReply::NetworkError err);
private:
QNetworkAccessManager *mAccessManager;
QNetworkReply *mReply;
};
#endif // HTTPSCLIENT_H
---HttpClient.cpp
#include <QSsl>
#include <QUrl>
#include <QSslSocket>
#include <QSslConfiguration>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QVariant>
#define LOG_TAG "HttpClient"
#include "../log/log.h"
#include "HttpClient.h"
HttpClient::HttpClient() : mAccessManager(NULL)
{
log_debug("NetworkAccessManager init...");
mAccessManager = new QNetworkAccessManager(this);
log_debug("NetworkAccessManager init done.");
}
HttpClient::~HttpClient()
{
log_info("desc");
if (mAccessManager) {
delete mAccessManager;
mAccessManager = NULL;
}
}
void HttpClient::doRequest(HttpClient::HttpMethod method, QString &url)
{
log_info("do request...");
QNetworkRequest request;
QSslConfiguration cfg = request.sslConfiguration();
cfg.setPeerVerifyMode(QSslSocket::VerifyNone);
cfg.setProtocol(QSsl::AnyProtocol);
request.setSslConfiguration(cfg);
request.setUrl(QUrl(url));
if (method == POST) {
request.setRawHeader("Content-Type: ", "application/json;charset=utf-8");
request.setRawHeader("Accept", "application/json");
QByteArray data;
data.clear();
mReply = mAccessManager->post(request, data);
} else if (method == GET) {
request.setRawHeader("Content-Type: ", "image/png");
request.setRawHeader("Accept", "image/png");
mReply = mAccessManager->get(request);
}
connect(mReply, SIGNAL(finished()), this, SLOT(postFinishSlot()));
connect(mReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(httpErrorSlot(QNetworkReply::NetworkError)));
}
void HttpClient::postFinishSlot()
{
QVariant statusCode = mReply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
int code = statusCode.toInt();
QByteArray resp = mReply->readAll();
QList<QByteArray> hdrNames = mReply->rawHeaderList();
log_info("response headers:");
for (int i = 0; i < hdrNames.size(); i ++) {
QByteArray hdrName = hdrNames.at(i);
QByteArray hdrCtx = mReply->rawHeader(hdrName);
log_debug("%s: %s", hdrName.constData(), hdrCtx.constData());
}
emit postFinish(code, resp);
}
void HttpClient::httpErrorSlot(QNetworkReply::NetworkError err)
{
emit httpError(err);
}
4.2 HttpClientView
---HttpClientView.h
#ifndef HTTPCLIENTVIEW_H
#define HTTPCLIENTVIEW_H
#include <QWidget>
#include "HttpClient.h"
namespace Ui {
class HttpClientView;
}
class HttpClientView : public QWidget
{
Q_OBJECT
public:
explicit HttpClientView(QWidget *parent = 0);
~HttpClientView();
private:
void init();
void initUI();
void initSlot();
private slots:
void onBtnExecuteClicked();
void postFinishSlot(int statusCode, QByteArray& response);
void httpErrorSlot(QNetworkReply::NetworkError err);
private:
Ui::HttpClientView *ui;
HttpClient *mHttpClient;
};
#endif // HTTPCLIENTVIEW_H
---HttpClientView.cpp
#define LOG_TAG "HttpView"
#include "../log/log.h"
#include <QImage>
#include <QPixmap>
#include "HttpClientView.h"
#include "ui_httpclientview.h"
HttpClientView::HttpClientView(QWidget *parent) :
QWidget(parent), mHttpClient(NULL),
ui(new Ui::HttpClientView)
{
ui->setupUi(this);
init();
initUI();
initSlot();
}
HttpClientView::~HttpClientView()
{
if (mHttpClient) {
delete mHttpClient;
mHttpClient = NULL;
}
delete ui;
}
void HttpClientView::init()
{
mHttpClient = new HttpClient();
connect(mHttpClient, SIGNAL(postFinish(int,QByteArray&)), this, SLOT(postFinishSlot(int,QByteArray&)));
connect(mHttpClient, SIGNAL(httpError(QNetworkReply::NetworkError)), this, SLOT(httpErrorSlot(QNetworkReply::NetworkError)));
}
void HttpClientView::initUI()
{
ui->labelImage->setStyleSheet("{border:2px dotted #242424;}");
}
void HttpClientView::initSlot()
{
connect(ui->btnExecute, SIGNAL(clicked()), this, SLOT(onBtnExecuteClicked()));
}
void HttpClientView::onBtnExecuteClicked()
{
int scheme = ui->cBoxScheme->currentIndex();
int method = ui->cBoxMethod->currentIndex();
ui->plainTextEdit->appendPlainText("request...");
log_info("http scheme %d, method %d", scheme, method);
if (0 == method) {
QString url("https://httpbin.org/post");
mHttpClient->doRequest(HttpClient::POST, url);
} else if (1 == method) {
QString url("https://httpbin.org/image/png");
mHttpClient->doRequest(HttpClient::GET, url);
}
}
void HttpClientView::postFinishSlot(int statusCode, QByteArray& response)
{
int size = response.size();
ui->plainTextEdit->appendPlainText("request finish.");
log_info("response code: %d, data size: %d", statusCode, size);
if (size > 0) {
if (0 == ui->cBoxMethod->currentIndex()) {
ui->plainTextEdit->appendPlainText(QString(response));
} else if (1 == ui->cBoxMethod->currentIndex()) {
QImage img;
bool res = img.loadFromData(response);
if (res) {
ui->labelImage->setAlignment(Qt::AlignCenter);
ui->labelImage->setPixmap(QPixmap::fromImage(img));
} else {
log_error("error convert image from http response data!");
}
}
}
}
void HttpClientView::httpErrorSlot(QNetworkReply::NetworkError err)
{
log_error("http error: %d\r\n", err);
}
4.3 UI
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>HttpClientView</class>
<widget class="QWidget" name="HttpClientView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>634</width>
<height>490</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="windowTitle">
<string>HttpClientView</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" rowspan="4">
<widget class="QPlainTextEdit" name="plainTextEdit">
<property name="minimumSize">
<size>
<width>370</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
</widget>
</item>
<item row="0" column="1" colspan="4">
<widget class="QLabel" name="labelImage">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>240</width>
<height>240</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">border:2px dotted #242424;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>144</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>144</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="labelScheme">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Schemes</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cBoxScheme">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<item>
<property name="text">
<string>HTTPS</string>
</property>
</item>
<item>
<property name="text">
<string>HTTP</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item row="2" column="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="3">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="labelHttp">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Methods</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cBoxMethod">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<item>
<property name="text">
<string>POST</string>
</property>
</item>
<item>
<property name="text">
<string>GET</string>
</property>
</item>
<item>
<property name="text">
<string>DELETE</string>
</property>
</item>
<item>
<property name="text">
<string>PUT</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item row="2" column="4">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>64</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="1" colspan="4">
<widget class="QPushButton" name="btnExecute">
<property name="minimumSize">
<size>
<width>240</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(73, 144, 226);
color: rgb(255, 255, 255);</string>
</property>
<property name="text">
<string>执行</string>
</property>
</widget>
</item>
</layout>
<zorder>layoutWidget</zorder>
<zorder>layoutWidget</zorder>
<zorder>plainTextEdit</zorder>
<zorder>labelImage</zorder>
<zorder>btnExecute</zorder>
<zorder>horizontalSpacer</zorder>
<zorder>verticalSpacer</zorder>
<zorder>horizontalSpacer_2</zorder>
<zorder>verticalSpacer_2</zorder>
</widget>
<resources/>
<connections/>
</ui>
4.4 关键代码

四、报错、原因分析及解决
1. qt.network.ssl: QSslSocket: cannot call unresolved function

原因:未找到libeay32.dll 和 ssleay32.dll,检查环境设置。
2.QNetworkReply::UnknownNetworkError

原因:由问题1引起,解决方式同上。
五、附录
①QUIC:QUIC协议原理分析
②安全相关技术参阅《图解密码技术》
2021.10.28
浙公网安备 33010602011771号