QtWebApp-詳細なコメント付きの段階的な噛み砕かれた例





Qtでアプリケーションを開発するプロセスでは、このアプリケーションにWebインターフェイスを追加する必要があります。これは、Qtを使用して組み込みシステムを開発する場合に特に関連します。 この問題を解決するには、独自のソリューションを作成するか、既製のソリューションを使用します。 たとえば、Webベースのインターフェイスを作成するために必要な機能を提供するQtWebAppライブラリ。



このライブラリの利点は次のとおりです。

  1. テンプレートによる動的コンテンツを含むページの形成。
  2. 完全に動的なページの形成。
  3. Cookieを使用します。これにより、アプリケーションに承認が追加されます。
  4. style.cssや画像などの静的ファイルを操作します。
  5. ファイルアップロードの実装。


QtWebAppライブラリを使用して動作する複数のWebページを持つ小さなQtアプリケーションを起動するためのオプションの1つを詳細に検討することをお勧めします。



この記事の執筆時点では、QtWebApp 1.6.3およびQt 5.6ライブラリが最初に使用されていました。 このプロジェクトは、MSVC2013およびMinGWビルドキットで正常に開始されました。 デバッグ中に、QtWebAppライブラリのTemplateクラスにバグが見つかりました。 バグを修正し、開発者と連絡を取り合った後、ライブラリバージョンは1.6.4にアップグレードされました。 これに基づいて、開発者が24時間以内にバグに関する情報に回答し、ライブラリのバージョンがアップグレードされたその日にライブラリが追加されたことにも注目できます。 サンプルアプリケーションの最終バージョンは、バージョン1.6.4で準備されました。



このプロジェクトでは、3つのページ、これらのページを選択するためのメニュー、および3つの静的ファイルを持つアプリケーションを作成することを提案しています。 ファイルの1つはstyle.cssで、他の2つは画像です。



プロジェクト構造





プロジェクトは、メインプロジェクトとQtWebAppライブラリのプロジェクトで構成されるSubdirsプロジェクトとして形成されます。

プロジェクト構造:



QtWebAppExample.pro-メインプロジェクトプロファイル

共通 -Webサーバーユーザープロジェクト





QtWebApp-ライブラリプロジェクト





QtWebAppExample.pro



一般的なプロジェクトプロファイルは、メインプロジェクトとQtWebAppライブラリが接続されたサブディレクトリテンプレートです。 ファイル内のプロジェクトを接続する順序は重要です。 QtWebAppライブラリを最初に登録する必要があります。登録しないと、プロジェクトのビルド時にエラーが発生します。

QtWebAppに依存するメインプロジェクトのビルド時に、収集されたライブラリファイル(.dllまたは.so)が利用できなかった場合、プロジェクトはアセンブルされません。

