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

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

タグ:ibm

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つだと思っています。


IBM Cloud から提供されている IBM CloudantApache CouchDB をベースとしたマネージドな NoSQL データベースのサービスです:
2018090300


ベース製品が同じなので、例えば REST API レベルでは互換性があります。注意が必要な点として自分が気づいた限りでは IBM Cloudant は標準で Apache Lucene ベースの検索機能が有効になっており、インデックスとなる Design Document を用意することでテキスト検索が可能になる、ということが挙げられますが、それ以外に大きな差はありません。 一方で IBM Cloud から提供されているライトアカウント(無料プラン)でも IBM Cloudant を利用することができるので、わざわざ Apache CouchDB を用意しなくても気軽に使うことができる DBaaS としてとても手軽で便利だと思っています:
2018090301


さて、自分は業務のプログラミングでは主に Node.js を使うのですが、Node.js のパッケージライブラリには IBM Cloudant 用のものと、Apache CouchDB 用のもの、両方が存在しています:
(IBM Cloudant)
2018090302

(Apache CouchDB)
2018090303


仮に対象となるデータベースが IBM Cloudant であれば前者の方が簡単に使えるという印象を持っています。ただし IBM Cloudant 用ライブラリは IBM Cloud 上の IBM Cloudant を想定していることもあり、例えばオンプレミス上の Apache CouchDB に対して使えるものではありません。

一方、Apache CouchDB 用ライブラリはローカルや社内サーバー、クラウド上にある Apache CouchDB データベース全般に対して利用することが可能です。この対象はクラウド上の IBM Cloudant であっても構いません。要するにこちらのライブラリを使えば Apache CouchDB だけでなく IBM Cloudant にも接続できる、ということです。


実際にこういった需要がどれだけあるのかわからないのですが、例えばあるシステムを作る際に、そのデータストアとして、
(1) 試しに動かす場合は IBM Cloud 上の IBM Cloudant を使って気軽に開発/テストを行い、
(2) 本番運用ではオンプレミスな Apache CouchDB を利用する(IBM Cloudant の独自機能は使わない想定)

といったことが接続先の切り替えだけでできると便利です。ただこれを実現するためには IBM Cloudant 用の便利なライブラリを使って開発しまうと (2) の本番の時に問題が起こってしまいます。以下では IBM Cloudant に対しても Apache CouchDB 用ライブラリ(以下 node-couchdb)を使ってアクセスするように実装してみたコードを紹介します。ベースが同じ製品なのでできることはできるんですが、そのための手順と注意点を含めて紹介します。


【準備】
まず Node.js のコードを記述する前に上述の node-couchdb を npm install しておきます:
$ npm install node-couchdb


【データベース接続】
node-couchdb を使って IBM Cloudant に接続します。こんな感じのコードを記述します:
var dblib = require( 'node-couchdb' );

var option = {
  auth: {
    user: 'username',
    pass: 'password'
  },
  'host': 'username.cloudant.com',
  'protocol': 'https',
  'port': 443
};
var db = dblib( option );

usernamepassword の部分にはそれぞれ IBM Cloudant の username と password を指定します(localhost の Apache CouchDB に接続する場合は option = {} で接続できます)。これで IBM Cloudant との接続ができました。ここで取得した db を使って、以下の主要な操作を行うことができます。


【主要な操作】

ドキュメント追加
insert() メソッドにデータベース名(以下の例では 'testdb')を指定して、ドキュメントを追加します。取得前に db.uniqid() でユニーク ID を取得し、_id に設定している点に注意してください:
var doc = { name: 'dotnsf', height: 170.0 };  //. 追加するドキュメント
db.uniqid().then( function( id ){
  doc._id = id[0];
  db.insert( 'testdb', doc ).then( function( body, headers, status ){
    console.log( body );
  }).catch( function( err ){
    console.log( err );
  });
});

ドキュメント読み取り
同様に get() メソッドにデータベース名と id を指定してドキュメントを取得します:
db.get( 'testdb', id ).then( function( doc, headers, status ){
  console.log( doc );
}).catch( function( err ){
  console.log( err );
});

