まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

2016/03

このエントリが特定の個人を揶揄する目的で作ったものではないことを最初にお断りしておきます。


ちょうど今(2016/Mar/24)話題になっている某氏の謝罪ウェブサイトを見ていて、あることに気付きました:
2016032400


このページの内容自体にどうこう言うつもりはありません。問題はこのページの URL です。http://ototake.com/ にアクセスするとこのページが表示されるようになっています。もともとはこの人のウェブページ用のサーバーだったはずだけど、トップページだけ作り変えたのでしょうか?


ではプロフィール紹介用のページ http://ototake.com/profile/ はどうなっているのか、と思ってアクセスすると、404 エラー(ページが見つからない)になりました:
2016032402


ちなみに(グーグルのキャッシュによると)以前はこんなページだったはず:
2016032403


トップページを変えただけでなく、プロフィールページも消したのか・・・ いや違う。このエラーページの内容は単なる「ページが見つからない」ではなく、"Code: NoSuchKey" とか、 "Key: profile" とか、何かをシステマチックに探した上で見つからない、というエラーに見えます。単にページ内容を入れ替えたものではなさそうです。


この謎はアドレスを逆引きして分かりました:
2016032401


なるほど、Amazon S3静的ウェブホスティング機能を使って作られたページだったようです。ということはどこかに元のページも残しつつ、DNS の切り替えだけでこの謝罪ページに飛ぶよう設定されていた、ということです。

なかなかうまい方法だと思いました。まず今回のような謝罪ページは一時的にアクセスが集中する可能性があるので、今まで使っていたウェブサーバーの中身を置き換えただけではアクセスを捌ききれる保証はありません。そこを S3 の静的ウェブホスティングにすれば比較的安価に(アカウントを新規に作れば1年間は無料で)それなりの可用性が保証されたこのページを作ることができます。 そして DNS の切り替えでこの新ページを運用しているとすれば、謝罪終了(?)後に元のページへ戻す作業も DNS を切り替え直すだけで簡単に済みますし、その後は謝罪ページも消してしまえば、それ以降の料金はかかりません。 ということは現在のサーバーを落とす必要もないので、再切り替え時の手間やトラブルも少なく済みそう、と推測されます。


このやり方はうまいなあ、と思いました。DNS の切り替えにかかるタイムラグや、各種キャッシュの切り替わりを意識する必要はあるにせよ、コスパのいい謝罪ページ運用だと感心してしまいました。もちろん謝罪用だけでなく、何らかの事情で一時的に異なるページを表示する必要が生じた場合の方策と考えても使えると思います。

S3 互換のオブジェクトストレージサービスを運用している会社にとってはビジネスの参考になります(苦笑)。


BotKit を使って導入した Slack のボットの挙動を自分なりにカスタマイズしてみましょう。まずは以下のエントリを参考にして、BotKit を導入し、ボットがデフォルトの挙動で動くような状態を作っておいてください:
Slack の BotKit を使う


さて、ボットの実体は bot.js ファイルです。このファイルをテキストエディタで開くと、初期化処理などの後にこのような contoller.hears() の処理がいくつか定義されていることに気付きます:
controller.hears( コマンド, 種類, 処理 );

例えばコマンドとして ['hello','hi'] が定義されているものとして、このような記載を見つけることができます:
controller.hears( ['hello','hi'], 'direct_message,direct_mention,mention', function(bot, message){
       :
  (処理の中身)
       :
} );

これは次のような意味になります:
(1) 'hello' または 'hi' というコマンドを、
(2) 'direct_message'(1対1でのメッセージ)か、'direct_mention'(@XXX hello のように直接メンションされた場合)か、'mention'(hello @XXX のようにメッセージ中でメンションされた場合)に、
(3) function(bot,message){ ... } の内容を実行する

つまり前回のエントリでボットに対して @XXX hello というメッセージをダイレクトメンションで送った時に実行される処理がこれ、ということになります。


では簡単な処理でカスタマイズしてみましょう。bot.js 内に以下のようなコードを追加します:
controller.hears(['double (.*)'],'direct_mention',function(bot, message) {
    var matches = message.text.match(/double (.*)/i);
    var msg = matches[1];
    bot.reply(message,msg+' '+msg);
});

これはボットに対してダイレクトメンションで "@XXX double ABCDE" のようなメッセージが送られた時にハンドルし、"ABCDE ABCDE" のように double に続く部分を2度喋る、という処理をするようなコードです。

この bot.js を保存して実行(実行方法は前回のエントリ参照)し、"@XXX double Happy" のようにダイレクトメンションでメッセージを送ると、"Happy Happy" のような反応をしてくれるはずです:
2016031802


この例では処理内容がかなりシンプルな例でしたが、5行程度の追加でここまでは出来てしまいました。実際には受け取ったパラメータを元にデータベースや外部などから情報を作り出して返す、といったより本格的な Slack ボットを作ることもできると思っています。

