Sunday, September 21, 2014

VXML and REST Web Services

Summary

I'll be describing how to make REST web service calls from VXML scripts in this post.  In a nutshell, the VXML <data> element can be utilized.  Though per spec, this element only provides support for XML input and output - many VXML browsers today provide support for more efficient encoding schemes, such as JSON.

REST POST then Transfer 

Main script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8"?>
<!-- 
 Author: Joey Whelan
 Simple VXML script to illustrate REST calls.  Script receives an inbound call then makes a
 subdialog call.  The subdialog handles the execution of the REST (HTTP POST) call.  
 The returned value from that POST is used as input to a blind transfer.
 -->
 
<vxml version="2.1">
 <form>
  <var name="value" expr="87654321" />
  
  <subdialog name="results" src="cticacheSubD.vxml">
   <param name="srcURI" expr="'https://username:password@yourDomain.com/cticache/DNIS-ANI/'" />
   <param name="methodType" expr="'post'" />
   <param name="val" expr="value"/>
   
   <filled>
    <if cond="results.respObj.error != undefined" >
     <prompt>
      <value expr="results.respObj.error"/>
     </prompt>
     <exit/>
    <else/>
     <assign name="target" expr="results.respObj.dnis"/>
     <goto next="#transfer"/> 
    </if>
   </filled>
  </subdialog> 
 </form>

 <form id="transfer">
  <transfer type="blind" destexpr="'tel:' +target">
   <prompt>
    Transferring this call to 
    <say-as interpret-as="digits">
     <value expr="target"/>
    </say-as>
   </prompt>
  </transfer>
 </form>

</vxml>

Lines 13-16  

A call is made to a subdialog which handles the REST transaction.  The input parameters to that subdialog are the URI of the REST service, the HTTP method to be used, and an input value to the service.

Regarding the URI parameter - HTTP Basic Authentication can be utilized with the <data> element by passing the username + password as part of the URI ('client:password' below).  Combining basic auth with TLS (as below) provides a modicum of security for the REST service.  The authentication credentials will be encrypted under TLS.

1
   <param name="srcURI" expr="'https://username:password@yourDomain.com/cticache/DNIS-ANI/'" />

Lines 18-28

Results of the subdialog are passed back as a JSON object.  I check to see if an error occurred as a result the REST call.  If so, I simply play out the error as TTS.  If the call was successful, I set a variable ('target') to the value of the results and then goto a form that will perform a call transfer.

Lines 32-41

This form takes the REST call result (a dialable number) and performs a blind transfer to that number.

REST Subdialog

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?xml version="1.0" encoding="UTF-8"?>
<!-- 
 Author: Joey Whelan
 Subdialog for making REST web service calls.  It is parameterized to provide flexibility on URI and 
 method types.
 -->
 
<vxml version="2.1">
 
 <catch event="error.badfetch.https.400 error.badfetch.http.400">
  <log label="Error" expr="'HTTP 400 - Bad Request'"/>
  <prompt>
   This request could not be understood
   <break time="1000"/>
  </prompt>
  <var name="respObj" />
  <script>
    respObj = {'error':'HTTP 400'};
  </script>
  <return namelist="respObj" />
 </catch>
 
 <catch event="error.badfetch.https.401 error.badfetch.http.401">
  <log label="Error" expr="'HTTP 401 - Unauthorized'"/>
  <prompt>
   This request did not have the proper authentication
   <break time="1000"/>
  </prompt>
  <var name="respObj" />
  <script>
    respObj = {'error':'HTTP 401'};
  </script>
  <return namelist="respObj" />
 </catch>
 
 <catch event="error.badfetch.https.403 error.badfetch.http.403">
  <log label="Error" expr="'HTTP 403 - Forbidden'"/>
  <prompt>
   This request can not be fulfilled on this server
   <break time="1000"/>
  </prompt>
  <var name="respObj" />
  <script>
    respObj = {'error':'HTTP 403'};
  </script>
  <return namelist="respObj" />
 </catch>
 
 <catch event="error.badfetch.https.404 error.badfetch.http.404">
  <log expr="'HTTP 404 - Not Found'"/>
  <prompt>
   This request does not match anything on this server
   <break time="1000"/>
  </prompt>
  <var name="respObj" />
  <script>
    respObj = {'error':'HTTP 404'};
  </script>
  <return namelist="respObj" />
 </catch>
 
 
 <catch event="error.badfetch.https.500 error.badfetch.http.500">
  <log label="Error" expr="'HTTP 500 - Internal Server Error'"/>
  <prompt>
   The server has experienced an internal error
   <break time="1000"/>
  </prompt>
  <var name="respObj" />
  <script>
    respObj = {'error':'HTTP 500'};
  </script>
  <return namelist="respObj" />
 </catch>
 
 <form>
  <var name="srcURI" />
  <var name="methodType" />
  <var name="val" />
  
  <block>
   <script>
    methodType = methodType.toLowerCase();
   </script>
   <var name="key" expr="session.calledid + ':' + session.callerid" />
   
   <!-- the ecmaxlmtype="e4x" and <assign name="respObj" ... JSON.parse(...)/> statements are hacks for the VXML
   browser I'm using.  It has 'challenges' with application/json input parameters.  If your browser supports JSON 
   input natively these statements are unnecessary
    -->
   <if cond="methodType == 'delete'">
    <data name="response" srcexpr="srcURI + key" ecmaxmltype="e4x" method="delete"/>
    <assign name="respObj" expr="JSON.parse(response.toString())"/>
    <return namelist="respObj" />  
   </if>

   <if cond="methodType == 'get'">
    <data name="response" srcexpr="srcURI + key" ecmaxmltype="e4x" method="get"/>
    <assign name="respObj" expr="JSON.parse(response.toString())"/>
    <return namelist="respObj" />  
   </if>
   
   <!--  Similar browser 'challenge' with JSON here, except now with output (enctype).
   If your browser supports JSON natively - change enctype to "application/json"
    -->
   <if cond="methodType == 'post'">
    <var name="ani" expr="session.callerid" />
    <var name="value" expr="val"/>
    <data name="response" srcexpr="srcURI" ecmaxmltype="e4x" enctype="application/x-www-form-urlencoded" method="post" namelist="ani value"/>
    <assign name="respObj" expr="JSON.parse(response.toString())"/>
    <return namelist="respObj" />  
   </if> 
  </block>
 </form>
