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

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

タグ:bluemix

最近、ボット関連の API や SDK をよく見るわりに、自分では使う機会が少なかったので、今更ながら1つ作ってみました。

今回作ってみたのは「シェルボット」です。名前の通り、ボットでシェルっぽいインターフェースを実現しました。何言ってるかよくわからない人はこちらの画面を参照ください:
2017060901



ボットっぽい対話型インターフェースを使って、対話しているかのようにシェルコマンドを実行します。今回はインターフェースに独自の HTML ページを用意しましたが、ぶっちゃけ LINE などでも(Messaging API を使うなどすれば)応用できると思っています。

#「チャットボット」とは違うけど「ボット」ですっ!


ソースコードを github で公開しておきました。セキュリティ的な面はまだまだ改良の余地があると思っています(rm コマンド他は実行不可にしていますが、本当は他にも実行を制御するコマンドを考慮したほうがいいと思う):
https://github.com/dotnsf/shellbot


↑このソースコードはビジュアルフローエディタである Node-RED で使う前提のものです。(IBM Bluemix などを使って)Node-RED 環境を用意し、func-exec ノードを追加した上でキャンバスに shellbot_nodes.txt の内容をインポートして、ブラウザで /shell にアクセスするだけです(詳しくは README.md 参照)。

#あー、あと今のところ Linux/Unix 系 OS に導入する前提です。


実際に Node-RED 環境上にインポートした様子のスクリーンショットがこちらです。2本のシンプルな HTTP リクエスト処理が定義されているだけのもの(うち1つは HTML 画面定義)ですが、本当にこれだけでこのシェルボットが動きます:
2017060902


実は今まで「ボット」=「チャットボット」=「コンシェルジュ」的なイメージを持っていてハードルが高かったのですが、今回のアプリを作った結果、難しく考えずに「既存アプリケーションのインターフェースを対話型にする」という実装もアリかな、と思うようになりました。


IBM Bluemix(Cloud Foundry) のプラットフォームが現在持っている制約の1つが「IPアドレスによるアクセス制限」に関するものです。残念ながら現時点ではベースとなっている Cloud Foundry にこの機能がなく、IBM Bluemix でも実装されていません。

というわけで、現状この機能を実現するにはプラットフォーム側ではなくアプリケーション側で用意する必要があります。Node.js アプリケーションでこれを実現する方法の1つとして、Express-IpFilter があります:
https://www.npmjs.com/package/express-ipfilter

2017060601



名前の通りの機能です。Node.js の Express フレームワークの中で IP アドレス制限(許可/拒絶)を簡単に実現することができます。

Express-IpFIlter をインストールするには npm で以下を実行します:
$ npm install express-ipfilter
(実際には express のインストールも必要です)

例えば、以下のような Node.js + Express のシンプルなアプリケーションを例に IP アドレス制御をかける例を紹介します。まずアプリケーション(app.js)は以下のような内容のものを使います:
//. app.js

var express = require( 'express' );
var app = express();

app.use( express.static( __dirname + '/public/' ) );

app.listen( 3000 );

アプリケーションの中でスタティックディレクトリを /public/ に指定しています。そこで /public/index.html というファイルを用意し、中身を以下のようなものにします:
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>はろーわーるど</h1>
</body>
</html>

これを普通に Node.js で実行します:
$ node app

3000 番ポートを listen するように指定しているので、このポートを指定して同一マシンからウェブブラウザでアクセスすると、スタティックディレクトリに用意した index.html を見ることができます:
2017060602


ではこのアプリにアクセス制御をかけてみます。まずは '127.0.0.1' からのアクセスを拒絶するようなフィルターをかけてみます。app.js の内容を以下のように変更します(赤字部分を追加):
//. app.js

var express = require( 'express' );
var ipfilter = require( 'express-ipfilter' ).IpFilter;
var app = express();

var ips = [ '127.0.0.1' ];
app.use( ipfilter( ips ) );

