Be an Engineer.

社会人からWEBエンジニアになった人間の備忘録的勉強記録

Facebook Messanger Platformを調べてみた

Bot熱が冷めないshirakiyaです。
昨日のLINE Botを調べてみたに引き続き、今回はFecebookのMessanger Platformについて調べてみました。

作成の流れと詳細

Messanger Platform 公式ドキュメント
↑のGetting Startedを参考に調べて行きました。

0. 必要なもの

いきなり流れとかじゃないのですが、前提として以下のものが必要になります。

  1. Facebookページ
    • 作成するFacebookページがBotのアカウントとなる
  2. Facebookアプリ
    • これがMessanger Platformと呼ばれるBotAPIを有効にするためのもの
  3. Webhook URL
    • ユーザーからのメッセージの送信などでMPからフックされる先のURL。いわば自前で用意しないといけないBotアプリ。

1. FacebookアプリとFacebookページを作成する

まず始めにFacebookアプリを作成します。
あまり重要なところじゃないので端折りますが、fecebook for developersからアプリを作成します。
作成するときは、Webサイトでもモバイルアプリでもなんでもないので下図のようにbasic setupから進めると、余計な作成フローを進まずにいけるのでおすすめです。

f:id:shirakiya:20160531151256p:plain

アプリと共にFacebookページも作成する必要があります。作成の方法は割愛します。(ヘッダーにある下向き三角マークから「ページを作成する」があるので、そこから作成できます。)
とりあえず適当に進めて作成してみました。

f:id:shirakiya:20160531151922p:plain

2. FacebookアプリにMessangerを追加する

Messangerの追加

左カラムにある「製品を追加」から、Messangerという製品("Product"の直訳なので日本語では不自然になってる?)を追加します。

f:id:shirakiya:20160531152205p:plain

Webhook設定

ページトークンの下にある「Setup Webhooks」から設定します。

f:id:shirakiya:20160531152255p:plain

f:id:shirakiya:20160531152309p:plain

ここでcallback URLとトークンを自由に設定できます。
が、 この時点でこちらで用意すべきBotのシステムができていないので、一旦パスして後で入力することとします

3. Botアプリを作成する

ここで言うBotアプリは「Botを動作させるためにユーザーが用意するシステム」のことで、Facebookアプリのことではないのであしからず。

3-1. Webhook設定のためのWebアプリを用意する

まず、「Webhook設定」を完了することを目指します。
ここでしないといけないのは、要は"疎通確認"ですので、以下を満たすようにサーバーや簡単なWebアプリを構築します。

  1. callback URLが用意できていなかったので、そのURLを用意する
  2. Facebook側から送られるGETリクエストに対し、Verify Tokenを確認してchallengeを返す

今回も簡単のため、Heroku+Flaskを用いて構築しました。
Herokuの構築手順は割愛することとして、Webアプリには以下のような処理を行います。

  • app.py
# -*- coding: utf-8 -*-

import os
from flask import Flask
from flask import request

app = Flask(__name__)

# エンドポイントはなんでも良い
@app.route('/webhook', methods=['GET'])
def varification():
    VERIFY_TOKEN = os.getenv('VERIFY_TOKEN')

    # Facebookからは
    # `?hub.mode=subscribe&hub.challenge=<value>&hub.verify_token=<verify-token>`
    # というパラメータが送られてくる
    # `hub.verify_tokenが一致していることを確認する
    if request.args.get('hub.verify_token') == VERIFY_TOKEN:
        # 一致している場合は`hub.challenge`をそのまま返す
        return request.args.get('hub.challenge')
    return 'Error, wrong validation token'

if __name__ == '__main__':
    app.run()

構築しアクセス可能な状態にした後に、Facebookアプリ側の画面に戻りWebhook設定を行います。
URLと任意のVerify Token(今回であればHerokuの環境変数に定義したものと同じもの)を入力し、フォロー入力欄には少なくとも「message」にチェックを入れます。
「確認して保存」を押すと、裏で疎通確認が行われます。失敗すればエラー原因と共にダメって言われます。

f:id:shirakiya:20160531152453p:plain

ちなみにここにある「フォロー入力欄」は以下の意味のようです。

