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

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

2016年12月

JavaScript で AJAX を多く使ったことがある人にとって、「クロスドメイン問題」は一度は体験する、避けて通れない問題だと思います。

簡単に補足すると、例えば異なる aaa.com と bbb.net という2つのドメイン間で AJAX の実行を許可しない、という問題です。以下の例では aaa.com ドメイン内に www.aaa.com というアプリケーションサーバーと、api.aaa.com という API サーバーがあり、bbb.net というドメイン内に api.bbb.net という API サーバーがあることを想定しています。www.aaa.com と api.aaa.com は同一ドメインなので、何の制約もなく AJAX で API を実行できるのですが、ドメインの異なる api.bbb.net に用意された API を www.aaa.net から AJAX で実行しようとすると、このクロスドメイン問題の制約に引っかかって実行できない、というものです:
2016121303

ちなみにこのクロスドメインの制約はあくまで「ウェブブラウザの制約」です。ドメインをまたいだ API を実行するような JavaScript はウェブブラウザが許可しない、という意味です。なので、例えば www.aaa.com 上の Java アプリケーションや PHP アプリケーション、はたまたサーバーサイド JavaScript である Node.js アプリケーションなどから api.bbb.net 上の API を実行する、という場合は何の制約もなく動きます。

またこの問題は api.bbb.net 側で「他のドメインからの AJAX 実行を許可する」という設定をしておけば回避することができます。具体的には API サーバーの HTTP レスポンスヘッダに Access-Control-Allow-Origin を含めるようにして、そこに "*"(どこからでも許可する)とか "*.aaa.com" (aaa.com からのリクエストは許可する)とかいった値が返されるような設定になっていれば使える、ということです(つまり API 側で許可するよう設定されている必要があります):
2016121304


さて、先日 curl で IBM WatsonVisual Recognition API (画像認識 API)を使っていてふと気付いたことがあります。curl は -i オプションを使うと HTTP レスポンスヘッダが表示されるようになるので、これを ON にしてデバッグしていました・・・:
2016121301


ん??
2016121302


上記で説明した "Access-Control-Allow-Origin" が HTTP レスポンスヘッダに含まれてます。しかも値が * ということは、この API はどこからでも AJAX で呼べるということ!?

以前にこの記事を作ってた頃は、このヘッダはなかったと記憶してる(Chrome Extension なので回避できている、という認識でした)。いつ仕様が変わったんだろ・・・