</vxml>

Lines 10-74

This is a series of <catch> blocks for the various HTTP error codes this particular REST service can throw.  In all cases here I'm setting the result to an object, playing an error prompt, then returning from the subdialog with that error object.

Lines 77-79

Input parameters for this subdialog.

Line 85

Here I'm setting a variable to the dialed number and caller ID.  This stored as a string delimited with a colon.

Lines 91-112

This is the main code of the subdialog.  <if> blocks determine which HTTP method is used.  The URI of the REST service is appended with the DNIS:ANI value from Line 85.  The REST result is returned from the subdialog as a JSON object.

REST DELETE (Fetch)

The VXML code below implements the analog of the previous script (which POSTed data).  The code below fetches that POSTed data and deletes it.

Main Script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<!-- 
 Author: Joey Whelan
 Simple VXML script to illustrate REST calls.  Script makes a fetch (implemented as a HTTP DELETE) to
 a REST service.
 -->
<vxml version="2.1">
 <form>
   <subdialog name="results" src="cticacheSubD.vxml">
    <param name="srcURI" expr="'https://username:password@yourDomain.com/cticache/DNIS-ANI/'" />
    <param name="methodType" expr="'delete'" />
   
    
   <filled>
    <if cond="results.respObj.error != undefined" >
     <prompt>
      <value expr="results.respObj.error"/>
     </prompt>
    <else/>
     <prompt>
      The account number is
      <say-as interpret-as="digits">
       <value expr="results.respObj.value"/>
      </say-as>
     </prompt>
    </if>
   </filled>
  </subdialog> 
 </form>
</vxml>

Lines 9-11

Similar to the previous <form>, the REST-handling subdialog is called with the appropriate input parameters.  The method type is set to 'delete'.

Line 14-27

The results of the subdialog are inspected.  If an error occurred, its message is played back as TTS.  If the call was successful, the result value is played back as TTS.


6 comments:

  1. Fatastic example. Thanks so much.

    ReplyDelete
  2. Hi Joey,

    I need doing a rest integration sending an api-key into the http header. How I can achieve this following the data tag approach ?

    thanks,

    Santiago.

    ReplyDelete
    Replies
    1. I don't see any way to manipulate HTTP headers in native VXML. What you could do - Make the initial 'data' tag call to a web service of your own, written in the language of your choice. Then, that web service could make the subsequent call with HTTP headers set. Net, you'd be doing a relay to enable use of language that allows for header manipulation.

      Delete
    2. Thanks for the input Joel

      Yes, I'm seeing the middle server as a possible alternative but I'm trying to avoid having it.

      Another option would be try to code the rest ws client in Javascript 1.1

      Do you know any rest ws library in compliance with Javascript 1.1 ? I already looked for ajax jquery and XMLHttpRequest but they are not recognized by the Voice Browser's ECMAscript parser.

      Thanks again,

      Santiago.

      Delete
    3. I made a couple attempts at this today myself with axios and superagent. Those js libs just won't function in the vxml broswer I'm using either. Unresolved references.

      Delete
    4. Another option (better than relay), leverage server-side scripting - organize the vxml app as a dynamic page (JSP as an example). Then you have access to a programming language that can support more advanced functions like http header manipulation.

      Delete