仕組みから理解するTwilio #2 - 通信フロー・データ構造

  • 4
    Like
  • 0
    Comment

この記事は下記記事の続きです。一連の記事で独自に定義している用語等がありますので、先にこちらを読むことを推奨いたします。
仕組みから理解するTwilio #1 - はじめに

ここでは、Twilioを利用する際の通信フローや、授受されるデータ構造について説明します。

  • 1. はじめに
    • 1. Twilio利用時の基本構成・用語の定義
    • 2. 今回検証した環境
  • 2. 通信フロー・データ構造 ←いまココ
    • 1. 認証・CapabilityToken授受
    • 2. 外部電話からTwilioクライアントへのCall(IncomingCall)
    • 3. Twilioクライアントから外部電話へのCall(OutgoingCall)
  • 3-1. AWS API Gateway+Lambda実装Walkthrough(前編)
    • 1. API Serverの処理の実装(Python on AWS Lambda)
    • 2. API Gatewayの設定
  • 3-2. AWS API Gateway+Lambda実装Walkthrough(後編)
    • 3. Twilio Clientの実装とデプロイ
    • 4. 動作確認!

今回実現するTwilio Clientは、まずは通常の電話と同じことができることを目指しました。
具体的な仕様は以下となります。

  • 出来ること
    • Twilio Clientから、任意の番号を指定して一般の電話にかけることができる(アウトバウンド通話)
    • 一般の電話からTwilio Clientに電話をかけることができる(インバウンド通話)
  • Twilio Clientの仕様
    • 自身を識別する値として、Twilio電話番号のみ保持する。自身のClient Nameは保持しない。
    • HTML/JavaScriptで実装され、S3上に配置される。

電話番号等のパラメータは下記であると仮定します。

パラメータ 説明・備考
Twilio電話番号 050-3123-4567 Twilio Clientに割り当てた電話番号。Twilioコンソールから事前に購入しておいて下さい。
Client Name DeviceId_0001 Twilio Server内で当該Twilio Clientを識別・制御するための名前。
外部の電話 090-5987-6543 Twilio外の電話。ご自身の携帯電話等を利用してください。

認証・CapabilityToken授受

処理フロー

まず、下記の図をご覧ください。
TwilioDiagrams_Fig2-1.png

処理のフローは下記のようになります。

  • 1-1. Capability Tokenの取得リクエスト
    • Twilio ClientはAPI Serverに対し、Capability Tokenを取得するためのリクエストを行います。
    • API ServerはTwilio Clientから送信された情報をもとに、必要に応じて認証・認可を行います。
  • 1-2. Capability Tokenの返却
    • API Serverは、Capability Tokenを生成し、Twilio Clientに返却します。
    • この際、当該Capability Tokenを利用して何ができるか(電話をかけることができる/受けることができる)、Twilio ClientのClient Name、有効期限といった情報が付与されます。
  • 1-3.Twilio Clientのセットアップ

1-1及び1-2の間の通信方式の実装は、完全にユーザに任されています。通信にHTTP/HTTPS及びそれ以外のプロトコルを使うことも可能です。

データ構造も完全に任意です。
Twilio Clientから送信する情報には、Twilio Clientを識別するための情報として、ユーザー名やパスワードやその他情報を利用することができます。任意の認証・認可を実現することも可能です。
API Serverから返却される情報としてCapability Tokenは必須ですが、他にも任意の情報を返却することが可能です。

今回の検証では、リクエスト・レスポンスともJSONで実装しました。具体的なデータ構造は下記のとおりです。

リクエストはシンプルに、Twilio電話番号のみとしました。指定されたTwilio電話番号が取得済みのものであれば認証成功とします。
{"twilioPhoneNumber": "+81-50-1234-9876"}

レスポンスは、Capability Token及び処理の成否としました。
{"capabilityToken": capabilityToken, "success": True}

1-3の通信については未検証です。詳細は不明です。JavaScriptクライアントの場合はWebSocketを利用し、CapabilityToken等の情報をTwilio Serverに送信しているようです。
Twilioが提供する各種SDKから初期セットアップが行われるため、通常はユーザーが意識する必要はありません。
# ただし、トラブルシュート時にはこの情報が必要になるケースはあり得ると思いますが。

