From Tony Mancill, 5 Months ago, written in Python.
- view diff
Embed
  1. #!/usr/bin/env python3
  2. """
  3. httapi pause during fetch repro case
  4.  
  5. Usage:
  6.    ./(script-name).py [<port> defaults to 8088]
  7.  
  8.  
  9. Configure dialplan:
  10.  
  11.    <extension name="httapi-pause-repro" continue="false">
  12.      <condition field="destination_number" expression="^(\+18005551212)$">
  13.        <action application="answer"/>
  14.        <!-- <action application="record_session" data="$${recordings_dir}/${strftime(%Y-%m-%d-%H-%M-%S)}_httapi-pause-repro.wav"/> -->
  15.        <action application="httapi" data="{url=http://localhost:8088,method=POST}"/>
  16.        <action application="playback" data="misc/error.wav"/>
  17.        <action application="hangup"/>
  18.      </condition>
  19.    </extension>
  20.  
  21.  
  22. Callflow:
  23.  
  24. fetch 1:
  25. - sleep 1000
  26. - play welcome
  27. - uuid_displace start looping playback
  28. - sleep 2000 (caller hears looping playback)
  29.        
  30. fetch 2:
  31. - (pause on server side - **caller does not hear looping playback**)
  32. - sleep 2000 (caller hears looping playback)
  33. - uuid_displace stop playback
  34. - play prompt 2
  35.  
  36. fetch 3:
  37. - play goodbye
  38. - hangup
  39. """
  40.  
  41. import logging
  42. import time
  43. import xml.etree.ElementTree as ET
  44.  
  45. from http.server import BaseHTTPRequestHandler, HTTPServer
  46. from urllib.parse import unquote
  47.  
  48. prompts = [
  49.     "/usr/share/freeswitch/sounds/en/us/callie/ivr/8000/ivr-welcome.wav",
  50.     "/usr/share/freeswitch/sounds/en/us/callie/digits/8000/1.wav",
  51.     "/usr/share/freeswitch/sounds/en/us/callie/base256/8000/hamlet.wav",
  52.     "/usr/share/freeswitch/sounds/en/us/callie/voicemail/8000/vm-goodbye.wav",
  53. ]
  54.  
  55.  
  56. def extract_var(v, s):
  57.     v_name_start = s.find(f"{v}=")
  58.     v_value_start = v_name_start + len(v) + 1
  59.     v_value_end = s.find("&", v_value_start)
  60.     if v_name_start < 0:
  61.         return None
  62.     if v_value_end < 0:
  63.         return s[v_value_start:]
  64.     return s[v_value_start:v_value_end]
  65.  
  66.  
  67. class S(BaseHTTPRequestHandler):
  68.  
  69.     def __init__(self, request, client_addr, server):
  70.         super().__init__(request, client_addr, server)
  71.  
  72.     # disable default BaseHTTPRequestHandler logging
  73.     def log_message(self, format, *args):
  74.         pass
  75.  
  76.     def _set_response(self):
  77.         self.send_response(200)
  78.         self.send_header('Content-type', 'text/xml')
  79.         self.end_headers()
  80.  
  81.     def do_GET(self):
  82.         logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers))
  83.         self._set_response()
  84.  
  85.     def do_POST(self):
  86.         content_length = int(self.headers['Content-Length'])
  87.         post_data = self.rfile.read(content_length)
  88.         post_data_str = post_data.decode('utf-8')
  89.  
  90.         session_id = extract_var("session_id", post_data_str)
  91.         exiting = (post_data_str.find("&exiting=true") >= 0)
  92.  
  93.         self.server.turn_count[session_id] = self.server.turn_count.get(session_id, -1) + 1
  94.         turn_count = self.server.turn_count[session_id]
  95.  
  96.         logging.info("----------------------------------------------------------------------")
  97.         logging.info(f"session_id={session_id}, turn={turn_count}")
  98.         if turn_count < 1:
  99.             logging.debug(f"turn_count={turn_count} POST request\nPath: {self.path}\nHeaders:\n{self.headers}\nBody:\n{post_data_str}\n")
  100.         else:
  101.             logging.debug(f"turn_count={turn_count} POST request\nBody:\n{post_data_str}\n")
  102.  
  103.         self._set_response()
  104.         root = ET.Element('document', attrib={'type': 'text/freeswitch-httapi'})
  105.         work = ET.SubElement(root, 'work')
  106.  
  107.         if turn_count == 0:
  108.             action = ET.SubElement(work, 'execute', attrib = { 'application': 'sleep', 'data': f'1000', })
  109.             action = ET.SubElement(work, 'playback', attrib={'file': prompts[0]})
  110.             action = ET.SubElement(work, 'execute',
  111.                 attrib = {
  112.                     'application': 'set',
  113.                     'data': f'api_result=${{uuid_displace {session_id} start {prompts[1]} 0 flmw}}',
  114.                 })
  115.             action = ET.SubElement(work, 'execute', attrib = { 'application': 'log', 'data': 'INFO uuid_displace start', })
  116.             action = ET.SubElement(work, 'execute', attrib = { 'application': 'sleep', 'data': f'1000', })
  117.             action = ET.SubElement(work, 'execute', attrib = { 'application': 'log', 'data': 'INFO long fetch start', })
  118.  
  119.         elif turn_count == 1:
  120.             # generate a pause during the fetch
  121.             time.sleep(2.0)
  122.  
  123.             action = ET.SubElement(work, 'execute', attrib = { 'application': 'log', 'data': 'INFO long fetch complete', })
  124.             action = ET.SubElement(work, 'execute', attrib = { 'application': 'sleep', 'data': f'1000', })
  125.             action = ET.SubElement(work, 'execute',
  126.                 attrib = {
  127.                     'application': 'set',
  128.                     'data': f'api_result=${{uuid_displace {session_id} stop {prompts[1]}}}',
  129.                 })
  130.             action = ET.SubElement(work, 'playback', attrib={'file': prompts[2]})
  131.  
  132.         elif turn_count > 1:
  133.             action = ET.SubElement(work, 'playback', attrib={'file': prompts[3]})
  134.             action = ET.SubElement(work, 'hangup')
  135.             action_tuples = [('hangup', ''),]
  136.         else:
  137.             pass
  138.  
  139.         # if exiting=true, the call is disconnected and we can return with an empty body and a 200
  140.         if exiting:
  141.             body = b'OK'
  142.         else:
  143.             body = ET.tostring(root)
  144.         self.wfile.write(body)
  145.         logging.info(f'POST response:\n{body.decode()}\n')
  146.  
  147.  
  148. class MyHttpServer(HTTPServer):
  149.     def __init__(self, server_address, handler_class=S):
  150.         super().__init__(server_address, handler_class)
  151.         self.headers = dict()
  152.         self.turn_count = dict()
  153.  
  154.  
  155. def run(server_class=MyHttpServer, handler_class=S, port=8088):
  156.     logging.basicConfig(format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
  157.         level=logging.DEBUG,
  158.         datefmt='%Y-%m-%dT%H:%M:%SZ')
  159.  
  160.     server_address = ('', port)
  161.     httpd = server_class(server_address, handler_class)
  162.     logging.info(f'Starting httpd on {port}...\n')
  163.     try:
  164.         httpd.serve_forever()
  165.     except KeyboardInterrupt:
  166.         pass
  167.     httpd.server_close()
  168.     logging.info('Stopping httpd...\n')
  169.  
  170. if __name__ == '__main__':
  171.     from sys import argv
  172.  
  173.     if len(argv) == 2:
  174.         run(port=int(argv[1]))
  175.     else:
  176.         run()
  177.