ドキュメント削除
del() メソッドにデータベース名と id と rev を指定して、データベースからドキュメントを削除します。以下の例では一度 get() メソッドを実行して id 値から rev 値を取り出してから del() を実行しています :
db.get( 'testdb', id ).then( function( doc, headers, status ){
  db.del( 'testdb', doc.data._rev ).then( function( data, headers, status ){
    console.log( data );
  }).catch( function( err ){
    console.log( err );
  });
}).catch( function( err ){
  console.log( err );
});

ビューを指定してドキュメント一覧取得
あらかじめ作成したビューを指定して、そのビューに含まれるドキュメントの一覧を取得します。以下の例ではデザイン名 : library, ビュー名 : byname というビューを指定して文書一覧を取得しています :
db.get( 'testdb', '_design/library/_view/byname', {} ).then( function( data, headers, status ){
  if( data && data.data ){
    var docs = data.data.rows;
    console.log( docs );
  }
}).catch( function( err ){
  console.log( err );
});


IBM Cloudant の npm だと最初にデータベース名を指定してそのデータベースのオブジェクトを取得した上で各種操作を行う、、という流れなんですが、Apache CouchDB 版だと毎回データベース名と一緒に各種操作を行う、、という点が大きな違いだと思いました。ただその辺りさえ理解していればまあ大丈夫かな。。


 

Node-RED を使うことで IoT データの収集や Web API の実装などが非常に簡単に実現できます。このブログでも何度か紹介していますし、公開されている外部モジュールを使って更にカスタム機能を追加することも可能です。

今回紹介するのは HTTP in ノードに認証機能を追加する node-red-contrib-httpauth ノードです。これを使うと Node-RED に標準装備されている HTTP in ノード(HTTP リクエストノード)に Basic 認証や Digest 認証を簡単に追加することができるようになります:
2018082900


実際に使う場合は、Node-RED の画面右上のメニューから「パレットの管理(Manage Pallette)」を選びます:
2018082901


設定ダイアログが表示されたら、"Palette" の "Install" タブで "httpauth" と検索します。すると node-red-contrib-httpauth ノードが見つかるので、"install" ボタンをクリックしてノードを追加します:
2018082902


インストールが成功すると以下のような表示になります。ここから実際にノードが使えるようになります:
2018082903


この時点でパレットにも "http auth" というノードが追加されていることが確認できます:
2018082904


実際に使う場合、http in ノードの直後に http auth ノードを配置します。この例では http in ノードの直後に http auth ノードを配置し、その後ろに(いつも使っているような)template ノードや function ノードを配置して、最後に http response ノードで HTTP リクエスト可能な API を作りました:
2018082905


template ノードの中身はシンプルにしています(認証が成功するとこの文字列が表示される、というテストです):
2018082906


そして http auth ノードに認証内容を設定します。この例では Basic 認証でレルム文字列は MyRealm 、そしてユーザー名 : user1 &パスワード : pass1 を設定しました。この状態でデプロイします:
2018082907


デプロイ後にウェブブラウザでこの API にアクセスすると、先程設定した http auth が機能し、指定した内容の認証が行われます。具体的にはユーザー名とパスワードを問い合わせるダイアログが表示され、先程指定した内容が入力されないと先へ進めません:
2018082908


上記で設定した内容(ユーザー名 : user1、パスワード : pass1)が正しく入力されると HTTP リクエストが正しく実行され、設定していた文字列が表示されます:
2018082909


この http auth ノードを使うことで、Node-RED で作成する API や Web ページに簡単に Basic 認証をかけることができそうです。


 

IBM Cloud から提供されているサーバーレス環境である Cloud Functions は、オープンソースの Apache OpenWhisk をベースとした FaaS(Functions as a Service)となっており、数あるサーバーレス環境の中でもよりオープンなものとなっています:
2018082301


IBM Cloud の Cloud Functions の場合、専用のダッシュボードが用意されており、手元に開発環境を用意することなく、ここから(ブラウザから)簡易的なアクションを記述/保存/実行することもできるようになっています:
2018082302
 ↑Node.js でアクションを記述している様子