Capability Tokenの作成・データ構造

API Serverでは、TwilioのHelperライブラリを利用してCapability Tokenを生成します。

Python2.7での実装をもとに説明します。必要なライブラリ類は既にインストール済みで、利用できるものとします。
https://www.twilio.com/docs/libraries/python#help
また、Twilioアカウントを開設済みでありAccount SID及びAuth Tokenは確認済みであること、TwiML Appを作成しApp SIDを確認済みであることとします。

    from twilio.util import TwilioCapability
    import json

    def generate_capability_token(twilio_phone_number):

        # 必要なパラメータを設定します。
        twilio_account_sid = "{{twilio_accound_sid}}"  # TwilioアカウントのSIDを指定します。
        twilio_auth_token = "{{twilio_account_auth_token}}" # Twilioアカウントに紐付くAuth Tokenを指定します。
        twilio_app_sid = "{{twilio_app_sid}}"  # 事前に作成したApp Sidを指定します。
        expiration_time_for_capability_token = 3600  # Capability Tokenの有効期限を指定します。

        # Capability Tokenを生成し、必要な権限を付与します。
        capability = TwilioCapability(twilio_account_sid, twilio_auth_token)
        capability.allow_client_incoming(get_client_name_by_phone_number(twilio_phone_number)) # 指定されたTwilio電話番号をもとに、Twilio Client Nameを取得します。get_client_name_by_phone_numberは自分で個別に実装した関数です。
        capability.allow_client_outgoing(twilio_app_sid)
        capabilityToken = capability.generate(expiration_time_for_capability_token)

        # 既定の構造に変換し、返却します。
        res = {"capabilityToken": capabilityToken, "success": True}
        return json.dumps(res)

基本的には下記ドキュメントに従った実装となります。
Generate Capability Tokens
http://twilio-python.readthedocs.io/en/latest/usage/token-generation.html

TwilioアカウントSIDとAuth TokenをもとにTwilioCapabilityを生成した後、allow_client_incoming及びallow_client_outgoingメソッドで権限を付与していきます。
最後にTwilioCapabilityのgenerateメソッドを実行することで、Capability Tokenを生成することができます。引数には有効期限を秒単位で指定することができます。

Capability Tokenの詳細は下記ドキュメントにあります。

Twilio Client: Capability Tokens
https://www.twilio.com/docs/api/client/capability-tokens

Capbility TokenはJWT(Json Web Token)形式です。生成の際にはTwilio Serverとの通信は必要としません。
未検証ですが、恐らく必要な情報をJSON化し、Auth Tokenで電子署名を生成して付与したのち、Base64デコードしているものと推測されます。

ここで重要なポイントは下記のとおりです。
- Twilio Clientから電話をかけるための権限と、Twilio Clientで電話を受けるための権限が違うこと
- Twilio Clientから電話をかけるための権限は、TwiML appのApp sidで指定すること
- Twilio Clientで電話を受けるための権限は、Client Nameで指定すること

次から、Twilio Clientと他の電話との通話の仕組みを見ていきます。

外部電話からTwilioクライアントへのCall(IncomingCall)

処理フロー

まず、下記の図をご覧ください。
TwilioDiagrams_Fig2-2.png

また、Twilioアカウントをセットアップし電話番号を取得した際に設定したURLを思い出してください。
「A CALL COMES IN」で指定したURLは、電話を受ける際に利用されます。
2016-10-16_03h52_20.png

