Qt中使用HTTPS


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. 安全層實現

安全層實現主流且常見的有OpenSSLMbed 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM