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:QtQt5.9.1Toolsmingw530_32optin。
把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 ", 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