一行掲示板 suzume.cgi

C++11 で書く CGI のとっかかりとして、実用性無視でとにかくまともに動作する CGI を書いてみることにしました。作ったのは一行掲示板、それも最新 20 ポストを表示するだけという素晴らしい手抜き具合の産物になりました。

https://github.com/tociyuki/suzume-cgi-cxx11

純粋な CGI なので、動かしてみるには CGI ハンドラを持つ HTTP サーバがいります。Apache httpd が定番ですし、そうでなくても mattn さんの「Big Sky :: C++で軽量Webサーバ書いた。」も良いものです。

モデルでは、カラムが id と body だけというシンプルな sqlite3 のテーブルを使っています。

CREATE TABLE entries (
  id INTEGER PRIMARY KEY
 ,body TEXT NOT NULL
);

ということで、モデル・オブジェクトも単純です。以前書いた sqlite3 の C++11 ラッパーを使っています。 一方、 データベースから読み出した行をどのようなデータ構造に格納するのが都合が良かろうかと考えてみて、今回はとりあえず使いまわしができて楽な JSON のオブジェクトの配列を作ることにしました。

struct suzume_data {
    suzume_data (std::string const& a) : dbname (a) {}

    void insert (std::wstring const& body) const
    {
        sqlite3pp::connection dbh (dbname);
        dbh.execute ("BEGIN;");
        auto sth = dbh.prepare ("INSERT INTO entries VALUES (NULL, ?);");
        sth.bind (1, body);
        if (SQLITE_DONE == sth.step ())
            dbh.execute ("COMMIT;");
        else
            dbh.execute ("ROLLBACK;");
    }

    void recents (wjson::json& doc) const
    {
        doc.as<wjson::object> ()[L"recents"] = wjson::json (wjson::array {});
        sqlite3pp::connection dbh (dbname);
        auto sth = dbh.prepare ("SELECT body FROM entries ORDER BY id DESC LIMIT 20;");
        while (SQLITE_ROW == sth.step ()) {
            wjson::json entry (wjson::object {});
            entry.as<wjson::object> ()[L"body"] = wjson::json (sth.column_string (0));
            doc[L"recents"].as<wjson::array> ().push_back (std::move (entry));
        }
    }

private:
    std::string const dbname;
};

当初、生の JSON をクライアントへ送り出して、Javascript で表示しようかと考えていたのですが、せっかく作った wmustache テンプレート・エンジンを使ってみるため、HTML に書き出すことにしました。ビューはテンプレートをテキストファイルから読み出してコードに変換して JSON のパラメータを当てはめています。コントローラは、モデルから読み取った JSON をテンプレートに受け渡す get_frontpage と、 POST されたフォームデータをモデルに追加する post_body からできています。call は runcgi からのエントリポイントです。

enum { POST_LIMIT = 1024 };

struct suzume_appl : public http::appl {
    suzume_data data;
    suzume_view view;

    suzume_appl (std::string const& dbname, std::string const& srcname)
        : data (dbname), view (srcname) {}

    bool get_frontpage (http::request& req, http::response& res)
    {
        wjson::json doc (wjson::object {});
        data.recents (doc);
        std::wostringstream content;
        if (! view.render (doc, content))
            return false;
        res.content_type = "text/html; charset=UTF-8";
        res.body = encode_utf8 (content.str ());
        return true;
    }

    bool post_body (std::vector<std::wstring>& param, http::request& req, http::response& res)
    {
        for (auto i = param.begin (); i < param.end (); i += 2) {
            if (i[0] == L"body") {
                data.insert (i[1]);
                res.status = "303";
                res.location = "suzume.cgi";
                return true;
            }
        }
        return false;
    }

    //call エントリポイント・メンバ関数は略
};

他は、ウェブ・アプリケーション・フレームワークに入っているようなコードの羅列になっています。