処理のフローは下記のようになります。TwiMLの取得は受信先のTwilio電話番号に設定されたURLが利用されます。

  • 2-1. 実際の電話からTwilio Clientへ電話をかける
    • 実際の電話から、Twilio Clientに紐付けられたTwilio電話番号に対し、電話をかけます。
    • 当然ですが、電話をかける際にTwilio ClientのClient Nameを指定することはできません。
  • 2-2. Twilio ServerからAPI ServerへのTwiML取得リクエスト
    • Twilio Serverは、call対象となった電話番号の「A CALL COMES IN」に指定された通りにHTTPリクエストを発行します。
    • 渡されるパラメータとして重要なものは、発信元の電話番号と受信先のTwilio電話番号です。
  • 2-3. API ServerからTwilio ServerへTwiMLを返却する
    • API Serverは、パラメータとして渡されたTwilio電話番号をもとに、これに紐付くClient Nameを取得する必要があります。
    • Client Nameが取得でき次第、TwiMLを生成して返却します。
  • 2-4. Twilio ServerはTwiMLに書かれた内容を実行する。
    • TwiMLには、指定されたClient Nameに電話をつなげるよう指示があります。
    • 発信元の電話をTwilio Clientにつなぎます。

API Serverから返却するべきTwiML

項番2-3で、API Serverから返却されるTwiMLは下記のようになります。
このTwiMLは、指定されたTwilio Clientに電話をかけることを意味しています。

<?xml version="1.0\" encoding="UTF-8"?>
<Response>
  <Dial timeout="60">
    <Client>DeviceId_0001</Client>
  </Dial>
</Response>

<Client/>エレメントには、Twilio電話番号そのものを指定することができません。
Twilio電話番号に紐付くTwilio Clientの、Client Nameを入れる必要があります。
# ここでは仮に「DeviceId_0001」としています。

従ってAPI Serverは、項番2-3において、受信先のTwilio電話番号情報をもとに、Client Nameを取得する必要があります。予めDBなどに紐付け情報を保存しておき、TwiML生成時に取得するのが良いでしょう。

なお、TwiMLでは様々な制御を行うことが可能です。詳細は公式ドキュメントを参照してください。

https://www.twilio.com/docs/api/twiml/dial
https://www.twilio.com/docs/api/twiml/client

Twilio ServerからのHTTP POSTリクエスト

Twilio ServerからAPI Serverへのリクエストは少し特殊です。
また、公式ドキュメントには詳しい情報が見当たりませんでした。以下はパケットキャプチャした情報をもとに説明します。

2016-10-16_04h19_49.png

上図はTwilio ServerからのHTTP POSTリクエストをパケットキャプチャしたものです。
ポイントは下記のとおりです。

  • Content-Type:はapplication/x-www-form-urlencodedであること
  • パラメータは、HTTPリクエストBodyに「&」で連結されたKey Valueペアで、1行で指定されること(GETパラメータのような形式)
  • HTTPリクエストBody内の各パラメータはURLエンコードされていること

各パラメータの詳細を見ていきましょう。
発信元の電話番号を「090-5987-6543」、受信先のTwilio電話番号を「050-3123-4567」としています。

パラメータ 説明(推測ベース)
AccountSid=AC3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TwilioアカウントのSID。
ApiVersion=2010-04-01 TwilioのAPIのバージョンか?
Called=%2B815031234567 受信先のTwilio電話番号。E.164形式。Toと同一の値が入る模様。
CalledCity=
CalledCountry=JP
CalledState=
CalledVia=05031234567 不明。Twilio内で電話転送された場合に利用されるものか?
CalledZip=
Caller=%2B819059876543 発信元の電話の電話番号。Fromと同一の値が入る模様。
CallerCity=
CallerCountry=JP
CallerState=
CallerZip=
CallSid=CA86nnnnnnnnnnnnnnnnnnnnnnnnnnnnnn 当該通話に付与されるUniqueなIDか?
CallStatus=ringing
Direction=inbound 通話の方向か?
ForwardedFrom=05031234567 不明。Twilio内で電話転送された場合に利用されるものか?
From=%2B819059876543 発信元の電話の電話番号。E.164形式。Callerと同一の値が入る模様。
FromCity=
FromCountry=JP
FromState=
FromZip=
To=%2B815031234567 受信先のTwilio電話番号。Calledと同一の値が入る模様。
ToCity=
ToCountry=JP
ToState=
ToZip=

ここで重要なパラメータは、Caller/From/Called/Toの4つと考えられます。
API Server内で、この情報をもとにTwiMLを生成します。

