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

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

タグ:cloudant

本ブログエントリは IBM Cloud アドベントカレンダー 2021 に参加しています。12/4 ぶんのネタです。


IBM Cloud から提供されているマネージド NoSQL データベースである IBM Cloudant をよく使っています。最大の魅力は無料アカウントでも容量 1GB まで使えるストレージで、かつデータが分散管理されているという安全性です。仕様が曖昧な状態からアジャイルに作り始める際は(テーブル定義などを意識する必要がないこともあって)むしろ NoSQL データベースの方が便利だったりもします。

この IBM Cloudant を Node.js プログラムから使う場合のライブラリとして長く @cloudant/cloudant を使ってきましたが、2021/12/31 を以って End of Support を迎えることになり、現在は deprecated なライブラリ、という扱いになっています。2021年8月の今は「まだ使えるけど、もうすぐ使えなくなるよ」って所でしょうか。可能であれば、これから新たに作り始める時には利用を避けたほうが安全だと思います。

で、その後継ライブラリとして公開されているのが @ibm-cloud/cloudant (IBM Cloudant Node.js SDK)です。IBM Cloudant だけでなく、そのベースとなっている Apache CouchDB に対してももちろん使うことができるものです。現在 @cloudant/cloudant ライブラリを使っているアプリケーションはなるべく今年中にこの新しい @ibm-cloud/cloudant に移植することをおすすめします。

・・・と言うのは簡単ですが、実際この @ibm-cloud/cloudant は @cloudant/cloudant と比べてどのくらい似ていて/同じで、移植はどの程度簡単/難しいのでしょう? というわけで、まずは @ibm-cloud/cloudant を使ってみることにしました。


【サンプルソースコード】
以下で紹介するサンプルアプリケーションのソースコードを github で公開しています:
https://github.com/dotnsf/cloudant-node-sdk_sample


【接続方法の決定】
@ibm-cloud/cloudant を使って Cloudant(CouchDB) にアクセスする場合の接続方法には3通りあります:
(1)IAM
(2)COUCHDB_SESSION
(3)BASIC


(1)の IAM は IBM Cloudant を IAM 接続サポート形式で作成した場合に利用できる方法です。公式ドキュメントでも「この方法が使える場合はこの方法で」と紹介されています。

(2)の COUCHDB_SESSION を使った方法でも接続が可能です。

(3)の BASIC はいわゆる「ベーシック認証」です。この3つの中では「この方法しか使えない場合の選択肢」と紹介されています。CouchDB を自分でインストールして使う場合など、この方法で接続する前提のセットアップをしているとこの方法でしか接続できないことになります。もちろん Cloudant でもベーシック認証をサポートした形式で作成している場合は利用可能です。

(1)、(2)、(3)のいずれの方法でアプリケーションとデータベースを接続するかを決めておく必要があります。正確には @ibm-cloud/cloudant は「環境変数を設定して接続する」のですが、どの方法で接続するかによって、設定が必要になる環境変数が変わる点に注意が必要です。


【Node.js から接続する際に必要な環境変数】
@ibm-cloud/cloudant を使って Cloudant/CouchDB に接続する際に設定が必要な環境変数は以下です(横のカッコ付き数字は、上述の(1)、(2)、(3)のどの方法を使った時に必要な環境変数か、を表しています)。また変数名の頭の "CLOUDANT" 部分は別の値でも構いませんが、同じ文字列に統一して設定する必要があります:

変数意味
CLOUDANT_AUTH_TYPE上述の接続方法。"IAM"(1), "COUCHDB_SESSION"(2), "BASIC"(3) のいずれか。デフォルト値は "IAM"
CLOUDANT_URLデータベースの URL((1)、(2)、(3))
CLOUDANT_APIKEYAPI キーの値((1))
CLOUDANT_USERNAMEユーザー名((2)、(3))
CLOUDANT_PASSWORDパスワード((2)、(3))


環境に合わせてこれらの値を用意して実際に接続してみます。


【Node.js から Cloudant に接続】
(1)IAM 接続を行う場合は、上述の表より以下の環境変数を設定します:
process.env['CLOUDANT_AUTH_TYPE'] = 'IAM'; //(デフォルト値なので設定しなくてもよい)
process.env['CLOUDANT_APIKEY'] = 'xxxxx'; //(API Key の値)
process.env['CLOUDANT_URL'] = 'https://xxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud'; //(Cloudant のURL)

そして @ibm-cloud/cloudant ライブラリを読み込んで、以下のコードを実行します:
var { CloudantV1 } = require( '@ibm-cloud/cloudant' );

var client = CloudantV1.newInstance( { serviceName: 'CLOUDANT' } ); 

このコード実行が成功すると、Cloudant に接続したインスタンスが client という変数に格納され、実際のデータの CRUD 処理が可能になります。

なお上記コードの serviceName 値として 'CLOUDANT' を指定していますが、この部分は変更可能です。ただ変更する場合は環境変数として指定した変数名の頭の CLOUDANT 部分をここで指定する値と同じものに変更してください。

(2)COUCHDB_SESSION 接続を行う場合は、上述の表より以下の環境変数を設定します:
process.env['CLOUDANT_AUTH_TYPE'] = 'COUCHDB_SESSION';
process.env['CLOUDANT_USERNAME'] = 'username'; //(ユーザー名の値)
process.env['CLOUDANT_PASSWORD'] = 'password'; //(パスワードの値)
process.env['CLOUDANT_URL'] = 'https://xxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud'; //(Cloudant/CouchDB のURL)

そして @ibm-cloud/cloudant ライブラリを読み込んで、以下のコードを実行します:
var { CloudantV1 } = require( '@ibm-cloud/cloudant' );

var client = CloudantV1.newInstance( { serviceName: 'CLOUDANT' } ); 

