Qt HTTP Server を試してみよう

# 快挙！

今年も [Qt Champion が決まり](https://blog.qt.io/blog/2018/12/18/welcome-2018-qt-champions/)、なんと日本から [Kazuo Asano](https://twitter.com/kazuo_asa) さんが選ばれました！！！
>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年以降、４年で３人もの Qt Champion が誕生しているわけで、 **オープンソース** を中心とした **ユーザーコミュニティ** の重要性を再度認識している次第です。Qt 自体のユーザーも、Qt Creator ユーザーも、Qt 関連の書籍も結構増えたなというのを実感した２０１８年でした。来年も楽しみですね！

# はじめに

昨日は @moguriso さんによる「[Qt SensorsでIoTしたつもりになる](https://qiita.com/moguriso/items/c8e895c6e420af97a064)」でした。
最初の一歩は無事踏み出せたようなので、今後の展開に期待しましょう。カレンダーを埋めるためだけにちょっと試してみたとかではないはず。

今日は、Qt Project の元で開発されているモジュールのうち、[qt-extensions/qthttpserver](https://codereview.qt-project.org/gitweb?p=qt-extensions%2Fqthttpserver.git;a=summary) を取り上げてみましょう。

# Qt HTTP Server #とは

2017年１０月に、Qt の開発者のメーリングリストに以下のメールが流れてきました。

> [Development] [Repository request: HTTP server](https://lists.qt-project.org/pipermail/development/2017-October/031027.html)
> 
> 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 のサーバー](http://git.qt-users.jp/git/?p=codereview/qthttpserver.git;a=commit;h=4c42197f9a6c861730c5ce0963ed2bcd31864141) を作り始めてはや５年、やっと~~時代~~ Qt が俺に追いついたな？という感じですね。

このリポジトリの [一番最初のコミットのメッセージ](https://codereview.qt-project.org/gitweb?p=qt-labs/qthttpserver.git;a=commit;h=b24745265dea23f7ca2d331e4432c082d1c2bc75) に、このモジュールのゴールが定義されていました。
> 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
        - [Http2Server](http://code.qt.io/cgit/qt/qtbase.git/tree/tests/auto/network/access/http2/http2srv.h)
        - [MiniHttpServer](http://code.qt.io/cgit/qt/qtbase.git/tree/tests/auto/network/access/qnetworkdiskcache/tst_qnetworkdiskcache.cpp#n92)
        - [MiniHttpServer](http://code.qt.io/cgit/qt/qtbase.git/tree/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp#n555)
        - [MiniHttpServer](http://code.qt.io/cgit/qt/qtbase.git/tree/tests/auto/network/socket/qhttpsocketengine/tst_qhttpsocketengine.cpp#n82)
        - [HttpServer](http://code.qt.io/cgit/qt/qtbase.git/tree/tests/benchmarks/network/access/qnetworkreply_from_cache/tst_qnetworkreply_from_cache.cpp#n73)
        - [MiniHttpServer](http://code.qt.io/cgit/qt/qtbase.git/tree/tests/manual/network_stresstest/minihttpserver.h#n42)
    - QtDecralative
        - [TestHttpServer](http://code.qt.io/cgit/qt/qtdeclarative.git/tree/tests/auto/shared/testhttpserver.h#n39)
    - QtNetworkAuth
        - [WebServer](http://code.qt.io/cgit/qt/qtnetworkauth.git/tree/tests/auto/shared/webserver.h#n39)
    - QtWebEngine
        - [HttpServer](http://code.qt.io/cgit/qt/qtwebengine.git/tree/tests/auto/shared/httpserver.h#n57)
    - QtWebglPlugin
        - [QWebGLHttpServer](http://code.qt.io/cgit/qt/qtwebglplugin.git/tree/src/plugins/platforms/webgl/qwebglhttpserver.h#n46)
- 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](https://github.com/nodejs/http-parser.git) が使われているようです。

ではビルドしてみましょう。

```
$ 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

```cpp:qabstracthttpserver.h
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

```cpp:qhttpserverrequest.h
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](http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/kernel/qobject.h#n470) などという滅多に見かけないクラスを継承しているということ。

そして、`value` ってなんだよ。と思って実装を見ると、

```cpp:qhttpserverrequest.cpp
QString QHttpServerRequest::value(const QString &key) const
{
    return d->headers.value(d->headerHash(key)).second;
}
```

なるほどヘッダか。命名バグですね。

## QHttpServerResponder

```cpp:qhttpserverresponder.h
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` が数パターン用意されていますね。

## 簡単なサーバーを書いてみる

```httpserver.pro
TEMPLATE = app
TARGET = httpserver
QT = httpserver

SOURCES = main.cpp
```

```cpp: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/ にアクセスすると以下のようになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/13867/ee5d1199-1346-67ef-bef8-bf9841923837.png)

# QtHttpServer に貢献してみました

`QAbstractHttpServer` に `QVector<QTcpServer *> servers() const;` という API があるのですが、その実装がビミョーだったので、パッチを送りました。

https://codereview.qt-project.org/#/c/243297/2/src/httpserver/qabstracthttpserver.cpp,unified

```diff
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();
}
```

一行でできることをですね、（ｒｙ

ついでに、その API のテストコードがなかったので [追加しておきました](https://codereview.qt-project.org/#/c/243297/2/tests/auto/qabstracthttpserver/tst_qabstracthttpserver.cpp,unified) 。

# さらに貢献してみました。

[Add qml module](https://codereview.qt-project.org/248689) というパッチを書いて、QML でもサーバーが作れるようにしてみましたｗ

```qml:main.qml
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')
    }
}
```

雑に実装したので色々ツッコミが入りそうですが、これは [どうしてもサポートしたい機能](https://qiita.com/task_jp/items/74b67bd85dc0e92a40e6) なので頑張ります！

# おわりに

[Qt Project](http://code.qt.io) では、様々なプロジェクトが開発されています。qtbase などのメジャーなものから、誰もメンテナンスしてないようなものもありますが、暇なときになんか面白そうなものが無いか、眺めてみるのはいかがでしょう？

明日は @shin1_okada が何かを書きます。お楽しみに！