応用編というわけではないのですが、REST API を使って外部から情報を取り出して表示する、というサンプルも1つ作ってみました。こちらも同様に bot.js に追加します:
   :
   :
controller.hears(['(usd|eur|gbp|aud|nzd|cad|chf)'],'direct_mention',function    (bot, message) {
  var matches = message.text.match(/(usd|eur|gbp|aud|nzd|cad|chf)/i);
  var cur = matches[0].toUpperCase() + "JPY";
  var request = require( 'request' );
  url = 'http://fx.mybluemix.net/';
  request( url, function( error, response, body ){
    if( !error && response.statusCode == 200 ){
      var json = JSON.parse( body );
      var dt = json['datetime'];
      var rate = json['rate'];
      var p = rate[cur];
      bot.reply( message, dt + ' ' + p );
    }
  });
});

以前作った為替の JSON サービス(http://fx.mybluemix.net/)を使い、ダイレクトメンションで USD(米ドル)EUR(ユーロ)GBP(英ポンド)AUD(豪ドル)NZD(NZドル)CAD(カナダドル)CHF(スイスフラン)のいずれかを受け取った場合、それらの通貨が日本円だといくらになるのか、をリアルタイムに計算して返信してくれる、というボットです:
2016032101


Node.js、というか JavaScript での実装に慣れない人がいるかもしれませんが、比較的簡単な言語だと思うので、この Slack ボットを作る機会と一緒に勉強して慣れておくと面白いと思います。


Slack のボットを作るためのフレームワークである BotKit の使い方(というか導入方法)を紹介します。


前提条件として、当然ですが Slack のアカウントは必要です。お持ちでない場合は Slack のページでサインアップおよびサインインを済ませておいてください。サインイン済みの環境に対してボットを作成します。

また BotKit は Node.js のアプリケーションとして動作するので、そのパッケージマネージャである npm が導入されている必要があります。(CentOS 環境用の内容ですが)以下のページを参照するなどして、npm コマンドが使える状態を作っておいてください:
CentOS に StrongLoop をインストールする


最初に Slack の Bot creation page にアクセスして、新しいボットを1つ作成します:
Bot creation page


アクセス先でボットの名前を指定(下図では @dotnsf_bot)して、"Add bot integration" ボタンをクリックすると、ボットが作成されます。ボットの名前は他の人が使っていると使えないので気をつけてください:
2016031701


ボットの作成に成功すると以下の様な画面になり、API Token が表示されます。この値を後で使います。メモ帳などにコピーしておきます:
2016031702


仮にこの API Token の値が xoxb-XXX(API Token)XXX であったとして以下を紹介します。適宜自分の環境と読み替えてください。

次に BotKit をダウンロードします。npm をセットアップしたシステムにログインし、適当なディレクトリ(以下の例では /usr/local/src/)で以下のコマンドを続けて実行し、必要なパッケージとその依存パッケージをダウンロードします:
# cd /usr/local/src
# git clone https://github.com/howdyai/botkit.git
# cd botkit
# npm install

全て導入が完了したら、上記で取得した API Token を使ってボットを動かします(以下を一行で実行します):
# token=xoxb-XXX(API Token)XXX node bot.js

すると以下の様な画面になって、ボットが起動します(途中でボットを強制終了させる場合は Ctrl+C で抜けます):
2016031703



デフォルトのボット(実体は bot.js)ではいくつかの決められた挙動をするように実装されています。いくつか試してみましょう。


まず自分の Slack ID で(ボットを作成した時の Slack ID で)Slack にアクセスし、適当なチャネル(例えば #general)に入ります:
2016031704


以下のコマンドをメッセージとして入力し、ボットをこのチャネルに招待します:
/invite @(ボットの名前)
2016031705


続けて "Hello" というメッセージをこのボット宛に送ります。頭に @(ボットの名前): を付けてメッセージを送ります:
@(ボットの名前): Hello


するとボットが反応し、Hello というメッセージを返してくれます。最初の会話が成立しました:
2016031706



続けて自分のことを聞いてみます。先程と同様にして "Who am i" というメッセージを送ってみますが、"I don't know yet!"(「まだ知らない」)という答が返ってきました。そりゃそうだ:
2016031707


というわけで自分のことを教えてあげましょう。"call me (名前)" というメッセージを送ります。「次からはそう呼ぶ」と分かってくれたっぽい感じ:
2016031708


では改めて "Who am i" と聞いてみます。今度は自分が指定した名前を返してくれました。一応覚えていてくれたっぽいです:
2016031709


最後にこのボットを Slack から終了する方法も紹介します。実行中のコマンドラインから Ctrl + C で強制終了することもできますが、"shutdown" と命令し、確認のメッセージに "Y" と回答するとボットはシャットダウンします(コマンドも終了します):
2016031801




と、デフォルトのボットの挙動はまあこんな感じです。この中身は bot.js の中で実装されていて、そのカスタマイズもこのファイルを編集することになりますが、それについては別のまた機会に。

(2016/Mar/22 追記)
続きはこちら


CentOS のデスクトップ環境での操作をそのまま動画として撮影する場合は recordMyDesktop というフリーのツールを使います:
2016031902


この recordMyDesktop の導入手順と実行手順を紹介します。なお以下の手順はいつものように CentOS 6.x 64bit 環境を前提としています。

導入そのものは yum で行いますが、標準リポジトリには含まれていないため、最初にリポジトリの追加を行います:
# cd /tmp
# wget http://dag.wieers.com/rpm/packages/RPM-GPG-KEY.dag.txt 
# rpm --import RPM-GPG-KEY.dag.txt

次にテキストエディタで /etc/yum.repos.d/dag.repo というファイルを、以下の内容で新規に作成して保存します:
[dag]
name=Dag RPM Repository for Red Hat Enterprise Linux
baseurl=http://apt.sw.be/redhat/el6/en/$basearch/dag/
gpgcheck=1
enabled=0

これでリポジトリの用意ができたので、yum でインストールします:
# yum --enablerepo=dag install recordmydesktop gtk-recordmydesktop

インストール後は CentOS デスクトップから アプリケーション - サウンドとビデオ - gtk-recordMyDesktop を選択して recordMyDesktop を起動します:
2016031901


recordMyDesktop が起動するとプレビュー画面と合わせてウィンドウが1つ起動します(画面全体がデスクトップで、中央に起動しているのが recordMyDesktop。その中で画面全体のプレビューも表示されている様子):
2016031903


このプレビュー部分をマウスでドラッグして、どのエリアを録画対象にするかを指定することができます。この図で赤くなっている部分が録画対象エリアに指定されている部分です:
2016031904


するとデスクトップにも黒枠が表示され、録画対象エリアが明示されます。録画対象エリアが定まったら「録画」ボタンをクリックして録画を開始します:
2016031905


録画中の様子です。黒枠内だけが録画されています:
2016031906


録画を止めるには画面上部のメニューから四角い部分をクリックします:
2016031907


すると録画した内容のファイル保存処理が実行されます。エンコードに時間がかかるのでその間はしばらく待ちます:
2016031908


保存が完了すると、再度 recordMyDesktop のウィンドウ画面になります。このまま別の動画の保存を続けることもできます。終了する場合は「終了」ボタンをクリックします:
2016031909


録画された内容は、ホームディレクトリ内に *.ogv という拡張子が付いたファイルとして保存されます:
2016031910


録画した内容は Totem などを使って再生することが可能です:
2016031911


これで CentOS デスクトップ環境でも画面の動画保存が可能です。

仕事で「スレッドセーフなサーブレット」の話に遭遇したのでまとめておきます。


通常のサーブレットは java.servlet.http.HttpServlet を拡張して作ります。この方法で作成した場合、サーブレットはシングルインスタンスがマルチスレッドで動作します。サーブレットのインスタンスそのものは1つで、この1つのインスタンスが複数のリクエストを並行処理する、という挙動になります。並行処理している間はスレッドセーフではありません:

import javax.servlet.http.*;

public class MyServlet extends HttpServlet{
  :
  :
}

仮にこのサーブレットがリクエストされ、その実行が終了する前に別のリクエストを受けた場合、別のリクエストは前のサーブレットの終了を待たずに実行が始まります。大半のケースではこれが期待する挙動になると思っています。 ただしスレッドセーフではないので、例えばサーブレットの処理の中にデータベースへの更新処理が含まれているような場合、排他制御できるかどうかはそのデータベースシステム側に求められるか、データベース側が排他制御していないのであればサーブレットの中に独自の排他制御の仕組みを実装する必要があります。

ではサーブレットをスレッドセーフに作る方法はないのか? というのが今回の話題でした。

その結論がこちらです。javax.servlet.SingleThreadModel インターフェースを継承して作成したサーブレットはシングルスレッドモードになり、実行中は他のスレッドリクエストは待ち行列に入ります。結果としてスレッドセーフな処理が実現できますが、処理速度を考えるとこのサーブレットで実現する部分は可能な限り小さく作るべきであると思っています:

import javax.servlet.http.*;
import javax.servlet.*; public class MyServlet extends HttpServlet implements SingleThreadModel{ : : }

実際の処理ではデータベースだけではなく、どのリソースにスレッドセーフ性が求められるのか、を意識して実装する必要があるのですが、そこが明確になっているのであれば上記のような方法で実装そのものは簡単にできる、ということになります。後はそこだけを綺麗に分離して、小さく作れるかどうか、、ということになるのかな。




このページのトップヘ