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

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

こんなニュースがありました:
グーグルが「Gmail」の利用規約を更新 - メール内容の解析を明示

グーグルは Gmail に書かれた内容を読んでいる、ということ。
その是非が色々言われているようですが、その件についてコメントは控えます。

僕は知ってました。というか、そういうものだと思ってました。
普段から自分でいくつかのウェブサービスを遊びで開発して公開したりしてます。
ある時、まだ誰にも話してないし、リンクも作っていない作りたてのウェブサイトのURLを Gmail で知り合いに送った所、その数日後から Google のクローラーボットがやってくるようになりました(苦笑)。本当にこのメールがきっかけだったのかどうかは分からないのですが、原因がメール内容しか思い当たらなかったので、以来そういうものだと思っていました。

で、それを逆手にとったというわけではないですが、裏ワザを1つ紹介します。

これから公開しようとしているウェブサービスを開発したら、公開前にわざと GMail にその URL を書いて誰かに(例えば自分の別のアドレスに)送ります。それでグーグルはその新サービスの URL を知ることになるので、数日中にクローラーボットが送り込まれて、グーグルの検索エンジンにインデックスしてくれます。

これで、サービスがスタートする際には既にサービス内の大半のページがグーグル検索結果に表示される状態を作っておくことができます。厳密には、サービススタート前に(キーワード次第では)検索結果に表示されるのはともかく、実際に訪問されては困る、ということもあると思います。そういう場合は UserAgent を見て、グーグルボットの場合だけは訪問を許し、それ以外の場合はエラーにする、という処理を加えておいて、サービス開始直前に無効にする、といった対策で訪問を回避することもできます。


と、まあグーグルが勝手にメールを解析してくれるのであれば、それを勝手に有効活用させてもらいましょう、という方法でした。


 

最初にお断りしておくと、今回紹介する機能は通話の API サービスで、実際に試すと少額ですが以下の料金が発生します。その点ご了承ください:
http://twilio.kddi-web.com/price/index.html



Twilio という変わり種サービスがあります:

http://twilio.kddi-web.com/


↑この URL を見てもわかるように、日本では KDDI ウェブコミュニケーションズ様がサービスを提供しています。

Twilio は「クラウド上の電話 API サービス」です。ウェブ上から電話をかけたり、特定の番号への着信を受けて(自動音声などで)処理したり、ということができるのですが、そのプログラミング用 API が公開されていることで開発者視点からも興味深いサービスです。なお、Twilio API の公式ドキュメントについてはこちらを参照ください:
http://www.twilio.com/docs/api/twiml


更に、最近 IBM がベータ公開している PaaS 環境である BlueMix の中で、この Twilio が連携サービスの1つになっていることが分かりました。BlueMix はアメリカの IBM が提供しているサービスで、日本で取得した Twilio アカウントが使えるか心配でしたが、私が使っている限りでは特に問題ないようです。 というわけで、この BlueMix のサービスとして Twilio を使った電話連携アプリを作る様子を紹介してみます。


BlueMix では Twilio サービスが提供されていますが、使うためにはあらかじめ Twilio のトライアルアカウントを取得しておく必要があります。既にアカウントをお持ちの方は自身のアカウント ID と認証トークンを確認してください。Twilio のアカウント作成に関しては Micorsoft のページに詳しく記載されていたので参照ください。こちらの手順でアカウント ID と認証トークン、そして通話用の電話番号(!)が取得できます:
Twilio (トゥイリオ)を触ってみた

ここで一点注意を。通話用の電話番号はトライアルアカウントの範囲内で取得できますが、その番号では SMS は利用できません。SMS 用の電話番号を取得するには初期チャージ料金 2000 円と、月額10ドルの番号維持費が必要になります。


Twilio のアカウントが取得できたら、そのアカウントを使った Twilio サーバーを BlueMix 上に作成します。まずは BlueMix にログインし、ダッシュボードから "Add a service" を選択します:
2014040401


サービスの種類は "Twilio" を選択します:
2014040402


確認画面で "ADD TO APPLICATION" を選択します:
2014040403