TEMPLATE = subdirs SUBDIRS += \ QtWebApp \ common CONFIG += ordered common.files = common/html-static/* CONFIG(debug, debug|release) { common.path = $$OUT_PWD/../HttpServiceDebug/html-static } else { common.path = $$OUT_PWD/../HttpService/html-static } INSTALLS += common
      
      







common.pro



この例でライブラリのプロファイルが根本的に修正されていない場合、Webサーバーのメインプロジェクトのプロファイルを設定すると、初心者ユーザーに何らかの不便が生じる可能性があります。 次のスクリプトからわかるように、アプリケーションはグラフィックライブラリを担当するモジュールを無効にしていますが、httpサーバーへの要求を処理するためのネットワークライブラリは有効になっています。



さらに、ヘッダーファイルとソースコードファイルへのリンク、およびライブラリアセンブリの出力フォルダーを正しく登録して、アセンブリされたプロジェクトがライブラリファイルへの正しいパスにアクセスできるようにする必要があります。



 QT += core network QT -= gui TARGET = common CONFIG += console CONFIG -= app_bundle CONFIG += c++11 TEMPLATE = app SOURCES += main.cpp \ webconfigurator.cpp \ webconfiguratorpage.cpp HEADERS += \ webconfigurator.h \ webconfiguratorpage.h \ httpsettings.hpp RESOURCES += \ resources.qrc CONFIG(debug, debug|release) { DESTDIR = $$OUT_PWD/../../HttpServiceDebug } else { DESTDIR = $$OUT_PWD/../../HttpService } win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../QtWebApp/release/ -lQtWebApp else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../QtWebApp/debug/ -lQtWebApp else:unix: LIBS += -L$$OUT_PWD/../QtWebApp/ -lQtWebApp INCLUDEPATH += $$PWD/../QtWebApp/httpserver DEPENDPATH += $$PWD/../QtWebApp/httpserver INCLUDEPATH += $$PWD/../QtWebApp/templateengine DEPENDPATH += $$PWD/../QtWebApp/templateengine INCLUDEPATH += $$PWD/../QtWebApp/qtservice DEPENDPATH += $$PWD/../QtWebApp/qtservice win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/release/libQtWebApp.a else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/debug/libQtWebApp.a else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/release/QtWebApp.lib else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/debug/QtWebApp.lib else:unix: PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/libQtWebApp.a DISTFILES += \ html-static/style.css \ html-static/favicon-32x32.png \ html-static/favicon.png
      
      







QtWebApp.pro



デフォルトのライブラリプロジェクトプロファイルを以下に示します。 プロジェクトでの唯一の変更点は、追加のビルド構成を静的ライブラリとして利用できることです。

 # Build this project to generate a shared library (*.dll or *.so). TARGET = QtWebApp TEMPLATE = lib QT -= gui CONFIG += staticlib VERSION = 1.6.4 mac { QMAKE_MAC_SDK = macosx10.10 QMAKE_CXXFLAGS += -std=c++11 CONFIG += c++11 QMAKE_LFLAGS_SONAME = -Wl,-install_name,/usr/local/lib/ } win32 { DEFINES += QTWEBAPPLIB_EXPORT } # Windows and Unix get the suffix "d" to indicate a debug version of the library. # Mac OS gets the suffix "_debug". CONFIG(debug, debug|release) { win32: TARGET = $$join(TARGET,,,d) mac: TARGET = $$join(TARGET,,,_debug) unix:!mac: TARGET = $$join(TARGET,,,d) } DISTFILES += doc/* mainpage.dox Doxyfile OTHER_FILES += ../readme.txt include(qtservice/qtservice.pri) include(logging/logging.pri) include(httpserver/httpserver.pri) include(templateengine/templateengine.pri)
      
      







main.cpp



それでは、WebインターフェースでQtアプリケーションを起動する方法を理解するために、共通プロジェクトのすべてのファイルを見ていきましょう。 アプリケーション起動ファイルと、アプリケーションを起動するメイン関数から始めましょう。



ここで、Webサーバー設定、TCP / IPポートなどを保存する設定ファイルへのパスを取得できます。

WebConfiguratorクラスのオブジェクトも作成されます。このクラスは、リクエストを処理し、リクエストに応じてWebサーバーの対応するページを発行します。

 #include <QCoreApplication> #include <QDir> #include <webconfigurator.h> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); a.setApplicationName("QtWebAppExample"); QString configPath = QDir::currentPath() + "/" + QCoreApplication::applicationName() + ".ini"; new WebConfigurator(configPath); return a.exec(); }
      
      





HttpSettings.hpp



実際には、必要な情報を超えてそれ自体を運ばない補助クラスが含まれていますが、便宜上、設定ファイルの初期化がその中で行われ、よりグローバルなプロジェクトの場合、このクラスは便利に拡張されます。 もちろん、この実施形態では、より好みの問題に似ている。



すべてのパラメーターは、接続ポートの構成、同時セッションの数、要求の期間に関連しています。



また、アプリケーション設定には、静的ファイルのコントローラーのパラメーター、特にWebサーバーの静的ファイルの検索が実行されるフォルダーへのパスが含まれます。 このアプリケーションでは、これはhtml-staticフォルダーであり、アプリケーションの実行可能ファイルと同じフォルダーに配置されます。



 #ifndef HTTPSETTINGS_H #define HTTPSETTINGS_H #include <QSettings> class HttpSettings : public QSettings { public: explicit HttpSettings(const QString& fileName, QObject* parent = nullptr) : QSettings(fileName,QSettings::IniFormat,parent) { //  - setValue("port", value("port", 8080)); setValue("minThreads", value("minThreads", 1)); setValue("maxThreads", value("maxThreads", 100)); setValue("cleanupInterval", value("cleanupInterval", 1000)); setValue("readTimeout", value("readTimeout", 60000)); setValue("maxRequestSize", value("maxRequestSize", 16000)); setValue("maxMultiPartSize", value("maxMultiPartSize", 10000000)); //     setValue("html-static/path", value("html-static/path", "html-static")); setValue("html-static/encoding", value("html-static/encoding", "UTF-8")); setValue("html-static/maxAge", value("html-static/maxAge", 60000)); setValue("html-static/cacheTime", value("html-static/cacheTime", 60000)); setValue("html-static/cacheSize", value("html-static/cacheSize", 1000000)); setValue("html-static/maxCachedFileSize", value("html-static/maxCachedFileSize", 65536)); } }; #endif // HTTPSETTINGS_H
      
      





Webconfigurator.h



ここで、WebConfiguratorクラスのコンテンツを詳しく見てみましょう。このクラスは、外部からリクエストに送信されるページを直接定義する役割を果たします。



ページはQHashクラスオブジェクトを使用して定義されます。このオブジェクトには、Webページのすべてのオブジェクトへのポインターと、リクエストURLに対応する対応するキー値が含まれます。 ただし、QHashは動的ページにのみ使用され、静的ページにはStaticFileControllerクラスのオブジェクトが使用されます。



 #ifndef WEBCONFIGURATOR_H #define WEBCONFIGURATOR_H #include <httprequesthandler.h> #include <httplistener.h> #include <webconfiguratorpage.h> #include <httpsettings.hpp> #include <staticfilecontroller.h> class WebConfigurator : public HttpRequestHandler { Q_OBJECT Q_DISABLE_COPY(WebConfigurator) public: WebConfigurator(QString &configPath); virtual ~WebConfigurator(); virtual void service(HttpRequest& request, HttpResponse& response) override; private: QString m_configPath; HttpSettings m_config; HttpListener m_httpListener; QHash<QString,WebConfiguratorPage*> m_pages; StaticFileController *m_staticFileController; }; #endif // WEBCONFIGURATOR_H
      
      





Webconfigurator.cpp



コンフィギュレーターは、対応するページおよび画像にリクエストをリダイレクトする責任があり、ページおよび画像のデータのリポジトリです。 ページまたは画像が存在しない場合、エラー404が返されます。

#include "webconfigurator.h"

 WebConfigurator::WebConfigurator(QString &configPath) : m_configPath(configPath), m_config(m_configPath), m_httpListener(&m_config, this) { /*   QHash    , *      - * */ m_pages.insert("/index.html", new IndexPage()); m_pages.insert("/second.html", new SecondPage()); m_pages.insert("/first.html", new FirstPage()); /*      *     ,    *        *    ,    *     * */ m_config.beginGroup("html-static"); m_staticFileController = new StaticFileController(&m_config); m_config.endGroup(); } WebConfigurator::~WebConfigurator() { foreach(WebConfiguratorPage* page, m_pages) { delete page; } delete m_staticFileController; } void WebConfigurator::service(HttpRequest &request, HttpResponse &response) { /*        *    . *   ,   ,   *          . *      404 * */ QByteArray path = request.getPath(); for(auto i = m_pages.begin(); i != m_pages.end(); ++i) { if(path.startsWith(i.key().toLatin1())) { return i.value()->handleRequest(request,response); } } if(path=="/") { response.redirect("/index.html"); return; } if(path.startsWith("/style.css") || path.startsWith("/favicon-32x32.png") || path.startsWith("/favicon.png")){ return m_staticFileController->service(request, response); } response.setStatus(404,"Not found"); }
      
      





