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

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

タグ:ibm

先日、IBM Watson のサービスが IAM (Identification & Access Management)およびリソースグループに対応した、というニュースがありました:
IBM Watson AI のサービスについて、IAM およびリソースグループが有効に


この対応により、IBM Watson API を使うサービスの認証方式が変更になります。現行の(この変更前に作ったものの)方式は 2019/10/31 まで有効ですが、それ以降は認証エラーになってしまいます。また 2018/11/01 以降に作成した Watson サービスインスタンスは新しい認証方式でないと接続できなくなっています。

要は Watson サービスを使って今動いているアプリケーションは1年以内にこの新しい認証/認可の方式に対応させる必要があり、また新しく作るアプリケーションに関しては新しい認証/認可方式を使って作成する必要がある、ということになります。この「Watson の新しい認証/認可方式を使ってアプリを作成」するための手順を紹介します。

【新しい認証/認可方式に対応した Watson サービスのインスタンス化】
まず、新しい認証/認可方式に対応した Watson サービスを用意する必要があります。今回は比較的多く使われていると思われる画像認識機能 Visual Recognition API を使って紹介しますが、基本的には全てのサービスで同様の方法を実装することになります。

2018/11/07 時点で、IBM Cloud 内で新しく Visual Recognition サービスを作成すると、新しい認証/認可方式に対応したインスタンスとなります。というわけで IBM Cloud にログインし、リソースの作成にて「AI」カテゴリの Visual Recognition を選択します:
2018110701


インスタンスの属性を選択します。以前はここで「ロケーション」に加え「組織」や「スペース」を選択していたのですが、新しいインスタンスではロケーションとリソースグループ(default のままで大丈夫です)を選択します:
2018110702


下にスクロールしてサービスのプランを選択します(下図では無料版の Lite を選択しています)。最後に右下の「作成」ボタンでインスタンスを作成します:
2018110703


作成したインスタンスの画面がこちらです。この画面で API KeyURL が確認できます(API Key は当初マスキング処理されています):
2018110704


クレデンシャル情報を表示させることで一時的に API Key を確認することができます。新しい認証/認可方式ではこの API Key を使ってサービス API を利用することになります:
2018110705



【新しい認証/認可方式に対応したアプリケーション】
次に、アプリケーション側をこの新しいサービスインスタンスに対応した形で作り直します。今回は Node.js と Watson Developer Cloud SDK を使う前提でアプリケーションを作る想定で以下を記述します。

新しい認証方式の Visual Recognition では以下のようなコードで認証し、インスタンスオブジェクトを作成します:
//. test01.js
var fs = require( 'fs' );

//. https://www.npmjs.com/package/watson-developer-cloud
var vr_v3 = require( 'watson-developer-cloud/visual-recognition/v3' );

var vr = new vr_v3({
  url: "https://gateway.watsonplatform.net/visual-recognition/api",
  version: '2018-03-19',
  iam_apikey: "(上記で取得した API Key)"
});

var filename = 'sample.png';
if( process.argv.length >= 3 ){
  filename = process.argv[2];
}

var params = {
  images_file: fs.createReadStream( filename )
};

vr.classify( params, function( err, result ){
  if( err ){
    console.log( err );
  }else{
    console.log( JSON.stringify( result, null, 2 ) );
  }
});

青字部分は API Key を取得した時に同時に表示されていた URL の値を url に指定しています。また赤字部分は取得した API Key 文字列を iam_apikey に指定しています。この形でインスタンスオブジェクト(上記コードだと vr 変数)を作成する必要がある、という点がこれまでと異なります。

なお、上記コードで作っているコマンドアプリ(test01.js)は
$ node test01 (画像ファイル名)

という形で指定して実行することを想定しています。(画像ファイル名)部分に画像認識を実行する画像のファイルパスを指定します(無指定の場合は同じフォルダにある sample.png に対して実行します)。

