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

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

2015年07月

JavaScript にちょっとした工夫をすると、サブドメインの異なる2つのサイト(例えば www1.mydomain.com と www2.mydomain.com)でクッキーを共有することができる、らしい!

例えばこんな感じです。まずは http://www1.mydomain.com/setCookie.html でクッキーをセットします:
(setCookie.html)

<html>
<head>
<script type="text/javascript">
function setMyCookie(username, password) {
  var s=setMyCookieWithDomain("username",username);
  document.cookie = s;
  var t=setMyCookieWithDomain("password",password);
  document.cookie = t;
  alert(s+"\n"+t); // 保存内容を確認表示
}
function setMyCookieWithDomain(key,val){
  var str = escape(key) + "=" + escape(val);
  str += "; domain=.mydomain.com"; // 頭に "." を付けたドメイン名を指定(ここがカギ!)
  return str;
}
</script>
</head>
<body>
<input type="button" value="test1-set" onclick="setMyCookie('username1','password1')">
<input type="button" value="test2-set" onclick="setMyCookie('username2','password2')">
</body>
</html>


次に http://www2.mydomain.com/getCookie.html で http://www1.mydomain.com/setCookie.html でセットしたクッキーを取り出します。サブドメインの異なる2つのサーバー間で同じクッキーをやりとりしている点に注目:
(getCookie.html)

<html>
<head>
<script type="text/javascript">
function getMyCookieWithDomain() {
  var s="";var t="";
  cookies = document.cookie.split("; ");  // www1 でセットしたクッキーを www2 で取り出している
  for (i = 0; i < cookies.length; i++) {
    str = cookies[i].split("=");
    if (unescape(str[0]) == "username") s="id = "+unescape(str[1]);
    else if (unescape(str[0]) == "password") t="pass= "+unescape(str[1]);
  }
  return s+"\n"+t;
}
</script>
</head>
<body>
<input type="button" value="TEST" onclick="alert(getMyCookieWithDomain())">
</body>

上記のように、クッキーを設定する際(getCookie.html)のドメイン指定時に、頭に "." を付けたドメイン("mydomain.com" ではなく ".mydomain.com")を指定して保存します。これがカギのようです。

これで、www1.mydomain.com でクッキーを設定した後に www2.mydomain.com でそのクッキーを取り出して使う、という処理が実現できます。異なるアプリケーションサーバーで同じ情報を覚えさせて使う場合に便利な方法ですよね。

なお、上の例ではユーザー名とパスワードをクッキーに保存して取り出す、というサンプルを紹介していますが、実際のアプリではパスワードをクッキーに入れるのは危険なので、あまりオススメしません(苦笑)。


IoT アプリケーションを作っていると、ローカルで MQTT 環境を使いたくなる時があります。

Mosquitto は、そんなオープンソースな MQTT のコントリビューションです。Windows や Mac 向けのバイナリも用意されているようですが、自分は Linux(ラズパイやCentOS) で使えると便利なので Linux 用の環境構築手順を紹介します。

まず、ラズパイだと話が早く、このコマンドを入力するだけで MQTT のブローカーが導入できてしまいます:
$ sudo apt-get install mosquitto

MQTT のパブリッシャーやサブスクライバーといったクライアントコマンドを使う場合は、更にコマンドを実行します:
$ sudo apt-get install mosquitto-clients


ブローカーは mosquitto というサービスとして導入されるので、service コマンドで他のサービス同様に扱うことができるようになります:
$ sudo service mosquitto status


一方、CentOS の場合は少し準備が必要です。インストールそのものは yum を使うので、まずは root でログインしてリポジトリを導入します:
# cd /etc/yum.repos.d
# wget http://download.opensuse.org/repositories/home:/oojah:/mqtt/CentOS_CentOS-6/home:oojah:mqtt.repo

リポジトリが準備できたら yum でインストールします。これはブローカーのインストールコマンドです:
# yum install mosquitto

パブリッシャーやサブスクライバーなどのクライアントコマンドをインストールする場合はこちらのコマンドを(も)実行します:
# yum install mosquitto-clients