app.use( express.static( __dirname + '/public/' ) );

app.listen( 3000 );

この例では ips という配列変数を用意し、その中に対象とする IP アドレスを文字列配列の形式で代入します。そして ipfilter を有効にしています。

この状態で app.js を起動し、先程と同じように同一マシンからウェブブラウザでアクセスすると以下のようになります:
2017060603


IP アドレス制御が有効になり、アクセスは拒否されました。

では今後は逆に '127.0.0.1' からのアクセスのみ許可するようなフィルタをかけてみます。app.js の内容を以下のように変更します(青字部分を追加):

//. app.js

var express = require( 'express' );
var ipfilter = require( 'express-ipfilter' ).IpFilter;
var app = express();

var ips = [ '127.0.0.1' ];
app.use( ipfilter( ips, { mode: 'allow' } ) );

app.use( express.static( __dirname + '/public/' ) );

app.listen( 3000 );

この状態で再度アプリケーションを起動し、同様にウェブブラウザでアクセスすると、今度は元のように表示されます(つまり IP Filter はデフォルトだと拒絶、'allow' モードを指定すると許可のフィルタをそれぞれ有効にします):
2017060604


これでアプリケーションレベルでの IP アドレス制限が実現できます。

話題(?)のブロックチェーンを小型コンピュータであるラズベリーパイ(以下、「ラズパイ」)から操作する、ということに挑戦してみました。


まずは操作の対象となるブロックチェーン環境を用意します。今回は IBM のクラウドプラットフォームである IBM Bluemix から提供されているマネージドハイパーレジャー環境である IBM Blockchain サービスを使うことにします(2017/Jun/04 現在では HyperLedger Fabric v0.6 と v1.0 がベータ提供されています)。IBM Bluemix アカウントが必要になりますが、30日間無料試用も用意されているので、必要に応じてアカウントを取得してください。

IBM Bluemix にログイン後、米国南部データセンターになっていることを確認し、「サービスの作成」をクリックします:
2017060404


そして「アプリケーション・サービス」カテゴリ内にある "Blockchain" を探してクリック:
2017060405


利用する Blockchain の種類をプランから選択します。今回は無料の "Starter Developer plan(beta)" を選択します。ちなみにこのプランの場合、Hyperledger Fabric は v0.6 を使うことになり、4つのブロックチェーンピアが作成されます。最後に「作成」ボタンをクリック:
2017060406


しばらく待つと IBM Blockchain サービスが起動します。ここでダッシュボードに移動することもできますが、この後で利用する情報を先に取得しておきます。左ペインの「サービス資格情報」を選択:
2017060407


サービス資格情報から「資格情報の表示」をクリックして、表示される JSON テキストを全てコピーしておきます(この後、ラズパイ内で使います):
2017060408


改めて「管理」タブを選択し、「ダッシュボードを起動」ボタンをクリックして、ダッシュボード画面に切り替えておきましょう:
2017060409


IBM Blockchain のダッシュボード画面が表示されます。4つのピアが動いていますが、この時点ではまだチェーンコードは作られていないことが確認できます。これでブロックチェーン側の準備は完了です:
2017060401


では続けてラズパイ側の準備に取り掛かります。以下はラズパイ上での作業です(僕の検証では3Bを使いました)。ラズパイにはネットワークのセットアップが済んだ Raspbian Jessie を用意し、そこに Hyperledger Fabric Node SDK ごとサンプルコードを導入してデモアプリケーションを動かします。というわけでまずは Node.js と npm を用意します。まずはこのドキュメント内にあるように、Node.js v6.2.x ~ v6.10.x のバージョンの環境を用意します(以下は v6.10.3 の例です。v7.x は未対応):
$ sudo apt-get install -y nodejs npm
$ sudo npm cache clean
$ sudo npm install n -g
$ sudo n v6.10.3
$ node -v
v6.10.3  <- v6.10.3 の導入が完了