チェック 説明
messaging_optins Send-to-Messanger Pluginボタンを押されてMessangerにやってきた時にcallbackするかどうか
messages ユーザーからのメッセージが送られた時にcallbackするかどうか
message_deliveries メッセージが送信できた後にcallbackするかどうか(?) 「This callback will occur when a message a page has sent has been delivered.」 ←Facebookのタイポ?
messaging_postbacks [構造化メッセージ」と呼ばれるボタン付きメッセージで、ボタンが押された時にcallbackするかどうか

3-2. メッセージを受信し、返信するようにする

次はユーザーから送信されたメッセージを受信して、オウム返しするプログラムを書いていこうと思います。

まず、作成しているFacebookアプリとFacebookページを紐付けます。
ページを選択から、先ほど作成したFacebookページを選択し、フォローします。

f:id:shirakiya:20160531152703p:plain

フォローしたら、トークン生成からFacebookページのページトークンを生成します。このトークンがあればAPI利用の認証ができるようになります。

f:id:shirakiya:20160531152903p:plain

実際の開発したものに関して、オウム返しするだけですがコードはこちらです。

github.com

イメージとしては、例えばユーザーがメッセージを送った場合ではMPから以下の1つ目の様なJSONがPOSTで送られてくるので、それに対応して2つ目のJSON共にAPIを叩くとBot側から簡単なメッセージを送信することができます。

  • receive
{
  "object":"page",
  "entry":[
    {
      "id":"PAGE_ID",
      "time":1460245674269,
      "messaging":[
        {
          "sender":{
            "id":"USER_ID"
          },
          "recipient":{
            "id":"PAGE_ID"
          },
          "timestamp":1460245672080,
          "message":{
            "mid":"mid.1460245671959:dad2ec9421b03d6f78",
            "seq":216,
            "text":"hogefugapiyo"
          }
        }
      ]
    }
  ]
}
  • request
{
    "recipient":{
        "id":"<entry[0]['sender']['id']>"
    }, 
    "message":{
        "text":"みゅ!"
    }
}

Facebookの画面にて、FacebookページとMessageのやり取りを行います。

f:id:shirakiya:20160531153133p:plain
(やっぱり感じるデバック感。。返事が来ないと二重で悲しい)

できました!
これで簡単なBotは作成できました。

参考:Messengerで簡単なBotつくる(Facebook Messenger Platform from F8) - Qiita

システム構成

まとめがてら、全体像はこんな感じです。

f:id:shirakiya:20160531153208p:plain

LINE Botと異なるのは、LINEではLINE Bot Trial Accountを作成すればその時点でアカウントが作成されましたが、FacebookではFacebookページのアカウントとMessangerでやりとりを行うため、Facebookページが必要になります。

何ができるのか

  • メッセージの送信
    • テキスト/画像の送信
    • Facebookが用意しているテンプレートメッセージ
      • Generic(テキスト&画像&ボタン)
      • Button(テキスト&ボタン)
      • Receipt(領収書)
  • メッセージ開始時のメッセージ送信
  • Web埋め込み用の"Send to Messanger" / "Message Us" ボタンをデフォルトでサポート
    • → エントリーポイントを増やしやすい
  • ユーザーの情報取得
    • (別にMPならではのAPIではなさそうですが)

https://developers.facebook.com/docs/messenger-platform/implementation
このページに全てが書かれているので詳細はこちらから。

その他情報

審査

作成したBotを公開するには、Facebookによる審査が必要のようです。

  • page_messaging
  • pages_messaging_phone_number (optional)

という二つの権限があり、それらの利用範囲を以下の表にまとめました。

page_messaging pages_messaging_phone_number
(OK) ユーザーとの初期接触時の体験 (OK) SMSを使ったメッセージの送信
(OK) 予約・購入・注文の確約 (NG) ユーザーの同意無しでSMSを使ったメッセージを送信すること
(OK) カスタマーサポートなメッセージの送信
(NG) サービス・商品のアップセリングやクロスセリング
(NG) 広告やニュースレター、お知らせ等の送信

https://developers.facebook.com/docs/messenger-platform/app-review

料金

Bot作成では基本無料のようです。
が、Customer Matchingなる機能を利用する場合は、Customer Matchingしたユーザー一人毎に$99を払わないといけないようです。
Cutormer Matchingとは電話番号からユーザーを特定してMessangerでそのユーザーにリーチすることを可能にする機能のようです。ただし、FacebookページがUSのアドレスで登録されているか、一人の管理者がUSのアドレスで登録されていることが条件のようです。

(ってかめっちゃ高い…)

Wit.ai

http://www.torchlight.co.jp/blog/facebook/f8-2016-messenger-bots.html
こちらの記事にある通り、Wit.aiという自然言語処理に長けた人工知能が含まれる「bots on Messanger」も利用可能なのかなと思いきや、今は使うことができなかったです(ドキュメントには一部に紹介があるのみです)。
が、現状で特別何か連携がないというだけで、Wit.aiは利用可能なので使ってみるのも面白いかもです。

簡単にブロック可能

ユーザーはボットを簡単にブロックすることができます。あるいは、広告メッセージだけブロックすることもできるようです。
完全に、お助けツールという立ち位置だけしか認めないし、プロモーションのためにBotを使うなというFacebookのメッセージが受け取れますね。

感想

  • MPは何か"契約"を結ぶための商用Botに特化している
    • テンプレートを用意しているのはLINE Botになかったところ
    • LINE Botは「rich message」で自分で構造を作らないといけない
  • LINEにはGreatingメッセージのようなものがBussiness Connectしかなかったので便利っぽい

Enjoy Bot Life!!

LINE Bot(LINE Bot API Trial)を調べてみた

(※2016/11/10追記 こちらの記事はBot API Trialについて書いた記事です。2016年9月29日でもってBot API TrialはDeprecatedとなり、2016年11月16日をもって廃止となります。ただし、新たなAPIとしてMessaging APIが公開されています。)

作成した時の流れに沿って、そこでやったことと調べてみたことを書きます。

1. アカウント作成

会社/事業者情報作成

まずはLINE BUSINESS CENTERにある「BOT API Trial Account」へ。

ページ下部の「利用開始」をするとログインを求められるので、今回は普段利用している個人のアカウントを使用しました。

そういえばLINEは(できなくはないけど)複数アカウント持ちづらいサービスなので、会社利用するにしてもここのログインアカウントは個人のものを使うことになりそうです。
(ちなみに複数アカウントを持つには、スマホ版のアプリを一度インストールしなおさないと二つ目のアカウントを作成して、また別のアカウントでログインし直したい場合は、再度インストールし直さないといけない、、という流れを踏むはず。ログアウトが無いというだけでこんなことになるんですね。完全にわざとだと思いますが。)

その後、会社/事業者情報を登録します…たしか…。(ここはずっと前に登録だけしておいて、メモをしてなかったので適当な感じになっていますが、会社/事業者情報を個人として登録した記憶があります。)

ビジネスアカウント作成

LINE BUSINESS CENTERでは、ビジネスアカウントというものがあります。
ビジネスアカウントは、いわば「サービス」を指し、ビジネスアカウントからBotを作ったり、LINE@を作ります。
例えばXというサービスを持つAという会社では、会社/事業者情報をAで登録し、ビジネスアカウントはXという単位でビジネスアカウントを作る形ですね。

LINE Business Centerの会社/事業者情報を登録後のトップページから「ビジネスアカウントを作成する」からビジネスアカウントを登録します。
今回は「モルちゃんBot」というBotを作ろうと思うので、それっぽい感じで作ります。

f:id:shirakiya:20160530104051p:plain

※ビジネスアカウントを作成しなくてもBot API Trial Accountは作成できます。1つの事業者で複数のサービスを持つ場合はビジネスアカウントは作らないと、事業者に紐付いてBotが1つしか作れないので注意。

2. Bot API Trial Account作成

ビジネスアカウントを作成したら次は、Bot API Trial Accountを作成します。
ビジネスアカウントを選択してBot API Trial Accountで「始める」から、次のページでページ最下部の「利用開始」を押すと、Bot API Trial Accountの作成確認画面に行きます。(ここから英語)

f:id:shirakiya:20160530104127p:plain

おもむろにCreate~!!

f:id:shirakiya:20160530104144p:plain

これがトップ画面で、Channel ID、Channel Secret、MID等の情報が書かれています。
一応この画面からBotの名前と画像は変更可能なので、なんかちゃうなってなっても安心です。

左カラムを見ると「Server IP Whitelist」とあるので、API call元のIPも制限できるようです。

f:id:shirakiya:20160530104221p:plain

3. アプリケーションを作成する

詳しいドキュメントはこちら
Bot API Trial Accountの画面の右上にある「Documents」から参照できます。)