インストール後は、このコマンドでブローカーのサービスが起動します。ちなみに TCP と UDP の 1883 番ポートを空けておく必要があります:
# /usr/sbin/mosquitto

まあどちらも簡単でした。これでローカル環境に MQTT ブローカーの構築ができてしまいました。



最後に動作確認をしてみます(動作確認には MTQQ クライアントコマンドを導入した環境が必要です。MQTT ブローカーと同じマシンでも異なるマシンでも構いません)。

MQTT ブローカーが稼働している状況で、MQTT クライアントコマンドの使えるターミナル(やコマンドプロンプト)を2つ用意します。1つがパブリッシャー(サーバー)、もう1つがサブスクライバー(クライアント)となります。

まず1つのターミナル内で MQTT サブスクライバー(クライアント)を起動します。その際に -h オプションで MQTT ブローカーが稼働しているホストを、-t オプションでトピック文字列をそれぞれ指定します。起動後はプロンプトが戻らず、(Ctrl+C で終了するまで)待ち続けます:
# mosquitto_sub -h 192.168.0.XXX -t "top/ic001" -v

次にもう1つのターミナル内で MQTT パブリッシャー(サーバー)を起動します。起動時に -h オプションでサブスクライバーと同じ MQTT ブローカーホストを、-t オプションでサブスクライバーと同じトピック文字列をそれぞれ指定します。また -m オプションでメッセージ(この例では "Hello, World.")を指定します:
# mosquitto_pub -h 192.168.0.XXX -t "top/ic0t01" -m "Hello, World."

成功するとパブリッシャー側はすぐにコマンドプロンプトが戻って終了しますが、サブスクライバー側には送信されたメッセージが表示され、そのまま次のコマンドを待ち続けるはずです:
# mosquitto_sub -h 192.168.0.XXX -t "top/ic001" -v
top/ic001 Hello, World. ←パブリッシャーから送られたこれがサブスクライバー側に表示される

動作確認もできました。3台(ブローカー、サブスクライバー、パブリッシャー)の役割分担も分かりましたね。



(参考にしたサイト)
http://flyerback.blogspot.jp/2014/04/mqtt-broker-mosquitto-amaazon.html


IBM Bluemix に最近(2015年7月15日)新しく追加された人工知能サービス Tone Analyzer を紹介します:
http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/tone-analyzer.html


Tone Analyzer は入力したテキストの内容を言語解析して、感情やライティングスタイルといった情報を検出して出力してくれる、という API サービスです。2015年7月21日時点ではまだ日本語には対応しておらず、サービス自体も Experimental(試験提供)という扱いです:
2015072101


この API を実際に使って作られたデモサイトがあるので、どういった API なのかはデモサイトを使って紹介します:
https://tone-analyzer-demo.mybluemix.net/


上記サイトにブラウザでアクセスすると "Input Text" と書かれた項目にメールの本文のようなサンプル英文が表示されています。この内容が API に入力するテキストです。このサンプル英文のままでも試せますが、このフィールドは編集可能なので、自分の書いたテキストに変更していただいても構いません。最後に "Analyze" ボタンをクリックします:
2015072102


API が実行され、その結果を視覚化したものが画面下部に出力されます:
2015072104


"JSON" と書かれた箇所をクリックすると、視覚化する前の、API としての実行結果がどんなものであったのかを確認することができます。要は API としては英文をインプットしたと、この JSON がアウトプットされた、ということになります。後はアプリケーションの設計次第でこの JSON をどう見せるのか?という部分を API 利用者の皆さんに作っていただくことになります:
2015072103


これが Watson Tone Analyzer API を使ったアプリケーションの例です。では次にこの API を実際に呼び出すアプリケーションの具体例とそのサンプルコードを紹介します。

まずは Bluemix にログインし、実際のアプリケーションサーバーとなるランタイムを作成します。Tone Analyzer API は REST API なのでプログラミング言語に依存していませんが、今回は Java のサンプルを紹介するので Liberty for Java のランタイムを作成します:
2015072007