Twilio サービスの簡易設定を行います。といってもここで設定する項目は3つだけで、下の2つは Twilio アカウント作成時やログイン後の画面から取得できるものです。入力後に "CREATE" をクリックします:
 - Add to: アプリケーションサーバーの指定。後で指定する場合は "Do not associate" でも構いません。
 - Account SID: Twilio API を利用する アカウント ID
 - Auth Token: 認証トークン文字列
2014040404


先ほどの手順でアプリケーションサーバーの指定をしなかった場合は別途アプリケーションサーバーを作って(あるいは既存のものを使って)Twilio サーバーと紐づけます。ここでは kkimura3 という名前の Java アプリケーションサーバーを作って、そこに紐づける前提で紹介しています。アプリケーションサーバーの "Add existing service" を選択します:
2014040405


紐づけるサービスを選択します。今回は上記で作成した Twilio サーバーを指定します:
2014040406


アプリケーションサーバーを再起動する、という確認画面が出たら OK をクリックします:
2014040407


アプリケーションサーバーが再起動し、Twilio のマークが表示されればサービスと紐づいていることが確認できます:
2014040408


この紐づいた状態で kkimura3 サーバーの環境変数を確認すると、VCAP_SERVICES 変数に Twilio アカウントや認証トークンが設定されていることが確認できます。したがって、この後作成するアプリケーションにはこれらの情報をハードコーディングする必要がなく、環境変数から取得する、というセキュアな手法も可能になります:
2014040409


また、Twilio API の SDK をダウンロードしておきます。各種言語の API はこちらに用意されています:
Twilio API Libraries

今回は Java を利用するので、ページの真ん中くらいの "Java" というセクションから "twilio-java" と書かれた箇所をクリックし、リンク先から最新版の Java SDK をダウンロードします(2014/04/04 時点では twilio-java-sdk-3.3.16.jar が最新版です)。
2014040410

あとは普通に(Eclipse などで) Java アプリケーションを作成します。その際に今ダウンロードした Twilio Java SDK ファイルをプロジェクトに(WEB-INF/lib/ とかに)インポートして、プロジェクト内で利用できるようにしておきます。さあこれで準備は完了です!


で、Twilio 連携アプリケーションを作るわけですが、今回は以下のような仕様のシンプルなアプリを作ります:
- 「指定の番号へ電話をかけて、特定のメッセージを流す」というアプリ。
- 発信元番号は Twilio で取得した番号(日本で取得した場合は 050-AAAA-BBBB という、050 で始まる番号になっているはずです)。
- 着信先番号はアプリ内で指定する(ただし自分で受け取れないと意味ないので、自分の携帯電話番号とか)。

以下でそのアプリのコードを紹介しますが、かなりシンプルなのがわかると思います。

なお、日本の電話番号はゼロから始まる(0**-****-****)のですが、Twilio から発信する場合は国際電話形式に変更する必要があります。そのため最初のゼロをとり、頭に +81 を付け、+81-**-****-**** という形式の電話番号をコード内で指定している、という点に注意してください。


Twilio 連携をするアプリケーションをこんな感じで作成します。今回は JSP ファイル(twilio.jsp)を1つ作成し、以下のような内容にしてみました:
<%@page import="java.util.*" %>
<%@page import="java.net.*" %>
  :
<%@page import="com.twilio.*" %>
<%@page import="com.twilio.sdk.*" %>
<%@page import="com.twilio.sdk.resource.factory.*" %>
<%@page import="com.twilio.sdk.resource.instance.*" %>

<%
String sid = "", token = "";
String env1 = System.getenv( "VCAP_SERVICES" );
if( env1 != null && env1.length() > 0 ){
  // BlueMix 環境なので、env1 から accountSID と authToken の値を取り出し、sid / token に格納する
    :
}

//. BlueMix でない環境のための設定
if( sid.length() == 0 ){
  sid = "(Twilio のアカウントSID)";
}
if( token.length() == 0 ){
  token = "(Twilio の認証トークン)";
}