システム構成

以下にLINE Botの全体像を表すシステムを図示してみました。

f:id:shirakiya:20160530104247p:plain

トークを始めたユーザーからメッセージが送られると、LINE Bot Serverが開発者が用意したcallback URLにPOSTが投げられます。BodyにはJSONで記述されたテキストが入っています。例えばメッセージを返答するようなBotでは、開発者はそれを受けて返答するメッセージをJSONで用意し、LINEが用意しているAPIをcallします。するとLINE Bot Serverが処理してLINEアプリ上に返答メッセージが表示される、という仕組みになっているようです。

また、アプリケーション側が用意するシステムに関してアーキテクチャの考察がなされており、大量メッセージが来ても安心なLINE BOTサーバのアーキテクチャ等が参考になります。(この記事はかなりなるほどなーと思ったので、必読だと思います。)
また、そのアーキテクチャを参考にAWSで構築したというLINE Bot を AWSを使ってシステム構築してみた。等も参考になります。

アプリケーションを作ってみる

今回はサクッと試したいというのと調査が目的だったため、無料で使える範囲でHerokuとPython(FLASK)で作ってみました。Herokuだとhttpsをデフォルトでサポートしてくれていて、かつサーバホスティングしてくれているので、調査用としてはちょうどよいです。

実際のコードです。ただメッセージが送られるとそれをトリガーとして「みゅ?」とメッセージを返すのみのものにしました。
github.com

大きな流れはこんな感じ。

  1. アプリを作る
  2. herokuにデプロイ
  3. LINE Bot Trial Accountにcallback URLを登録
  4. Server IP Whitelistに用意したサーバーのIPを登録

注意するべきは、

  • LINE Bot ServerからhttpsをサポートしているURLに対してPOSTリクエストされること
  • Server IP Whitelistに登録しないとAPIへのリクエストが通らないこと
  • (細かい実装の話ですが)Botからのメッセージ送信の際のtoは配列であること(30分ハマった)

などでした。いずれもわかっていればそんなに問題ではないですね。

作成にあたっての参考記事:
LINE BOT をとりあえずタダで Heroku で動かす - Qiita
今更だけどラウル様と会話できる LINE bot を作ってみようとしてやめました→動きました - Qiita

4. Botとトークする

Botとトークするには、まずLINEアプリでBotと友だちにならないといけません。
作ったLINE Bot Trial Accountのトップページの下の方にQRコードがあるので、そのQRコードから友だちになってトークの画面まで行きます。

で、メッセージを送ると返ってきました。
f:id:shirakiya:20160530104440j:plain
(返事をしてもらえてない感じ、なんともデバック感を感じさせる…)

できること

https://developers.line.me/bot-api/overview#bot_accounts
を見ると、ビジネスアカウントが「Business Connect」なのか「Bot API Trial」なのかで利用可能なAPIに違いがあるようです。

Business Connect BOT API Trial
Send and receive messages using APIs Send and receive messages using APIs
Send link messages Send rich messages
Send rich messages
Send mission stickers
Use rich menus
Use Channel Web Applications
Have a searchable official account

https://developers.line.me/type-of-accounts/business-connect#anc1