アプリ名は適当に(この例では kkimura-java-ta と)付けておきます:
2015072105


ランタイムが作成できたら「概要」メニューから「サービスまたは API の追加」をクリックします:
2015072101


サービスの一覧が表示されます。今回の目的である Tone Analyzer はまだ試験提供のため、標準構成の一覧には表示されません。画面を一番下まですくリロールして "Bluemix Labs Catalog" をクリックして、試験提供サービスの一覧ページに移動します:
2015072102


試験サービスの Watson カテゴリの中に "Tone Analyzer" があるので、これを選択します:
2015072103


Tone Analyzer の説明が表示されます。まだ試験提供のため、今後の仕様変更の可能性があることにご注意ください(という内容が書かれています)。「作成」ボタンをクリックして、ランタイムにこのサービスをバインドします(再ステージングを促すダイアログが表示されたら再ステージングを選択します):
2015072104


バインド後にランタイムの「環境変数」メニューを参照すると、この Tone Analyzer サービスを利用するための認証情報が含まれた JSON テキストを確認することができます:
2015072105


この JSON テキストは以下の様な内容になっているはずです。"url" に API のエンドポイント(のベースとなる)値、そして "username" と "password" にはこの API を利用する際に必要になる認証情報が記載されています。これらの値を後で使います:
{
  "tone_analyzer": [
    {
      "name": "Tone Analyzer-a7",
      "label": "tone_analyzer",
      "plan": "experimental",
      "credentials": {
        "url": "https://gateway.watsonplatform.net/tone-analyzer-experimental/api",
        "username": "(username)",
        "password": "(password)"
      }
    }
  ]
}

実際に API を使う場合の Java コードはこのような感じになります。
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;