Cloud Functions はこのブラウザからアクションを記述して実行することができる、というメリットがあるのですが、個人的にはここで記述できるアクションはかなり限定的なものだと思っています。理由はここで編集できるものはアクションとして実行する関数の中身だけであって、関連する設定ファイルを変更したりすることはできません。Node.js では npm という強力な外部モジュール連携の仕組みがあり、npm を使って標準の Node.js では提供されていない機能を呼び出して利用することができます。が、この仕組を使うには npm コマンドであらかじめ利用する機能を導入しておくか、 package.json と呼ばれる設定ファイルを変更して利用を宣言しておく必要があります。実行する関数の記述を変更するだけでは対応しきれない仕組みがあり、この機能に関してはこのウェブブラウザからのアクション記述だけでは使えないのでした。


前置きが長くなりましたが、ここからが本エントリの本番です。この外部パッケージを使ったアクションの記述は CLI(Command Line Interface)を使うことで実現できます。その手順を紹介します。

まず利用したい外部パッケージを明示した package.json を用意します。この例では "request" という HTTP クライアントパッケージ(のバージョン 2.88.0)を利用することと、エントリーポイントとなる JavaScript ファイル名(main.js)を指定しています:
{
  "name": "requestAction",
  "version": "0.0.1",
  "main": "main.js",
  "dependencies": {
    "request": "^2.88.0"
  }
}

次にエントリーポイントとなる JavaScript ファイル main.js を以下の内容で用意します。この中で request パッケージを require して使っている点に注目してください:
// main.js
async function myAction( params ){
  try{
    const result = await getHtml( params );
    return result;
  }catch( err ){
    return err;
  }
}

function getHtml( params ){
  return new Promise( function( resolve, reject ){
    if( params.url ){
      const request = require( 'request' );

      var options = {
        method: 'GET',
        url: params.url,
        encoding: null
      };
      request( options, function( err, res, buf ){
        if( err ){
          reject( { status: false, error: err } );
        }else{
          var html = buf.toString( 'utf-8' );
          resolve( { status: true, html: html } );
        }
      });
    }else{
      reject( { status: false, error: 'parameter url is needed.' } );
    }
  });
}

exports.main = myAction;

このアクションでは実行時に url パラメータを指定します(未指定の場合はエラー)。指定された url の HTML コンテンツを request パッケージを使って取得し、その結果を return します。 なお request() は非同期に実行される関数のため、HTML コンテンツを取得する部分だけを関数化(getHTML())し、この関数は Promise オブジェクトを(実行結果が得られたら)返すようにしています。 なお、このアクションは Node.js V8 で実行することを想定しているので、getHTML() 関数を実行する際に async/await を使って非同期に呼ぶようにしています。

この2つのファイル(package.json と main.js)でアクションに必要な設定と内容が用意できました。なお、同じファイルをこちらに用意しておいたので、興味ある方は参照ください:
https://github.com/dotnsf/requestAction


ではこのアクションを実際に IBM Cloud Functions にデプロイして実行するまでの手順を紹介します。今回用意したスクリプトはブラウザからデプロイするのではなく、 CLI (ibmcloud コマンド)を使ってデプロイする必要があります。というわけで、まだ CLI の導入ができていない場合で最初に CLI をデプロイする必要があります。

【ibmcloud コマンド CLI のインストール&セットアップ】
Windows や MacOS、Linux 向けの ibmcloud コマンドは以下のページからインストールできます:
https://console.bluemix.net/docs/cli/reference/ibmcloud/download_cli.html

Windows はダウンロードしてインストール、MacOS や Linux であれば以下のコマンドを実行してインストールします:
$ curl -sL http://ibm.biz/idt-installer | bash

ibmcloud コマンドが導入できたら IBM Cloud Functions を使うためのプラグインをあわせて導入/更新しておきます:
$ ibmcloud plugin install cloud-functions -r Bluemix

また、このあとのコマンドを実行する際に IBM Cloud にログインしている必要があるため、この段階で ibmcloud コマンドによる IBM Cloud へのログインとターゲットの設定を済ませておきます:
$ ibmcloud login

$ ibmcloud target --cf


【Node.js および npm のインストール】
ブラウザからアクションを記述する場合はクライアントに Node.js をインストールする必要はなかったのですが、今回の CLI を使った手順ではローカルで npm コマンドを実行することになります。つまり Node.js や npm がインストールされた環境が必要です。まだ導入していない場合は、公式ページなどから(V8 以上の Node.js の)ダウンロード&インストールを済ませておいてください:
http://nodejs.org/