基本的に「Send and receive messages using APIs」では、

が行えるようです。
rich messages」はLINEの公式アカウントとかが普段クーポンを送ってきたりとかあると思うのですが、あれを想像してもらえるとOKです。

※1. 今回作ってみたのはBot API Trialの方のアカウントです。Business Connectの詳細については公式ページを参考にしてください。
※2. LINE@はBot API Trial側に含まれるようです。

その他の情報

複数人でBotとトークする

できないようです。
まだなのか将来的にも無いのかはわかりませんが、公式アカウント的な振る舞いをするので、複数人がいるトークに招待しても、グループに入れても参加してくれませんでした。

利用条件

詳細な日付は調べていませんが、昔は10000人に限りこのLINE Bot Trial Accountが登録できたという状況だったようですが、現在は基本的にLINEのアカウントさえ持っていれば試用可能です。

料金

料金については、今のところ公表されておらず、まだトライアル段階です。ただトライアルのため、Botとの友だち数の上限が50人までになっているようです。 ただ、ビジネス利用で法人アカウントに限り、BOT API Trial Accountの制限解除プログラムに登録できるようで、デフォルトで友だち数上限が5000人まで可能で、それ以上の制限解除は応相談のようです。(料金もそこで決まる?)
ただ、プログラムには審査があるようなので、そこは1つのハードルになりそうです。
https://feedback.line.me/enquete/public/915-RRUi5HII

Heroku Add-onのProcess SchedulerでAPI Keyを変更する方法

久しぶりの更新ですが、ショートにメモ。

先日Herokuに作成しているアカウントのパスワードを変更したらHeroku API Key(以降、API Key)の値も変わる仕組みらしく、Heroku Add-onのProcess Scheduler から [<Project Name>] Failure to scale web process type という件名のメールが送られてきました。(Process Schedulerが何者なのかは今回は割愛します。)

メール本文には、さらに

Process Scheduler failed to scale the web process type for your application shirachan from to . The following error occurred : Invalid credentials provided.

This error means the API key you provided is no longer valid. This is the case when you change your Heroku's account password.
To resolve this error, you must connect on Process Scheduler web interface and change your API Token in the settings panel.

という内容が親切にも書いてくれていたので、不適切にAPI Keyが設定されてしまっているとのことでした。

ただ、このProcess Schedulerの画面からAPI Keyの設定を編集するリンクがとても気づきにくかったので、ご紹介。

Heroku Add-ons → Process Scheduler画面

Process Schedulerを使っているならば説明しなくてもいいとは思いますが、一応。
下の画像の赤く囲ったリンクから、Process Schedulerの設定画面にいけます。

f:id:shirakiya:20160322095833p:plain

Process Scheduler → API Keyの設定画面

今回めっちゃ苦労したのは、下の画像で歯車のアイコンが見つけられなかったことでした。(あまりコントラストがはっきりしているディスプレイを使っていなかったというのも相まって見つけづらかったです。)

f:id:shirakiya:20160322100532p:plain

この歯車をクリックすれば、以下に例示するAPI Keyを設定できる画面にいけます。

f:id:shirakiya:20160322101057p:plain

いやー見つけられなかった自分もアレかなとは思いますが、いいディスプレイを使っているユーザーばかりではないので、こういったUIは考えないといけないですね!
デザインは難しい…。

iOSアプリの実装 - オレオレ証明書と戦う(webview、Alamofire)

iOSアプリを作る過程で、webview や Alamofire などを使って、Webサイトを表示させたり、APIを叩いたりすることは当たり前のようにあると思います。
ただ実装過程では、サーバーを立てて仮想的にHTTPS通信にするためにオレオレ証明書を作ってテストすることがままあると思うのですが、アプリ側の実装をそのままにしていると基本以下のようなエラーが表示されたりします。

2016-01-16 21:36:19.940 Webview[8069:1398803] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)

調べた記事内容とその参考記事を簡単にまとめました。
(あまり深いことはわかっていません、すみません。)

オレオレ証明書なサイトをWebviewで閲覧する時

  1. 新しく.swiftのファイルを作る
  2. 以下のコードを書くだけ
import Foundation

// NSURLRequestの非公開APIをオーバーライド
extension NSURLRequest {
    static func allowsAnyHTTPSCertificateForHost(host: String) -> Bool {
        return true
    }
}

(参考)
SwiftでHTTPS通信時に自己認証証明書の警告によるエラーを無視させる - Steel Dragon 14106

オレオレ証明書なサーバーとAlamofireを使って通信する時

Aalamofireを使う画面のviewDidLoad() などで、実際にAlamofireで通信させる前に、以下のコードを実行させるだけで良い。

let manager = Alamofire.Manager.sharedInstance
        
manager.delegate.sessionDidReceiveChallenge = { session, challenge in
    var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling
    var credential: NSURLCredential?

    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
        disposition = NSURLSessionAuthChallengeDisposition.UseCredential
        credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
    } else {
        if challenge.previousFailureCount > 0 {
            disposition = .CancelAuthenticationChallenge
        } else {
            credential = manager.session.configuration.URLCredentialStorage?.defaultCredentialForProtectionSpace(challenge.protectionSpace)
            if credential != nil {
                disposition = .UseCredential
            }
        }
    }
    return (disposition, credential)
}