今回はいらすとや様の、この画像を sample.png として用意しました:
sample01


この画像に対して実行してみます。まず Watson Developer Cloud SDK を導入します:
$ npm install watson-developer-cloud

そして実行(実行結果は緑字):
$ node test01

{
  "images": [
    {
      "classifiers": [
        {
          "classifier_id": "default",
          "name": "default",
          "classes": [
            {
              "class": "first-aid kit",
              "score": 0.879,
              "type_hierarchy": "/kit of things/first-aid kit"
            },
            {
              "class": "kit of things",
              "score": 0.879
            },
            {
              "class": "dado (dice)",
              "score": 0.5
            },
            {
              "class": "figure",
              "score": 0.79
            },
            {
              "class": "jade green color",
              "score": 0.77
            },
            {
              "class": "emerald color",
              "score": 0.511
            }
          ]
        }
      ],
      "image": "sample.png"
    }
  ],
  "images_processed": 1,
  "custom_classes": 0
}

(結果はともかく)新しい認証/認可方式のコードで実行できました!


IBM Cloud の旧 Bluemix に代表される、いわゆる PaaS 環境ではアプリケーション・サーバーなどのミドルウェアまでをベンダー側が提供し、利用者はサーバーやその管理を意識せずにアプリケーションのみ提供して動かすことができます。

これはこれで便利な環境なのですが、一方で実際に動かしている環境の詳細(ミドルウェアや言語ランタイムのバージョンなど)を知りたくなることもあります。知らなくても使えるんだけど知りたい、という要望です。そういった情報が公開されていればいいのですが、多くのケースで常に最新バージョンが使われるようにメンテナンスされており、公開情報が最新版の内容を反映していないケースもあります。というわけで IBM Cloud の SDK for Node.js ランタイムを対象に実際に動いている環境からバージョンを調べる、という方法で調べてみました。

SDK for Node.js ランタイムで以下のコードをデプロイして動かします:
// app.js

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

var appEnv = cfenv.getAppEnv();

app.get( '/', function( req, res ){
  var process_version = process.version;
  res.write( JSON.stringify( { version: process_version }, 2, null ) );
  res.end();
});


var port = appEnv.port;
app.listen( port );
console.log( 'server started on ' + port );

↑プログラムコード内から process.version を参照して、現在動いている node のバージョンを取り出しています。

ちなみに package.json は以下のように Node.js v6.x の最新版が使われるように指定しています:
{
    "name": "node-v",
    "version": "0.0.1",
    "scripts": {
        "start": "node app.js"
    },
    "dependencies": {
        "cfenv": "~1.0.0",
        "express": "4.1.x"
    },
    "repository": {},
    "engines": {
        "node": "6.x"
    }
}

このコードを cf push でデプロイし、(2018/10/27 時点で)GET / を実行した結果がこちらです:
2018102701

v6.x の最新版、が使われた結果がこのようになりました。

また、package.json の同部分を "node": "8.x" に変えて再デプロイして実行するとこうなりました:
2018102702


動的に Node.js の実行バージョンを取得することができました。


IBM CloudantApache CouchDB をベースとしたマネージド NoSQL DB サービスです。IBM Cloud のライトアカウントを利用することで無料枠内で利用することも可能です。

そんな便利な IBM Cloudant ですが、IBM Cloud では(特に無料枠で使った場合)では、どのようなクラスタリング構成で運用されているのか気になりました。もともと NoSQL DB はスケーリングに優れていて、大規模運用向きと言われています。ではこの(特に無料のライトプランで提供されている)IBM Cloudant はどのような運用構成で提供されているのでしょう? 理論上は1サーバーノードで(クラスタリングなしで)提供することも可能だし、無料プランということを加味して、クラスタリング無しだったとしてもまあそうだよね・・とも考えられます。一方で無料プランと有償プランで(わざわざ)差をつけて運用しているのか?という疑問もあります。このあたりをそっと調べてみました。


