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

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

タグ:bluemix

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





以前に PouchDB を使った IBM Cloudant(Apache CouchDB) との同期について紹介しました:
IBM Cloudant と PouchDB で同期をとる

multiuser-couch-pouch


PouchDB は JavaScript 上で動作する CouchDB 互換の NoSQL データベースで、最大の特徴の1つが CouchDB との同期機能です(だと思ってます)。JavaScript で動作するということはブラウザ内のローカル DB で同期することもできて、PWA(Progressive Web Application) を作る際にも非常に有用な機能だと思っています。上記エントリではブラウザ内の PouchDB データベースと、サーバー上の CouchDB データベース間で双方向に全同期したり、双方向に部分同期する方法を紹介しました。

実際の PouchDB の使いみちを考えると、単方向にのみ同期させたいこともあると思います。変更作業はローカルのみで行って、その変更した内容のみをサーバー側に反映させたいとか、逆にサーバーで変更された内容をローカルに取り込みたいとかいったケースです。この実現方法を紹介します。

まずローカル側とサーバー側、両方でデータベースを指定します:
<script src="//cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js"></script>

   :
   :

<script>
var local_db = new PouchDB( 'localdb' );
var remote_db = new PouchDB( 'https://remoteserver/remotedb' );


この2つのデータベース間で全文書を対象に双方向同期を行うのであれば前回紹介した方法を使って、以下のように実行することで実現できました:
local_db.sync( remote_db, {
  live: true,
  retry: true
});

ここを「ローカルでの変更をサーバー側にのみ反映させる(サーバー側の変更はローカルには反映させない)」場合は以下のように指定します:
local_db.replicate.to( remote_db, {
  live: true,
  retry: true
});

または

remote_db.replicate.from( local_db, {
  live: true,
  retry: true
});

逆に「サーバー側での変更をローカル側にのみ反映させる(ローカル側での変更はサーバーには反映させない)」場合は以下のように指定します:
local_db.replicate.from( remote_db, {
  live: true,
  retry: true
});

または

remote_db.replicate.to( local_db, {
  live: true,
  retry: true
});

全文書を対象とするのではなく、一部の文書を対象とする場合は、前回紹介した方法で doc_ids を指定することで同様に実現できます:
local_db.replicate.from( remote_db, {
  doc_ids: [ 001, 002, 003, ... ],
  live: true,
  retry: true
});

または

remote_db.replicate.to( local_db, {
  doc_ids: [ 001, 002, 003, ... ],
  live: true,
  retry: true
});

参考:
https://pouchdb.com/api.html#replication



 

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 外部パッケージを使ったアクションを実行する」方法については実現できそうだ、という目処がたちました。


このページのトップヘ