Qt による Qt のためのウェブサーバー Silk

Qt Project で使われているウェブインターフェースの一覧

以下のように、Qt Project では開発やコミュニティの運営などを行うため、様々なシステムが活用されています。

http://qt-project.org/
ExpressionEngine という CMS で、PHP で作られています。
http://lists.qt-project.org/
Mailman というメーリングリストのシステムです。ウェブのインターフェースは Python で作られています。
https://bugreports.qt-project.org/
JIRA というバグトラッキングシステムで、Java で開発されています。
https://codereview.qt-project.org/
Gerrit というコードレビューツールで、Java で開発されています。
http://builds.qt-project.org/
Jenkins という CI ツールで、Java で開発されています。
http://planet.qt-project.org/
rawdog という RSS 収集ツールで、Python で開発されています。

このように様々な言語で作られたシステムを色々使っているわけですが、全てをメンテナンスし続けるのはとても大変で、Python のエンジニアを雇ったり、Java のコードが分かる人に協力を仰いだりしてきました。Qt Project の中には Qt のプログラムができる人は山ほどいるのでこれらのものが Qt で書かれていたらと思ったことのある人は少なくないと思います。

Qt では作れないのか?

Qt を使ったウェブサーバーの実装は、これまで何度も試みた人がいるようで、「qt webserver」と検索すると以下のようなものが見つかります。

しかし、これらを使用して作られた本格的なウェブアプリケーションはあまりないように思われます。C++ で書くという需要があまりないのでしょうか。

ないものは作る

Qt 4.7 から QML というとてもシンプルな言語が使えるようになりました。

QML は、HTML に比べてとてもシンプルな記述になります。また、JavaScript でロジックを記述することも可能です。さらに、複雑なロジックやパフォーマンスを求められる部分については C++ による最適化が簡単にできるようになっています。

import QtQuick 2.0

Rectangle {
    width: 100; height: 100
    color: 'gray'

    Text {
        anchors.centerIn: parent
        text: "Hello World"
        font.pixelSize: 50
    }

    MouseArea {
        anchors.fill: parent
        onClicked: Qt.quit()
    }
}

今回はこの QML という言語でウェブのアプリケーションが作成できるようなフレームワークを作りました。

そもそも QML という言語は、GUI アプリケーションのユーザーインターフェースの記述を目的として開発されたものではありますが、GUI 向けであるという観念にはとらわれず、単純にプログラミング言語としての良い点を利用したいと思います。

QtHttpServer

「QML で書ける」と言っても、やはりベースの部分は Qt/C++ でも使えるようにしておくのが Qt 流です。

ウェブサーバーの部分は、前述の一覧の中にアクティブなプロジェクトがあればそれを流用しようと思ったのですが、結局 QtHttpServer という名前で1から自分で実装をしました。

以下のコードが QtHttpServer を使った簡単な サンプル になります。とても自然な Qt の API にしてあります。

/* Copyright (c) 2012 QtHttpServer Project.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the QtHttpServer nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL QTHTTPSERVER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <QtCore/QCoreApplication>

#include <QtHttpServer/QHttpServer>
#include <QtHttpServer/QHttpRequest>
#include <QtHttpServer/QHttpReply>

class HttpServer : public QHttpServer
{
    Q_OBJECT
public:
    explicit HttpServer(QObject *parent = 0);

private slots:
    void hello(QHttpRequest *request, QHttpReply *reply);
};

HttpServer::HttpServer(QObject *parent)
    : QHttpServer(parent)
{
    connect(this, SIGNAL(incomingConnection(QHttpRequest *, QHttpReply *)), this, SLOT(hello(QHttpRequest *, QHttpReply *)));

}

void HttpServer::hello(QHttpRequest *request, QHttpReply *reply)
{
    Q_UNUSED(request)
    reply->setStatus(200);
    reply->setRawHeader("Content-Type", "text/html; charset=utf-8;");
    reply->write("<html>\r\n"
                 "    <head>\r\n"
                 "        <title>Hello QtHttpServer</title>\r\n"
                 "    </head>\r\n"
                 "    <body>\r\n"
                 "        <h1>Hello QtHttpServer</h1>\r\n"
                 "    </body>\r\n"
                 "</html>\r\n");
    reply->close();
}

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    HttpServer server;
    if (!server.listen(QHostAddress::Any, 8080)) {
        qWarning() << "failed to listen.";
        return -1;
    }

    return app.exec();
}

#include "main.moc"

Silk

上記のサンプルをより実用的な形にしたのが Silk です。以下のようなソースから このようなページ を生成します。

/* hello.qml */
import Silk.HTML 5.0

Html {
    Head {
        Title { id: title; text: "Hello silk world" }
        Link { rel: "stylesheet"; href: "./hellocss.qml" }
    }
    Body {
        Article {
            Header {
                H1 { text: title.text }
            }
            Section {
                P { text: "QML is a Simple and Powerful language" }
            }
            Footer {
                P { text: "This HTML is generated by silk" }
            }
        }
    }
}

Silk では以下のような特徴があります。

現在使用できる基本的な機能毎に とても簡単なサンプル が用意されています。JavaScript というサンプルでは同じ .js ファイルをサーバーサイドでもクライアントサイドでも同じように使用できることを示していますし、chat のサンプルは WebSocket を使用したリアルタイム通信を行っています。どのサンプルもとてもシンプルなソースで構成されているので、是非ソースの方も見てみてください。

ちなみに、http://qtwebservice.io/ 自体も Silk 上で動いています し、http://qt5.jp/ も Silk 上で動いています。

試してみたい方は

QtWebService – Getting Started をご覧ください。

現時点では Linux でしか動作確認をしていませんが、Qt 5 が動作するプラットフォームであれば(多少の変更は必要かもしれませんが)動作するはずです。

まだまだ未熟なプロジェクトですが、Gerrit や Jenkins のようなみんなの役に立つシステムが Silk で作られることを目標に、今後も地道に開発を続けていきたいと思います。

おすすめ