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

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

2017/02

ウェブブラウザの検索履歴機能が有効になっていると、アドレスバーに一文字入力しただけで過去の履歴から参照ページの候補を出してくれます:
2017022101
 ↑ アドレスバーに "b" とだけ入力した時の画面。僕の第一候補は IBM Bluemix です!


これ、当然アルファベット毎に候補が変わります。また人によっても(細かなことを言うとブラウザによっても)結果は異なってくるわけです。

で、自分の場合の各アルファベット文字ごとに出てくる第一候補のサイトはどんな感じになるのかを面白そうなので調べてみました。なお 'L' の第一候補は localhost だったのですが、結果としてつまらなかったので排除しています。また http と https の混在は http で統一しています。その結果は以下の通りでした:

#URL説明
ahttp://analytics.google.com/Google Analytics
bhttp://bluemix.net/IBM Bluemix トップページ
chttp://console.ng.bluemix.net/IBM Bluemix コンソールページ
dhttp://dotnsf.blog.jp/まだプログラマーですが何か(個人ブログ)
ehttp://eclipse.org/Eclipse
fhttp://facebook.com/facebook
ghttp://github.com/dotnsf/GitHub
hhttp://h********.mybluemix.net/IBM Bluemix 上に作りかけのサービスサイト
ihttp://info.mybluemix.net/業務用ブログ
jhttp://juge.me/所有ドメインである juge.me の Tumblr サイト
khttp://kuku.lu/PGO サーチ(ポケモンGo)
lhttp://livedoor.blogcms.jp/livedoor Blog
mhttp://manholemap.juge.me/マンホールマップ
nhttp://nikkansports.com/日刊スポーツ
ohttp://openntf.org/OpenNTF.org
phttp://product.rakuten.co.jp/楽天商品価格ナビ
qhttp://qiita.com/キータ
rhttp://rakuten.co.jp/楽天市場
shttp://status.ng.bluemix.net/IBM Bluemix の稼働ステータス確認ページ
thttp://tweetdeck.twitter.com/TweetDeck(Twitter クライアント)
uhttp://uken.or.jp/IBM ユーザー研究会
vhttp://vm-171qzx1tim.sova.bz/wp.sova.jp 内に個人的実験目的で作った WordPress サイト
whttp://watson-api-explorer.mybluemix.net/IBM Watson の API Explorer
xhttp://www.play-asia.com/ゲームやゲーム機の情報サイト PLAY-ASIA.com(XBox などのキーワードでアクセスが多かったと思われる)
yhttp://youtube.com/YouTube
zhttp://japan.zdnet.com/article/35094997/ZDNet 内の IBM Bluemix 記事


2点ほど補足すると、"H" のサイトはいま開発途中のもので、まだ URL をお見せしたくないので伏せ字にさせていただきました。また "X" と "Z" は厳密には頭文字がこれらで始まるサイトではないのですが、他に候補がなかったのか、これらのページが第一候補になっていたので、そのまま記載しています。


結果を見た感想としては「マジメだなあ(笑)」という感じ。業務や自分の運営サービス、およびその関連サイトが多くなっています。あと O とか U とか V とか Z あたりはそんなに頻繁に使っている印象もないサイトなのですが、おそらく他の候補が弱すぎるのと、一時期何回もアクセスしていた時期が昔あったのでその影響もあるんだと思います。

1つ気になったのは T 。ここは自分が開発&運用しているツイートマッパー http://tweetsmapper.juge.me/ になってほしいところだったけど、実際によく使っているのは TweetDeck でした。ツイッター関連という意味では同様のサービスですが、やっぱり自分でも使わないとね。。



 

EclipseNode.js アプリケーションの開発(というかデバッグ)を行うことができるような環境を作ります。そのために Nodeclipse というプラグインを導入します:
2017022001


導入手順は Eclipse のメニューから Help - Install New Software を選択し、インストール元の URL には "http://nodeclipse.org/updates/" を指定します。しばらく待つと導入可能なプラグインの一覧が表示されるので、"1st Nodeclipse Core" と "Enide Tools Collection" の2つにチェックを入れてインストール作業を続行します。最後に Eclipse を再起動するよう指示されるので再起動します:
2017022002


導入作業はこれで終わり。以下は使い方の説明です。Eclipse が再起動したら、Node.js のアプリケーションプロジェクトが作れるようになっています。

Eclipse のメニューから File - New - Project を選択するとプロジェクトウィザードが起動します。その選択肢の中に Nodeclipse カテゴリがあり、そこから Node.js や Node.js Express のプロジェクトが作れるようになっているはずです。今回は試しに "Node.js Express Project" を選んでみました:
2017022003


いつものようにプロジェクト名(以下の例では "MyExpress01")を入力し、属性を設定します。テンプレートエンジンが何種類かの中から選べそうだったので、今回は個人的に慣れている ejs を選んでみました。最後に "Finish" をクリックしてプロジェクトを作成します:
2017022004