(参考)
[iOS]Alamofireでオレオレ証明書なSSLサーバへの接続を可能にする | 開発メモ

注意

非公開APIを書き換えているために、App Store提出時のReleaseビルドに含めてしまうとAppleの審査でリジェクトを食らう可能性が高いので、Releaseビルドには含めないように注意!

hubot-google-images を復活させた

先日、Hubotのhubot-google-imagesが急に使えなくなったので、その修復で行った作業内容を備忘録的に記事にします。

ちなみに使えなくなった理由ですが、これは2011年5月からdeprecatedになっていたGoogle Image Search APIが完全に利用不可になってしまったためでした。

また、hubot-google-imagesがv0.1.5以降のバージョンであれば下記の手順で使えるようになります。

1. Google Custom Search API Key を取得する

Google Developers Console
Google Custom Search API Key(CSE_KEY)はGoogle Developers Consoleから取得します。

プロジェクトを作成する

プロジェクトが1つもない状態ならば、プロジェクトを作成します。
Google APIを利用する」をクリックすると、プロジェクト作成モーダルが出てくるので、プロジェクト名を入力して作成します。

f:id:shirakiya:20151207011935p:plain

f:id:shirakiya:20151207011953p:plain

Custom Search APIを有効にする

Custom Search APIを選択して、有効にします。

f:id:shirakiya:20151207012011p:plain

f:id:shirakiya:20151207012042p:plain

APIキーを取得

「認証情報」タブに移動し、「認証情報を追加」します。
APIキーを選択すると、下の画像のモーダルが表示されるので、ここはサーバーキーを選択します。(Hubotはサーバーで動いていますので。)

f:id:shirakiya:20151207012055p:plain

f:id:shirakiya:20151207012115p:plain

APIキーの名前とリクエスト元のIPアドレスを入力します。
※リクエストを受け入れるサーバーIPアドレスには、セキュリティを鑑みて入力してください。

f:id:shirakiya:20151207012127p:plain

f:id:shirakiya:20151207012141p:plain

これで取得できました!

2. Google Custom Search Engine ID を取得する

Googleカスタム検索
Google Custom Search Engine ID(CSE_ID)はGoogleカスタム検索から取得します。

カスタム検索を作成する

「新しい検索エンジン」から作成画面にいきます。

f:id:shirakiya:20151207012300p:plain

などとして作成します。

CSE_IDを取得する

CSE_IDを取得していきます。
「コントロールパネル」をクリックすると詳細情報が見れます。
詳細ページで画像中央の「検索エンジンID」をクリックすると、モーダルが表示されるのでこの中に入っているのがCSE_IDです。

f:id:shirakiya:20151207012323p:plain

f:id:shirakiya:20151207012347p:plain

f:id:shirakiya:20151207012357p:plain

Tips

「検索するサイト」に先ほど入力したwww.google.co.jpが入っています。

これを設定したままだと、実際にHubotにimage機能で検索文字列を投げかけると、www.google.co.jpに含まれるコンテンツの中からその検索文字列を探してくるので、ある種googleに偏った検索結果になってしまいます。

そこで、 検索するサイト は全て削除して、「追加したサイトだけ検索する」と表示されているプルダウンから 「追加したサイトを重視して、ウェブ全体を検索する」に変更 します。

f:id:shirakiya:20151207012414p:plain

こうするとことでウェブ全体から重み付けなく検索してくれるようになります。

CSE_KEY/CSE_IDのチェック

CSE_KEYとCSE_IDが有効であるかどうかは実際にAPIにリクエストを送って確認します。

https://www.googleapis.com/customsearch/v1?key={CSE_KEY}&cx={CSE_ID}&searchType=image&fields=items(link)&q={何か検索したい文字列}

↑のURLのCSE_KEYとCSE_IDにこれまでで取得した実際のものを使って、ブラウザでリクエストします。
itemsというキーが存在するJSONが返ってきたら成功です。

3. HubotにCSE_KEYとCSE_IDを設定する

最後の仕上げに、以上の手順で取得したCSE_KEYとCSE_IDを環境変数に設定します。

変数名は、

  • CSE_KEY:HUBOT_GOOGLE_CSE_KEY
  • CSE_ID:HUBOT_GOOGLE_CSE_ID

とします。

herokuでHubotを動かしていたり、色々な環境で動かしているので設定方法は一概になんとも言えないので、具体的な方法は割愛します。
(起動スクリプトを用意している場合は、exportさせて環境変数を設定したりとかです。)

さいごに

ちなみにCustom Search APIは無料枠であれば1日100クエリまでとなっています。
なかなか少ないですね…。

リクエストがどんな場合でカウントされてしまうのかは下記の記事で軽く書かれていましたので、掲載しておきます。

Google Custom Search API を使ってみる

Enjoy Hubot Life !!

iOS9から導入されたATSとは?そして回避する方法は?

(2016/06/18 追記) 以下の記事によると、2017年1月1日から全てのiOSアプリにATSに対応しないといけなくなるそうです。
その場合はこの記事にあるような回避策は有効じゃない可能性があります。ご承知ください。 japan.cnet.com

(以下本編)

※ この記事は2015年10月3日の情報を元に公開しています。

概要

Xcode6を使ってた時代はWebViewがちゃんと動いていたのですが、最近Xcode7にアップデートして動かしてみると、ページが表示されなくなって焦ったわけで。。
そこで調べてみるとATSという機能によって動かないことがわかったので、その知見を共有しておきます。

