___2017

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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

posted on 2021-10-28 12:38  yin'xiang  阅读(5094)  评论(0编辑  收藏  举报