(3)BASIC 接続を行う場合は、上述の表より以下の環境変数を設定します:
process.env['CLOUDANT_AUTH_TYPE'] = 'BASIC';
process.env['CLOUDANT_USERNAME'] = 'username'; //(ユーザー名の値)
process.env['CLOUDANT_PASSWORD'] = 'password'; //(パスワードの値)
process.env['CLOUDANT_URL'] = 'http://xxx.xxx.xxx.xxx:5984'; //(Cloudant/CouchDB のURL)

そして @ibm-cloud/cloudant ライブラリを読み込んで、以下のコードを実行します:
var { CloudantV1 } = require( '@ibm-cloud/cloudant' );

var client = CloudantV1.newInstance( { serviceName: 'CLOUDANT', disableSslVerification: true } ); 

なお(CouchDB の場合に多いと想像していますが) SSL 接続が不要の場合は上述のように接続時のパラメータに disableSslVerification: true を追加してください。

参考までに、@cloudant/cloudant の場合は、
var Cloudantlib = require( '@cloudant/cloudant' );
var cloudant = Cloudantlib( { account: "username", password: "password", url: 'https://xxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud' } );
var db = cloudant.db.use( "dbname" );

といった感じでデータベースまでを取得した上で、
db.get( "id", function( err, result ){
  if( err ){
    console.log( err );
  }else{
    console.log( result );
  }
});

のようにして特定データを取得したりしていましたが、この @ibm-cloud/cloudant ではまだデータベースが特定されていない点にご注意ください。


【接続後の CRUD 処理の例】
では接続後の Cloudant クライアントを使って、IBM Cloudant のデータを読み書きしてみましょう。まず対象データベースと id がわかっている特定データを取得してみます。@ibm-cloud/cloudant では以下のような getDocument() メソッドを使うコードとなります:
client.getDocument( { db: "dbname", docId: "id" } ).then( function( result ){
  console.log( result );
}).catch( function( err ){
  console.log( err );
});

client.getDocument() を実行し、そのパラメータ内でデータベースと id を指定する、という方式になります。この点からして @cloudant/cloudant とは異なってますね。

また特定データベースの全データをまとめて取得(@cloudant/cloudant だと db.list() )する場合は以下のような postAllDocs() メソッドを使うコードです:
client.postAllDocs( { db: "dbname", includeDocs: true } ).then( function( result ){
  console.log( result );
}).catch( function( err ){
  console.log( err );
});

データを一件追加する場合は以下のような postDocument() メソッドを使うコードになります:
client.postDocument( { db: "dbname", document: { name: "Kimura" } } ).then( function( result ){
  console.log( result );
}).catch( function( err ){
  console.log( err );
});

以上、あくまでいくつかのメソッドを紹介しただけですが、全般的に行うオペレーションとメソッド名の関係がわかりやすく整頓されているように感じて、比較的慣れやすいのではないかと感じています。



IBM Cloud から提供されている 30 日間無料 Kubernetes サービスIBM Kubernetes Service 、以下 "IKS")環境を使って利用することのできるコンテナイメージを1日に1個ずつ 30 日間連続で紹介していきます。

環境のセットアップや制約事項については Day0 のこちらの記事を参照してください。

Day 6 からはデータベース系コンテナとその GUI ツールを中心に紹介してます。Day 16 では個人的に大ファンな IBM Cloudant データベースサービスのベース製品でもあるApache CouchDB イメージをデプロイする例を紹介します。
couchdb



【イメージの概要】
元 IBM 社員だった Damien Katz さんが開発した NoSQL 型データベースです。単にデータを格納するだけでなく、画面の UI データを格納して文書データと合わせて表示する、といった(ノーツに似た)デザインドキュメントの概念も実装されていて、ノーツファンとしては非常に感銘を受けるデータベースです。IBM Cloudant ではこの CouchDB をマルチサーバーによる分散データ管理する仕組みがマネージドサービスとして提供されています。



【イメージのデプロイ】
まずはこちらのファイルを自分の PC にダウンロードしてください:
https://raw.githubusercontent.com/dotnsf/yamls_for_iks/main/couchdb.yaml

次にこのファイルをテキストエディタで開いてパラメータを編集します。具体的には以下2箇所の value 値を変更してください:
・COUCHDB_USER : ログイン時のユーザー名(初期値 admin)
・COUCHDB_PASSWORD : ログイン時のパスワード(初期値 P@ssw0rd)


ではこのダウンロード&編集した couchdb.yaml ファイルを指定してデプロイします。以下のコマンドを実行する前に Day 0 の内容を参照して ibmcloud CLI ツールで IBM Cloud にログインし、クラスタに接続するまでを済ませておいてください。

そして以下のコマンドを実行します:
$ kubectl apply -f couchdb.yaml

以下のコマンドで CouchDB 関連の Deployment, Service, Pod, Replicaset が1つずつ生成されたことと、サービスが 30984 番ポートで公開されていることを確認します:
$ kubectl get all

NAME                           READY   STATUS    RESTARTS   AGE
pod/couchdb-7866c5d4c6-z462x   1/1     Running   0          12s

NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/couchdbserver   NodePort    172.21.71.164   <none>        5984:30984/TCP   13s
service/kubernetes      ClusterIP   172.21.0.1      <none>        443/TCP          27d

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/couchdb   1/1     1            1           14s

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/couchdb-7866c5d4c6   1         1         1       14s

この後に実際にサービスを利用するため、以下のコマンドでワーカーノードのパブリック IP アドレスを確認します(以下の例であれば 161.51.204.190):
$ ibmcloud ks worker ls --cluster=mycluster-free
OK
ID                                                       パブリック IP    プライベート IP   フレーバー   状態     状況    ゾーン   バージョン
kube-c3biujbf074rs3rl76t0-myclusterfr-default-000000df   169.51.204.190   10.144.185.144    free         normal   Ready   mil01    1.20.7_1543*