試しに、curl で実行したのと同様の Visual Recognition API を実行する HTML ファイルを作ってみました(実行する場合、ソースコード内の (APIKEY) 部分は自分の Visual Recognition API サービスインスタンスの API キーを指定してください:
<html>
<head>
<title>DHTML</title>
<script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script>
function classify(){
  var imgurl = $('#imgurl').val();
  var url = "https://gateway-a.watsonplatform.net/visual-recognition/api/v3/classify?api_key=(APIKEY)&version=2016-05-20&url=" + encodeURI( imgurl );
  $.ajax({
    type: "GET",
    url: url,
    success: function( data ){
      var html = "<img src=\"" + imgurl + "\"/><br/><table border=\"1\">";
      var classes = data.images[0].classifiers[0].classes;
      for( i = 0; i < classes.length; i ++ ){
        var obj = classes[i];
        var class_name = obj['class'];
        var class_score = obj['score'];
        html += ( "<tr><td>" + class_name + "</td><td>" + class_score + "</td></tr>" );
      }
      html += "</table>";
      $('#result').html( html );
    },
    error: function( XMLHttpRequest, textStatus, errorThrown ){
      console.log( "error: " + textStatus );
    }
  });
}
</script>
</head>
<body>
<input type="text" name="imgurl" id="imgurl" value=""/>
<input type="button" value="classify" onClick="classify();"/>
<hr/>
<div id="result"></div>
</body>
</html>

これをウェブブラウザからローカルファイル扱いで開き(URL は file:/// で始まる)、画像 URL をテキストボックスに入れて classify ボタンをクリックします。すると・・・
2016121305


↑ 全く異なるドメイン(というかローカルファイルシステム)からの HTTP リクエストであるにも関わらず、AJAX 内で呼び出している Visual Recognition API が正しく実行され、画像認識結果を表示することができました。こりゃ便利!

IBM Bluemix の利用料金は Bluemix ログイン後にアカウントメニューの「使用状況ダッシュボード」から確認することができます:
2016120801


組織とデータセンターの地域を指定して課金対象を絞りこむことができます。以下は月毎の推移グラフ:
2016120802


下にスクロールすると現在の月の、現時点での消費額を確認することもできます:
2016120803


さて、これらは利用料金を Bluemix のウェブコンソールにログインした上で確認する、という手順です。この内容を Bluemix のウェブコンソールを使わずに API で取得する方法は・・・ ありました!以下に Billing API を使って外部から利用料金を確認する手順を紹介します。

Billing API を実行する上で、以下の情報を指定する必要があります:
(1) 対象組織の GUID
(2) 対象地域(データセンター)
(3) 対象年月(YYYY-MM)
(4) OAuth トークン

(2) と (3) は指定するだけですが、(1) の組織 GUID と (4) の OAuth トークンはあらかじめ調べておく必要があります。仮に (2) はアメリカ南部("us-south")、(3) は 2016-11 を指定するものと仮定しておきます。


(1) の GUID は cf ツールを使って調べます。まずは利用料金を調べたい地域の API サーバーに cf ツールでログインします(下の例では US データセンターを指定しています):
$ cf login -a https://api.ng.bluemix.net/ -u (ユーザーID)

対象とする組織の名称(ID)がわかっている場合は不要ですが、組織 ID を確認する場合は "cf orgs" コマンドを実行します。ログインした ID で利用中の組織 ID の一覧が表示されます:
$ cf orgs

組織 ID が確認できている場合は、以下のコマンドでその組織の GUID を取得できます:
$ cf org (組織ID) --guid


次に (4) の OAuth トークンを取得します。これは同様に cf ツールで "cf oauth-token" コマンドを実行します。すると "bearer " から始まるトークン文字の羅列が戻ってきます。これが OAuth トークンです:
$ cf oauth-token
bearer XXXXXXXXXX....(文字の羅列)....XXXXXXXXXX

これで (1), (2), (3), (4) 全ての情報が揃いました。これらの情報を使って目的月(今回は 2016-11)の利用料金をコマンドラインから調べてみます。実行コマンドと、その実行結果(の一部)は以下のようになります:
$ curl -v -X GET -H "Authorization: bearer (OAuth トークン文字列)" "https://rated-usage.ng.bluemix.net/v2/metering/organizations/us-south:(組織 GUID)/usage/2016-11" | python -m json.tool

{
  "organizations": [
    {
      "billable_usage": {
        "spaces": []
      },
      "country_code": "JPN",
      "currency_code": "JPY",
      "id": "********-****-****-****-************",
      "name": "dotnsf@jp.ibm.com",
      "non_billable_usage": {
        "spaces": [
          {
            "applications": [],
            "containers": [],
            "id": "********-****-****-****-************",
"name": "********-****-****-****-************",
"services": [ { "id": "********-****-****-****-************",
"instances": [ { "id": "********-****-****-****-************",
"name": "IBM Insights for Twitter-5b", "plan_id": "********-****-****-****-************",
"usage": [ { "applicationId": "********-****-****-****-************",
"cost": 0, "name": "dotnsf-php-20161026", "quantity": 22066, "unit": "TWEET", "unitId": "TWEETS_PER_MONTH" } ] } ], "name": "twitterinsights" } ] }, { "applications": [ { "id": "********-****-****-****-************",
"name": "dotnsf-cloudant", "usage": [ { "buildpack": "********-****-****-****-************",
"cost": 2557.8000000000002, "quantity": 348, "runtime": { "id": "********-****-****-****-************",
"name": "liberty-for-java_v3_2-20160822-2200" }, "unit": "GB-HOURS", "unitId": "GB_HOURS_PER_MONTH" } ] }, : :

青字部分が実行コマンドで、その下が実行結果です。実行結果のところどころに "cost": ***** という形で各ランタイムやサービスごとの料金が表示されていることがわかります。

というわけで、この API を使うことで Bluemix の利用料金をコマンドラインから参照する、ことが実現できそうです。


(参考)
http://theblasfrompas.blogspot.jp/2016/02/invoking-billing-api-for-bluemix-public.html

IBM Bluemix のアプリケーションベースの1つが Cloud Foundry です。オープンソース版の PaaS 環境でもある Cloud Foundry を拡張したものが IBM Bluemix ですが、この Cloud Foundry が利用するコンテナ技術に新しいテクノロジーが加わりました。それが Diego です:
https://docs.cloudfoundry.org/concepts/diego/diego-architecture.html

2016120700


これまでの Cloud Foundry では DEA(Droplet Execution Agent) というコンテナ技術を使っていました。この進化版としての、新しいコンテナフォーマットや SSH ログインをサポートした新しいコンテナ、それが Diego です。DEA と Diego の技術的な相違点など、詳細はこちらをご覧ください:
https://docs.cloudfoundry.org/concepts/diego/dea-vs-diego.html


さて、この Diego は先日 IBM Bluemix でも有効になりました。現状は DEA と Diego が両方サポートされている状態で、デフォルトは DEA になっています。またこれまでに Bluemix 上で作成して動いていた CloudFoundry ランタイム(以下、 "CF ランタイム")アプリケーションは DEA コンテナのまま実行されています。なので、以下に紹介するような特別なことをしなければ特に何も変わらずに動いている/動く、ということになります。


一方、Diego コンテナでは DEA ではできなかったいくつかの新しい機能が有効になっています。その1つが ssh によるリモートログインです。というわけで、Bluemix アプリケーションを Diego コンテナに切り替えて、ssh ログインができることを確認する手順を以下に紹介します。


まず、現状で Diego コンテナを利用するには cf コマンドラインツールを利用する必要があります。また Diego コンテナを使う場合、cf ツールのバージョンは 6.13.0 以上である必要があります。なお cf ツールのバージョンを確認するには "cf -v" と実行してください:
$ cf -v
cf version 6.22.2+a95e24c-2016-10-27

cf コマンドを使ったことがなかったり、古い cf コマンドを使っている Bluemix ユーザーは、まずは自分の環境にあった cf ツールを GitHub からダウンロードしてインストールしておいてください:
https://github.com/cloudfoundry/cli/releases


次に Diego 機能を使うために cf コマンドに Diego-Enabler プラグインをインストールします。これは以下のコマンドでインストールできます:
$ cf add-plugin-repo CF-Community https://plugins.cloudfoundry.org/
$ cf install-plugin Diego-Enabler -r CF-Community

これで Diego-Enabler が使える cf コマンドが用意できました。では実際に Bluemix 上の CF ランタイムアプリケーションを Diego コンテナに移行してみましょう。

今回、サンプルとして "dotnsf-diego-java" という名前の CF アプリケーションを Liberty for Java ビルドパックで作成しました。以下、この名前を使ってコマンドを実行する様子を紹介しますので、みなさんのアプリケーションの名前に置き換えてお読みください。なお、この時点では(これまで通り)DEA コンテナ上で動いているアプリケーションです:
2016120701


ためしにブラウザからアクセスすると、デフォルトの "Hello World!" が表示されます:
2016120702


このランタイムを Diego コンテナ上に移行してみましょう。まずは cf ツールでログインを行います:
$ cf login -a https://api.ng.bluemix.net/ -u (ユーザーID)
API エンドポイント: https://api.ng.bluemix.net/

Password> (パスワードを入力)
※↑の例では USA データセンターにログインしています。他のデータセンターの場合は "ng" の部分をデータセンターに合わせて変更してください。

最初に対象のランタイムアプリケーションを停止させる必要があります:
$ cf stop dotnsf-diego-java

停止が確認できたら、このランタイムのコンテナを Diego に変更します:
$ cf enable-diego dotnsf-diego-java

そして再スタートです:
$ cf start dotnsf-diego-java
  :
  :
要求された状態: started
インスタンス: 1/1
使用: 512M x 1 インスタンス
URL: dotnsf-diego-java.mybluemix.net
最終アップロード日時: Wed Dec 7 01:51:43 UTC 2016
スタック: cflinuxfs2
ビルドパック: Liberty for Java(TM) (WAR, liberty-16.0.1_3, buildpack-v3.5-20161114-1152, ibmjdk-1.8.0_20161019, env)

     状態   開始日時                 CPU      メモリー            ディスク           詳細
#0   実行   2016-12-07 11:05:40 AM   181.1%   512M の中の 97.9M   1G の中の 189.8M

この時点でアプリケーションは Diego コンテナ上で動いています。ただアプリケーションそのものには何の変更も加えていないので、見た目は変わっていません:
2016120702


ではこの Diego コンテナに SSH でログインして、アプリケーションを直接書き換えてみましょう。SSH でログインするには以下のコマンドを実行します:
$ cf ssh dotnsf-diego-java
vcap@18ce933a-65ca-4e3f-5f0c-d9fac300d6f9:~$  ←プロンプトが変わった!

ここは既に CF アプリケーションの中です。もちろん使えるコマンドには制約がありますが、例えば top コマンドを使ってサーバーリソースの利用状態を確認することもできます:
2016120703


ではアプリケーションを書き換えてみましょう。Liberty for Java ビルドパックの場合、アプリケーションは app/wlp/usr/servers/defaultServer/apps/myapp.war/ ディレクトリにあるのでここで移動し、vi で index.html を直接編集します:
$ cd app/wlp/usr/servers/defaultServer/apps/myapp.war
$ vi index.html

変更内容はどうでもいいのですが、とりあえず <body> タグの直後に目立つように <h1> タグのメッセージを追加してみました(↓赤枠部分を追加して保存):
2016120704


この状態で(再プッシュとかなしで)同アプリケーションをブラウザでリロードすると、入力したメッセージが画面に表示されて、アプリケーションが書き換わったことが確認できました:
2016120705


CF ランタイムは今までこれができないという制約があったのですが、Diego コンテナに切り替えることで課題を1つ克服できることになりますね。

先日のブログで、IBM Domino のログを外部の Web API 経由で取得する、という方法を紹介しました:
ノーツの Web エージェントで外部からログを取得する

要は log.nsf には手を付けずに、log.nsf の中身を取得して XML 出力するような API を外部データベースに Web エージェントとして作成して呼び出す、 という内容でした:
2016120302


今回はその応用編です。API でログが取得できるようになったので、その取得したログの(テキストの)内容を更に分析してみます。

今回新たに使う API は Watson Tone Analyzer です:
2016120500


Tone Analyzer は IBM Watson が提供するコグニティブ API の1つで、会話の中で発生する喜び、悲しみ、怒りといった「トーン」をテキストの内容から検出する API です。現在は大きな分類として「感情(怒り:Anger、嫌悪:Disgust、不安:Fear、喜び:Joy、悲しみ:Sadness)」、「社交性(開放性:Openness、誠実性:Conscientiousness、外向性:Extraversion、協調性:Agreeableness、情緒不安定性:Emotional Range)、「文体(分析的:Analytical、確信的:Confident、あいまい:Tentative)」という3種類のトーンを分析します:
2016120501


なお 2016/Dec/05 時点では、この API は日本語テキストには未対応です。なので、日本語の会話テキストをそのまま API のインプットデータに使うことは現時点ではできません。今の段階では使い方も限られてしまいますが、英語テキストを使えばどんな API なのかを確認する程度のことはできると思います。


この API は与えられたテキストをもとに、上記3種類のトーン毎に各要素の割合を識別して結果を 0 から 1 までの数値で返してくれます。結果の判断の方法は一概に言えないのですが、一般的には 0.75 以上であればその要素が表面化していて、0.5 以上の場合は表面化せずにも含まれている(それ以下は要素として見られない)と判断するようです。この辺りの数値結果の考え方について詳しくはこちらを参照ください:
Understand your tone score


というわけで、IBM Domino の log.nsf から取り出したログの英語テキストを、この Tone Analyzer API を使って「IBM Domino はどんな感情のログを吐いているか?」を調べてみることにします。ちなみに PHP 版のソースコードはこちらに公開しておきます:
https://github.com/dotnsf/ToneAnalyzerWithDominoLog

上記リポジトリの内容をダウンロード&展開するかクローンして、credentials.php ファイルの内容を自分の環境(Domino ログを取得する歳の URL と、Tone Analyzer の username 及び password)に合わせて編集します。そして index.php をブラウザから呼びだすと、こんな感じの画面が表示されると思います:
2016120502


index.php の内容を見ていただくとわかりますが、まず前回紹介した内容を使って Domino の log.nsf からログを Web エージェント経由で取得します。そしてその取得したテキストログをまとめて Tone Analyzer へ送り、感情分析結果を取得して、表にしている、というものです。jquery-ui を使って、感情スコアのスライダー表示も実装しています。

この表の部分だけを拡大したものがこちらです:
2016120503


この結果から、0.75 を超えているのは感情の5要素にはありません。機械的なログを分析しているので、これはある意味で正しい結果と考えられます。次に文体の3要素の中では Analytical(分析的)が 0.75 を超えているので、分析的なテキストであったといえます(これもログなのである意味正しいと言えます)。そして社交性の5要素のうちでは Conscientiousness(誠実性)と Emotional Range(情緒不安定性) が 0.75 を超えていました。な、なんかメンヘラっぽいけど、うちの Domino サーバーは大丈夫なんだろうか? (^^;


と、まあ Tone Analyzer はこんな感じで使えます。このブログでは IBM Domino のログを対象に解析していますが、もちろん他のミドルウェアサーバーのログや、テキストを元に解析できます。現状は日本語未対応なのでできることも限られてしまいますが、ある意味今のうちにいい勉強ができるともいえます。

なお、Watson Tone Analyzer はサンプルアプリのサイトから実際にテキストを与えて、感情分析結果を確認することも可能です。試してみたいテキストがあればこちらから確認してみてください:
https://tone-analyzer-demo.mybluemix.net/


先日紹介したこのエントリの続きです。今度はノーツデータベースの中身を取得する Web API をエージェントで作成してみます:
ノーツの Web エージェント


対象とするノーツデータベースは「ログ」にします:
2016120301


このログデータベースは Domino サーバー上に log.nsf として存在しています。個々のログ内容は Events フォームのドキュメント内の EventList フィールド内に配列の形で格納されています:
2016120303


というわけで、同データベースからログを取り出して、XML 化して出力する、という Web API(Web エージェント)を作ってみました。基本的な考え方や作り方は前回紹介したものと同様ですが、実際の処理内容だけを変えています:
  :
:
public class JavaAgent extends AgentBase{ public void NotesMain(){ try{ Session session = getSession(); AgentContext agentContext = session.getAgentContext(); // (Your code goes here) String xml = "<?xml version=\"1.0\"?>\n<eventlist>\n"; //. 直近1週間のログのみ対象とする Date dt0 = new Date(); long t = dt0.getTime(); t -= ( 7 * 24 * 60 * 60 * 1000 ); dt0.setTime( t ); //. この(エージェントのある)データベースと同じサーバー上の log.nsf を対象とする Database cdb = session.getCurrentDatabase(); String server = cdb.getServer(); Database logdb = session.getDatabase( server, "log.nsf" ); //. Events フォームの文書を取り出す DocumentCollection docs = logdb.search( "Form=\"Events\"" ); for( int i = 1; i <= docs.getCount(); i ++ ){ Document doc = docs.getNthDocument( i ); //. EventList フィールドの配列値を取り出す Vector eventlist = doc.getItemValue( "EventList" ); Enumeration event = eventlist.elements(); while( event.hasMoreElements() ){ String line = ( String )event.nextElement();      //. 日付とログ文字列に分離 int x = line.indexOf( " " ); if( x > 0 ){ String dt = line.substring( 0, x ); //. YYYY/MM/DD hh:mm:ss String msg = line.substring( x + 3 ); //. 対象日(7日以内)のログかどうかを確認 Date dt1 = new Date( dt ); if( dt1.after( dt0 ) ){ //. XML 用にサニタイズ msg = msg.replaceAll( "\n", "" ); msg = msg.replaceAll( "\r", "" ); msg = msg.replaceAll( "&", "&amp;" ); msg = msg.replaceAll( "<", "&lt;" ); msg = msg.replaceAll( ">", "&gt;" ); msg = msg.replaceAll( "\"", "&quot;" ); xml += ( "<event datetime=\"" + dt + "\">" + msg + "</event>\n" ); } } } } xml += "</eventlist>\n"; //. 画面に出力 PrintWriter pw = getAgentOutput(); pw.println( "Content-Type: text/xml" ); pw.println( "" ); pw.println( xml ); pw.close(); }catch( Exception e ){ e.printStackTrace(); } } }

↑基本的な考え方は前回紹介したものと同じです。出力内容は log.nsf の内容なので、このデータベースを取得して、対象(1週間以内)のログを探して1行ずつ XML を生成し、最後に text/xml として出力する、という内容を Java で記述しています。

※同様の Web エージェントを LotusScript で記述する場合、最後の出力部分は Print 文で代用してください。


このエージェントをブラウザから
  http://(domino サーバー名)/(DB名)/(エージェント名)?OpenAgent
という URL を指定して実行します
2016120302


Domino サーバーのログの内容が Web API で外部から取得できるようになりました。他のデータベースでも同様に応用できると思います。

このページのトップヘ