[PYTHON] Understanding from the mechanism Twilio # 3-1 --AWS API Gateway + Lambda implementation Walkthrough (Part 1)

This article is a continuation of the following article. We recommend that you read this first. Understanding how it works Twilio # 1-Introduction Understanding from the mechanism Twilio # 2-Communication flow data structure

Make a call from Twilio Client / We will implement an application to make a call to Twilio Client. Since the amount will be large, we will divide it into two parts. The following content is intended for those who have some experience developing on AWS, especially those who have developed and published APIs by combining AWS Lambda and API Gateway. If you have no experience, we recommend that you combine the two to create an API and publish it to get a feel for it.

―― 1. Introduction ―― 1. Definition of basic structure and terms when using Twilio ―― 2. Environment verified this time ―― 2. Communication flow data structure ―― 1. Authentication / Capability Token transfer ―― 2. Call from an external phone to your Twilio client (Incoming Call) ―― 3. Call from Twilio client to external phone (OutgoingCall) --3-1. ** AWS API Gateway + Lambda implementation Walkthrough (Part 1) ** ** ← Now here ** -** 1. Implementation of API Server processing (Python on AWS Lambda) ** -** 2. API Gateway settings ** --3-2. AWS API Gateway + Lambda Implementation Walkthrough (Part 2) --3. Implementing and deploying Twilio Client ―― 4. Operation check!

Walkthrough overview

Let's reconfirm the specifications of the Twilio Client that will be realized this time.

――What you can do --From Twilio Client, you can specify any number to make a general call (outbound call) --You can make a call to Twilio Client from a regular phone (inbound call) --Twilio Client specifications --Hold only your Twilio phone number as a value to identify yourself. It does not retain its own Client Name. --Implemented in HTML / JavaScript and placed on S3.

It is assumed that the parameters such as the telephone number are as follows.

Parameters value Explanation / Remarks
Twilio phone number 050-3123-4567 The phone number you assigned to your Twilio Client. Please purchase in advance from the Twilio console.
Client Name DeviceId_0001 A name for identifying and controlling the Twilio Client within Twilio Server.
External phone 090-5987-6543 Phones outside of Twilio. Please use your own mobile phone.

Let's reconfirm the configuration realized this time. TwilioDiagrams_Fig1-1.png

Implement each API using AWS Lambda and build it so that it can be used from API Gateway. API name, Lambda function name, and API Gateway Resource name correspond as follows.

API name AWS Lambda function name Resource name
Capability Token acquisition API TwilioRetrieveCapabilityToken /twilio/retrieve_capability_token
TwiML Return API for Incoming Call TwilioRetieveTwimlCallIncoming /twilio/retrieve_twiml/call_incoming
TwiML Return API for Outgoing Call TwilioRetieveTwimlCallOutgoing /twilio/retrieve_twiml/call_outgoing

Each API will be deployed to the prod stage at API Gateway. Therefore, API Server will eventually provide the following URL.

The Twilio Client implemented in JavaScript will have the following screen. From this screen, you can make a call to a regular phone or receive a regular call. twilio_client_webbrowser.png