つまりこの時点で(上述の結果であれば)アプリケーションは http://169.51.204.190:30984/ で稼働している、ということになります。CouchDB はこの URL が管理ダッシュボードの URL になっているので早速実行してみます。ウェブブラウザを使って、アプリケーションの URL(上述の方法で確認した URL)にアクセスしてみます:
couchdb1


(シンプルすぎて)一瞬びっくりしますが、稼働中の CouchDB に関する情報が表示されました。CouchDB も IKS 上で動かすことができました。


【YAML ファイルの解説】
YAML ファイルはこちらを使っています(編集する前の状態です):
apiVersion: v1
kind: Service
metadata:
  name: couchdbserver
spec:
  selector:
    app: couchdb
  ports:
  - port: 5984
    protocol: TCP
    targetPort: 5984
    nodePort: 30984
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: couchdb
spec:
  replicas: 1
  selector:
    matchLabels:
      app: couchdb
  template:
    metadata:
      labels:
        app: couchdb
    spec:
      containers:
      - name: couchdb
        image: couchdb
        env:
        - name: COUCHDB_USER
          value: "admin"
        - name: COUCHDB_PASSWORD
          value: "P@ssw0rd"
        ports:
        - containerPort: 5984

Deployment 1つと、Service 1つのごくごくシンプルな YAML ファイルですが、一応解説を加えておきます。アプリケーションそのものは 5984 番ポートで動作するように作られているため、NodePort 30080 番を指定して、外部からは 30080 番ポートでアクセスできるようにしています(NodePort として指定可能な番号の範囲は 30000 ~ 32767 です、指定しない場合は空いている番号がランダムに割り振られます)。また ReplicaSet は1つだけで作りました(データベースなので、別途クラスタ構成の準備をしない限りはこの数値だけを増やしてもあまり意味ないと思います)。


デプロイしたコンテナイメージを削除する場合はデプロイ時に使った YAML ファイルを再度使って、以下のコマンドを実行します。不要であれば削除しておきましょう:
$ kubectl delete -f couchdb.yaml


【紹介したイメージ】
https://hub.docker.com/_/couchdb


【紹介記録】
Dayカテゴリーデプロイ内容
0準備準備作業
1ウェブサーバーhostname
2Apache HTTP
3Nginx
4Tomcat
5Websphere Liberty
6データベースMySQL
7phpMyAdmin
8PostgreSQL
9pgAdmin4
10MongoDB
11Mongo-Express
12Redis
13RedisCommander
14ElasticSearch
15Kibana
16CouchDB
17CouchBase
18HATOYA
19プログラミングNode-RED
20Scratch
21Eclipse Orion
22Swagger Editor
23R Studio
24Jenkins
25アプリケーションFX
262048
27DOS Box
28VNC Server(Lubuntu)
29Drupal
30WordPress

いま自分の空き時間を使って、過去に Node.js + Cloudant を使って(普通のウェブアプリケーションとして)作っていたウェブサービスの Node-RED 環境への移植に挑戦しています。要はサーバーサイド JavaScript 実行環境である Node.js を使って過去に開発したウェブアプリケーション(画面や REST API)を、Node-RED の HTTP リクエスト/レスポンスノードや、HTML テンプレートノードを使っても動くように移植することに挑戦している、ということです。それなりに実績のある Node.js アプリケーションを Node-RED 上でも動かすことができれば、プラットフォームとしての Node-RED のポテンシャルを証明することができるのではないか、と考えています。

これを具体的に進めようとすると、まずウェブ画面は(i18n とかを考慮しなければ)HTML テンプレートノードを使えば一通りのことはできると思っています。要するに HTML テンプレートの中に HTML や CSS, フロントエンド JavaScript を含めてしまえば、見た目や挙動含めて一通りの画面を作ることはできると思っています。

問題は REST API 部分です。例えばデータベース(今回は Cloudant)のデータを読み書きするインターフェースを REST API で用意しておき、フロントエンドの画面から REST API を呼び出すことでデータの読み書き更新削除を行うことができるようになります(理論上は)。この REST API を Node-RED の HTTP リクエストノードと、HTTP レスポンスノードと、function などのノードを駆使して必要な機能を実装することができるかどうかが移植の可否になりそうだと思っています。まあ普通にデータを読み書き更新削除検索・・・する程度であれば標準の Cloudant ノードの機能範囲内でできそうな感触を持っています。


さて、今とある Node.js + Cloudant 製ウェブアプリケーションの Node-RED プラットフォームへの移植を設計している中で1つの壁に当たってしまいました。上述のように「普通の」データの読み書きの REST API 化はさほど問題にならないのですが、Cloudant が持つ特徴を使った部分が普通のデータのように扱うことができず、一筋縄ではいかない内容でした。結論としてはなんとなく解決の目処はたったと思っているのですが、その内容と解決までの経緯を含めて以下にまとめてみたので、興味ある方はご覧いただきたいです。


さて、問題となった Cloudant が持つ特徴を使った部分です。Cloudant は NoSQL 型(JSON 型)データベースですが、特殊な JSON フォーマットで格納することでバイナリデータを格納することができます。またその格納されたバイナリデータを(Content-Type 含めて)出力することもできます。この機能を使うことで、例えば画像データを Cloudant に格納して、画像データとして出力する、といったことも可能です。この機能は Cloudant の各種 SDK からも便利に使えるよう関数化されていたりします。