ATSとは

ATSとは「App Transport Security」の略で、ざっくり言うと「接続先URLがHTTPSでかつセキュアでないサーバー証明書を使ってないなら接続しねーぞこら。」という機能です。

OSX, iOS アプリケーションが NSURLConnection, CFURL, NSURLSession を利用してサーバに接続する際 現時点で最善に近いセキュアな接続を達成するために導入されたものとのこと。

開発者はATS導入を受けて、サーバー証明書Appleが要求する暗号化スイートに変更するか、ATS機能側の例外設定を適切にする必要があります。
ATSはiOS9.0以降、MacOSX10.11以降で使用可能となります(というか勝手に機能しだします。)

ATSのデフォルト動作

NSURLConnection, CFURL, NSURLSessionを利用してサーバーと接続する場合はATSが動作し、下記要求を満たさない場合は接続できなくなります。(CFNetwork SSLHandshake failed (-9824)なんてエラーが表示される)

These are the App Transport Security requirements:

  • The server must support at least Transport Layer Security (TLS) protocol version 1.2.
  • Connection ciphers are limited to those that provide forward secrecy (see the list of ciphers below.)
  • Certificates must be signed using a SHA256 or greater signature hash algorithm, with either a 2048-bit or greater RSA key or a 256-bit or greater Elliptic-Curve (ECC) key.

Invalid certificates result in a hard failure and no connection.

日本語でおk

  • サーバは TLS 1.2 をサポートしていなければならない
  • 利用できる暗号化スイート(Cipher Suite)は、Forward Secrecy を提供できる(下記リスト)ものに限られる
  • 利用されるサーバ証明書はSHA256以上のハッシュアルゴリズムによって署名されており、2048ビット以上のRSA 鍵、もしくは256ビット以上のECC鍵が使われている必要がある
    検証できない証明書はエラーとなり接続ができない

なお利用できる暗号化スイート(Cipher Suite)のリストは以下の通り。

  • TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
  • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
  • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
  • TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
  • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  • TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA

Cipher suiteを調べる

エラーCipher suiteを確認するために接続先のWebサーバーで

$ openssl ciphers -v

と叩いても確認はできるらしいですが、今回はQUALYS SSL LabsのSSL Testを使って確認してみました(使い方はドメイン貼り付けてスタートさせるだけなので割愛)。

実行させてみると以下の様なリストがあるので、AppleのデフォルトのCipher suiteと比較します。

f:id:shirakiya:20151004014455p:plain

結果を見てみると、僕の場合はどうも鍵交換アルゴリズム(kx)がDHEが使われていることが問題のようです(ATSのデフォルト設定ではECDHEのみ許可されている)。

回避方法

もちろん証明書は現時点で最もセキュリティが高い暗号化スイートを利用しているのにこしたことが無いが、どうしてもATSを有効にするのは厳しい状況もあるはず(例えば自社で証明書を管理していないとかの政治的な事情とか)。
公式ドキュメントにも書かれている通り、ATS機能についての例外設定が可能です。

例外設定はinfo.plistに書きます(Xcode使ってるならポチポチで可能)。
設定構造はこんな感じです。

  • NSAppTransportSecurity (Dictionary)
    • NSAllowsArbitraryLoads (Boolean)
    • NSExceptionDomains (Dictionary)
      • <domain-name-for-exception-as-string> (Dictionary)
        • NSExceptionMinimumTLSVersion (String)
        • NSExceptionRequiresForwardSecrecy (Boolc)
        • NSExceptionAllowsInsecureHTTPLoads (Boolean)
        • NSRequiresCertificateTransparency (Boolean)
        • NSIncludesSubdomains (Boolean)
        • NSThirdPartyExceptionMinimumTLSVersion (String)
        • NSThirdPartyExceptionRequiresForwardSecrecy (Boolean)
        • NSThirdPartyExceptionAllowsInsecureHTTPLoads (Boolean)

NSAppTransportSecurity

トップレベルのDictionary。この配下に設定を加えていく

NSAllowsArbitraryLoads

こいつをYESすると、ATS機能が無効となる。つまりデフォルトはNO

NSExceptionDomains

特定のドメインに対する個別の設定を行うDictionary

<domain-name-for-exception-as-string>

特定のドメインを記載するDictionaly。keyに「apple.com」のようにドメインを設定する

NSExceptionMinimumTLSVersion

接続を許可するTLSのミニマムのバージョンを記載する。使用できる文字列は

  • TLSv1.0
  • TLSv1.1
  • TLSv1.2(デフォルトはこれ)

NSExceptionRequiresForwardSecrecy

許可する鍵交換アルゴリズムの設定。デフォルトはYES。
YESの場合は上記のCipher Suiteのリストに限られるが、Noにすると以下のリストも許可するようになる。

NSExceptionAllowsInsecureHTTPLoads

セキュアでないHTTPS接続を許可するかどうか。
NOにすると許可せず、YESにすると許可する。(デフォルトはNO)

  • セキュアでないHTTPS接続
    • no certificate
    • with an error for a self-signed
    • expired
    • hostname mismatch certificate.

NSIncludesSubdomains

指定したトップレベルドメインに対する全てのサブドメインにおいて、設定を適用されるかどうか。
NOにすると設定を適用せず、YESにすると設定を適用させる。(デフォルトはNO)