Node.js Express プロジェクトが作成された直後の様子です。ejs テンプレートを使ったシンプルなアプリケーションが生成されています。興味ある方は中身を個別に確認してみてください:
2017022009


Eclipse の全体画面がこちら。右上を見ると Node.js 用のパースペクティブが追加され、同パースペクティブで開いていることが確認できます:
2017022000


特に明示的には何も手を加えていないこの状態で一度実行してみることにします。app.js を右クリックし、Run As - Node Application を選択します:
2017022006


コンソールに起動メッセージが表示されます。3000 番ポートで起動しているようです:
2017022007


というわけでウェブブラウザで localhost:3000 にアクセスしてみます。こんな画面になれば成功です。このページ自体は app.js から routes/index.js が呼ばれ、views/index.ejs をテンプレートとしたレンダリングが行われた結果が表示されています。ここまで確認できたら一旦サーバーを停止しておきましょう:
2017022008


では次に今の様子をデバッグしてみることにします。まず routes/index.js を開き、レンダリングを行っている箇所にブレークポイントを仕掛けてみます(該当行の左側をダブルクリックしてブレークポイントマークが付いたことを確認します):
2017022001


今度はデバッグモードで Node.js を起動します。app.js を右クリックして Debug As - Node Application を選択します:
2017022002


デバッグモードでの起動を明示したので、Eclipse のパークペクティブをデバッグ用に切り替えるか?という確認ダイアログが表示されることがあります。その場合は "Yes" を選択します:
2017022003


Debug パースペクティブに切り替わります。同時にアプリケーションがデバッグモードで起動し、最初の1行目で止まった状態になります(まだ起動していないのでブラウザからはアクセスできません)。F8 を押すか、メニュー下の Resume アイコン(右向きの緑の矢印)をクリックして、実行を続けます:
2017022004


コンソールに先程のようなメッセージが表示されるとサーバーが起動してことを意味します。ではあらためてこの状態でウェブブラウザから localhost:3000 にアクセスしてみます:
2017022005


先程のような画面が表示される前にブレークポイントを設定していたので、そこで停止しているはずです。Eclipse の画面を確認すると指定したブレークポイントで止まっている様子が確認できるはずです:
2017022006


この状態で各種変数の値を確認したり、ステップ実行したり、・・・といった、いわゆるデバッグ作業を行うことができるようになっています。最後にもう一度 Resume して処理を続けます:
2017022007


Resume 後にウェブブラウザを確認すると、先程と同じ画面が表示されているはずです:
2017022008



Nodeclipse の紹介はざっとこんな感じ。ところで、Node.js の開発に Eclipse を使うのって、結構マイノリティな気がしているが、実際のところどうなんでしょ??


(参考)
http://qiita.com/suesan/items/7f2c4863feb87a623517

自分は(慣れの要素が大きいのですが)CentOS は未だに 6.x をメインに使っています。「別に不便じゃないし~」程度にしか考えていなかったのですが、その姿勢を改めた方がいいかなあ、と思うことがあったので、その時の様子と対処がいかに大変だったかをまとめました。