実は Cloudant のこのバイナリデータ格納機能を使っている場合が Node-RED 移植をする上でのネックとなります。Node.js などのプログラミング言語で Cloudant を利用する場合(特に上述の機能を使ってバイナリデータを Cloudant に格納する要件が含まれる場合)、Cloudant の REST API や各種 SDK を使ってデータの読み書きを実装することになります。上述のバイナリデータの読み書きも同様です。バイナリデータを書き込んだり、バイナリデータを Content-Type 含めて(つまり画像データであれば画像データとして)取り出して出力したりできます。実際にアプリケーション開発の中でこの機能を使って実装していることは(個人的にはバイナリデータの格納先に Cloudant を使うことが多いので)珍しくありません。

しかし、この部分を Node-RED に移植できるか? となると話は変わってきます。まず Node-RED からは Node.js 向けの Cloudant SDK を利用することはできません。function ノードの中でがんばって  Cloudant の REST API を呼び出すような JavaScript を書けば Node-RED でできるかもしれません(認証情報をどのように管理するかの問題は残ります。またどうせ JavaScript でゴリゴリ書くというのであれば、そもそも Node-RED をプラットフォームに選択しない方が正しいような気もします)。 この問題を標準の Cloudant in/out ノードだけでバイナリデータの読み書きを扱うことはできないか? と読み替えて考えることにしました。


【方法1 正攻法】
そもそも何が正攻法なのか、という問題もありますが、実は標準の Cloudant in ノード(Cloudant にデータを格納するノード)はバイナリデータを格納することもできます。上述の Cloudant のバイナリデータ格納機能は単に JSON データフォーマットをうまい具合に指定することで実現しているので、データを格納する点までは少しの工夫で実現できるのでした。

ただし、この方法の問題点は格納時ではなく取り出し時にあります。標準の Cloudant out ノード(Cloudant からデータを取り出すノード)は _id 値を指定してデータを取り出すことはできるのですが、肝心のこの部分がバイナリデータ格納を意識することなく、普通に JSON データとして取り出してしまうことしかできないのでした。特殊なフォーマットで格納することでバイナリデータ格納を実現しているのですが、この特殊なフォーマットに合わせた取り出しができないため、書き込むことはできても読み出せない、という問題が残ってしまうのでした。。


【方法2 BASE64 エンコードを利用して独自実装】
なんとなく解決の目処が立っているのがこちらの方法です。データの読み書きそのものは Cloudant の標準ノードを使うのですが、扱うバイナリデータは格納前に BASE64 でエンコードして(標準 Cloudant in ノードで)格納します。そして取り出す際も普通に標準 Cloudant out ノードで取り出した後に該当部分を BASE64 でデコードします。最後に HTTP レスポンスノードの属性で Content-Type ヘッダを指定して、デコード結果(画像バイナリ)を返信する、という方法です。プログラミングによるカスタマイズを駆使した、いかにもプログラマーらしい方法ですが、こちらの方法であれば格納時だけでなく取り出し時にも問題なく実現できそうです。

試しにフローを作ってみました。Github でも公開したので良かったらこちらからフローをダウンロードするなどして後述の手順で試してみてください:
https://github.com/dotnsf/nodered_cloudant_binarydata_io


【方法2 サンプルフローの使い方】
このサンプルを使って、実際にバイナリデータ(画像データ)を Node-RED で読み書きできることを確認してみます。

まずは Node-RED 環境を用意します。個別に用意していただいても構いませんが、最終的に Cloudant データベースを用意する必要もあるので、IBM Cloud を使って用意する方法がおすすめです。なお IBM Cloud を使ってここに書かれた方法で Node-RED 環境を構築した場合は、始めから Cloudant-in / Cloudant-out ノードがインストールされた状態になっているので、後述のこれら Cloudant 関連ノードのインストールは不要です。無料のライトアカウントを使って構築することもできる内容なので、Node-RED 環境がない人が試す上でおすすめの方法ではあります。

上記以外の方法で(普通にインストールするなどして)Node-RED 環境を用意した場合は node-red-node-cf-cloudant ノードを別途インストールする必要があります。右上のメニューから「パレットの管理」を選択し、「ノードを追加」から "node-red-node-cf-cloudant" を検索して追加してください:
2021032101

2021032102


併せて IBM Cloud にログインして Cloudant サービスを追加して利用できるようにしておいてください。繰り返しますが、このあたりあまり詳しい自信がない場合は上述の方法で IBM Cloud 環境内に Node-RED 環境を Cloudant データベースや Cloudant ノードなどとまとめて用意するのがおすすめです。


Node-RED が準備できたら、上述の Github リポジトリを使ってサンプルのフローを構築します。この flow.json ファイルがサンプルのフローそのものです。リンク先のテキスト内容をまとめてコピーし、Node-RED の右上メニューから「読み込み」を選択します:
2021032103


読み込みのダイアログで「クリップボード」を選択し、コピーしていた内容をペーストします。そして「新規のタブ」を選択し、最後に「読み込み」ボタンをクリックします:
2021032104


するとこのようなフロー画面が再現されるはずです:
2021032105


このままだとまだ2つの Cloudant ノード(画面上では "mydb" と表示されている2つの水色ノード)が未接続で使えません。どちらかをダブルクリックして設定ダイアログを表示します。すると Service 欄が一瞬だけ空のまま表示されますが、IBM Cloud の Node-RED 環境であれば接続済みの Cloudant サービスを見つけて接続してくれます。Service 欄に Cloudant サービス名が表示されたら「完了」ボタンをクリックします(もう1つの Cloudant ノードも同様にして Service 欄が埋まった状態にします):
2021032106


このように2つの Cloudant ノードの右上に表示されていた赤い印が2つとも消えればサンプルを動かすための準備は完了です。画面右上の「デプロイ」ボタンでデプロイして動作前の準備は完了です:
2021032101


改めてこのタブを見ると、3つの HTTP リクエストを処理するフローが定義されています:
#HTTP リクエスト処理内容
1GET /home画像ファイルアップロード画面
2POST /file画像ファイルアップロード処理
3GET /file(?_id=XXXX)アップロードした画像ファイルを画像として取り出す処理