【ライブラリのインストール、zip ファイル化、デプロイ】
まず package.json の内容にしたがってパッケージ(今回のケースでは request パッケージ)とその依存ライブラリをインストールします:
$ npm install

次にこのディレクトリ内の全ファイルを zip ファイルにまとめます。IBM Cloud Functions では zip ファイルでまとめたアクションをデプロイすることができるため、ここで実行に必要な全てのファイルが揃った zip ファイル(myAction.zip)を作成します:
$ zip -r myAction.zip *

そして、この zip ファイルをアクションとして ibmcloud コマンド CLI でデプロイします:
$ ibmcloud wsk action create requestAction myAction.zip --kind nodejs:8

↑この例ではアクションの名前を "requestAction" として myAction.zip をデプロイしています(アクション名やファイル名は任意です)。なおこの方法でアクションをデプロイする場合、--kind オプションで実行ランタイムの種類を指定する必要があります。今回は Nodejs V8 を指定しています(V6 では未対応の機能を使っているためです)。 また新規にデプロイする場合はこのコマンドになりますが、アクションを変更して同名で上書きデプロイする場合は上記の create 部分を update に変更して実行してください。


【動作確認】
デプロイしたアクションは CLI でもダッシュボード画面からも、どちらでも動作を確認することができます。まずはダッシュボードで確認してみます。ダッシュボードから Actions メニューを選び、アクション一覧の中にデプロイしたアクション(requestAction)が含まれていることを確認します:
2018082401


デプロイしたアクションを選択します。zip ファイルという特殊(?)な方法でデプロイしたので、この画面から直接コードを参照したり変更することはできないのですが、実行(Invoke)したり、実行時の入力パラメータを編集することはできます。今回のアクションは url パラメータを受け取って動作するので、実行前にパラメータを指定します。"Change Input" をクリックします:
2018082402


入力パラメータを編集する画面が表示されます。今回は何らかの url パラメータを指定したいので、
{ "url": "http://dotnsf.blog.jp/" }
のように何らかの実在する(パスワード等なしで参照できる)URL を url パラメータに指定して "Apply" をクリックします:
2018082403
 ↑このブログの URL を指定した例


改めて "Invoke" をクリックして、このアクションを先程の入力パラメータで実行します:
2018082404


アクションが実行され、少し待つと結果の JSON が表示されます。正しく実行されていれば status = true と、指定した URL から取得した HTML が返ってくることが確認できます:
2018082405


全く同じことを ibmcloud CLI からも実行してみます。CLI の場合は以下のようにパラメータを指定して実行し、その実行結果を確認します(実行そのものは --result オプションなしでも行えますが、--result オプションをつけると実行結果が同じ画面に出力され、確認ができます)。期待していたような HTML が表示されれば確認成功です:
$ ibmcloud wsk action invoke requestAction --param url "http://dotnsf.blog.jp/" --result

2018082406


とりあえず、本来の目的であった「Cloud Functions で npm 外部パッケージを使ったアクションを実行する」方法については実現できそうだ、という目処がたちました。


ベータ2が公開されたばかりの IBM Notes(以下「ノーツ」)V10 の、個人的に待望だった新機能「LotusScript で HTTP リクエスト」を早速試してみました。


1989 年(平成元年!)にバージョン1がリリースされたノーツはメールやインターネットの標準機能を取り込みながら進化してきました。特徴の1つでもあるマクロ言語の機能も簡易的な@関数式から VBA 互換の LotusScript 、Java 、そして JavaScript と時代に即して対応範囲を広げてきました。

ただ、ここ数年のカスタマイズの中で不便に感じることも出てきました。それが(この下で紹介する新機能でもある)HTTP リクエスト機能です。各種 REST API などと連携しようとすると HTTP リクエストを実行して、その結果を受け取って・・という一連の処理が必要になります。

ノーツで HTTP リクエストを実行できなかったわけではありません。ノーツは JVM を内蔵しており、Java で(エージェントと呼ばれる)マクロを記述して実行することができます。この方法を使うことで(つまり Java のネットワーク機能を使うことで)HTTP リクエストを実行することはこれまでも可能でした。

