2014/07/18

Recent entries from same category

  1. C言語から分かりやすいAPIで扱える JSON パーサ「cJSON」
  2. C++ で STL フレンドリに扱えるJSONパーサ「json.hpp」
  3. SSE を使わなくても HTML エスケープはある程度速くできる。
  4. glib を使ったマイクロフレームワーク「balde」
  5. ODBC 接続なアプリケーションの開発が楽になる C++ ライブラリ nanodbc

先日、github で crow という、python の flask からインスパイアされた C++ 製 micro web framework を見つけました。

ipkn/crow - GitHub
https://github.com/ipkn/crow

ルーティングの書き方が flask ぽく、かつモダンな C++ な書き方だったの気に入りました。

crow::Crow app;

CROW_ROUTE(app, "/")
    .name("hello")
([]{
    return "Hello World!";
});
app.port(18080)
    .multithreaded()
    .run();

何か作ってみたくなったので、lingr の bot を書いてみました。crow には JSON パーサ(シリアライザ)が同梱されているので API サーバを作るのも簡単そうです。

#include "crow.h"
#include "json.h"

#include <iostream>
#include <sstream>
#include <regex>

static std::vector<std::string>
str_split(const string s, const string d) {
  std::vector<string> r;
  std::string::size_type n;
  std::string b = s;
  while ((n = b.find_first_of(d)) != b.npos) {
    if(n > 0) r.push_back(b.substr(0, n));
    b = b.substr(n + 1);
  }
  if(b.length() > 0) r.push_back(b);
  return r;
}

static std::string&
str_replace(std::string& s, const std::string p, const std::string r) {
  std::string::size_type n(s.find(p));
  while (n != std::string::npos) {
    s.replace(n, p.length(), r);
    n = s.find(p, n + r.length());
  }
  return s;
}

class logger : public crow::ILogHandler {
public:
  void log(string message, crow::LogLevel level) override {
    cerr << message;
  }
};

int
main() {
  std::vector<std::pair<const std::regex, const std::string>> resp;
  std::ifstream ifs("pattern.txt");
  if (ifs.fail()) return 1;
  string buf;
  while(ifs && getline(ifs, buf)) {
    auto t = str_split(buf, "\t");
    if (t.size() != 2continue;
    std::cout << "Loading pattern: " << t[0] << "=" << t[1] << std::endl;
    resp.push_back(make_pair(std::regex(t[0]), t[1]));
  }
  ifs.close();

  crow::Crow app;

  CROW_ROUTE(app, "/")
    ([]{
        return "lingr-cppbot";
    });

  CROW_ROUTE(app, "/lingr")
    ([&](const crow::request& req){
      auto x = crow::json::load(req.body);
      if (!x) return crow::response(400);

      std::stringstream os;
      for (const auto& e : x["events"]) {
        std::string t = e["message"]["text"].s();
        for (const auto& r : resp) {
          std::smatch matches;
          if (!regex_match(t, matches, r.first)) continue;
          std::string output = r.second;
          for (std::smatch::size_type i = 0; i < matches.size(); ++i) {
            std::stringstream ss;
            ss << "$" << i;
            str_replace(output, ss.str(), matches[i]);
          }
          os << output;
          break;
        }
      }
      return crow::response{os.str()};
    });

  crow::logger::setLogLevel(crow::LogLevel::INFO);
  crow::logger::setHandler(std::make_shared<logger>());

  uint16_t port;
  std::stringstream ss;
  ss << getenv("PORT");
  ss >> port;
  if (port == 0) port = 8080;
  app.port(port).multithreaded().run();
}

// vim:set et:

仕組みは至って簡単で、カレントディレクトリにある pattern.txt という、以下の様なファイルを読み込み、正規表現(タブ文字で区切られた左側)に対する置換文字列(右側)を設定します。

^あー$  いー
^いー$  うー
^うー$  えー
^えー$  おー
^おー$  もうええやろ!
^(.+)(大好き|だいすき)  $1は俺の物だ。勝手に呼び捨てにするな。

起動して、正規表現にマッチする発言を受信すると、置換文字列をサブマッチなどで入れ替えて返答します。

cppbot

サブマッチ置換が使えるので、ある程度人間っぽい発言も可能ですし、gh:mattn という文字列から https://github.com/mattn という返答をさせてアンカーを作る等の応用例もあります。(参考資料)

crow は内部でスレッドを使っており、複数同時リクエストにも応答出来る様です。色々遊べそうなので試してみてはいかがでしょうか。


blog comments powered by Disqus