1番目の GET /home は後述の 2 と 3 の動作を確認するための UI として、ファイルを指定してアップロードできる画面を表示するものです。実際に /home へアクセスすると、以下のような画面が表示されます:
2021032201


非常にシンプルなファイルアップロード機能を持ったページです。「ファイルを選択」ボタンを選んでローカル PC からファイル(今回は画像ファイル)を選択して「送信」ボタンをクリックです。「送信」すると、2番目 POST /file が実行されて、選択したファイルが Cloudant に格納される、というものです。

ここで試しに以下の画像ファイルを指定してアップロードしてみます(お好きな画像で試してください):
dotnsf_logo_200x200


画像ファイルを指定して「送信」します:
2021032202


こんな感じの HTTP POST の結果が表示されます(実際のアプリでは AJAX を使うなどしてこの結果をそのまま表示しないようにします):
2021032203


この後に Cloudant のダッシュボードなどから mydb データベースの中を確認するとデータが1件追加されているはずです:
2021032204


表示を JSON 形式などに切り替えると、格納されたデータファーマットも確認できます(type に画像フォーマット、data に base64 でエンコードした画像バイナリデータが格納されています):
2021032205


このデータの id 値を確認します(上図だと c7c3eb8e3b9ac0fffcd45c1beea6c62a )。この値と3番目の GET /file を使って格納された画像を表示してします。ウェブブラウザで /file?_id=(id の値) にアクセスして、アップロードした画像が表示されることを確認します:
2021032206
(↑アップロードした画像が復元できた!)


Node-RED を使ってバイナリ(画像)ファイルを Cloudant に格納し、また Node-RED から画像を復元することも実現できることがわかりました。


【方法2 解説】
Node-RED の HTTP リクエストでバイナリデータを格納したり、Node-RED の HTTP リクエストで格納したバイナリデータを取り出すことができる、ということがわかりました。以下はこれを実現している上記フローの解説です。

まず画面 UI である GET /home ですが、これはごく普通に enctype="multipart/form-data" を指定したフォームを定義しているだけです。テンプレートノードの中身は以下の内容の HTML です:
<html>
<body>
<form method="POST" action="/file" enctype="multipart/form-data">
<input type="file" accept="image/*" capture="camera" name="image" id="image"/>
<input type="submit" value="送信"/>
</form>
</body>
</html>

次に POST /file の各ノードを説明します。まず HTTP in ノード(POST /file と書かれたノード)はファイルアップロードに対応する処理を行うため「ファイルのアップロード」にチェックを入れている点に注意してください:
2021032207


また直後の function ノードの内容は以下のようになっています。アップロードされたファイルは msg.req.files に配列で格納されます(今回はファイル1つだけですが、配列の0番目に格納されます)。その mimetype と buffer を取り出し、buffer を base64 エンコードして msg.payload に格納し直して、最後にタイムスタンプを追加する、という処理を行っています(Cloudant データベースにはこのフォーマットで格納されていたはずです):
2021032208
//. アップロードしたファイルを base64 エンコーディング
var type = msg.req.files[0].mimetype;
var img64 = new Buffer( msg.req.files[0].buffer ).toString( 'base64' );

//. 独自フォーマット化
msg.payload.type = type;
msg.payload.data = img64;

//. タイムスタンプを追加
msg.payload.timestamp = ( new Date() ).getTime();

return msg;


この function ノードで処理された msg.payload の内容を Cloudant out ノードが受け取って格納します。このノードでは「Only store msg.payload object?」にチェックを入れて、ヘッダ情報などを格納しないようにしています。これで指定したバイナリファイルを(base64 エンコードして)Cloudant に格納する(同時に _id が割り振られます)、までの処理を実現しています:
2021032209


最後に GET /file(?_id=XXXX) のノードを紹介します。まず Cloudant in ノードでは特別な処理は行っておらず、パラメータとして与えられた _id を使って Cloudant の mydb 内を検索して結果を返す内容にしています:
2021032201


直後の function ノードでは mydb から取り出した結果を画像に戻す処理をしています。上述の function ノードの逆を行う形で、msg.payload.data の値を base64 デコードして画像バイナリに戻して msg.payload に代入し直しています:
2021032202
//. base64 エンコードされているバイナリデータをデコード
if( msg.payload && msg.payload.data ){
  msg.payload = new Buffer( msg.payload.data, 'base64' );
}

return msg;


その結果を HTTP レスポンスノードに渡して処理は終了です。が、このノードでは HTTP ヘッダをカスタマイズし、"Content-Type: image/png" を付けています。つまり直前の function ノードで取り出した画像のバイナリを画像(image/png)として送信するための処理を最後に加えています:
2021032203


これらのノードや処理を組み合わせることで Node-RED の HTTP リクエストからバイナリデータを Cloudant に格納したり、格納したデータからバイナリデータを取り出して Content-Type ヘッダを付けて返す、といった一連の処理を実現していました。


この例は Cloudant にバイナリデータを格納する場合のサンプルでしたが、おそらくほぼ同様の方法で他のデータストアにも応用できると思っています。



IBM Cloud から提供されている NoSQL のマネージドデータベースである Cloudant (実体は Apache CouchDB)を便利に使っています。最大の特徴の一つが「無料でも一定条件下で使える」点ですが、今回は無料である以外の便利な使い方を紹介します。

この記事の中で紹介するのはこのような使いかたです:
(1) Cloudant の CORS を有効に設定する(特定ドメインのページからの REST API リクエストを受け付けて読み書きできるようにする)
(2) Github ページに用意したウェブページから (1) で有効に設定した Cloudant のデータを読み書きする


2021022401