Set up Twilio [Open / Prepare Twilio Account](http://qiita.com/tmiki/items/ac28b6f6ad745f3e1b2e#twilio%E3%82%A2%E3%82%AB%E3%82%A6%E3% 83% B3% E3% 83% 88% E3% 81% AE% E9% 96% 8B% E8% A8% AD% E6% BA% 96% E5% 82% 99).

The specific work related to AWS is as follows. It is assumed that you have already opened an AWS account.

―― 1. Implementation of API Server processing (Python on AWS Lambda) --1-1. Implement and deploy CapabilityToken acquisition API as Lambda function --1-2. Implement and deploy the TwiML return API for Incoming Call as a Lambda function. --1-3. Implement and deploy TwiML return API for Outgoing Call as Lambda function --2 API Gateway settings --2-1. Create an API from the Amazon API Gateway menu in the AWS Management Console --2-2. Define Resource for each API --2-3. Set CORS --2-4. Define Method for CapabilityToken acquisition API (/ twilio / retrieve_capability_token) --2-5. Define Method for TwiML Return API (/ twilio / retrieve_twiml / call_incoming) for Incoming Call --2-6. Define Method for TwiML Return API (/ twilio / retrieve_twiml / call_outgoing) for Outgoing Call ―― 2-7. Prod Create a stage and deploy ―― 2-8. Check the operation of each API --3. Implementing and deploying Twilio Client --3-1. Implement Twilio Client with JavaScirpt --3-2. Create S3 Bucket and enable Static Website Hosting --3-3. Place on S3 Bucket ―― 4. Operation check! --4-1. Access Twilio Client on S3 with a web browser via HTTPS --4-2. Make a call to Twilio Client from a verified phone number ―― 4-3. Call a verified phone number from Twilio Client

Let's look at each one from the following.

1. Implementation of API Server processing (Python on AWS Lambda)

The API created in this verification is implemented in Python on AWS Lambda. If you're new to Lambda, it's a good idea to follow the steps in the documentation below to get a feel for what it's like.

AWS Lambda Developer Guide-Getting Started https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/getting-started.html

As you can see by using it, JSON is the easiest to use as the Input / Output format of the Lambda function. Therefore, the function implemented this time also uses JSON as much as possible.

1-1. Implement CapabilityToken acquisition API as Lambda function

Interface (Input / Output)

First, determine the Input and Output structures of this Lambda function. As defined in Authentication / Capability Token transfer Let's make it the structure of.

Input


{"twilioPhoneNumber": "+81-50-1234-9876"}

The Twilio Client assumes that you have a Twilio phone number associated with you in the source code. This API will only authenticate by checking if the Twilio phone number specified in Input is a registered one.

Output


{"capabilityToken": capabilityToken, "success": true}

If the authentication is successful, this API will generate a Capability Token. It also returns the success or failure of the process.

Implementation

Now let's actually write the Lambda function.

TwilioRetrieveCapabilityToken.py


from __future__ import print_function
from twilio.util import TwilioCapability

print('Loading function')


class UnregisteredPhoneNumberException(Exception):
    def __init__(self,value):
        self.value = value


def get_client_name_by_phone_number (phone_number):
    ''' This function returns a valid clientName.
        If the parameter "phone_number" is not registered, this function raise an "UnregisteredPhoneNumberException".
    '''

    # regularize the input string "phone_number" to match registered phone numbers.
    phone_number_wo_hyphen = phone_number.replace('-','')

    # match and get the correct clientName
    if (phone_number_wo_hyphen == "+8150312345678"):
        clientName= "DeviceId_0001"

    else:
        raise UnregisteredPhoneNumberException('Unregistered phone number "' + phone_number + '"')

    return clientName


def lambda_handler(event, context):
    ''' Creates and responds a Twilio Capability Token.
    This function will be received a json formatted string like below.
    {"twilioPhoneNumber": "+81-50-1234-9876"}

    This function will return a json formatted string like below.
    If this function succeed, the parameter "success" will be set as true.
    {"capabilityToken": capabilityToken, "success": true}
    '''

    # configure parameters belong to Twilio
    twilio_account_sid = "{{twilio_accound_sid}}"
    twilio_auth_token = "{{twilio_account_auth_token}}"
    twilio_app_sid = "{{twilio_app_sid}}"
    expiration_time_for_capability_token = 3600


    # get necessary parameter from "event" and "context"
    twilio_phone_number = event.get("twilioPhoneNumber")


    # Create a Capability token with twilio_account_sid and its twilio_auth_token
    # It enables a Twilio client to receive an incoming call and to make an outgoing call.
    try:
        capability = TwilioCapability(twilio_account_sid, twilio_auth_token)
        capability.allow_client_incoming(get_client_name_by_phone_number(twilio_phone_number))
        capability.allow_client_outgoing(twilio_app_sid)
        capabilityToken = capability.generate(expiration_time_for_capability_token)
        res = {"capabilityToken": capabilityToken, "success": True}

    except UnregisteredPhoneNumberException:
        res = {"capabilityToken": None, "success": False}

    return res

Let's take a look at the Lambda function we created.

First, there are the following items as values that depend on the environment. Please read according to your environment.

--in the lambda_handler function

    # configure parameters belong to Twilio
    twilio_account_sid = "{{twilio_accound_sid}}"
    twilio_auth_token = "{{twilio_account_auth_token}}"
    twilio_app_sid = "{{twilio_app_sid}}"
    expiration_time_for_capability_token = 3600

At the beginning of the function, the Twilio account SID, the Auth Token associated with it, and the App SID created in advance are specified, so change them according to your environment. The expiration date of the Capability Token is set to 3600 seconds here, but please change this as appropriate.

--in the get_client_name_by_phone_number function

    # match and get the correct clientName
    if (phone_number_wo_hyphen == "+815012349876"):
        clientName = "DeviceId_0001"

Specify the Twilio phone number you got when you set up your Twilio account. Change the clientName to your liking. This function currently has a link between the Twilio phone number and the Client Name in the source code, but in the future it is assumed that a DB connection will be made and the link information will be retrieved from the DB.

The processing flow is as follows.

--Get "twilioPhoneNumber" from the Input parameter to the Lambda function. --Create a TwilioCapability object. (* This is not a Capability Token yet.) --Get the Client Name associated with this from "twilioPhoneNumber". (Execute the get_client_name_by_phone_number function) --Allow inbound calls to the TwilioCapability object. For the argument, specify the Client Name obtained above. --Allow outbound calls to the TwilioCapability object. In the argument, specify the SID of the TwiML App created in advance. --Execute the TwilioCapability # generate function to generate the Capability Token. Specify the expiration date in the argument. --Generate the data returned as Output as dictionary type. --Return the generated data. (* If you return the dictionary type as the return value from the Lambda function, it will be automatically converted to JSON and returned.)

Deploy

This Lambda function requires the twilio.util package. Therefore, simply pasting the above source code from the inline editor of the AWS Management Console will not work. You need to create a deployment package and upload it. Follow the steps below to create a deployment package (zip file).

Creating a deployment package (Python) https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html

Log in to a suitable Linux server and execute as follows. Place the source code and all necessary libraries under the current directory, and zip this directory.

#Clone the Twilio Client Quickstart for Python to get information about Twilio library dependencies
#All you need is requirements.Since it is only txt, you can bring this manually
$ git clone https://github.com/TwilioDevEd/client-quickstart-python

#Create a working directory
$ mkdir ./twilio_on_aws
$ cd ./twilio_on_aws

#Create the source code. Copy and paste the above source.
$ vi TwilioRetrieveCapabilityToken.py

#Install related libraries in your working directory
$ pip install -r ../client-quickstart-python/requirements.txt -t ./

#Compress the source code and related libraries into a zip file and create a deployment package
$ zip -r ../TwilioRetrieveCapabilityToken.zip ./

Once you have the above deployment package, upload it from the AWS Management Console. LambdaFunction_TwilioRetrieveCapabilityToken_1.png

I think that there is no problem with Configuration by default as long as the Handler settings are correct. This Lambda function doesn't use any other AWS service, so the IAM Role can be lambda_basic_execution. LambdaFunction_TwilioRetrieveCapabilityToken_2.png

This completes the implementation and deployment of the CapabilityToken acquisition API.

1-2. Implement the TwiML return API for Incoming Call as a Lambda function.

Interface (Input / Output)

The details of the interface between Twilio Server and API Server are summarized below. [Call from an external phone to a Twilio client (Incoming Call)](http://qiita.com/tmiki/items/fad1a22ba1fe14d32e34#%E5%A4%96%E9%83%A8%E9%9B%BB%E8%A9 % B1% E3% 81% 8B% E3% 82% 89twilio% E3% 82% AF% E3% 83% A9% E3% 82% A4% E3% 82% A2% E3% 83% B3% E3% 83% 88 % E3% 81% B8% E3% 81% AEcall incoming call)

The parameters passed from Twilio Server as Input are close to the structure of HTTP GET parameters, and Output needs to return XML, so both seem to have a bad affinity with JSON. It is possible to perform data structure conversion processing within a Lambda function, but it is not architecturally preferable to describe non-essential processing. But sticking to JSON is also a waste of time. Therefore, let API Gateway handle the conversion process only for Input. Input shall be converted to JSON format by API Gateway. Output will generate XML with Lambda function and return it as it is.

Let API Gateway translate the HTTP POST request from Twilio Server, and this API expects JSON like the one below.

Input


{
    "AccountSid": "AC3exxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "ApiVersion": "2010-04-01",
    "Called": "+815031234567",
    "CalledCountry": "JP",
    "CalledVia": "815031234567",
    "Caller": "+819059876543",
    "CallerCountry": "JP",
    "CallSid": "CA1fnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
    "CallStatus": "ringing",
    "Direction": "inbound",
    "ForwardedFrom": "815031234567",
    "From": "+819059876543",
    "FromCountry": "JP",
    "To": "+815031234567",
    "ToCountry": "JP"
}

I will return TwiML as it is.

Output


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

Implementation

Now let's actually write the Lambda function.

TwilioRetrieveTwimlCallIncoming.py


from __future__ import print_function
import json

print('Loading function')

def get_client_name_by_phone_number(phone_number):
    # regularize the input string "phone_number" to match registered phone numbers.
    phone_number_wo_hyphen = phone_number.replace('-','')

    # match and get the correct clientName
    if (phone_number_wo_hyphen == "+815031234567"):
        clientName = "DeviceId_0001"

    else:
        clientName = None

    return clientName

def lambda_handler(event, context):
    ''' Returns a TwiML to enable a real phone to call a Twilio device.
    This function will be received the following parameters.

    {
        "Caller": "+819012345678",
        "To": "+815098765432"
    }
    '''

    call_incoming_phone_number = event.get("To")
    client_name = get_client_name_by_phone_number(call_incoming_phone_number)

    if (client_name is None):
        res = '<?xml version="1.0" encoding="UTF-8"?><Response><Say language="en-US" loop="0">An error occurred. Your Twilio phone number {CallIncomingPhoneNumber} is invalid.</Say></Response>'
    else:
        res = '<?xml version="1.0\" encoding="UTF-8"?><Response><Dial timeout="60"><Client>{ClientName}</Client></Dial></Response>'


    strfmt = {"ClientName": client_name, "CallIncomingPhoneNumber": call_incoming_phone_number}

    return res.format(**strfmt)

Let's take a look at the Lambda function we created.

First, there are the following items as values that depend on the environment, so let's rewrite them.

def get_client_name_by_phone_number(phone_number):
    #Omission

    if (phone_number_wo_hyphen == "+815031234567"):
        clientName = "DeviceId_0001"

Specify the Twilio phone number you got when you set up your Twilio account. Change the clientName to your liking. This get_client_name_by_phone_number function is supposed to make a DB connection and retrieve the association information from the DB in the future. For convenience, we'll currently have a link between your Twilio phone number and your Client Name in your source code.

The processing flow is as follows.

--Get "To" from the Input parameter to the Lambda function. This will be the recipient's Twilio phone number --Get the Client Name that corresponds to the recipient's Twilio phone number --Based on the obtained Client Name, generate TwiML to call this. If the recipient's Twilio phone number isn't pre-registered, generate a TwiML to read the error message. --Return the generated TwiML as it is.

Deploy

This Lambda function does not require an external library and can be deployed from the AWS Management Console inline editor. LambdaFunction_TwilioRetrieveTwimlCallIncoming_1.png

I think that there is no problem with Configuration by default as long as the Handler settings are correct. This Lambda function doesn't use any other AWS service, so the IAM Role can be lambda_basic_execution. LambdaFunction_TwilioRetrieveTwimlCallIncoming_2.png

1-3. Implement the TwiML return API for Outgoing Call as a Lambda function

Interface (Input / Output)

The details of the interface between Twilio Server and API Server are summarized below. [Call from Twilio Client to External Phone (Outgoing Call)](http://qiita.com/tmiki/items/fad1a22ba1fe14d32e34#twilio%E3%82%AF%E3%83%A9%E3%82%A4%E3% 82% A2% E3% 83% B3% E3% 83% 88% E3% 81% 8B% E3% 82% 89% E5% A4% 96% E9% 83% A8% E9% 9B% BB% E8% A9% B1% E3% 81% B8% E3% 81% AEcall outgoingcall)

Similar to the TwiML return API for Incoming Call, this is also a method of converting Input to API Gateway and inputting it to the Lambda function, and returning TwiML directly from the Lambda function.

Let API Gateway translate the HTTP POST request from Twilio Server, and this API expects JSON like the one below.

Input


{
    "Direction": "inbound",
    "CallSid": "CA48nnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
    "From": "client:DeviceId_0001",
    "Caller": "client:DeviceId_0001",
    "ApiVersion": "2010-04-01",
    "ApplicationSid": "APf7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "AccountSid": "AC3exxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "CallStatus": "ringing",
    "callerPhoneNumber": "+81-50-3123-4567",
    "callOutgoingPhoneNumber": "+81-90-5987-6543"
}

I will return TwiML as it is.

Output


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

Implementation

Now let's actually write the Lambda function.

TwilioRetrieveTwimlCallOutgoing.py


from __future__ import print_function
import json

print('Loading function')

def lambda_handler(event, context):
    ''' Returns a TwiML to enable a Twilio device to call a real phone.
    This function will be received the following parameters.

    {
        "callerPhoneNumber": "+81-50-3123-4567",
        "callOutgoingPhoneNumber": "+81-90-59876543"
    }
    '''

    print ("event: " + json.dumps(event))
    caller_phone_number = event.get("callerPhoneNumber")
    call_outgoing_phone_number = event.get("callOutgoingPhoneNumber")


    res = '<?xml version="1.0" encoding="UTF-8"?><Response><Dial timeout="60" callerId="{callerId}"><Number>{callOutgoingPhoneNumber}</Number></Dial></Response>'
    strfmt = {"callerId": caller_phone_number, "callOutgoingPhoneNumber": call_outgoing_phone_number}

    return res.format(**strfmt)

This Lambda function is just a process to generate TwiML based on the parameters passed as Input. Therefore, there is no part that depends on the environment.

The processing flow is as follows.

--Get the "callerPhoneNumber" (source Twilio phone number) and "callOutgoingPhoneNumber" (destination normal phone number) from the Input parameter to the Lambda function. This will be a custom parameter passed by the Twilio Client. --Generate a TwiML with "callerPhoneNumber" as the caller ID and "callOutgoingPhoneNumber" as the callee. --Return the generated TwiML as it is.

Deploy

This Lambda function does not require an external library and can be deployed from the AWS Management Console inline editor. LambdaFunction_TwilioRetrieveTwimlCallOutgoing_1.png

I think that there is no problem with Configuration by default as long as the Handler settings are correct. This Lambda function doesn't use any other AWS service, so the IAM Role can be lambda_basic_execution. LambdaFunction_TwilioRetrieveTwimlCallOutgoing_2.png

Summary

The implementation of the required API is completed above. Enter the required parameters in JSON and make sure you get the results you want. You can easily check the operation from the AWS Management Console. I will summarize the Input / Output formats.

API name Input Output
Capability Token acquisition API JSON JSON
TwiML Return API for Incoming Call JSON XML
TwiML Return API for Outgoing Call JSON XML

2. API Gateway settings

2-1. When using API Gateway

AWS API Gateway provides useful functions for realizing RESTful APIs. There is a unique concept, so let's suppress it in advance.

Amazon API Gateway concept https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-basic-concept.html

The important concepts in this Walkthrough are as follows.

I don't think the above concept comes to mind just by reading the documentation. Try the tutorials below to get a feel for API Gateway and understand the concepts.

Get started with Amazon API Gateway https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/getting-started-intro.html

2-1. Create an API from the Amazon API Gateway menu in the AWS Management Console

From the AWS Management Console, open the Amazon API Gateway Console (https://ap-northeast-1.console.aws.amazon.com/apigateway/home?region=ap-northeast-1#/apis).

Press the "Create APIs" button to create a new API Gateway API. Here, create it with the name "twilio_on_aws". Once created, the API Gateway API will be added to the "APIs" list. APIGateway_Create_4.png

2-2. Define Resources for each API

API Gateway Once the API is created, we will add the necessary resources. You can think of it as a component of the Path of Resource ≒ API.

First, create a Resource called "/ twilio". We will add the necessary Resources below this so that we can finally configure the following paths. The Resource Name and Resource Path are specified separately, but I don't think there is much merit in specifying them separately, so make them the same. By default, the Resource Path delimiter is "-", but this should be "_".

/twilio/retrieve_capability_token /twilio/retrieve_twiml/call_incoming /twilio/retrieve_twiml/call_outgoing

APIGateway_Resources_1.png

2-3. Define Method for CapabilityToken acquisition API (/ twilio / retrieve_capability_token)

Select / twilio / retrieve_capability_token and select Actions> Create Method. Select "POST" as the Method. Since the screen for selecting what kind of implementation to connect this Method to will be displayed, set the value as shown below and link it to the Lambda function created earlier.

item name Set value
Integration Type Lambda Function
Lambda Region ap-northeast-1
Lambda Function TwilioRetrieveCapabilityToken

APIGateway_retrieve_capability_token_1.png

When you press the "Save" button, the pop-up "Add Permission to Lambda Function" will be displayed, so press "OK". APIGateway_retrieve_capability_token_2.png

This means that you have changed the "Lambda function policy" that is set for each Lambda function. We have granted permission to execute the Lambda function "TwilioRetrieveCapabilityToken" from the resource "/ twilio / retrieve_capability_token". For more information on Lambda function policies, please refer to the following documents.

Use resource-based policies with AWS Lambda (Lambda function policies) https://docs.aws.amazon.com/{ja_jp/lambda/latest/dg/access-control-resource-based.html

This completes the implementation of the CapabilityToken acquisition API.

You can test the created API Gateway and Lambda function from "TEST", so let's execute the API by specifying the default Input and confirm that the intended result is returned.

RequestBody


{
  "twilioPhoneNumber": "+81-50-1234-9876"
}

ResponseBody


{
  "success": true,
  "capabilityToken": "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJ...."
}

At this point, it cannot be called from other hosts as a RESTful API. Deploy work described later is required.

2-4. Define Method for TwiML Return API (/ twilio / retrieve_twiml / call_incoming) for Incoming Call

Select / twilio / retrieve_twiml / call_incoming and select Actions> Create Method. Select "POST" as the Method. Since the screen for selecting what kind of implementation to connect this Method to will be displayed, set the value as shown below and link it to the Lambda function created earlier.

item name Set value
Integration Type Lambda Function
Lambda Region ap-northeast-1
Lambda Function TwilioRetrieveTwimlCallIncoming

APIGateway_retrieve_twiml_call_incoming_1.png

When you press the "Save" button, the pop-up "Add Permission to Lambda Function" will be displayed, so press "OK". This also modifies the "Lambda function policy" APIGateway_retrieve_twiml_call_incoming_2.png

Once you have set the Resource / Method, let's test it. You can test from "TEST". Specify the following JSON in Request Body and test whether the intended TwiML is returned.

RequestBody


{
    "Caller": "+819059876543",
    "From": "+819059876543",
    "Called": "+815031234567",
    "To": "+815031234567"
}

ResponseBody


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

In addition to the above work, the API built this time needs to realize the following two things.

--Convert input to API (Convert Key = Value pair separated by "&" to JSON) --The request from Twilio Server is not JSON, but a Key = Value pair like the GET parameter, so you need to convert this. (Requests from Twilio Server will be application / x-www-form-urlencoded instead of application / json) --Stop the automatic escape processing of the response --If you check the TwiML of the response, you can see that if it contains "" ", the escape symbol" \ "is automatically inserted immediately before it. As it is, it will not be well-formed XML, so it is necessary to set it so that the Output from Lambda can be returned without any processing.

Stop the automatic escape processing of the response

First, stop the automatic escape processing of the response. This is set in "Integration Response". Open "Integration Response", expand "Method response status" to 200 lines, and then expand "Body Mapping Templates". Since "application / json" is specified by default, press the "-" button to remove it. APIGateway_retrieve_twiml_call_incoming_3.png

After that, press the "+" button of "Add mapping template" and add "application / xml" as Content-Type. At the same time, you can specify the contents of "Body Mapping Templates", so specify one line below and press the "Save" button.

$input.path('$')

APIGateway_retrieve_twiml_call_incoming_4.png

Let's perform Method TEST with the same Request as before and check the response contents. The "" "has been removed to make the XML well-formed.

ResponseBody


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

Convert input to API (Convert Key = Value pair separated by "&" to JSON)

The parameters passed by Twilio Server are not JSON, but Key = Value pairs like GET parameters. In order to convert this, it is necessary to define "Body Mapping Template" in "Integration Request".

Open "Integration Request" and expand "Body Mapping Templates". Check "We hen there are no templates defined (recommended)" from "Request body pass through". Press the "+" button of "Add mapping template" to add "application / x-www-form-urlencoded". APIGateway_retrieve_twiml_call_incoming_6.png

Paste the following content as "Body Mapping Template" and press "Save".

#set($raw_input = $input.path("$"))

{
  #foreach( $kvpairs in $raw_input.split( '&' ) )
    #set( $kvpair = $kvpairs.split( '=' ) )
      #if($kvpair.length == 2)
        "$kvpair[0]" : "$kvpair[1]"#if( $foreach.hasNext ),#end
      #end
  #end
}

APIGateway_retrieve_twiml_call_incoming_7.png

The details of Mapping Template are summarized in the following article. Please refer to it if necessary.

Convert strings like GET query parameters to JSON with API Gateway Mapping Template

After setting, I want to test, but I can't specify the request parameters and it seems that I can't test well. APIGateway_retrieve_twiml_call_incoming_8.png

Therefore, check the operation after Deploy. Before creating the TwiML return API for Outgoing Call, let's perform the work of 2-7 and 2-8 and check the operation.

2-5. Define Method for TwiML Return API (/ twilio / retrieve_twiml / call_outgoing) for Outgoing Call

Select twilio / retrieve_twiml / call_outgoing, then under Actions, select Create Method. Select "POST" as the Method. Since the screen for selecting what kind of implementation to connect this Method to will be displayed, set the value as shown below and link it to the Lambda function created earlier. APIGateway_retrieve_twiml_call_outgoing_2.png

item name Set value
Integration Type Lambda Function
Lambda Region ap-northeast-1
Lambda Function TwilioRetrieveTwimlCallOutgoing

When you press the "Save" button, the pop-up "Add Permission to Lambda Function" will be displayed, so press "OK". This also modifies the "Lambda function policy" APIGateway_retrieve_twiml_call_outgoing_2.png

Once you have set the Resource / Method, let's test it. You can test from "TEST". Specify the following JSON in Request Body and test whether the intended TwiML is returned.

RequestBody


{
    "callerPhoneNumber": "+81-50-3123-4567",
    "callOutgoingPhoneNumber": "+81-90-5987-6543"
}

ResponseBody


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

Similar to the TwiML return API for Incoming Call, this API needs to realize the following two things in addition to the above work.

--Convert input to API (Convert Key = Value pair separated by "&" to JSON) --The request from Twilio Server is not JSON, but a Key = Value pair like the GET parameter, so you need to convert this. --Stop the automatic escape processing of the response --If you check the TwiML of the response, you can see that if it contains "" ", the escape symbol" \ "is automatically inserted immediately before it. As it is, it will not be well-formed XML, so it is necessary to set it so that the Output from Lambda can be returned without any processing.

Stop the automatic escape processing of the response

First, stop the automatic escape processing of the response. This is set in "Integration Response". Open "Integration Response", expand "Method response status" to 200 lines, and then expand "Body Mapping Templates". Since "application / json" is specified by default, press the "-" button to remove it. APIGateway_retrieve_twiml_call_outgoing_3.png

After that, press the "+" button of "Add mapping template" and add "application / xml" as Content-Type. At the same time, you can specify the contents of "Body Mapping Templates", so specify one line below and press the "Save" button.

$input.path('$')

APIGateway_retrieve_twiml_call_outgoing_4.png

Let's perform Method TEST with the same Request as before and check the response contents. The "" "has been removed to make the XML well-formed.

ResponseBody


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

Convert input to API (Convert Key = Value pair separated by "&" to JSON)

The parameters passed by Twilio Server are not JSON, but Key = Value pairs like GET parameters. In order to convert this, it is necessary to define "Body Mapping Template" in "Integration Request".

Open "Integration Request" and expand "Body Mapping Templates". Check "We hen there are no templates defined (recommended)" from "Request body pass through". Press the "+" button of "Add mapping template" to add "application / x-www-form-urlencoded". APIGateway_retrieve_twiml_call_outgoing_6.png

Paste the following content as "Body Mapping Template" and press "Save".

#set($raw_input = $input.path("$"))

{
  #foreach( $kvpairs in $raw_input.split( '&' ) )
    #set( $kvpair = $kvpairs.split( '=' ) )
      #if($kvpair.length == 2)
        "$kvpair[0]" : "$kvpair[1]"#if( $foreach.hasNext ),#end
      #end
  #end
}

APIGateway_retrieve_twiml_call_outgoing_7.png

The details of Mapping Template are summarized in the following article. Please refer to it if necessary.

Convert strings like GET query parameters to JSON with API Gateway Mapping Template

After setting, I want to test, but I can't specify the request parameters and it seems that I can't test well. APIGateway_retrieve_twiml_call_outgoing_8.png

Therefore, check the operation after Deploy. Perform steps 2-7 and 2-8 first to check the operation.

2-6. Set CORS

After creating the Method, let's set each Resource to allow CORS (Cross Origin Resource Sharing). This is a specification that applies to web browser applications implemented in JavaScript, and is set to allow / deny API calls across domains. The Twilio Client we're implementing this time is provided with HTML itself at https://twilio-on-aws-us.s3.amazonaws.com, which is the S3 domain. However, the API is provided in the domain for API Gateway, such as https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/. Therefore, the API you are currently creating must be configured with CORS to allow API calls from content in your S3 domain.

Please refer to the following sites for a detailed explanation of CORS.

HTTP Access Control (CORS) https://developer.mozilla.org/ja/docs/Web/HTTP/HTTP_access_control

Originally, it is necessary to specify an individual domain name from which domain content can call the API, but this Walkthrough does not control that much and allows API calls from all domains.

The setting in this case is easy. With the target resource selected, select "Enable CORS" from "Actions". You can customize the headers to be added, but just press the "Enable CORS and replace existing CORS headers" button. APIGateway_CORS_1.png

A confirmation window will be displayed, so just press the "Yes, replace existing values" button. APIGateway_CORS_2.png

"OPTIONS" has been added to Method. If you check the "Integration Response" and "Method Response" settings, you can see that the CORS settings have been added.

APIGateway_CORS_3.png APIGateway_CORS_4.png

Let's make the same settings for all Resources that provide the API.

2-7. Prod Create a stage and deploy

After defining the required Resource / Method, deploy it so that it can be called from the outside. First, select "Deploy API" from "Actions". Initially, Stage is not defined. Select [New Stage] from [Deployment stage]. Set each value as shown below and press the "Deploy" button.

item name Set value
Stage name* prod
Stage description ※Any. Here, it is called Production Environment.
Deployment description ※Any. Here, it is called First release.

APIGateway_Deploy_1.png

After deploying, you can check the created Stage from "APIs"-> "API name"-> "Stages" in the left pane. "Invoke URL" is the URL that can be used from the outside. APIGateway_Deploy_2.png

If you expand the Stage name, you can see that the Resource / Method created so far are mapped as they are. It is necessary to check the operation, so let's check the specific Invoke URL of each API. APIGateway_Deploy_3.png

2-8. Check the operation of each API

Check the operation with curl. Let's create a Linux instance in advance. Note that curl can output detailed information by specifying the "-v" option. If it doesn't work, use the "-v" option to troubleshoot.

Capability Token acquisition API

Execute the following command on Linux.

$ curl -v -H "Content-type: application/json" -X POST -d '{"twilioPhoneNumber": "+81-50-3123-4567"}' https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/twilio/retrieve_capability_token

#Confirm that the default response is returned.
{"success": true, "capabilityToken": "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9....."}

TwiML Return API for Incoming Call

Execute the following command on Linux. The Body of the POST request is [HTTP POST request from Twilio Server during inbound call](http://qiita.com/tmiki/items/fad1a22ba1fe14d32e34#twilio-server%E3%81%8B%E3%82%89% E3% 81% AE http-post% E3% 83% AA% E3% 82% AF% E3% 82% A8% E3% 82% B9% E3% 83% 88) Specify the same content.

curl -v -H "Content-type: application/x-www-form-urlencoded; charset=UTF-8" -X POST -d 'ApplicationSid=AP75zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz&ApiVersion=2010-04-01&Called=&Caller=%2B819059876543&CallStatus=ringing&CallSid=CCA86nnnnnnnnnnnnnnnnnnnnnnnnnnnnnn&To=%2B815031234567&From=%2B819059876543&Direction=inbound&AccountSid=AC3exxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/twilio/retrieve_twiml/call_incoming

#Confirm that the default response is returned.
<?xml version="1.0" encoding="UTF-8"?><Response><Dial timeout="60"><Client>DeviceId_0001</Client></Dial></Response>

TwiML Return API for Outgoing Call

Execute the following command on Linux. The Body of the POST request is [HTTP POST request from Twilio Server during outbound call](http://qiita.com/tmiki/items/fad1a22ba1fe14d32e34#twilio-server%E3%81%8B%E3%82%89% E3% 81% AE http-post% E3% 83% AA% E3% 82% AF% E3% 82% A8% E3% 82% B9% E3% 83% 88-1) Specify the same content.

curl -v -H "Content-type: application/x-www-form-urlencoded; charset=UTF-8" -X POST -d 'AccountSid=AC3exxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&ApiVersion=2010-04-01&ApplicationSid=AP75zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz&Called=&Caller=client%3ADeviceId_0001&CallSid=CA06nnnnnnnnnnnnnnnnnnnnnnnnnnnnnn&CallStatus=ringing&Direction=inbound&From=client%3ADeviceId_0001&To=&callerPhoneNumber=%2B81-50-3123-4567&callOutgoingPhoneNumber=%2B81-90-5987-6543' https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/twilio/retrieve_twiml/call_outgoing

#Confirm that the default response is returned.
<?xml version="1.0" encoding="UTF-8"?><Response><Dial timeout="60" callerId="+81-50-3123-4567"><Number>+81-90-5987-6543</Number></Dial></Response>

This completes the implementation on the API Server side. We'll be implementing the Twilio Client next.

Recommended Posts

Understanding from the mechanism Twilio # 3-1 --AWS API Gateway + Lambda implementation Walkthrough (Part 1)
The simplest AWS Lambda implementation
[AWS] Create API with API Gateway + Lambda
[AWS SAM] Create API with DynamoDB + Lambda + API Gateway
LINE BOT with Python + AWS Lambda + API Gateway
[AWS] Try tracing API Gateway + Lambda with X-Ray
Amazon API Gateway and AWS Lambda Python version
Tweet from AWS Lambda
AWS Amplify + API Gateway + Lambda + Python returns a normal response
Create API with Python, lambda, API Gateway quickly using AWS SAM
AWS CDK-Lambda + API Gateway (Python)
Send images taken with ESP32-WROOM-32 to AWS (API Gateway → Lambda → S3)
[Python] I wrote a REST API using AWS API Gateway and Lambda.
LINE BOT (Messaging API) development with API Gateway and Lambda (Python) [Part 2]