PythonのQuick Startアプリは、発信元の情報としてCallerを、受信先の情報としてToを利用しているようです。そのためIncoming Call用TwiML返却APIの実装も、このパラメータを利用するようにします。

https://github.com/TwilioDevEd/client-quickstart-python

Twilioクライアントから外部電話へのCall(OutgoingCall)

処理フロー

まず、下記の図をご覧ください。
TwilioDiagrams_Fig2-3.png

また、Twilioアカウントをセットアップし、TwiML Appを作成した際に設定したURLを思い出してください。
2016-10-16_05h14_56.png

処理のフローは下記のようになります。TwiMLの取得はTwiML Appに設定されたURLが利用されます。

  • 3-1. Twilio Clientから発信リクエストを行う
  • 3-2. Twilio ServerからAPI ServerへのTwiML取得リクエスト
    • Twilio Serverは、CapabilityTokenに紐付けられたTwiML Appの、「REQUEST URL」に指定された通りにHTTPリクエストを発行します。
    • 渡されるパラメータとして重要なものは、Twilio ClientのClient Name、発信先電話番号、Twilio Clientから渡されたパラメータです。
  • 3-3. API ServerからTwilio ServerへTwiMLを返却する。
    • API Serverは、パラメータとして渡された情報をもとに、発信元となるTwilio電話番号、発信先の電話番号を取得する必要があります。
    • 必要な情報を取得でき次第、TwiMLを生成して返却します。
  • 3-4. Twilio ServerはTwiMLに書かれた内容を実行する。
    • Twilio Clientからの電話を、実際の電話につなぎます。

API Serverから返却するべきTwiML

項番3-3で、API Serverから返却されるTwiMLは下記のようになります。
発信元のTwilio電話番号を「050-3123-4567」、発信先の電話番号を「090-5987-6543」としています。

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Dial timeout="60" callerId="+81-50-3123-4567">
    <Number>+81-90-5987-6543</Number>
  </Dial>
</Response>

<Number/>エレメントには、発信先の電話番号を指定します。
<Dial/>エレメントのcallerId属性には、必ずTwilio電話番号を指定する必要があります。
https://jp.twilio.com/docs/api/twiml/dial#examples-3

Twilio ServerからのHTTP POSTリクエスト

公式ドキュメントには詳しい情報が見当たりませんでした。以下はパケットキャプチャした情報をもとに説明します。
2016-10-16_05h30_26.png

各パラメータの詳細を見ていきましょう。
なお、Twilio Clientからconnectを実行した際、下記のパラメータを付与しています。

  • callerPhoneNumber: 発信元となる、自分自身に紐付けられた電話番号。
  • callOutgoingPhoneNumber: 発信先となる、実際の電話番号。
パラメータ 説明(推測ベース)
AccountSid=AC3exxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TwilioアカウントのSID。
ApiVersion=2010-04-01 TwilioのAPIのバージョンか?
ApplicationSid=AP75zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz CapabilityTokenに紐付けられたTwiML AppのSID
Called=
Caller=client%3ADeviceId_0001 発信元のTwilio ClientのClient Name。
CallSid=CA06nnnnnnnnnnnnnnnnnnnnnnnnnnnnnn 当該通話に付与されるUniqueなIDか?
CallStatus=ringing
Direction=inbound
From=client%3ADeviceId_0001 発信元のTwilio ClientのClient Name。Callerと同一の値が入る模様。
To=
callerPhoneNumber=%2B81-50-3123-4567 Twilio Clientで指定したカスタムパラメータ
callOutgoingPhoneNumber=%2B81-90-5987-6543 Twilio Clientで指定したカスタムパラメータ

デフォルトで、Caller及びFromにClient Nameが設定されます。
必要に応じて、Twilio Clientの実装の中で必要なパラメータを指定することが可能です。
前述したとおり、Twilio ClientがCapability Tokenを取得する際の通信において、API Serverから任意の値を返すことができますので、ここでconnect時に必要なパラメータを渡しておくこともできます。

最後に

通信フロー・データ構造は以上となります。
次からはいよいよ実装に入っていきます。
AWS API Gateway+Lambda実装Walkthrough(前編)