まず、調べる方法は CouchDB REST API の GET /db を使うことにしました。この REST API を実行すると指定したデータベースの情報を得ることができ、その中には(クラスタリング構成になっていれば)クラスターに関する情報が含まれていることになっています。この方法で自分がローカルで構成した単一構成の CouchDB と、IBM Cloud のライトプランで契約した IBM Cloudant の2つのデータベースに対して実行し、その結果を比較してみることにします。

まず前者の単一構成 CouchDB のデータベースに対してこのコマンドを curl で実行しました(実行結果は青字):
$ curl 'http://localhost:5984/ccdb'

{"db_name":"ccdb","update_seq":"6-

g1AAAAEzeJzLYWBg4MhgTmHgzcvPy09JdcjLz8gvLskBCjMlMiTJ____PyuRAYeCJAUgmWSPX40DSE08WA0TLjUJIDX1eM3JYwGSDA1ACqhsPiF1C

yDq9uO2E6LuAETdfULmPYCoA_khCwCKxmL8","sizes":{"file":58598,"external":615,"active":1730},"purge_seq":0,"other":

{"data_size":615},"doc_del_count":2,"doc_count":2,"disk_size":58598,"disk_format_version":6,"data_size":1730,"com

pact_running":false,"instance_start_time":"0"}

指定したデータベース(上例では ccdb)の現在の状態が表示されています。各項目の意味は上述の GET /db API のリンク先で説明されているのでそちらを参照いただきたいのですが、この実行結果にはクラスタリング情報が含まれていません。実際クラスタリング構成ではなく単一構成で動いているので、この実行結果もその運用状態を正しく表しています。

次に同じコマンドを IBM Cloudant のライトプランで作成したデータベースに対して実行しました。その結果がこちらです:
{"status":true,"info":{"update_seq":"20-

g1AAAAQneJzLYWBgEMhgTmHQTElKzi9KdUhJMtFLytVNTtYtLdYtzi8tydA1NNBLzskvTUnMK9HLSy3JAWphSmRI4v___39WIgNIsxZcs6EhMbqTB

IBkkjzYAGZU24nTrwDSr49NvzlR-g1A-u0RHiDR90kOIP3-

CPtJDoAAkAHx2BxAnP4EkP58bPqJC4ACkP56sgMgjwVIMjQAKaAR_VmJTOQEAsSQCRBD5pMXEBAzFkDMWE9eYEDM2AAxYz_UM2QFyAGIGeezEhnJD

5ALEEPuUxIgDyBmvCcve0DM-AAxA5TEswCIaVtl","db_name":"statedb","sizes":

{"file":11774513,"external":10511286,"active":10543509},"purge_seq":0,"other":

{"data_size":10511286},"doc_del_count":7,"doc_count":2,"disk_size":11774513,"disk_format_version":6,"data_size":1

0543509,"compact_running":false,"cluster":{"q":16,"n":3,"w":2,"r":2},"instance_start_time":"0"}}

↑特に赤字部分に注目してほしいのですが、先程の実行結果には存在しなかったクラスタリングに関する情報が含まれています。そしてこの結果を見ると、このデータベースは
 ・シャード数: 16(!)
 ・1つのドキュメントの分散数: 3
 ・書き込みコマンドを実行した場合、2つ以上に書き込めたら書き込み成功とする
 ・読み取りコマンドを実行した場合、2つ以上から結果が返ってきたら読み取り成功とする

という条件でクラスタリングが構成されていることがわかります。無料のライトプランでも結構な好条件でクラスタリングされていたんですね、へぇ~。


 

Node-RED の HTTP ノード(HTTP in ノードと HTTP Response ノード)を使うと簡単に REST API を作ることができて便利です。自分もデータベースへの CRUD 操作を作る際などによく使っています。

が、この方法で作った REST API にはクロスオリジン制約(いわゆる CORS)が付きます。例えば https://xxxx.mybluemix.net/ というホストで Node-RED を動かしている場合、作成する REST API のエンドポイント URL は https://xxxx.mybluemix.net/getdata とかになるわけですが、この API を AJAX などのブラウザ上の JavaScript から呼ぼうとすると、同一サーバー上の( https://xxxx.mybluemix.net/**** というアドレスのページの) HTML からでないとエラーになってしまうのでした。サーバーサイドのプログラムから実行することはできるのですが、ブラウザ上の JavaScript から実行するには同一ホストからでないといけない、という制約が付くのでした(ま、この制約自体はある方が一般的ですけど)。

この CORS の制約を外して、外部の(https://xxxx.mybluemix.net/ 以外の)ページやローカルシステム上ページの JavaScript からでもこの API を呼べるようにする、そのための設定方法と手順を紹介します。

まず Node-RED で REST API を作成します。今回は以下のような HTTP in ノードと、Function ノードと、HTTP Response ノードをつなげただけのシンプルな REST API を用意しました:
2018101801


HTTP in ノードの設定は以下のように GET /corstest で呼び出せるような設定にしています:
2018101802


Function ノードは以下のような JavaScript を記述し、実行時のタイムスタンプ値を JSON で返す、という関数にしています:
msg.payload = { timestamp: ( new Date() ).getTime() };
return msg;

2018101803


HTTP Response ノードにはこの段階では特に手を加えません。配置しただけの状態のまま接続してデプロイします。これで REST API 側は準備できました。

次に HTML ファイルを用意します。今回はサーバー上ではなくローカルシステム上に以下のような内容の HTML ファイルを用意しました:
<html>
<head>
<meta charset="utf8"/>
<title>CORS テスト</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
function corstest(){
  $.ajax({
    type: 'GET',
    url: 'http://xxxx.mybluemix.net/corstest',  // 上記で作った REST API のエンドポイントURL
    success: function( result ){
      console.log( result );
    },
    error: function( err ){
      console.log( "error" );
      console.log( err );
    }
  });
}
</script>
</head>
<body>
<input type="button" value="CORS" onClick="corstest()"/>
</body>
</html>

この HTML ファイルをブラウザから(Ctrl+O などでファイルを指定して)開くと、"CORS" と書かれたボタンが1つだけ配置されたページが開きます:
2018101807


HTML を見るとわかるのですが、このボタンをクリックすると GET https://xxxx.mybluemix.net/corstest という API が実行され、成功するとその結果が、失敗すると "error" というメッセージに続いてエラーメッセージが、それぞれ表示される内容になっています。なおこのエンドポイント URL の xxxx 部分が実際に作成した Node-RED 環境のホスト名にあわせて変更してください。


ブラウザのコンソールを開いて(F12)、この CORS ボタンをクリックします。現状は CORS の対策を何もしていないので当然のようにエラーになります。エラーの内容はコンソールに表示され、原因はクロスオリジン制約のようです。これをどうにかしたい、というのが今回のテーマです:
2018101804


では、この REST API の実行が成功するよう API 側をカスタマイズします。Node-RED のフロー画面に戻って、HTTP Response ノードをダブルクリックして編集状態にします。そして「ヘッダ」と書かれた欄の「+追加」という部分をクリックし、HTTP Response ヘッダを追加します。そして左側(ヘッダ名)の欄には Access-Control-Allow-Origin と、そして右側(ヘッダ値)の欄には *(どのドメインからのリクエストでも許可するの意)とそれぞれ入力し、最後に「完了」→「デプロイ」します:
2018101805


この設定によって REST API の実行結果を返す際のヘッダに Access-Control-Allow-Origin: * という一行が追加されて返るようになり、このヘッダによってクロスオリジンが許可されているとブラウザ側からも判断され、期待通りの結果が得られるようになります。再度 CORS ボタンをクリックして REST API を実行するとコンソールにはリクエストが成功した時の結果が表示されるようになりました:
2018101806


CORS の制約を理解した上で外す(あるいは特定のドメイン名やホスト名を指定した上で許可する)、という点に注意してください。





IBM Cloudant のバイナリ・アタッチメント機能を使って、簡易的なファイルサーバー代わりに使う方法を紹介します。具体的には curl コマンドを使って手元の画像ファイル(でなくてもよい)をアップロードし、URL を指定して画像を表示できるようにする、というものです。

とりあえず以下の作業を行うには IBM Cloudant のデータベースと curl コマンドが必要です。前者は IBM Cloud のライトアカウントを使って無料で入手することも可能です。IBM Cloud にログイン後、IBM Cloudant のインスタンスを(無料であれば Lite プランで)作成しておいてください:
2018101601


また、以下のコマンド実行時に必要になるため、この Cloudant サービスのアクセス情報を確認しておきます。サービスを選択し、「サービス資格情報」と書かれたタブを選んで、「資格情報の表示」をクリックします(「資格情報の表示」がなかったら新規に作成してからクリックします):
2018101603


すると以下のような表示が画面下に出てきます:
2018101604


この中の username の値と、password の値を後でコマンド実行する際に指定することになります。どこかにメモしておくか、コピー&ペーストできるようにしておいてください。


また curl コマンドは使う方のシステムにあわせて適宜入手してください。Linux や MacOS であればたいてい標準で入っているはずですが、Windows の場合は別途インストールが必要です。私はこちらのをダウンロードしてインストールしました:
https://curl.haxx.se/download.html

実際に以下の作業を行う前に、IBM Cloudant 内に今回の作業の対象となるデータベースを用意します。今回は testdb という名称のデータベースを1つ用意しました。このデータベースを簡易ファイルサーバー代わりに使う方法を紹介します:
2018101602


まずはファイルを保存する手順です。今回は仮に cloudant.png という名前の以下のような画像ファイルを保存することにします:
cloudant1


コマンドプロンプト(Linux や MacOS の場合はターミナル)を開き、この cloudant.png が存在しているフォルダに移動して、以下のコマンドを入力します:
$ curl https://(username の値):(password の値)@(username の値).cloudant.com/testdb/(作成するドキュメントの _id)/(作成するアタッチメントの名称) -X PUT -H "Content-Type: image/png" --data-binary @cloudant.png

仮に username の値が "user1"、password の値が "pass1"、ドキュメントIDを "doc001"、アタッチメント名を "att001" とすると以下のようなコマンドになります:
$ curl https://user1:pass1@user1.cloudant.com/testdb/doc001/att001 -X PUT -H "Content-Type: image/png" --data-binary @cloudant.png

このコマンドが成功すると以下のような JSON が返されます:
{ "ok": true, "id": "doc001", "rev": "1-xxxxxxxx" }


コマンド実行が成功すると同じディレクトリにある cloudant.png ファイルを Cloudant の testdb にアップロードします。なお Content-Type の異なるファイルをアップロードする場合は -H オプションで指定する Content-Type を変えて指定してください。


Cloudant に保存したファイルを取り出す場合は以下の URL をブラウザで指定します:
https://(username の値):(password の値)@(username の値).cloudant.com/testdb/(作成するドキュメントの _id)/(作成するアタッチメントの名称)

同様に username の値が "user1"、password の値が "pass1"、ドキュメントIDを "doc001"、アタッチメント名を "att001" とすると以下のような URL になります:
https://user1:pass1@user1.cloudant.com/testdb/doc001/att001

ブラウザのアドレス欄に指定すると、こんな感じで表示できます:
2018101605



Cloudant のアタッチメント機能を使った簡易的なファイル保存のやりかたを紹介しました。Node-RED でウェブページを作る際の静的画像をどうするか、という問題を比較的簡単に解決する方法の1つだと思っています。


このページのトップヘ