最終的に実現したいのは、世の中の SaaS(といっていいのかな)の中でもかなり可用性高く、安定して動いている印象の Github ページを使ったウェブアプリケーションを作ることです。ただ静的ページそのものは安定して稼働していてもデータの読み書きが出来なければウェブアプリケーションとしては不十分で、そこを REST API で読み書きできる IBM Cloud の Cloudant データベースで賄えないか、と考えました。ただそのままでは CORS の制約もあって API が実行できないのですが、Cloudant 側の設定で CORS を正しく設定して Github ページからの読み書きを可能な状態にする、そのための Cloudant の設定手順を確認する、というのが目標です。

以下では Github のアカウントを持っている方を対象に、Github ページを使って作成するウェブページから Cloudant にアクセスするサンプルを紹介します。Github ページでなくても(静的ページのホスティング環境があれば)同様に可能なはずですが、その場合は適宜読み直してください(特に CORS 設定時のドメインなど)。


【CORS】
"Cross-Origin Resource Sharing" 、つまり「オリジン間リソース共有」の略語です。ここでの「オリジン」は「ドメイン」と読み替えたほうが理解しやすいかもしれまえん。

例えば A というウェブサーバーが https://aaa.com/ 上で、また B というデータベース・サーバーが https://bbb.net/ 上で動いていると仮定します(つまり全く別々に管理されている2つのサーバーです。この状態を Cross-Origin と呼びます)。A 上のウェブページで表示される画面の中で B から取得したデータを表示しようと思っています。

例えば A が Java などのプログラミング言語によって作られたアプリケーションサーバーであった場合、A は B からのデータ取得に Java を使うことになります。A の中で Java が実行され、B にアクセスしてデータを取り出し、その結果を使って A のページの画面を作成することができます。A の内部でプログラミング言語が使われている場合は、Cross-Origin であってもこのような方法でデータを取得することができます。

ところが A がアプリケーションサーバーではなく、ただの(既存の HTML ページを表示するだけの)ウェブサーバーであった場合、A から B にアクセスしてデータを取得する手段はかなり限られてしまいます。その限られた手段の1つが AJAX などに代表される JavaScript 処理です。A のサーバーにプログラミング言語が用意されていなくても、ウェブブラウザ自体が持つ JavaScript 実行環境を使って B サーバーにアクセスしてデータを取得する、という考え方です。ただし Cross-Origin の場合、A にアクセスしたウェブブラウザから B のサーバーに JavaScript で HTTP アクセスすることは原則できないことになっています(Cross-Origin でなければ、つまり A = B であれば可能です)。これが CORS の制約です。

CORS の制約はウェブブラウザが持っている制約なので、ウェブブラウザを使わない HTTP 通信(上述の Java 言語によるプログラミングによる通信など)には関係ありませんが、(ウェブブラウザの)利用者側でこの制約を解除する方法はありません。唯一の方法がリソース提供側(この場合だと B サーバー)による許可のみです。

今回のブログエントリは IBM Cloud のデータベースの1つである Cloudant の CORS をうまく設定することで、代表的な静的ウェブページの1つである Github ページから(Cloudant 内の)外部データを読み書きする方法について紹介する、というものです。


【Cloudant の用意】
まず IBM Cloud のアカウントを取得します。IBM Cloud アカウントを新規に取得した時点では「ライトアカウント」と呼ばれる無料の制約のあるアカウントとなります。今回紹介する作業を実行するだけであれば(ライトアカウントだとデータは 1GB までの格納となりますが、この容量などに問題なければ)ライトアカウントのまま実行いただいても構いません。

改めて取得したアカウントで IBM Cloud にログインし、「リソースの作成」ボタンクリック後に「Cloudant」を選択します:
2021022501

2021022502


インスタンス作成前にいくつか設定箇所があります。主なものは以下になりますが、"Authentification Method" だけはデフォルトの "IAM" ではなく "IAM and legacy credentials" を選択してください:
・Available Regions: インスタンスを作成するエリア、お好きな場所でいいが「東京」あたりが無難
・Authentification Method: 今回は必ず "IAM and legacy credentials" を選択
・Plan: 料金プラン。「Lite」であれば無料

最後に "Create" ボタンで作成します:
2021022503


Authentification Method について補足しておくと、デフォルトの "IAM" だといわゆる API キーを使った認証/認可を行います。ただ今回はアプリケーションサーバーからではなく(GitHub ページの)静的なページから Basic 認証を使ってデータベースの REST API を使うことを想定しています。この場合は "IAM" ではなく "IAM and legacy credentials" (API キーまたは Basic 認証を使う)を選択しておく必要があるためです。


少し待つと Cloudant インスタンスが起動済みになります。インスタンスを開いて「サービス資格情報」タブを選び、「新規資格情報」ボタンを押して資格情報を作成します。作成後に作成した資格情報(「サービス資格情報-1」のような名前になっていると思います)を選択・展開して、中身を確認します:
2021022504


資格情報の中身は以下のような内容の JSON 文字列になっているはずです(この中身は他人に見せないように注意してください):
{
  "apikey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "host": "xxxxxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud",
  "iam_apikey_description": "Auto-generated for key xxxxxxxxxxxxxxxxxxx",
  "iam_apikey_name": "サービス資格情報-1",
  "iam_role_crn": "crn:v1:bluemix:public:iam::::serviceRole:Manager",
  "iam_serviceid_crn": "crn:v1:bluemix:public:iam-identity::a/xxxxxxxxxxxxxxxxxxxx::serviceid:ServiceId-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "password": "パスワード",
  "port": 443,
  "url": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud",
  "username": "ユーザー名"
}

この情報のうち、後で必要になるのが以下の(赤字の)4つです。メモするか、コピペできるようにしておいてください:
キー意味補足
hostnameホスト名*******-bluemix.cloudantnosqldb.appdomain.cloud といった感じの文字列になっています(******* 部分が可変)
portポート番号443 で固定のはずです
usernameユーザー名ログイン時にこの2つの値が必要です
passwordパスワード