WebConfiguratorPage.h



このヘッダーファイルには、ページの形成を担当するメインクラスの宣言と、プロジェクト用に継承された3つのクラスのページ:index.html、first.html、second.htmlが含まれています。

 #ifndef WEBCONFIGURATORPAGE_H #define WEBCONFIGURATORPAGE_H #include <QObject> #include <httprequesthandler.h> #include <httplistener.h> #include <template.h> class WebConfiguratorPage : public QObject { Q_OBJECT public: WebConfiguratorPage(const QString& title); virtual void handleRequest(HttpRequest&, HttpResponse&) {} virtual ~WebConfiguratorPage() {} protected: Template commonTemplate() const; private: QString m_title; }; class IndexPage : public WebConfiguratorPage { Q_OBJECT public: IndexPage() : WebConfiguratorPage("EDISON") {} virtual ~IndexPage() {} public: virtual void handleRequest(HttpRequest &request, HttpResponse &response) override; }; class FirstPage : public WebConfiguratorPage { Q_OBJECT public: FirstPage() : WebConfiguratorPage("First Page") {} virtual ~FirstPage() {} public: virtual void handleRequest(HttpRequest &request, HttpResponse &response) override; }; class SecondPage : public WebConfiguratorPage { Q_OBJECT public: SecondPage() : WebConfiguratorPage("Second Page") {} virtual ~SecondPage() {} public: virtual void handleRequest(HttpRequest &request, HttpResponse &response) override; }; #endif // WEBCONFIGURATORPAGE_H
      
      