一方で制限事項もありました。ノーツの Java エージェントはバックエンド機能として提供されており、そのフロントエンド機能は提供されていませんでした。具体的にいうと「編集中の(未保存の)画面の Subject フィールドの値を取り出して、その値をパラメータとして HTTP リクエストを実行」といった処理をしようとすると、青字部分は Java を使えばいいのですが、赤字の部分の処理は Java からは実行できないのでした。これを実現するために例えば LotusScript のフロントエンド機能を使って赤字部分の処理を実行し、そこで取り出した値を(ファイル渡しなどの)何らかの方法でバックエンドの Java に渡して処理させる、という連携が必要でした。なので、技術的に不可能でなかったのですが、2つの言語記述による処理を連携させる必要があり、ちと面倒だったのでした。

今回リリースされた V10 ベータ2の新機能「LotusScript から HTTP リクエスト」はこの制約を取り除くことができると思っています。具体的にはフロントエンドの LotusScript で編集中の値を取り出して、そのままバックエンドの LotusScript で HTTP リクエストを実行することができるようになる、というものです。全てが LotusScript の1処理で完結することになり、自然に実装することができるようになると期待していました。その機能を使ってみたので以下で紹介します:



まずはノーツ V10 をインストールします、about 画面はこんな感じです:
20180821


で、肝心の HTTP リクエストは・・・どうやって使うんだろ?? (^^; ベータ版だからなのか、全くドキュメントが見つかりません。。。

それっぽいキーワードでぐぐって、唯一なんとか見つけたのがこの PDF ・・・
http://collabsphere.org/ug/collabsphere2018.nsf/0/789F066AA1414139862582A900620D93/$File/CollabSphere2018%20-%20Elementary.pdf

2018082105
 ↑NotesSession から createHttpRequest() して・・・ってこと?

これ、どこまで信用できる情報かわからないし、情報は充分ではないけれど、今はこれを信じて使ってみることにします。まずは超シンプルに僕が IBM Cloud 上に作った「リアルタイム外為情報」 API をそのまま HTTP GET で呼んでみることにします:
2018082105
 ↑HTTP GET されたら、こんな JSON を返すだけの API


上記 PDF の内容に従って、NotesSession のインスタンスから createHTTPRequest() して NotesHTTPRequest のセッションを生成し、URL を指定して HTTP GET 、その結果を MsgBox でそのまま出力、という内容です。なお PDF に記述されていた Use "class.RemoteHTTP" という宣言をするとエラーになってしまうため使っていません:
2018082103

Sub Click(Source As Button)
  Dim session As New NotesSession
  Dim http As NotesHTTPRequest
	
  url = "http://fx.mybluemix.net/"
	
  Set http = session.CreateHTTPRequest()
  response = http.Get( url )
  Msgbox response
End Sub

実行したら・・・  動いた!!
2018082104


ついに LotusScript だけで HTTP GET Request が動いた瞬間です。わーい、結果は HTTP レスポンスヘッダを含むテキストになってるわけね、ふむふむ。。

あ、よく見るとリクエスト先 URL のプロトコルが "https" ではなく、"http" になっていたので、もう一度 "https" にしてやり直し:
2018082101

Sub Click(Source As Button)
  Dim session As New NotesSession
  Dim http As NotesHTTPRequest
	
  url = "https://fx.mybluemix.net/"
	
  Set http = session.CreateHTTPRequest()
  response = http.Get( url )
  Msgbox response
End Sub


・・・したらエラー(苦笑):
2018082102


なんだ、このエラーは (^^; ベータ版だからなのだろうか?それとも回避手順がある?? あと HTTP GET はいいんだけど、HTTP POST がよくわかりません。http.Post( url, content ) らしいんだけど、content をどう書けばいいんだろ??それから認証が必要な場合は・・・


まだ情報不足もあって調べられることに限界がありますが、一方で LotusScript のみで REST API が実行できそうだ、という感触も得ることができました。これでドキュメントが揃ってくればノーツからの Watson API 連携ももっと楽にできるようになるかな。。


このページのトップヘ