public class ToneAnalyzerServlet extends HttpServlet {
  @Override
  protected void doPost( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException{
    String contenttype = "application/json; charset=UTF-8";
    String body = null;
    String out = "";

// Tone Analyzer を使うための username と password(環境変数に書かれているもの) String username = "(username)", password = "(password)"; req.setCharacterEncoding( "UTF-8" ); try{ String _body = req.getParameter( "body" ); if( _body != null && _body.length() > 0 ){ body = _body; } if( body != null ){ HttpClient client = new HttpClient(); byte[] b64data = Base64.encodeBase64( ( username + ":" + password ).getBytes() );
// /v1/tone にテキストをポストする PostMethod post = new PostMethod( "https://gateway.watsonplatform.net/tone-analyzer-experimental/api/v1/tone" ); post.setRequestHeader( "Authorization", "Basic " + new String( b64data ) ); post.setRequestHeader( "Content-Type", "text/plain" ); post.setRequestBody( body ); int sc = client.executeMethod( post ); out = post.getResponseBodyAsString(); // ポスト結果の JSON テキスト //System.out.println( "out = " + out ); } }catch( Exception e ){ e.printStackTrace(); } res.setContentType( contenttype ); res.setCharacterEncoding( "UTF-8" ); res.getWriter().println( out ); } }

肝になっているのは赤字で記載した部分です。Tone Analyzer には何種類かの API がありますが、今回はシンプルにトーン分析を実行する /v1/tone を使った例を紹介しています。この API は非常にシンプルな構成になっており、Content-Type ヘッダ(text/plain)で指定したフォーマットで解析したいテキストを本文としてポストする、というものです。この例では解析テキストを body パラメータで受け取り、その内容を Tone Analyzer の /v1/tone で解析してその結果を受け取って、そのまま出力する、という内容をサーブレット化したサンプルです。

このサーブレットに実際にテキストを渡して実行した結果、ブラウザ画面にはこのようなテキストが表示されます(実際には1行の JSON テキスト文字列が表示されますが、改行を加えて見やすくしています)。これが API の返り値そのものです(赤字は私が加えたコメントです):
{
 "scorecard":"email",
 "children":[
  {
   "name":"Emotion Tone", 感情のトーン
   "id":"emotion_tone",
   "children":[
    {
     "name":"Cheerfulness", (感情の子要素としての)陽気さ
     "id":"Cheerfulness",
     "word_count":44, 44単語使っていた
     "normalized_score":0.47821730244258, 正規化後のスコアは47.82%
     "raw_score":0.009617486338797814, 正規化前のスコアは0.96%
     "linguistic_evidence":[ この陽気さに関する結果のエビデンス
      {
       "evidence_score":0.47821730244258, スコア47.82%
       "word_count":44,
       "correlation":"positive", "positive"に関連している
       "words":[ "surely", "good", ... ] 実際に使われていた単語
      }
     ]
    },
    {
     "name":"Negative", (感情の子要素としての)ネガティブさ
     "id":"Negative",
     "word_count":15, 15単語使っていた
     "normalized_score":0.7486994535519125, 正規化後のスコアは74.87%
     "raw_score":0.0033060109289617485, 正規化前のスコアは0.33%
     "linguistic_evidence":[ このネガティブさに関する結果のエビデンス
      {
       "evidence_score":0.7486994535519125, スコア74.87%
       "word_count":15,
       "correlation":"positive", "positive"に関連している
       "words":[ "lies", "uncomfortable", .. ] 実際に使われていた単語
      }
     ]
    },
    {
     "name":"Anger", (感情の子要素としての)怒り
     "id":"Anger",
     "word_count":0, 0単語使っていた
     "normalized_score":0.0, 正規化後のスコアは0%
     "raw_score":0.0, 正規化前のスコアは0%
     "linguistic_evidence":[ この怒りに関する結果のエビデンス
      {
       "evidence_score":0.0, スコア0%
       "word_count":0,
       "correlation":"positive", 
       "words":[] 実際に使われていた単語(がない)
      }
     ]
    }
   ],
   "word_count":59
  },
  {
   "name":"Writing Tone", ライティングのトーン
   "id":"writing_tone",
   "children":[
    {
     "name":"Analytical", (ライティングの子要素としての)分析要素
     "id":"Analytical",
     "word_count":144, 144単語使っていた
     "normalized_score":0.8802873231049461, 正規化後のスコアは88.03%
     "raw_score":0.0217896174863388, 正規化前のスコアは2.18%
     "linguistic_evidence":[ この分析要素に関する結果のエビデンス
      {
       "evidence_score":0.8802873231049461, スコア88.03%
       "word_count":144,
       "correlation":"positive",
       "words":[ "but", "knowing", .. ] 実際に使われていた単語
      }
     ]
    },
    :    
    :

この辺りのデータフォーマットは正式リリース後にどうなるか、なんとも言えない所ではありますが、とりあえず現時点では上記のようなフォーマットが API の結果として得られるようです。要は入力テキストをいくつかのカテゴリと、その子要素であるサブカテゴリに分けて分析してスコア付けを行い、その結果を返してくれる、という API のようです。



なお、Tone Analyzer API のリファレンスはこちらを参照ください:


IBM Bluemix から提供される各種 API の使い方をリファレンスで調べていると、確かにドキュメント化されてはいるのですが、具体的にどう使えばいいのか? と悩むことはないでしょうか?

例えば、このリンク先には Watson の Speech To Text サービス内の /v1/sessions/{$sessionid}/recognize 関数の使い方についての説明が書かれています:
http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/apis/#!/speech-to-text/recognizeSession

2015072101


このページを見て、具体的にどのようなエンドポイントにどのようなヘッダとパラメータでアクセスすればよいか、わかりますか? 分かる人はすぐに分かると思いますが、ちょっとクセのある書き方なので、具体的な指定方法がイメージしにくいかもしれません。その確認の意味も込めて説明します。

まず、上記ページの "Parameters" にはこのように書かれています:
2015072102


この表の "Parameter" と "Parameter Type" と書かれた2列に注目して、この2列だけを取り出してみるとこのようになります:
ParameterParameter Type
Content-Typeheader
session_idpath
bodybody
continuousquery
max_alternativesquery
time_stampsquery
word_confidencequery
inactivity_timeoutquery
Transfer-encodingheader
X-WDC-PL-OPT-OUTheader


"Content-Type" パラメータのタイプは "header" ということになっています。これが何を意味するかというと、「Content-Type を HTTP ヘッダで指定する」ということを意味しています。Transfer-encoding や X-WDC-PL-OPT-OUT も HTTP ヘッダとして指定する必要がある、ということになります。

次に "session_id" パラメータのタイプは "path" です。これは「API エンドポイントの URI を指定する際のパスの一部として session_id を指定する」ということです。実際、この API のエンドポイントは /v1/sessions/{$sessionid}/recognize であり、この {$sessionid} 部分に session_id を指定することになるので、確かにパスの一部に session_id を指定していると言えますね。

そして body パラメータタイプは body 、つまり body に相当する情報をポストの本文として指定することが想定されている、ということになります。

最後に continuous や max_alternatives パラメータのタイプは query です。これはエンドポイントのクエリーとして、例えば /v1/sessions/{$sessionid}/recognize?continuous=true&max_alternatives=5 のように指定することを意味します。


以上をまとめると、リファレンスのパラメータータイプはこのように理解すればよい、ということになります:
(1) path はエンドポイント URI の一部に埋め込む形で指定
(2) query はエンドポイント URI のパラメータとして指定
(3) header は HTTP ヘッダとして指定
(4) body はポストの本文として指定

この記述ルールがわかっていると Bluemix の API リファレンスも読みやすくなりますね。


普段は MySQL 使いの自分が IBM DB2 を使おうとして、意外とハマったのが以下に紹介する2点です。

まず1点目。MySQL ではテーブル列の属性に auto_increment という指定を付与することができます:
mysql> create table langs(
  id int primary key auto_increment,
  name varchar(256)
);

この属性が付いた列はデータ挿入時に値を指定する必要がなく、定義に従ったルールでユニークな値を勝手に挿入してくれる便利な属性です(赤字はコメントです):
mysql> insert into langs( name ) values( 'Java' );  nameだけを指定してinsert
mysql> insert into langs( name ) values( 'PHP' );
mysql> insert into langs( name ) values( 'Ruby' );

mysql> select * from langs;
+----+------+
| id | name |
+----+------+
|  2 | Java | ユニークなid値が勝手に挿入されている
| 12 | PHP  |
| 22 | Ruby |
+----+------+

これと同じことを DB2 利用時にもやりたい! のですが、DB2 では create table 時に auto_increment 属性を認識してくれません。


では DB2 で同じことをするには、create table 時にどのような指定をすればよいのでしょうか?

その答がこちらです。indentity 構文を使いますが、ちと面倒:
db2> create table langs(
  id int primary key generated always as identity (start with 1 increment by 1),
  name varchar(256)
);

これをテーブル定義時に指定しておけば insert 時にはユニークな値を自動生成してくれるので楽ちんです。



2点目。同様に、MySQL では auto_increment 指定のある列に挿入された値を知りたい、と思うことがあります:
mysql> insert into langs( name ) values( 'Python' );
↑今、インサートしたこのレコードの ID 値が何だったのかを知りたい

それは LAST_INSERT_ID() で取得できました。
mysql> select last_insert_id() from langs;

これと同じことを DB2 の identity 指定した列に対してやるにはどうすればいいでしょうか?
その答は IDENTITY_VAL_LOCAL() です:
db2> select IDENTITY_VAL_LOCAL() from langs;

MySQL と DB2 で文法の違いがありますが、とりあえずはどちらも出来るということで。 

自分が迷ったのは今のところこの位ですが、Bluemix を使うユーザーが増えると必然的(?)に SQL Database や dashDB を使う人も増えると思います。DB2 をベースにしたこれらのサービスを使う時に、今回紹介したような点で戸惑う人の助けになれば幸いです。


(参考にしたページ)
http://stackoverflow.com/questions/13466347/how-to-auto-increment-in-db2
http://stackoverflow.com/questions/3087836/db2-how-to-get-the-last-insert-id-from-a-table
 

このページのトップヘ