WebConfiguratorPage.cpp



 #include "webconfiguratorpage.h" #include <QFile> #include <QDebug> WebConfiguratorPage::WebConfiguratorPage(const QString &title) : m_title(title) { } Template WebConfiguratorPage::commonTemplate() const { /*       common.htm. *      ... * */ QFile file(":/html/common.htm"); Template common(file, QTextCodec::codecForName("UTF-8")); common.setVariable("Title", m_title); /*    . *        , *        . *        ,  *     . *       common.htm,  *      "Navigation" * */ bool navigation = true; common.setCondition("Navigation", navigation); if(navigation) { /*          * ,        common.htm * */ common.loop("Items", 3); common.setVariable("Items0.href", "/index.html"); common.setVariable("Items0.name", "Main page"); common.setVariable("Items1.href", "/first.html"); common.setVariable("Items1.name", "First page"); common.setVariable("Items2.href", "/second.html"); common.setVariable("Items2.name", "Second page"); } return common; } /*         . *      ,     *        * */ void IndexPage::handleRequest(HttpRequest &request, HttpResponse &response) { if (request.getMethod() == "GET") { //     Template common = commonTemplate(); QFile file(":/html/index.htm"); Template contents(file, QTextCodec::codecForName("UTF-8")); /*           *       ,      *      {Content} * */ common.setVariable("Content", contents); response.setHeader("Content-Type", "text/html; charset=ISO-8859-1"); response.write(common.toUtf8()); return; } else { return; } return; } void FirstPage::handleRequest(HttpRequest &request, HttpResponse &response) { if (request.getMethod() == "GET") { Template common = commonTemplate(); QFile file(":/html/first.htm"); Template contents(file, QTextCodec::codecForName("UTF-8")); common.setVariable("Content", contents); response.setHeader("Content-Type", "text/html; charset=ISO-8859-1"); response.write(common.toUtf8()); return; } else { return; } return; } void SecondPage::handleRequest(HttpRequest &request, HttpResponse &response) { if (request.getMethod() == "GET") { Template common = commonTemplate(); QFile file(":/html/second.htm"); Template contents(file, QTextCodec::codecForName("UTF-8")); common.setVariable("Content", contents); response.setHeader("Content-Type", "text/html; charset=ISO-8859-1"); response.write(common.toUtf8()); return; } else { return; } return; }
      
      





Common.htm





最後に、テンプレートの内容を検討してください。

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>{Title}</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" type="text/css" href="style.css"> <link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32"/> </head> <body> <div class="content"> <a href="http://edsd.ru"><div class="logo"></div><h1>{Title}</h1></a> {if Navigation} <ul class="menu"> {loop Items} <li class = "menuitem"> <a href={Items.href}>{Items.name}</a> </li> {end Items} </ul> {end Navigation} {Content} </div> </body> </html>
      
      





index.htm



 <h2>EDISON</h2> <p>   </p>
      
      





結果



その結果、Webサーバーで動作するアプリケーションを取得できます。これは、組み込みシステムに最適です。



そして、このアプリケーションは次のWebページを形成します。



ご注意



ドラフトアプリケーションはこちらからダウンロードできます: download

プロジェクトをアセンブルするとき、必要な静的ファイルが実行可能ファイルの適切なフォルダーにインストールされるように、インストール手順を必ず配置してください。



バグについて少し



バグに関するいくつかの言葉を追加しましょう。バグ自体は、コードリファクタリングの失敗の結果のようでしたが、開発者はある時点で疲れていました。 実際、QtWebAppの以前のバージョン、つまりバージョン1.5.10では、コードは正しく、次のように見えました。

 if (data.size()==0 || file.error())   {       qCritical("Template: cannot read from %s, %s",qPrintable(sourceName),qPrintable(file.errorString()));   } else {        append(textCodec->toUnicode(data));   }
      
      







一方、バージョン1.6.3では、1行がスキップされました。

 if (data.size()==0 || file.error())   {       qCritical("Template: cannot read from %s, %s",qPrintable(sourceName),qPrintable(file.errorString()));       append(textCodec->toUnicode(data));   }
      
      







その結果、データはページテンプレートに追加されず、ユーザーは空白のページを受け取りました。 QtWebAppの開発者であるStefan Frings氏によると、彼は通常、Webインターフェースの形成に私たちとは異なるアプローチを使用しているため、このような問題に気付いていませんでした。



All Articles