快挙!
今年も Qt Champion が決まり、なんと日本から Kazuo Asano さんが選ばれました!!!
This year’s *Ambassador* title goes to Kazuo Asano who’s a very active IRL Qt promoter in Japan. His various activities including study-sessions, seminar and now a book are doing wonder making Qt known in the empire of the rising sun.
これで日本からはこの制度が始まった2014年以降、4年で3人もの Qt Champion が誕生しているわけで、 オープンソース を中心とした ユーザーコミュニティ の重要性を再度認識している次第です。Qt 自体のユーザーも、Qt Creator ユーザーも、Qt 関連の書籍も結構増えたなというのを実感した2018年でした。来年も楽しみですね!
はじめに
昨日は @moguriso さんによる「Qt SensorsでIoTしたつもりになる」でした。
最初の一歩は無事踏み出せたようなので、今後の展開に期待しましょう。カレンダーを埋めるためだけにちょっと試してみたとかではないはず。
今日は、Qt Project の元で開発されているモジュールのうち、qt-extensions/qthttpserver を取り上げてみましょう。
Qt HTTP Server #とは
2017年10月に、Qt の開発者のメーリングリストに以下のメールが流れてきました。
[Development] Repository request: HTTP server
Hi all,
We have recently been working on a research project looking into the possibilities for creating a lightweight server component that can easily enable Qt applications to serve over HTTP. We would like to make this work public and therefore request a repository.
This work is intended to continue as a research project, to explore alternatives and reveal areas that need work, so it should be under qt-labs.
Name of the project: Qt HTTP Server
Responsible person: ******* ** ****
Gerrit user/email: *.@qt.io
Desired repository name: qthttpserver
2012年に、自分用の Qt で HTTP のサーバー を作り始めてはや5年、やっと時代 Qt が俺に追いついたな?という感じですね。
このリポジトリの 一番最初のコミットのメッセージ に、このモジュールのゴールが定義されていました。
Introduce Qt HttpServer framework
Small, Qt integrated framework for creating specialized http server.
Goals of the project:
- Create a framework allowing creation of a specialized web server
running in non public networks (home and company networks, stealth
or hidden services)- Create an easy tool for developers to embed http servers in their
apps.- Playground to narrow down problems in Qt, related to network
stack, but also to explore general usability.- Potentially reduce code duplication in Qt.
Not goals:
- Standalone server, in particular not Apache or nginx replacement
- すっごいのを作る気は無い
- アプリにウェブサーバーを簡単に組み込めるようにしたいよ
- QtNetwork のHTTP系のクライアントのテストに使いたいよ
- Qt 内部にたくさん HTTP サーバーあるしまとめられたらいいね
- QtNetwork
- QtDecralative
- QtNetworkAuth
- QtWebEngine
- QtWebglPlugin
- Apache とか nginx みたいなのは目指さないよ!
とのことでした。
ソースコードの入手とビルド
$ git clone http://code.qt.io/qt-extensions/qthttpserver.git
$ cd qthttpserver
$ ls -a
. .gitmodules src
.. .qmake.conf sync.profile
.git qthttpserver.pro tests
ということで、ごくごく普通の Qt のモジュールの形式になっています。git の submodule があるようなので、それも合わせて取得しましょう。
$ git submodule
-edeedb1b4d2f34e4c7d8045ac8b92adbc35e7ed7 src/3rdparty/http-parser
$ git submodule update --init
Submodule 'src/3rdparty/http-parser' (https://github.com/nodejs/http-parser.git) registered for path 'src/3rdparty/http-parser'
Cloning into '/Users/tasuku/io/qt/code/qt-extensions/qthttpserver/qthttpserver/src/3rdparty/http-parser'...
Submodule path 'src/3rdparty/http-parser': checked out 'edeedb1b4d2f34e4c7d8045ac8b92adbc35e7ed7'
nodejs 由来の http-parser が使われているようです。
ではビルドしてみましょう。
$ cd ..
$ mkdir build
$ qmake -r ../qthttpserver
$ make -j4
$ make install # 必要な場合のみ、root 権限が必要な場合は sudo で実行
使い方
まだ、examples などという親切なものも、オンラインで参照可能なドキュメントも無いようなので、ソースコードをみてみましょう。
$ cd ../qthttpserver/src/httpserver
$ ls
httpserver.pro qhttpserverrequest.cpp qhttpserverresponder.h
qabstracthttpserver.cpp qhttpserverrequest.h qhttpserverresponder_p.h
qabstracthttpserver.h qhttpserverrequest_p.h qthttpserverglobal.h
qabstracthttpserver_p.h qhttpserverresponder.cpp
_p.h
はプライベートな API (=普通は使わない)なので、
- qabstracthttpserver.h
- qhttpserverrequest.h
- qhttpserverresponder.h
を見ればいいようだ。
QAbstractHttpServer
を継承したクラスを作って、QHttpServerRequest
の内容に応じて QHttpServerResponder
の API で実際のコンテンツを返してやれば良さそうだ。ということがなんとなくわかる。
QAbstractHttpServer
class Q_HTTPSERVER_EXPORT QAbstractHttpServer : public QObject
{
Q_OBJECT
public:
QAbstractHttpServer(QObject *parent = nullptr);
int listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
void bind(QTcpServer *server = nullptr);
QVector<QTcpServer *> servers() const;
Q_SIGNALS:
void missingHandler(const QHttpServerRequest &request, QTcpSocket *socket);
#if defined(QT_WEBSOCKETS_LIB)
void newWebSocketConnection();
public:
bool hasPendingWebSocketConnections() const;
QWebSocket *nextPendingWebSocketConnection();
#endif // defined(QT_WEBSOCKETS_LIB)
protected:
QAbstractHttpServer(QAbstractHttpServerPrivate &dd, QObject *parent = nullptr);
virtual bool handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) = 0;
static QHttpServerResponder makeResponder(const QHttpServerRequest &request,
QTcpSocket *socket);
...
};
QTcpSocket
と同様に、QHostAddress
と port
で listen()
する API と、ユーザー側で用意した QTcpSocket
に bind()
する API があるようだ。
QtWebSockets
が利用可能な場合には WebSockets 対応もできるようだ。
継承したクラスでは、virtual
になっている handleRequest()
を実装すれば良さそうで、makeResponder
の API で、なんかコンテンツを返すクラスを使うことで色々簡単になるっぽい。
QHttpServerRequest
class Q_HTTPSERVER_EXPORT QHttpServerRequest : public QObjectUserData
{
...
public:
~QHttpServerRequest() override;
enum class Method
{
Unknown = -1,
Get,
Put,
Delete,
Post,
Head,
Options,
Patch
};
QString value(const QString &key) const;
QUrl url() const;
Method method() const;
QVariantMap headers() const;
QByteArray body() const;
...
};
特筆すべきは QObjectUserData などという滅多に見かけないクラスを継承しているということ。
そして、value
ってなんだよ。と思って実装を見ると、
QString QHttpServerRequest::value(const QString &key) const
{
return d->headers.value(d->headerHash(key)).second;
}
なるほどヘッダか。命名バグですね。
QHttpServerResponder
class QHttpServerResponderPrivate;
class Q_HTTPSERVER_EXPORT QHttpServerResponder final
{
Q_DECLARE_PRIVATE(QHttpServerResponder)
friend class QAbstractHttpServer;
public:
enum class StatusCode {
// 1xx: Informational
Continue = 100,
SwitchingProtocols,
Processing,
// 2xx: Success
Ok = 200,
Created,
...
};
QHttpServerResponder(QHttpServerResponder &&other);
~QHttpServerResponder();
void write(QIODevice *data, const QByteArray &mimeType, StatusCode status = StatusCode::Ok);
void write(const QByteArray &data,
const QByteArray &mimeType,
StatusCode status = StatusCode::Ok);
void write(const QJsonDocument &document, StatusCode status = StatusCode::Ok);
void write(StatusCode status = StatusCode::Ok);
QTcpSocket *socket() const;
bool addHeader(const QByteArray &key, const QByteArray &value);
...
};
コンテンツを書き出す用の write
が数パターン用意されていますね。
簡単なサーバーを書いてみる
TEMPLATE = app
TARGET = httpserver
QT = httpserver
SOURCES = main.cpp
#include <QtCore/QCoreApplication>
#include <QtHttpServer>
class HttpServer : public QAbstractHttpServer
{
public:
bool handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) override {
makeResponder(request, socket).write("Hello World!", "text/plain");
return true;
}
};
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
HttpServer server;
server.listen(QHostAddress::LocalHost, 8080);
return app.exec();
}
これで、ビルドし実行し、http://localhost:8080/ にアクセスすると以下のようになります。
QtHttpServer に貢献してみました
QAbstractHttpServer
に QVector<QTcpServer *> servers() const;
という API があるのですが、その実装がビミョーだったので、パッチを送りました。
QVector<QTcpServer *> QAbstractHttpServer::servers() const
{
- auto c = children();
- QVector<QTcpServer *> result;
- result.reserve(c.size());
- for (auto i = c.constBegin(); i != c.constEnd(); ++i) {
- if ((*i)->inherits("QTcpServer"))
- result.append(static_cast<QTcpServer*>(*i));
- }
- result.squeeze();
- return result;
+ return findChildren<QTcpServer *>().toVector();
}
一行でできることをですね、(ry
ついでに、その API のテストコードがなかったので 追加しておきました 。
さらに貢献してみました。
Add qml module というパッチを書いて、QML でもサーバーが作れるようにしてみましたw
import QtHttpServer 1.0
HttpServer {
id: root
listen: true
port: 8080
onPortChanged: console.debug(port)
onRequest: {
var headers = []
for (var i in request.headers) {
headers.push('<dt>%1</dt><dd>%2</dd>'.arg(i).arg(request.headers[i]))
}
responder.write('<dl><dt>URL:</dt><dd>%1</dd>%2</dl>'.arg(request.url).arg(headers.join('')), 'text/html')
}
}
雑に実装したので色々ツッコミが入りそうですが、これは どうしてもサポートしたい機能 なので頑張ります!
おわりに
Qt Project では、様々なプロジェクトが開発されています。qtbase などのメジャーなものから、誰もメンテナンスしてないようなものもありますが、暇なときになんか面白そうなものが無いか、眺めてみるのはいかがでしょう?
明日は @shin1_okada が何かを書きます。お楽しみに!