認証情報を確認したら、実際に Cloudant データベースのダッシュボードを見てみましょう。画面左のメニューから「管理」タブを選択し、右上の「Launch Dashboard」ボタンをクリックします:
2021022505


(初期状態では空の)ダッシュボード画面が表示されます。最初にアクセスした時点ではまだ何のデータベースも作成されていないため、下図のような画面になります。後述の作業のためのデータベースをここで1つ作成しておくことにします。画面右上の「Create Database」ボタンをクリックします:
2021022506


データベースを作成するメニューが画面右に表示されます。ここでは Database name に「mydb(異なる名前を指定しても構いませんが、その場合は後述のコマンド等を作成した名前に読み替えて実行してください)」、Partitioning は「Non-partitioned」を選択し、「Create」ボタンをクリックします:
2021022507


作成した mydb データベースが出来上がりました。このデータベースもまだデータが1件もないので、このような画面になります。一旦元の画面に戻るため、画面左上の "<" マークをクリックします:
2021022508


一つ前のデータベース一覧画面に戻りました。先程は空でしたが、作成したデータベースが1つ追加された画面になっているはずです:
2021022509


Cloudant 側の準備はいったんここまでできていればOKとします。 次に CORS のための設定を行います。


【Cloudant に CORS を設定する】
次に Cloudant に CORS の設定をして外部からのアクセスを許可します。ダッシュボードのメニューから「アカウント」(下から3番め)を選び、「CORS」タブを選択します。以下のような画面(「CORS は有効になっていて、外部のどこからのリクエストも受け付けない」という内容)になるはずです:
2021022501


画面下部の「Restrict to specific domains」欄にアクセスを許可するドメインの URL を指定して「Add Domain」ボタンで追加します。ここでは "https://(github のアカウント名).github.io/" を指定することで自分の Github ページからのアクセスを許可することになるので、自分の github アカウントに合わせて設定してください:
2021022502


このような画面になれば自分の github ページ用の CORS 設定ができたことになり、自分の github ページからは JavaScript で Cloudant データを読み書きできることになります:
2021022503


【Cloudant に初期データを入力する】
今回の紹介では Github ページから Cloudant のデータを REST API 経由で表示する(CORS の設定をしていれば取得できるはず)、ということを試してみます。そのための「表示するデータ」をあらかじめ Cloudant に入力しておきます。

もちろん(ダッシュボードから)自分で好きにデータを入力いただいても構いませんが、比較的簡単に動作を確認できるサンプルを用意しており、そこで対象とするデータを curl 一発でまとめて入力できるように準備しているのでこちらの方法を紹介します。

まず以下の URL からサンプルデータをダウンロード(表示後に右クリック→名前を付けて保存で "prefs.json" という名前で保存)してください:
https://raw.githubusercontent.com/dotnsf/cloudant_cors/main/prefs.json

2021022701


このファイルの中身は以下のようなフォーマットになっており、各都道府県庁所在地と、その位置情報が格納されています:
{
  "docs": [
    { "code": 1, "prefecture": "北海道", "capital": "札幌市", "geometry": { "type": "Point", "coordinates": [ 141.34694, 43.06417 ] } },
    { "code": 2, "prefecture": "青森県", "capital": "青森市", "geometry": { "type": "Point", "coordinates": [ 140.74, 40.82444 ] } },
    { "code": 3, "prefecture": "岩手県", "capital": "盛岡市", "geometry": { "type": "Point", "coordinates": [ 141.1525, 39.70361 ] } },
    { "code": 4, "prefecture": "宮城県", "capital": "仙台市", "geometry": { "type": "Point", "coordinates": [ 140.87194, 38.26889 ] } },
    { "code": 5, "prefecture": "秋田県", "capital": "秋田市", "geometry": { "type": "Point", "coordinates": [ 140.1025, 39.71861 ] } },
    { "code": 6, "prefecture": "山形県", "capital": "山形市", "geometry": { "type": "Point", "coordinates": [ 140.36333, 38.24056 ] } },
    { "code": 7, "prefecture": "福島県", "capital": "福島市", "geometry": { "type": "Point", "coordinates": [ 140.46778, 37.75 ] } },
    { "code": 8, "prefecture": "茨城県", "capital": "水戸市", "geometry": { "type": "Point", "coordinates": [ 140.44667, 36.34139 ] } },
    { "code": 9, "prefecture": "栃木県", "capital": "宇都宮市", "geometry": { "type": "Point", "coordinates": [ 139.88361, 36.56583 ] } },
    { "code": 10, "prefecture": "群馬県", "capital": "前橋市", "geometry": { "type": "Point", "coordinates": [ 139.06083, 36.39111 ] } },
    { "code": 11, "prefecture": "埼玉県", "capital": "さいたま市", "geometry": { "type": "Point", "coordinates": [ 139.64889, 35.85694 ] } },
    { "code": 12, "prefecture": "千葉県", "capital": "千葉市", "geometry": { "type": "Point", "coordinates": [ 140.12333, 35.60472 ] } },
    { "code": 13, "prefecture": "東京都", "capital": "新宿区", "geometry": { "type": "Point", "coordinates": [ 139.69167, 35.68944 ] } },
    { "code": 14, "prefecture": "神奈川県", "capital": "横浜市", "geometry": { "type": "Point", "coordinates": [ 139.6425, 35.44778 ] } },
      :
      :
  ]
}

このデータを先程作成した Cloudant 内の mydb データベースに格納します。curl コマンドを使うと以下のコマンド(※)で格納できます:
$ curl -u "username:password" -XPOST "https://host/mydb/_bulk_docs" -H "Content-Type: application/json" -d @prefs.json