続けて npm を最新版(以下の例では v5.0.2)に更新します:
$ sudo npm update -g npm
$ sudo npm outdated -g
$ npm -v
v5.0.2  <- v5.0.2 の導入が完了

git でデモアプリをダウンロードし、必要なライブラリをインストールします:
$ git clone https://github.com/IBM-Blockchain/SDK-Demo.git
$ cd SDK-Demo
$ npm install

ダウンロードしたデモアプリの中(直下のディレクトリ)にある ServiceCredentials.json ファイルの中身を、上記でコピーしたサービス資格情報の JSON テキストの中身で全て書き換えます:
{
  "peers": [
    {
      "discovery_host": ******

      :
      :
"cert_path": "/certs/peer/cert.pem" }

これでラズパイ側の準備も完了しました。後は以下のコマンドを実行して、デモアプリを実行します:
$ node helloblockchain.js

以下のようにコマンドが次々を流れていきます。実際にはこの中でチェーンコードの初期化や、初期データ入力、データ変更などが行われています。最後に "Successfully ****" というメッセージがいくつか表示されていればコマンドは成功です:

2017060403


この最後のメッセージについて補足します。この helloblockchain.js ではチェーンコード初期化などの作業を行った後に「"a" という入れ物に 100 、"b" という入れ物に 200 のデータを保存」します(ここまでが初期化)。そして「"a" から "b" へ 10 移動」するようなトランザクションが実行されます(なのでここまでの処理が完了すると "a" に 90 、"b" に 210 入っている状態になるはずです)。最後に「"a" の値を取り出す」処理が実行されます。その辺りの様子が以下のメッセージから分かります("a" が 90 になっています):
  :
  :
Chaincode ID : XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Successfully deployed chaincode: request={"fcn":"init","args":["a","100","b","200"],"chaincodePath":"chaincode","certificatePath":"/certs/peer/cert.pem"}, response={"uuid":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}

Successfully submitted chaincode invoke transaction: request={"chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","fcn":"invoke","args":["a","b","10"]}, response={"uuid":"zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"}

Successfully completed chaincode invoke transaction: request={"chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","fcn":"invoke","args":["a","b","10"]}, response={"result":"Tx zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz complete"}
Custom event received, payload: "Event Counter is 1"


Successfully queried  chaincode function: request={"chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","fcn":"query","args":["a"]}, value=90


※↑数値の移動は Submit して Complete しているのが別行で表示されているので、全部で4行表示されています。


なお初期化部分は最初の1回しか実行されないため、この helloblockchain.js を何度か実行すると、"a" の値が 10 ずつ減っていく様子がわかります:
$ node helloblockchain.js

  :
  :
Successfully queried  chaincode function: request={"chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","fcn":"query","args":["a"]}, value=80

$ node helloblockchain.js

  :
  :
Successfully queried  chaincode function: request={"chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","fcn":"query","args":["a"]}, value=70


また、これらの処理を実行すると、ダッシュボード上からもチェーンコードが生成されて、実行されている様子が確認できます(初期状態だと画面更新が3分ごとなので、画面に反映されるまで少し時間がかかるかもしれません):
2017060402


まだ環境の一部がベータ版だったりする状態ではありますが、今やラズパイでもブロックチェーンが動くようになったんですねー。

先日、HTML5 Canvas のサーバーサイド版である Node-Canvas を紹介しました:
Node.js から使えるサーバーサイド Canvas : node-canvas


HTML5 Canvas と互換性のある API を使って動的に Canvas 上に描画をします。実際には単に描画をするだけでなく、描画した内容を画像として取り出したりすることで、サーバー側の JavaScript で動的に画像ファイルを生成することができるようになり、とても便利なライブラリです。

実際に使っていて、1点問題気になることがありました。開発中は(日本語環境バッチリな)自分のマシンを使って動作テストなどを行うので気が付きにくいのですが、実際の本番サーバー上で動かした際にキャンバス上に描画した日本語文字列が化けてしまうケースを発見しました。

例えばこのようなコードを実行して、サーバーサイドで画像を動的に生成したとします:
  :
var fs = require( 'fs' );
var Canvas = require( 'canvas' ),
    Image = Canvas.Image;

  :

var canvas = new Canvas( 300, 300 );
var ctx = canvas.getContext( '2d' );

//. 赤い直線を1本描画する
ctx.beginPath();
ctx.moveTo( 100, 100 );
ctx.lineTo( 200, 200 );
ctx.strokeStyle = 'red';
ctx.stroke();

//. 文字列を表示する
ctx.fillText( 'ハローワールド!', 10, 30 );

//. 画像データをファイルにして保存する
var b64 = canvas.toDataURL().split( ',' )[1];
var buf = new Buffer( b64, 'base64' );
fs.writeFileSync( __dirname + '/images/aaa.png', buf );

  :


このコードを Node.js で実行すると、./images/aaa.png というファイル名で動的に画像ファイルが生成されて保存されます。が、その時に生成される画像で「ハローワールド!」と明示されている文字列はサーバー実行環境によって正しく表示されたり、されなかったりします:

(正しく表示される場合)
2017052901

(文字化けを起こす場合)
2017052902


これはサーバー OS 側に日本語フォントが導入されているかどうかで決まります。特にクラウド環境においては OS は(デフォルトでは)英語版が用意されることが多く、そのまま利用しても日本語フォントが用意されていないため、「ハローワールド」という文字列を正しく描画できず、結果として文字化けを起こしてしまうことになります。

ということは、仮に文字化けを起こしても、OS に日本語フォントを導入した上でアプリケーションサーバーを再起動すれば回避できる、ということになります。

が、ここで問題が発生します。IBM Bluemix のような PaaS 環境の場合、OS 環境設定の自由度が低く、用意されたプラットフォームをカスタマイズして使うハードルが高くなります。現に SSH でログインすることはできますが apt-get などは使えません。またフォントファイルを後から無理やりコピーしてもアプリケーションサーバーを再起動するとそのファイルは消えてしまいます。このような自由度があまり高くないプラットフォーム上で日本語を正しく表示させることが結構難しかったりします。

そしてなんと更に、Node-Canvas の Issue ボードによると、この部分にはどうもバグがあるらしく、(2017/May/29 現在)ステータスが Open になっているので、まだ解決できていない模様でした:
https://github.com/Automattic/node-canvas/issues/783


・・・と、とても困った状況なのですが、結論から言うとなんとかアプリケーション側の工夫で回避することができました。まずは日本語フォントが必要になるので、フリーの日本語フォントを適当にダウンロードしてきます。例えば eFont プロジェクトから配布されているさざなみフォントの最新版を使うことにします。アーカイブファイル(*.tar.bz2)をダウンロード&展開し、sazanami-***.ttf というファイルをアプリケーション内の fonts/ ディレクトリに格納しておきます:
2017052903

(以下のコードではゴシックフォントしか使いませんが、とりあえず明朝フォントと合わせて展開しておきます)


そしてコードの該当部分を以下のように変更します:
  :
var fs = require( 'fs' );
var Canvas = require( 'canvas' ),
    Image = Canvas.Image;

  :

var canvas = new Canvas( 300, 300 );
var ctx = canvas.getContext( '2d' );

//. 赤い直線を1本描画する
ctx.beginPath();
ctx.moveTo( 100, 100 );
ctx.lineTo( 200, 200 );
ctx.strokeStyle = 'red';
ctx.stroke();

//. フォントを強制指定する
var Font = Canvas.Font;
var myFont = new Font( 'myFont', 'fonts/sazanami-gothic.ttf' );
ctx.addFont( myFont );
ctx.font = '20px myFont';

//. 文字列を表示する
ctx.fillText( 'ハローワールド!', 10, 30 );

//. 画像データをファイルにして保存する
var b64 = canvas.toDataURL().split( ',' )[1];
var buf = new Buffer( b64, 'base64' );
fs.writeFileSync( __dirname + '/images/aaa.png', buf );

  :


これで実行すると、フォントが指定のものに強制指定され、日本語が化けずに表示できるようになります(下図は IBM Bluemix 環境下で実行した結果です):
2017052904


なお、上記の方法を使う場合、Ubuntu はこのままでも動くのですが、Mac OS X 環境においては Pango ライブラリを無効にした上で実行する必要があります。具体的には Node-Canvas を npm install した後で、node_modules/canvas/binding.gyp ファイルを編集し、15行目の値を 'false' にする(8行目と同じにする)作業を行い、再度 npm rebuild した上で node コマンドを実行する必要がありました。ご注意ください。


Watson API の1つで、複数の選択肢の中から自分の与えた条件に合うアイテムをトレードオフ判断で絞り込んでくれる、という便利な Tradeoff Analytics API があります(ありました)が、残念ながら5月11日をもって新規インスタンスの生成ができなくなってしまいました(作成済みインスタンスは2018年4月まで使えるようです。詳しくはこちら)。

Bluemix に Watson API が追加された当初から存在していた API の1つであるだけでなく、個人的にもデモなどで使っていて、非常にわかりやすくて便利な API だっただけに残念でした。

このアナウンスがされたタイミングから準備はしていたのですが、実はこの Tradeoff Analytics と同様の(近い互換性を持つ)API を自分でオープンソース化前提で作っています。まだ途中といえば途中ですが、制約付きで一応動く状態にはなっています。新インスタンスが生成できなくなったこのタイミングで一旦公開してみようと思いました:
https://github.com/dotnsf/yatradeoff


なお、この API を実際にアプリケーションサーバー上で稼働させているものもこちらに用意しました。ただ API だけ使いたい人はこちらをどうぞ:
https://yatradeoff.au-syd.mybluemix.net/dilemmas


この URL にブラウザで GET アクセスしても何も起こりません(エラーメッセージが表示されるだけ)が、POST アクセスすると API として動きます。その際にはユーザー名: username &パスワード: password を指定してください(オリジナル同様 Basic 認証です)。


元の Tradeoff Analytics API 同様、/dilemma という POST API エンドポイント1つだけが定義されています。ポストする JSON 型インプットデータのフォーマットには互換性があります。

API の実行結果としての JSON データにも互換性をもたせました。ただ現状はオリジナルのサブセットになっています。現状、以下の3点の制約事項があります:
(1) preferable_solutions データは常にブランクになります
(2) 解析結果は solutions に含まれますが、'FRONT' ステータスを持つデータだけが含まれます
(3) map information を出力するようパラメータで指定しても map information は返されません



ただ1点、オリジナルにはなかった優先順位を考慮する機能も加えています。エンドポイント URL のパラメータに prioritised=1 を追加して実行(例: /dilemma?prioritised=1)すると、インプットデータの JSON データは優先順位順に解釈されます(先にある方が高い優先順位であると解釈されて実行されます。例えば「項目AとBを両方ともトレードオフの材料にする」のではなく、「項目AとBを両方ともトレードオフの材料にするが、Aの方をBよりも優先する」というトレードオフを可能にしています)。


そして、このパラメータを付けて実行した時の API 実行結果も優先順位順に返されます。先にある方がよりオススメな結果、ということになります。まあ preferable_solutions の代わりという位置付けです。


※実はこの機能を付けたくて自分で API を改良した、という経緯があります。


結果の JSON テキストの solutions 内がトレードオフの結果です。オリジナル API では全ての選択肢に対して 'FRONT' か 'EXCLUDE' かの結果を含めていましたが、現状この API では 'FRONT' のもの(トレードオフ判断の結果、選択肢の候補として残ったもの)だけが返される点に注意してください。


このページのトップヘ