String r = "";
try{
  TwilioRestClient client = new TwilioRestClient( sid, token );
  Map params = new HashMap();
  params.put( "To", "+8180xxxxyyyy" );  // +81-80-XXXX-YYYY 形式での(自分が受け取れる)電話番号
  params.put( "From", "+8150aaaabbbb" ); // +81-50-AAAA-BBBB 形式での Twilio で取得した電話番号
  params.put( "Url", "http://demo.twilio.com/welcome/voice/ja/" ); // 確認用ウェルカムメッセージ
  CallFactory callFactory = client.getAccount().getCallFactory();
  Call message = callFactory.create( params );  // 実際に電話をかける
  r = "" + message.getSid();
}catch( Exception e ){
  e.printStackTrace();
  r = "Exception: " + e;
}
%>

result = <%= r %>

ここでの内容はシンプルなので眺めていればなんとなくわかると思います。発信番号と着信番号を指定して、着信するとウェルカムメッセージを流す、というだけの内容です。また成功した場合のウェブ画面には SID が表示されるようになっています。

ちなみに上記コード内の params.put( "Url", "***" ); で指定した箇所ですが、Twilio ではメッセージの内容を特定のフォーマット(TwiML)で定義するのですが、その定義内容が参照できる URL を指定することで、そのメッセージを音声で喋らせることができます。上記で指定しているのはデフォルトの確認用メッセージ(「トライアルアカウントへようこそ・・」みたいな感じ)が流れる URL ですで、その中身は以下のようになっています:
<Response>
<Say language="ja-JP" voice="alice">VOICE U R L の設定を変更することでこの文章を変更できます。</Say>
<Pause length="1"/>
<Say language="ja-JP" voice="alice">仕様のことでご不明点が御座いましたらご連絡ください。</Say>
</Response>

この内容は自分でオリジナルのものを定義/用意することも可能ですし、TwiML を生成/保管するサービスも提供されているようです。TwiML の仕様についてはこちらを参照するか、あるいはウェブで情報を探してみてください。


こうして作成したウェブアプリケーションを WAR ファイル(twilio.war)にして、上記で作成した BlueMix の kkimura3 サーバーに cf を使ってデプロイします(この辺りの詳しい手順はこちらを参照ください):
# cf push kkimura3 -p twilio.war

そして、ウェブブラウザで BlueMix のアプリケーションサーバーの twilio.jsp にアクセスすると、画面には SID が表示されて・・・
2014040411


そして携帯電話には Twilio の番号からの着信が・・・
写真


おお! できました!! このコールを着信すると機械的な声で Twilio のデモメッセージが(自分で作ったメッセージの URL を指定した場合はそのメッセージが)音声で流れてきます。つまりウェブアプリから電話番号に発信してメッセージを流す、というアプリがこんな簡単に作れてしまったことになります。


このアプリは放置しておくと1アクセスごとに電話がかかってきて(しかも19円ずつかかって)しまうので、検証が終わった段階で止めておくのがいいでしょう。でも電話や SMS の連携が数行のコードでできてしまうのはアプリ開発の幅が広がりますよね。

そしてそんな Twilio サービスを自分のアプリ用に、しかも選択するだけで簡単に用意できてしまう BlueMix プラットフォームにも可能性を感じました。




ウェブ制作において、画像やサイトの一部を別モーダルウィンドウでプレビューのように表示する、通称「ライトボックス」と呼ばれる機能があります。 具体的にはこんな感じのものです:

マンホールマップ  ←クリックするとライトボックス内でマンホールマップを表示します

↑CSS が適当なので少し見にくいかも。あとスマホだとうまく表示されないかもしれません・・・


元のページに戻ってくることを前提に、違うページの情報を一瞬表示させる、という目的で便利に使える機能です。Facebook などでも写真を表示する時に使われてますね。こういった機能を実現するための JavaScript ライブラリも多く公開されています(ちなみに上記機能は Colorbox というライブラリを使って実現しています)。

Colorbox で iframe を使った簡単な実装例を紹介します。上記ページから js と css をダウンロードし、jQuery と合わせて <head> 内に以下のような記述をしておきます:
<link  href="css/colorbox.css" rel="stylesheet">
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.colorbox-min.js"></script>
<script type="text/javascript">
$(function(){
	$('.box').colorbox( { iframe:true, width:"90%", height:"90%" } );
});
</script>