username, password, host の値は上述で確認した以下の値を指定して入力してください:
 username: 上述の資格情報で確認した username の値
 password: 上述の資格情報で確認した password の値
 host: 上述の資格情報で確認した host の値


このコマンドが成功すると Cloudant の mydb データベースに47都道府県のデータが挿入されます。ダッシュボードから mydb データベースを選択すると以下のように 47 件のデータが表示されるようになります:
2021022702


試しにどれか1つ選択してみると、個々の内容を確認することも可能です(下図は北海道の例、"Cancel" で一覧に戻ります):
2021022703


ここまでの作業で Cloudant に Github ページからアクセスするための CORS を設定し、表示用のデータも格納できました。ではこのデータを Github ページから REST API 経由で取得して表示してみましょう。


【Github ページを用意する】
では実際に Github ページからクロスオリジンな Cloudant データベースに REST API 経由でアクセスできることを確認してみます。

まず Github にログインし、その後で以下のページにアクセスし、画面右上の "Fork" をクリックしてください:
https://github.com/dotnsf/cloudant_cors

2021022706


Fork 処理が成功すると、
 https://github.com/(あなたの github アカウント名)/cloudant_cors
というリポジトリができるはずです。

このリポジトリの Github ページを有効にします。リポジトリページの右上にある "Settings" をクリックします:
2021022707


画面を下に "GitHub Pages" と書かれている箇所までスクロールします。"Source" に "main" ブランチを指定して保存すると、"main" ブランチの内容がそのまま Github ページとして公開され、この中にある CORS の動作確認用に作った index.html を参照するための URL(https://(あなたの github アカウント名).github.io/cloudant_cors/)とそのリンクが表示されます(リンク先が有効になるまで1分程度かかります):
2021022708


少し待ってからリンク先にアクセスします。以下のような画面が表示されるはずです(Github ページなので、****.github.io というドメインのページになっていることを再確認してください):
2021022704


改めて資格情報から取得した値を画面内の各該当箇所に入力します。上にある横幅の大きなフィールドには host の値、その下は左から作成した DB 名(mydb)、username、password の値を入力します。最後に "Refresh" ボタンを押すと入力された情報を使ってこのページから AJAX で Cloudant へ REST API を実行して文書一覧を取得し、表形式で出力します。成功すると以下のように47都道府県の情報が(クロスオリジンの制約を乗り越えて)表示されます:
2021022705


以上、正しく設定することでクロスオリジンの壁を超えて Github ページから Cloudant のデータを利用することができました。Github も Cloudant も(一定制限の中で)無料で利用できるので、安定したウェブページを Github で運用しつつ、表示データを Cloudant から提供する、といった形で利用することができそうです。


なお、Cloudant(CouchDB) の REST API についての詳しくは、こちらのリファレンスを参照してください:
https://docs.couchdb.org/en/stable/api/index.html


なお、今回使った「特定データベース内の全文書を取得する」 REST API はこちらです:
GET /{db}/_all_docs

また準備段階で都道府県データをバルクインサートしましたが、その REST API はこちらで紹介されています:
POST /{db}/_bulk_docs


このフローは以前に少し違う形で業務やイベントのネタ(の裏側)として使っていたことがあったのですが、埋もれてしまうのはもったいない気がしたので公開しちゃいます。

Node-REDFX (外国為替)情報を取得するフローを作りました。FX というのは例えば USDJPY だと USD と JPY 、つまり米ドルと日本円の関係です。「1ドル=107円23銭」みたいなやつですね。これの EURUSD (ユーロドル)やら EURJPY (ユーロ円)やら AUDJPY (豪ドル円)やら、、主に日本円が絡む通貨ペアを中心に 20 ペアの情報を1分おきにリアルタイムで取得するものです。

フローはこちらで公開しています:
https://flows.nodered.org/flow/9d045f691b6d7c5cb3259c197ad365d0

2020060105



このページ内のフロー定義を "Copy" して、Node-RED 環境に「クリップボードから読み込み」するだけでフローが再現できます。フロー1本だけの、それも標準ノードの組み合わせだけで構成されているシンプルな内容です。動く条件は「Node-RED 環境がインターネットに接続されていること」だけでいけると思います:
2020060101


↓ペースト後、こんなフローのタブが作られていれば成功:
2020060102


あとはこのまま「デプロイ」すれば1分おきに inject ノードが動き出し、取得した FX 情報を debug タブに出力し続けます:
2020060103


1回実行した時の debug タブの様子はこんな感じです。_id に実行時の日付時刻が入り、あとは通貨ペアとその瞬間の価格がまとめて出力されます:
2020060100



公開しているフローではこれだけ(debug タブに出力するだけ)ですが、IBM Cloud 内の Node-RED として動いている環境であれば、バインド済みの Cloudant out ノードをフローの最後に追加して、DB 名を指定するだけで出力される情報を1つのレコードとして DB に格納する所まで簡単に実現できます。他の環境でも各種データベースノードに渡すことで取得データの DB 格納ができます:
2020060104


中身は inject node が一分ごとに発火してオープンな API を使って FX 相場を取得し、(Cloudant DB に格納する前提での)JSON フォーマットに変換して debug ノードに渡す、というものです。FX は24時間相場が動くので、1日に 60 * 24 = 1440 データ集まります。(データ量に気をつけながら)1ヶ月程度動かしっぱなしにしておくとそこそこの為替情報データベースができあがります。シンプルですが API やフォーマットを変えることで応用範囲が広くなりそうだと思っています。

本来は集まったデータをグラフ表示したり、上下動の予測をしたり、、、といった使いみちになると思っています。サンプルではない実データを簡単に集めることができるので、説得力のあるデモアプリに応用しやすいと思っています。興味ある方は使ってみてください。


このページのトップヘ