Linux で比較的新しめのアプリケーションを使おうとすると、たまにこんなエラーメッセージが出ることがあります:
./xxxxx: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by ./xxxxx)
./xxxxx: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.14' not found (required by ./xxxxx)
./xxxxx: /usr/lib64/libstdc++.so.6: version `CXXABI_1.3.5' not found (required by ./xxxxx)
./xxxxx: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by /opt/Xxxxx/libnode.so)

これはシステムにインストールされている libstdc++(GLIBC) ライブラリが古く、動かそうとしているアプリケーションが要求するバージョン(上の例では 3.4.14 や 3.4.15)と合わないためのエラーが発生していることを意味しています。


※実はこのエラーはこのブログエントリの最後に「Rodeo は CentOS/RHEL 7.x 上で動かすのが無難」と書いた時の話です。CentOS 6.8 で普通に Rodeo をインストールして動かすと、ここで紹介しているようなエラーに遭遇するのでした:
CentOS に Rodeo(Python IDE)をインストールする


実際にシステムに組み込まれている libstdc++ のバージョンを確認するには以下のコマンドを実行します(青字は出力結果):
# strings /usr/lib64/libstdc++.so.6 | grep GLIBCXX

GLIBCXX_3.4
GLIBCXX_3.4.1
GLIBCXX_3.4.2
GLIBCXX_3.4.3
GLIBCXX_3.4.4
GLIBCXX_3.4.5
GLIBCXX_3.4.6
GLIBCXX_3.4.7
GLIBCXX_3.4.8
GLIBCXX_3.4.9
GLIBCXX_3.4.10
GLIBCXX_3.4.11
GLIBCXX_3.4.12
GLIBCXX_3.4.13
GLIBCXX_FORCE_NEW
GLIBCXX_DEBUG_MESSAGE_LENGTH

この例の場合、3.4.1 から 3.4.13 までは組み込まれているシステムであることが分かります。このシステムで上記アプリケーションを動かそうとすると 3.4.14 や 3.4.15 は組み込まれていないため、上述のようなエラーが発生してしまう、ということになるのでした。エラーの原因はこれで特定できました。


この状況を回避するには libstdc++ を必要なバージョンが組み込まれた新しいものと入れ替える必要があります。そして libstdc++ は gcc に含まれるモジュールなので、(まず gcc ビルドの前提となる glibc を更新して、)gcc をビルドして、そこから libstdc++ を取り出して既存のものと入れ替える、というちと面倒な手順を実行する必要があるのでした。要するに libstdc++ を新しくするためには gcc をビルドする必要があるのです。。

更に、ここまでは全ての Linux ディストリビューションに言える内容なのですが、実際にこの手順を行う場合、CentOS/RHEL 6.x だとものすごく面倒な手順が待ち構えているのでした。実際に行った長い道のりを以下に紹介します。



まず、gcc のビルドに必要なヘッダーファイルをインストールします。具体的には gmp-devel, mpfr-devel, libmpc-devel が必要で、かつ 64bit 環境では glibc-devel.i686 までも必要になります。このうち libmpc-devel 以外は標準の yum リポジトリからインストールできますが、libmpc-devel は EPEL から入手する必要があります。

というわけで、まずは EPEL リポジトリをダウンロード:
# yum install epel-release

続けて上記3つのヘッダファイルと 32bit 版 glibc ヘッダをダウンロードします:
# yum install gmp-devel mpfr-devel libmpc-devel
# yum install glibc-devel.i686

ヘッダファイルの準備ができた所で最初に前提となる glibc をダウンロードしてビルドします。以下の例では /usr/local/src 以下にソースコードを展開しています:
# cd /usr/local/src
# wget http://ftp.gnome.org/pub/gnome/sources/glib/2.32/glib-2.32.4.tar.xz
# tar Jxvf glib-2.32.4.tar.xz
# rm glib-2.32.4.tar.xz
# cd glib-2.32.4
# ./configure
# make
# make install

そして、ここからやっと gcc のソースコードをダウンロードしてビルドします。ちなみにここから先の手順を行うためには 5GB 弱の空きディスク容量が必要になります。また make の実行を開始してから完了するまでに数時間かかります。スペックにもよりますが、昼寝ではなく一度マジ寝して起きる頃に終わっている、というくらいの時間を見ておく必要があります (^^;:
# cd /usr/local/src
# curl -LO http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-4.8.4/gcc-4.8.4.tar.gz
# tar fxz gcc-4.8.4.tar.gz
# rm gcc-4.8.4.tar.gz
# cd gcc-4.8.4
# ./configure
# make (僕の仮想環境ではここで4~5時間かかりました・・)
# make install

・・・で、gcc のビルドが無事に完了したら libstdc++ の入れ替えを行います。まずは現在の状況を確認します:
# ls -l /usr/lib64/libstdc*

lrwxrwxrwx 1 root root     19  1月 23 12:35 2017 /usr/lib64/libstdc++.so.6 -> libstdc++.so.6.0.13
-rwxr-xr-x 1 root root 989840  5月 10 18:38 2016 /usr/lib64/libstdc++.so.6.0.13

ビルドした結果から、目的のライブラリをコピーして、シンボリックリンクを作り直します:
# cp /usr/local/src/gcc-4.8.4/x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.19 /usr/lib64
# cd /usr/lib64
# mv libstdc++.so.6 libstdc++.so.6.bak
# ln -s libstdc++.so.6.0.19 libstdc++.so.6
# ls -l /usr/lib64/libstdc*

lrwxrwxrwx 1 root root      19  2月 14 12:07 2017 /usr/lib64/libstdc++.so.6 -> libstdc++.so.6.0.19
-rwxr-xr-x 1 root root  989840  5月 10 18:38 2016 /usr/lib64/libstdc++.so.6.0.13
-rwxr-xr-x 1 root root 6468627  2月 14 12:06 2017 /usr/lib64/libstdc++.so.6.0.19
lrwxrwxrwx 1 root root      19  5月 26 11:23 2016 /usr/lib64/libstdc++.so.6.bak -> libstdc++.so.6.0.13

最後に目的のバージョン(今回であれば 3.4.14 や 3.4.15)が有効になっているかどうかを確認します:
# strings /usr/lib64/libstdc++.so.6 | grep GLIBCXX

GLIBCXX_3.4
GLIBCXX_3.4.1
GLIBCXX_3.4.2
GLIBCXX_3.4.3
GLIBCXX_3.4.4
GLIBCXX_3.4.5
GLIBCXX_3.4.6
GLIBCXX_3.4.7
GLIBCXX_3.4.8
GLIBCXX_3.4.9
GLIBCXX_3.4.10
GLIBCXX_3.4.11
GLIBCXX_3.4.12
GLIBCXX_3.4.13
GLIBCXX_3.4.14
GLIBCXX_3.4.15
GLIBCXX_3.4.16
GLIBCXX_3.4.17
GLIBCXX_3.4.18
GLIBCXX_3.4.19
GLIBCXX_FORCE_NEW
GLIBCXX_DEBUG_MESSAGE_LENGTH

期待通りのライブラリに更新できました。これで CentOS 6.x でも Rodeo が動くようになりました。いやあ、長かった。めでたし、めでたし:
rodeo_centos6
↑今回のブログエントリは、この「CentOS 6.x 上で動く Rodeo」のスクリーンショットを撮るまでがいかに大変であったかを分かっていただきたいがために書きました。 (^^;


ここまで確認できれば glibc や gcc のソースコード一式は不要なので、消してしまっても構いません(何しろこの環境のために 5GB 前後のディスクを専有しているので・・・)
# cd /usr/local/src
# rf -rf glib-2.32.4 # rm -rf gcc-4.8.4




(参考)
http://qiita.com/dozo/items/de393588d5c267794ced

https://www.saintsouth.net/blog/update-libstdcpp-on-centos6/



 

前2回の続きです:
IBM Bluemix にカスタムサービスを追加する(1/3)
IBM Bluemix にカスタムサービスを追加する(2/3)


前回作成した REST API を実際に IBM Bluemix のカスタムサービスとして登録し、利用するまで(正確には使用を終えてカスタムサービスから削除するところまで)の手順を紹介します。なおこの手順はコマンドラインツールである cf が必要になるので、インストールしていない場合は各自の環境にあった cf をあらかじめダウンロード&インストールしておいてください:
https://github.com/cloudfoundry/cli/releases


また、今回カスタムサービスとして登録する REST API は前回紹介した app.js が https://dotnsf-operation.mybluemix.net/ にデプロイされて動いているものとします。以下の説明でのこのホスト名部分を実際にみなさんがデプロイしたホスト名に合わせて読み替えてください:
2017021401

2017021401


まず最初に、以下で登録するカスタムサービスをバインドして動作確認するためのランタイムを用意しておきます。動作確認用のアプリケーション(後述)を PHP で用意したので、IBM Bluemix 上に PHP のランタイムを1つ作成します(以下の例では teyande-php-env という名前で作成しています)、なおこの段階ではサービスは何もバインドしないでください:
2017021402


このランタイムに以下の内容の PHP アプリケーションをプッシュします:
https://github.com/dotnsf/phpEnv


上記アプリケーション(index.php)はシンプルに「ランタイムサーバー上の全環境変数とその値を表示する」というアプリケーションです。プッシュ後にサーバーにアクセスすると以下のような画面になります。この時点ではサービスを1つもバインドしていないので、環境変数 VCAP_SERVICES の値は空オブジェクトを示す "{}" となっていることが確認できるはずです:
2017021403


ここまでで準備は完了です。では前回紹介したサービスを IBM Bluemix のカスタムサービスとして登録した上でインスタンス化し、このランタイムにバインドした上で、この環境変数がどのように変わるかを確認してみます。

コマンドプロンプトかターミナルを起動し、上記の PHP ランタイムと同じデータセンター(この場合は US-SOUTH)の自分の組織に cf コマンドでログインします:
2017021404


カスタムサービスを利用するにためには Cloud Foundry の「サービスブローカー」(カスタムカタログのように理解してください)に登録しておく必要があります。まず現在のサービスブローカー一覧を確認するため "cf service-brokers" コマンドを実行して、この時点では何も登録されていないことを確認します:
2017021301
 ↑"No service brokers found"(何も登録されていません)です


ではサービスブローカーを新規に1つ作成します。以下のパラメータを付けてコマンドを実行します:
> cf create-service-broker サービスブローカー名 認証ユーザー名 認証パスワード サービスの基本URL --space-scoped
2017021302


今回、サービスブローカー名は "dotnsf-operation" とします。認証のユーザー名とパスワードは dotnsf-operation の Catalog API などが実行される時用に指定した auth_user / auth_pass の値を指定します(詳しくは前回の記事参照)。サービスの基本 URL は今回 "https://dotnsf-operation.mybluemix.net" です。そして最後にオプションの "--space-scoped"(同一組織内のユーザーが使えるサービスとするための指定)を加えています。

コマンドが成功したことを確認し、再度 "cf service-brokers" を実行してみます。先程は結果に何も含まれていませんでしたが、今度は直前に作成したばかりの "dotnsf-operation" サービスブローカーが、指定した URL で登録されていることを確認してください:
2017021303


これで dotnsf-operation API がカスタムカタログに登録された状態になりました。ただ IBM Bluemix のウェブ UI ではカスタムカタログを表示する画面が存在していないので、この時点ではまだウェブ UI には現れませんが、cf からは同様にインスタンスを作成することができるようになっています。

というわけで、続けて cf でこのサービスをインスタンス化してみます:
> cf create-service サービスブローカー名 プラン名 サービス名

サービスブローカー名は dotnsf-operation の Catalog API(GET /v2/catalog) 内で定義したもの(今回の場合は dotnsf-operation-service)を指定します。プランも同様に Catalog API 内で定義したもの(今回は "free" と "enterprise" の2つ)の中から指定できますが、今回は "free" を指定しています。最後に IBM Bluemix 内でのサービス名として "dotnsf-operation-1" と指定しています:
2017021304


ここまでの手続きが完了すると、dotnsf-operation サービスがインスタンス化され、ウェブ UI の利用中サービス一覧の中にも表示されるようになります:
2017021301


この時点でサービスを選択し、管理タブを開くと Dashboard API で定義した内容が表示されるはずです(https で Dashboard API にアクセスできることが条件です):
2017021000


ではこのサービスを最初に作成した PHP ランタイムにバインドしてみましょう。PHP ランタイムの画面に移動し、接続タブの「既存に接続」をクリックします:
2017021302


現在利用中のサービスインスタンスの一覧が表示されます。その中に "dotnsf-operation-1" という名前のサービスが含まれているはずなので、これを選択して「接続」をクリックします。そしてこの内容をランタイムの環境変数にも反映させるため「再ステージング」します:
2017021303


改めて同ランタイムの接続タブを参照すると、"dotnsf-operation-1" サービスが追加されていることを確認できます。ここで「資格情報の表示」をクリックします:
2017021304


今回の dotnsf-service の Bind API では credentials 情報として uri だけを追加していました。そのためこのサービスインスタンスの資格情報にも uri 1つだけが credentials 情報として表示されていることが確認できるはずです:
2017021305


改めてランタイムのトップ画面にアクセス(リロード)します。今回は先程と異なり dotnsf-operation-1 サービスがバインドされているので、環境変数 VCAP_SERVICES は空オブジェクトではなく、dotnsf-operation-1 の情報が表示されているはずです。credentials 情報も先程確認したものと同じものが表示されています:
2017021306



・・・というわけで、IBM Bluemix のカスタムサービスを作成して、登録して、インスタンス化して、ランタイムとバインドして環境変数に反映させる、という一連の作業ができることを確認できました!

最後に使い終わったサービスブローカーを削除する手順を紹介しておきます。まず(同サービスブローカーを使っているランタイムからのバインドを全て解除した上で)サービス一覧画面からインスタンス化されたサービスを削除します:
2017021305


サービスが削除されるとウェブ UI からは見えなくなります。が、まだサービスブローカーには登録されているので、以下の cf コマンドでサービスブローカーからも削除します:
> cf delete-service-broker サービスブローカー名
2017021306


このコマンドが成功すると、サービスブローカー一覧からも削除されます。念のため "cf service-brokers" コマンドで確認します:
2017021307




久しぶりの超大作ブログとなり、3回に分けて紹介しましたが、IBM Bluemix のカスタムサービスを作るために実装しないといけない内容や、実装後の登録方法などを紹介できたつもりです。自分が作った REST API を IBM Bluemix に統合して使いたい場合や、公式なサードパーティ API としての登録を検討する場合に必要となる実装がどんなものかを説明しました。

今回紹介した内容はあくまで「同一組織内でのみ有効なカスタムサービス」の登録方法および手順の紹介でしたが、全 IBM Bluemix ユーザー向けに実装する場合であっても同様の API 実装は必要になります。そういったエコシステムを活性化する上での役立つ情報になれば幸いです。


(参考)
 https://www.ibm.com/blogs/bluemix/2017/01/extend-bluemix-service-broker/

前回の続きです。IBM Bluemix に独自のカスタムサービスを追加する手順を紹介しています:
IBM Bluemix にカスタムサービスを追加する(1/3)

前回紹介した Node.js で実装済みの REST API を IBM Bluemix のカスタムサービスとして利用できるように改良する、その改良内容を紹介します。


前回のおさらいになりますが、改良前の API はこちらです:
https://github.com/dotnsf/dotnsfOperation/blob/master/app0.js
//. app0.js
var express = require( 'express' ),
    cfenv = require( 'cfenv' ),
    appEnv = cfenv.getAppEnv(),
    app = express();


//. Service APIs
app.get( '/', function( req, res ){
  var html = '<title>My Broker</title>';
  html += '<h1>My Broker</h1>';
  html += '<p>ドキュメントは<a target="_blank" href="./doc">こちら</a>を参照ください</p>';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

app.get( '/:operation/:x/:y', function( req, res ){
  var operation = req.params.operation;
  var x = parseFloat( req.params.x );
  var y = parseFloat( req.params.y );
  var z = 0;

  if( operation == 'plus' ){
    z = x + y;
  }else if( operation == 'minus' ){
    z = x - y;
  }else if( operation == 'multiply' ){
    z = x * y;
  }else if( operation == 'divide' ){
    z = x / y;
  }

  res.writeHead( 200, [ { 'Content-Type': 'text/plain' } ] );
  res.write( '' + z );
  res.end();
});

app.get( '/doc', function( req, res ){
  var html = '<title>My Operation Document</title>';
  html += '<h1>My Operation Document</h1>';
  html += '<p>This is a document for my operation</p>';
  html += '<hr/>';
  html += '/plus/x/y => return ( x + y )<br/>';
  html += '/minus/x/y => return ( x - y )<br/>';
  html += '/multiply/x/y => return ( x + y )<br/>';
  html += '/divide/x/y => return ( x / y )<br/>';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

var service_port = appEnv.port ? appEnv.port : 1337;
app.listen( service_port );
console.log( "server starting on " + service_port );


最終型を先に紹介しますが、これを以下のように改良します(追加箇所を赤くしています):
https://github.com/dotnsf/dotnsfOperation/blob/master/app.js
//. app.js
var express = require( 'express' ),
    basicAuth = require( 'basic-auth-connect' ),
    bodyParser = require( 'body-parser' ),
    cfenv = require( 'cfenv' ),
    uuid = require( 'uuid' ),
    appEnv = cfenv.getAppEnv(),
    app = express();

app.use( bodyParser.urlencoded( {
    extended: true
}));
app.use( bodyParser.json() );

//. API Version
const X_BROKER_API_VERSION = 2.0;
const X_BROKER_API_VERSION_NAME = 'X-Broker-Api-Version';

//. Plans
const plan0 = {
  'id': uuid.v4(),
  'name': 'free',
  'description': 'Free plan',
  'free': true
};
const plan1 = {
  'id': uuid.v4(),
  'name': 'enterprise',
  'description': 'Enterprise plan',
  'free': false
};

//. service endpoint
var service_host = 'dotnsf-operation.mybluemix.net';
var service_port = appEnv.port ? appEnv.port : 1337;
var service_base = service_host; // + ':' + service_port;
var service_instance = 'http://' + service_base + '/operation/';
var service_dashboard = 'http://' + service_base + '/operation/dashboard';

//. Service
var my_service_id = uuid.v4();
var my_service = {
  'id': my_service_id,
  'name': 'dotnsf-operation-service',
  'description': '四則演算 API',
  'bindable': true,
  'tags': [ 'private' ],
  'plans': [ plan0, plan1 ],
  'dashboard_client': {
    'id': uuid.v4(),
    'secret': 'secret-1',
    'redirect_uri': 'http://bluemix.net'
  },
  'metadata': {
    'displayName': 'My operational service',
    'longDescription': 'WebAPI で四則演算を行う',
    'providerDisplayName': 'dotnsf',
    'documenttationUrl': 'http://' + service_base + '/doc',
    'supportUrl': 'https://stackoverflow.com/questions/tagged/ibm-bluemix'
  }
};


//. Auth for /v2/*
const auth_user = 'username';
const auth_pass = 'password';
app.all( '/v2/*', basicAuth( function( username, password ){
  return username === auth_user && password === auth_pass;
}));


//. Cloud Foundry Broker APIs

//. Catalog
app.get( '/v2/catalog', function( req, res ){
  console.log( 'GET /v2/catalog' );

  var api_version = req.get( X_BROKER_API_VERSION_NAME );
  if( !api_version || X_BROKER_API_VERSION > parseFloat( api_version ) ){
    res.writeHead( 412, [ { 'Content-Type': 'text/plain' } ] );
    res.write( 'Precondition failed. Missing or imcompatible ' + X_BROKER_API_VERSION_NAME + '. Expecting version ' + X_BROKER_API_VERSION + ' or later.' );
    res.end();
  }else{
    var services = {};
    services['services'] =[];
    services['services'][0] = my_service;
    res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
    res.write( JSON.stringify( services ) );
    res.end();
  }
});

//. Provision
app.put( '/v2/service_instances/:instance_id', function( req, res ){
  var instance_id = req.params.instance_id;

  var contentType = req.get( 'Content-Type' );
  if( contentType != 'application/json' ){
    res.writeHead( 415, [ { 'Content-Type': 'text/plain' } ] );
    res.write( 'Unsupported Content-Type: expecting application/json.' );
    res.end();
  }

  var provision_details = req.body;
  //. {
  //    "service_id": "<service-guid>",
  //    "plan_id": "<plan-guid>",
  //    "organization_guid": "<org-guid>",
  //    "space_guid": "<space-guid>"
  //  }
  console.log( 'PUT /v2/service_instances/' + instance_id );
  console.log( provision_details );

  //. Provisioning process here.

  //. Return basic service information
  var new_service = {};
  //new_service['dashboard_url'] = service_dashboard + '/' + instance_id;
  new_service['dashboard_url'] = service_dashboard + '/' + instance_id;
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( new_service ) );
  res.end();
});


//. Deprovision
app.delete( '/v2/service_instances/:service_id', function( req, res ){
  var service_id = req.params.service_id;

  console.log( 'DELETE /v2/service_instances/' + service_id );

  //. Deprovisioning process here.

  //. Return basic information
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( {} ) );
  res.end();
});

//. Bind
app.put( '/v2/service_instances/:service_id/service_bindings/:binding_id', function( req, res ){
  var service_id = req.params.service_id;
  var binding_id = req.params.binding_id;

  var contentType = req.get( 'Content-Type' );
  if( contentType != 'application/json' ){
    res.writeHead( 415, [ { 'Content-Type': 'text/plain' } ] );
    res.write( 'Unsupported Content-Type: expecting application/json.' );
    res.end();
  }

  var binding_details = req.body;
  //. {
  //    "plan_id": "<plan-guid>",
  //    "service_id": "<service-guid>",
  //    "app_guid": "<app-guid>"
  //  }
  console.log( 'PUT /v2/service_instances/' + service_id + '/service_bindings/' + binding_id );
  console.log( binding_details );

  //. Binding process here.

  //. Return result to the Bluemix Cloud Controller
  var result = {};
  result['credentials'] = {};
  result['credentials']['uri'] = 'http://' +  service_base + '/';
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( result ) );
  res.end();
});

//. Unbind
app.delete( '/v2/service_instances/:service_id/service_bindings/:binding_id', function( req, res ){
  var service_id = req.params.service_id;
  var binding_id = req.params.binding_id;

  console.log( 'DELETE /v2/service_instances/' + service_id + '/service_bindings/' + binding_id );

  //. Unbinding process here.

  //. Return basic information
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( {} ) );
  res.end();
});



//. Service related functions
app.all( '/operation/:instance_id', function( req, res ){
  var instance_id = req.params.instance_id;

  console.log( req.method + ' /operation/' + instance_id );

  //. Return service information
  var service_info = {};
  service_info['greeting'] = instance_id;
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( service_info ) );
  res.end();
});

app.get( '/operation/dashboard/:instance_id', function( req, res ){
  var instance_id = req.params.instance_id;

  console.log( req.method + ' /operation/' + instance_id );

  //. Return hard-coded HTML
  var html = '見つかっちゃった。。。(*/ω\*)';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

app.all( '/operation/:instance_id/:binding_id', function( req, res ){
  var instance_id = req.params.instance_id;
  var binding_id = req.params.binding_id;

  var contentType = req.get( 'Content-Type' );
  if( contentType != 'application/json' ){
    res.writeHead( 415, [ { 'Content-Type': 'text/plain' } ] );
    res.write( 'Unsupported Content-Type: expecting application/json.' );
    res.end();
  }

  //. Return service information
  var service_info = {};
  service_info['instance_id'] = instance_id;
  service_info['binding_id'] = binding_id;
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( service_info ) );
  res.end();
});



//. Service APIs
app.get( '/', function( req, res ){
  var html = '<title>My Broker</title>';
  html += '<h1>My Broker</h1>';
  html += '<p>ドキュメントは<a target="_blank" href="./doc">こちら</a>を参照ください</p>';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

app.get( '/:operation/:x/:y', function( req, res ){
  var operation = req.params.operation;
  var x = parseFloat( req.params.x );
  var y = parseFloat( req.params.y );
  var z = 0;

  if( operation == 'plus' ){
    z = x + y;
  }else if( operation == 'minus' ){
    z = x - y;
  }else if( operation == 'multiply' ){
    z = x * y;
  }else if( operation == 'divide' ){
    z = x / y;
  }

  res.writeHead( 200, [ { 'Content-Type': 'text/plain' } ] );
  res.write( '' + z );
  res.end();
});

app.get( '/doc', function( req, res ){
  var html = '<title>My Operation Document</title>';
  html += '<h1>My Operation Document</h1>';
  html += '<p>This is a document for my operation</p>';
  html += '<hr/>';
  html += '/plus/x/y => return ( x + y )<br/>';
  html += '/minus/x/y => return ( x - y )<br/>';
  html += '/multiply/x/y => return ( x + y )<br/>';
  html += '/divide/x/y => return ( x / y )<br/>';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

app.listen( service_port );
console.log( "server starting on " + service_port );

追加した箇所をで記載しています。通常の Web API を IBM Bluemix(Cloud Foundry) サービスとして対応させる場合には少なくともこれだけの追加が必要になる、と理解してください。

具体的な追加の内容は8種類12個の Web API を追加しています。以下にそれぞれの内容を紹介します。

(1) Catalog API (GET /v2/catalog)

(コマンドライン版の)サービス一覧に表示される情報を取得する API です。リクエスト時に HTTP ヘッダ 'X-Broker-Api-Version' が設定されており、その値が実装 API バージョン(この場合は 2)以上でない場合はエラーとします。実行後の返り値についてはソースコード内の変数: services 参照していただきたいのですが、返り値のオブジェクトの中にサービス名称や説明、選択可能なプランや資料情報 URL などを含めます。


(2) Provision API (PUT /v2/service_instances/:instance_id)

サービスをプロビジョンして作成する際にコールされる API です。HTTP ヘッダ 'Content-Type' の値が 'application/json' 以外の場合はエラーとします。実際にはパラメータ :instance_id の値を使ってインスタンスを生成して管理するところまでを実装する必要があるのですが、今回のサンプル内ではそこまでは実装していません。実行後の返り値についてはソースコード内の変数: new_service を参照してください。


(3) Deprovision API (DELETE /v2/service_instances/:instance_id)

サービスでデプロビジョンして削除する際にコールされる API です。実際にはパラメータ :instance_id の値を使って管理中のインスタンス情報を削除するところまでを実装する必要があるのですが、今回のサンプル内ではそこまでは実装していません。実行後の返り値は空オブジェクトです。


(4) Bind API (PUT /v2/service_instances/:instance_id/service_bindings/:binding_id)

サービスのバインドを作成する際にコールされる API。HTTP ヘッダ 'Content-Type' の値が 'application/json' 以外の場合はエラーとします。実際にはパラメータ :instance_id , :binding_id の値を使ってバインドを生成して管理するところまでを実装する必要があるのですが、今回のサンプル内ではそこまでは実装していません。実行後の返り値の中にランタイムの環境変数 VCAP_SERVICES に含まれる credentials 情報を含めます(つまり実行時用の id や password が必要な場合は、ここで作成して返り値に含める、という実装を行ってください)。具体的にはソースコード内の変数: result を参照してください。


(5) Unbind API (DELETE /v2/service_instances/:instance_id/service_bindings/:binding_id)

サービスをアンバインドして削除する際にコールされる API です。実際にはパラメータ :instance_id , :binding_id の値を使って管理中のバインド情報を削除する(例えば (4) で生成した username と password を無効にする、など)ところまでを実装する必要があるのですが、今回のサンプル内ではそこまでは実装していません。実行後の返り値は空オブジェクトです。


↑上記 (1) ~ (5) の API 実行時には Basic 認証をかけておきます。


(6) Instance Info APIs (PUT,GET,DELETE /operation/:instance_id)

サービスインスタンス情報を作成(PUT)/取得(GET)/削除(DELETE)するための API です。具体的な返り値はコースコード内の変数: service_info を参照してください。


(7) Bind Info APIs (PUT,GET,DELETE /operatoin/:instance_id/:binding_id)

サービスインスタンスとのバインド情報を作成(PUT)/取得(GET)/削除(DELETE)するための API です。HTTP ヘッダ 'Content-Type' の値が 'application/json' 以外の場合はエラーとします。具体的な返り値はコースコード内の変数: service_info を参照してください。


(8) Dashboard (GET /operation/dashboard/:instance_id)

サービスの管理情報やダッシュボードとして表示する画面です。IBM Bluemix のウェブ画面に表示される内容です:
2017021401

  ↑この「管理」タブの内容です

なお、この中身が正しく表示されるためには https でこの API が実行できる必要があります。具体的な返り値の例はコースコード内の変数: html を参照してください。



元のコード(app0.js)に、上記8つの API を追加実装することで IBM Bluemix のサービスとしての最低限の振る舞いが可能になります。また (1) ~ (5) の API については認証を付けることができるので、そのための実装も追加したものが新しいコード(app.js)、ということになります。この追加実装のためにいくつかのミドルウェアも追加で必要になったため、新しいコードではその部分も含めて追加しています。


そして、パッケージ情報を記述したファイル(packages.json)や、IBM Bluemix 上で実行するための manifest.ymlREADME.md までを追加した一通りのセットがこちらです:
https://github.com/dotnsf/dotnsfOperation

2017021402


IBM Bluemix 環境で動かす場合の使い方は README.md にも記載していますが、ダウンロードするか git clone した後に manifest.yml 内のアプリ名、ホスト名、ドメイン名をランタイム環境に合わせて編集します。加えて app.js 内に "dotnsf-operation.mybluemix.net" というホスト上で、"dotnsf-operatoin-service" というサービス名で動かすことを前提とした内容が記述されているので、該当箇所を実際の内容に合わせて書き換えます。また Basic 認証は username : password となっているので、ここを変更する場合はコード内の auth_user および auth_pass 変数を書き換えてください。

こうしてコードを追加した app.js を IBM Bluemix にプッシュすることでサービスが稼働することになります。なお、サービスそのものは IBM Bluemix 内で稼働させる必要はありませんが、上記 (8) の API は https 対応している必要があるので、IBM Bluemix 以外の環境で動かす場合は https 対応できる環境下で稼働させてください。


実際に dotnsf-operation.mybluemix.net 上にプッシュして動かしている様子が以下になります:
2017021402
 ↑元々の機能+αが実装された API を動かしています。

これで IBM Bluemix のサービスとして統合可能な Web API の用意ができました。次回は実際にこの API を IBM Bluemix のカスタムサービスとして登録し、ランタイムにバインドして使ってみる様子とその手順を紹介します。


(追記)
続きはこちら:
http://dotnsf.blog.jp/archives/1064290977.html

このページのトップヘ