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

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


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

- 1. [はじめに](http://qiita.com/tmiki/items/ac28b6f6ad745f3e1b2e)
    - 1. Twilio利用時の基本構成・用語の定義
    - 2. 今回検証した環境
- 2. **通信フロー・データ構造　←いまココ**
    - **1. 認証・CapabilityToken授受**
    - **2. 外部電話からTwilioクライアントへのCall(IncomingCall)**
    - **3. Twilioクライアントから外部電話へのCall(OutgoingCall)**
- 3-1. [AWS API Gateway+Lambda実装Walkthrough(前編)](http://qiita.com/tmiki/items/7f55a81c46dd07b2e4fa)
    - 1. API Serverの処理の実装(Python on AWS Lambda)
    - 2. API Gatewayの設定
- 3-2. [AWS API Gateway+Lambda実装Walkthrough(後編)](http://qiita.com/tmiki/items/31671548bd755606625a)
    - 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](https://qiita-image-store.s3.amazonaws.com/0/137777/c5757fe8-57c2-d4c2-3e46-2e054ddee844.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のセットアップ
    - Twilio ClientはCapability Tokenの受け取りに成功した後、必要なセットアップ処理を行います。
    - JavaScriptであれば、Twilio.Device.setupを実行します。(https://www.twilio.com/docs/api/client/device#method-reference)
    - Androidであれば、Twilio#createDeviceを実行し、Deviceオブジェクトを生成します。(https://jp.twilio.com/docs/api/client/android/Twilio#createDevice_String__DeviceListener_)
    - セットアップ処理の実行の際、Capability TokenがTwilio Serverに渡されるようです。Twilio ServerにてCapability Tokenを検証し、その成否を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を確認済みであることとします。

```python
    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](https://qiita-image-store.s3.amazonaws.com/0/137777/68a9481c-01a4-629f-d830-cbf4fd9d2df9.png)

また、Twilioアカウントをセットアップし電話番号を取得した際に設定したURLを思い出してください。
「A CALL COMES IN」で指定したURLは、電話を受ける際に利用されます。
![2016-10-16_03h52_20.png](https://qiita-image-store.s3.amazonaws.com/0/137777/eeeadce0-a1a1-a7c8-50e8-fc6c4644daa3.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
<?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](https://qiita-image-store.s3.amazonaws.com/0/137777/f7de3f0d-ce90-0d3c-3027-6ba871e0519c.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](https://qiita-image-store.s3.amazonaws.com/0/137777/73e8fd2b-1c3c-f68f-d62a-b54eaf49be93.png)

また、Twilioアカウントをセットアップし、TwiML Appを作成した際に設定したURLを思い出してください。
![2016-10-16_05h14_56.png](https://qiita-image-store.s3.amazonaws.com/0/137777/2ec117d0-fda4-a884-1dfa-e81dba09ebe3.png)


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

- 3-1. Twilio Clientから発信リクエストを行う
    - Twilio Clientから、発信するためのリクエストを行います。
    - このとき、Twilio Clientから任意のパラメータを付与することができます。
    - Androidであれば、セットアップ時に生成されたDeviceオブジェクトのconnectメソッドを実行します。(https://www.twilio.com/docs/api/client/android/device#connect_java_util_Map__ConnectionListener_)
    - JavaScriptであれば、Twilio.Device.connectメソッドを実行します。(https://www.twilio.com/docs/api/client/device#connect)
- 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
<?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](https://qiita-image-store.s3.amazonaws.com/0/137777/2d171809-8c06-2911-4025-df527d52381e.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(前編)](http://qiita.com/tmiki/items/7f55a81c46dd07b2e4fa)