NSThirdPartyExceptionMinimumTLSVersion

アプリ開発者が変更不可能なサービスに対するTLSのミニマムのバージョンを設定する。
Appleがどうやって変更不可能かどうかを判断しているのかは不明でした...)

NSThirdPartyExceptionRequiresForwardSecrecy

アプリ開発者が変更不可能なサービスに対する、許可する鍵交換アルゴリズムを設定する。
(これもNSThirdPartyExceptionMinimumTLSVersion同様不明...)

NSThirdPartyExceptionAllowsInsecureHTTPLoads

アプリ開発者が変更不可能なサービスに対する、セキュアでないHTTPS接続を許可するかどうかを設定する。
(これもNSThirdPartyExceptionMinimumTLSVersion同様不明...)

今回の場合は鍵交換アルゴリズムが許可されていないものだったので、NSExceptionRequiresForwardSecrecyをNOにすることに。
Xcode上では感じで設定。

f:id:shirakiya:20151003234647p:plain

内部ではこんな感じ。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <false/>
        <key>NSExceptionDomains</key>
        <dict>
            <key>example.com</key>
            <dict>
                <key>NSExceptionMinimumTLSVersion</key>
                <string>TLSv1.2</string>
                <key>NSExceptionRequiresForwardSecrecy</key>
                <false/>
                <key>NSExceptionAllowsInsecureHTTPLoads</key>
                <false/>
                <key>NSIncludesSubdomains</key>
                <false/>
            </dict>
        </dict>
    </dict>
    ...
</dict>
</plist>

そして再ビルドするとページが表示されました!!
ひとまず助かったという感じですが、理想はサーバー証明書Appleが要求する暗号化スイートを使ったものにすることなので油断はしてはいけないですねー。

参考資料

AWS SDK for PHP を使ってみる

環境要件

  • PHP5.3.3 以上のバージョンであること
  • cURLJSONXML・OpenSSL・zlib の拡張モジュールと入れてコンパイルしていること
    • $ php -mでどのモジュールが入っているか確認可能
  • cURLモジュールは 7.16.2 以上のバージョンであること

(参考)Requirements — AWS SDK for PHP 2.8.17 documentation

インストール方法

3種類の方法がある。

  1. Composer を利用してインストールする
  2. Pharをインストールする
  3. Zipファイルをダウンロードして使う

AWS SDK for PHPライブラリのバージョン管理ができて依存関係が簡単に解決できるComposerがAWS的にも推奨されているのでComposerを使った方法を紹介する。

(参考)Installation — AWS SDK for PHP 2.8.17 documentation

ComposerでAWS SDK for PHPを使う

公式ドキュメントと重複する部分もあるが、全体の流れを追うということで紹介。 ※ composerはcpanやBundlerなどと違ってバージョンが5.3.2以上のPHPが入っていれば使える。 (実際は細かい拡張モジュールが必要らしいが、公式ドキュメントにすら詳細が書かれていないので、エラーが表示されたら残念ぐらいの気持ちで良さそう)

1. composer.pharをダウンロードする

$ curl -sS https://getcomposer.org/installer | php

これでcomposer.pharがカレントディレクトリにダウンロードされる。

2. composer.jsonを作成する

composer.jsonは1のコマンドを実行させても作成されるわけではない。 新たに自分で作成し、インストールする AWS SDK for PHP の情報とそのバージョンを書く。

{
    "require": {
        "aws/aws-sdk-php": "2.*"
    }
}

3. インストール開始

$ php composer.phar install

これで AWS SDK for PHP のライブラリ本体がvender/以下にインストールされる。

4. 実際にSDKを利用するスクリプトでロードして利用する

<?php

require(dirname(__FILE__).'/../vendor/autoload.php');
// ↑公式とは書き方を変えている。requireはスクリプトを実行しているディレクトリが基準になるので、安易に相対パスを使うと痛い目に遭うことが多いため。

5. credential情報を取得するためにIAMユーザーを作成する

あんまりこの記事で伝えたいところでもないので、割愛。 参考となる公式記事はこちら

  • ここでアクセスキーとシークレットアクセスキーを取得しておく
  • 作成したユーザーの管理ポリシーは(状況のよりけりなので選定する必要があるが)以下のようなポリシーをアタッチしておく
    • AmazonRoute53DomainsFullAccess
    • AmazonRoute53FullAccess

6. credential情報を使う

SDKクライアントオブジェクトを生成するために、アクセスが許可されているユーザーなのか(credential情報)やどこリージョンにリクエストを送るのか等の情報を付与する必要がある。 その情報の与え方が複数あるようで。

1. ~/.aws/credentialsファイルに情報を記載するパターン

~/.aws/credentials

[project1]  // ①
aws_access_key_id = YOUR_AWS_ACCESS_KEY_ID
aws_secret_access_key = YOUR_AWS_SECRET_ACCESS_KEY

hogehoge.php

<?php

require(dirname(__FILE__).'/../vendor/autoload.php');

use Aws\Route53\Route53Client;

$client = Route53Client::factory(array(
    'profile' => 'project1',  // ①
));

2. 設定ファイルを作成して、コンストラクタに食わせるパターン

config.php

<?php