この例では box というクラスが付与されたリンクに対してライトボックスを実行するような記述になっています。 
その上で以下のような <a> リンクを用意、これだけです:
<a class="box" href="xxx">XXXX</a>


ところが、このライトボックスが使えないページ(ライトボックスの中身として表示できないページ)、というのがたまに存在しています。例えばグーグルのトップページ(http://www.google.co.jp/)です:

Google  ←クリックするとダイアログは表れますが、中に何も表示されません


実は同じようなページは他にもあります。僕が知る限りではアマゾン価格コムのページもライトボックスでは表示されません。

この違いは何でしょうか? 実はページを HTTP リクエストで取得する際のレスポンスヘッダ X-Frame-Optionsによって、そのコンテンツが iframe の内部に表示することを許可するかどうかを制御できます。上記の挙動の違いはこのレスポンスヘッダ X-Frame-Options の値によるものでした。
2014040600


デフォルト値は「なし」です。その場合(つまり特に何もしない場合)、そのページは iframe 内に表示可能です。一方この値が DENY に設定されていると、そのページを iframe 内に表示することはできません。また SAMEORIGIN(生成元と同じフレーム内に限り表示可)や、ALLOW-FROM xxx(生成元が xxx の場合に限り表示可)といった設定も可能です。詳しくはこちらを参照してください。
2014040601


こういう制限をかける側としてはそれなりの意図があって制限しているんでしょうけど、やはり表示できるものなら表示したい、という作成側の意図もあるわけです。また AJAX のクロスドメイン制限にも言えることですが、制限自体は JavaScript での挙動や iframe 内での挙動に制限をかけているだけであって、情報の取得そのものを制限しているわけではないので、回避することもできます。 で、今日はその(X-Frame-Options 制限の)回避に挑戦してみます。

考え方としては iframe での表示に制限をかけられているサイトに対して、Java や PHP などのサーバーサイドプログラミングによって情報を取得(これらには制限がないので可能)し、その結果を iframe 内に入れてしまおう、というものです。まあクロスドメイン対策と同じ考え方です。
2014040602


ということで、上記の紫色の部分を別途作成する(加えて、それをどこかのサーバーに設置しておく)必要があるわけです。この部分は例えば Java のサーブレットであれば、おおまかにはこんな感じになるでしょうかね:
public class SpoofXFrameOptionsServlet extends HttpServlet {
  public void doGet( HttpServletRequest req, HttpServletResponse res ) throws IOException{
    req.setCharacterEncoding( "UTF-8" );

    String html = "";
    String url = "", _url = req.getParameter( "url" );
    if( _url != null && _url.length() > 0 ) url = _url;
    if( url != null && url.length() > 0 ){
      try{
        GetMethod method = new GetMethod( url );
        HttpClient client = new HttpClient();
        int sc = client.executeMethod( method );
        html = method.getResponseBodyAsString();
      }catch( Exception e ){
        e.printStackTrace();
      }
    }

    res.setContentType( "text/html; charset=UTF-8" );
    res.setCharacterEncoding( "UTF-8" );
    res.getWriter().println( html );
}

このサーブレットが http://xxxx/spoofxframeoptions という名前でアクセスできるようになっていたとすると、
http://xxxx/spoofxframeoptions?url=(iframe 内に入れたいページのURL)

のように指定することで、目的のページのコンテンツを取得できることになります。 なので、この上記の URL を iframe 内で指定すればいい、ということになります。
<iframe class="box" href="http://xxxx/spoofxframeoptions?url=(iframe 内に入れたいページのURL)"></iframe>

で、これを実際にやってみたのがこちらのリンクです:
Google  ←ライトボックス内でGoogleトップページが表示できるようになりました

厳密には上記のサーブレットのサンプルはまだ不完全で、実際には文字コードを考慮したり、HTML 内の <base href="..."> 要素を追加する必要があったり、ページによっては Referer を指定しないと取得できなかったり、、、といった細かな修正が必要になるのですが、とりあえず X-Frame-Options の制約を回避する手段としては使えそうですね。


 

このページのトップヘ