return array(
    'includes' => array('_aws'),
    'services' => array(
        'default_settings' => array(
            'params' => array(
                'credentials' => array(
                    'key'    => 'YOUR_AWS_ACCESS_KEY_ID',
                    'secret' => 'YOUR_AWS_SECRET_ACCESS_KEY',
                ),
                'region' => 'us-east-1'  // Route 53 の場合。他はこちら参照→http://docs.aws.amazon.com/ja_jp/general/latest/gr/rande.html
            )
        )
    )
);

hogehoge.php

<?php

require(dirname(__FILE__).'/../vendor/autoload.php');

use Aws\Route53\Route53Client;

$client = Route53Client::factory('path/to/config.php');

3. credentialオブジェクトを生成してコンストラクタに食わせるパターン

hogehoge.php

<?php

require(dirname(__FILE__).'/../vendor/autoload.php');

use use Aws\Route53\Route53Client;
use Aws\Common\Credentials\Credentials;

$credentials = new Credentials('YOUR_ACCESS_KEY', 'YOUR_SECRET_KEY');

$client = Route53Client::factory(array(
    'credentials' => $credentials
));

4. 生で書くパターン

hogehoge.php

<?php

require(dirname(__FILE__).'/../vendor/autoload.php');

use use Aws\Route53\Route53Client;

$client = Route53Client::factory(array(
    'credentials' => array(
        'key'    => 'YOUR_AWS_ACCESS_KEY_ID',
        'secret' => 'YOUR_AWS_SECRET_ACCESS_KEY',
    )
));

7. あとはAPIメソッドドキュメント眺めてAPI叩いて楽しむ

ドキュメントを読むと色々できて夢広がる感じの時が、一番楽しいポイントですよねー 色々できそうですが、ホストゾーン内のリソースレコードをCRUD操作するコードを書いたので参考までにベロっと貼っておきます。

(参考)Class Aws\Route53\Route53Client | AWS SDK for PHP

hogehoge.php

<?php

require(dirname(__FILE__).'/../vendor/autoload.php');

use Aws\Route53\Route53Client;

$client    = Route53Client::factory(array(
    'profile' => 'someproject',
));

/* レコード一覧取得 */
try {
    // http://docs.aws.amazon.com/aws-sdk-php/v2/api/class-Aws.Route53.Route53Client.html#_listResourceRecordSets
    $res = $client->listResourceRecordSets(array(
        'HostedZoneId' => 'Host Zone ID をコンソールから調べて記述',
    ));
    foreach ($res['ResourceRecordSets'] as $record) {
        echo $record['Name']."\n";
    }
} catch (Exception $e) {
    // 例外クラスは大量にあるので、エラー処理難しそう。。
    // http://docs.aws.amazon.com/aws-sdk-php/v2/api/namespace-Aws.Route53.Exception.html
    echo $e->getMessage();
}

/* レコード追加 */
try {
    // http://docs.aws.amazon.com/aws-sdk-php/v2/api/class-Aws.Route53.Route53Client.html#_changeResourceRecordSets
    $client->changeResourceRecordSets(array(
        'HostedZoneId' => 'Host Zone ID をコンソールから調べて記述',
        'ChangeBatch'  => array(
            'Comment' => 'from my PHP script',
            'Changes' => array(
                array(
                    'Action' => 'CREATE',  // string: CREATE | DELETE | UPSERT
                    'ResourceRecordSet' => array(
                        'Name' => 'www.hogehoge.com',
                        'Type' => 'A',
                        'TTL'  => 86400,
                        'ResourceRecords' => array(
                            array(
                                'Value' => 'some IP address',
                            ),
                        ),
                    ),
                ),
            ),
        ),
    ));
} catch (Exception $e) {
    echo $e->getMessage();
}

/* レコード変更 */
try {
    $client->changeResourceRecordSets(array(
        'HostedZoneId' => 'Host Zone ID をコンソールから調べて記述',
        'ChangeBatch'  => array(
            'Comment' => 'from my PHP script',
            'Changes' => array(
                array(
                    'Action' => 'UPSERT',  // string: CREATE | DELETE | UPSERT
                    'ResourceRecordSet' => array(
                        'Name' => 'www.shirakiya.com',
                        'Type' => 'A', // UPSERT の場合でも必要
                        'TTL'  => 7200,
                        'ResourceRecords' => array(
                            array(
                                'Value' => 'some IP address',  // UPSERT の場合でも必要
                            ),
                        ),
                    ),
                ),
            ),
        ),
    ));
} catch (Exception $e) {
    echo $e->getMessage();
}

/* レコード削除 */
try {
    $client->changeResourceRecordSets(array(
        'HostedZoneId' => 'Host Zone ID をコンソールから調べて記述',
        'ChangeBatch'  => array(
            'Comment' => 'from my PHP script',
            'Changes' => array(
                array(
                    'Action' => 'DELETE',  // string: CREATE | DELETE | UPSERT
                    'ResourceRecordSet' => array(
                        'Name' => 'www.shirakiya.com',
                        'Type' => 'A', // DELETE の場合でも必要
                        'TTL'  => 7200,
                        'ResourceRecords' => array(
                            array(
                                'Value' => 'some IP address',  // DELETE の場合でも必要
                            ),
                        ),
                    ),
                ),
            ),
        ),
    ));
} catch (Exception $e) {
    echo $e->getMessage();
}

これでいい感じにRoute 53のレコード管理ができると思います。 今回はRoute 53だけ紹介しましたが、もちろん他のAWSサービスでも使えますので!

(参考)Namespace Aws | AWS SDK for PHP