#!/usr/bin/env python3 """ httapi pause during fetch repro case Usage: ./(script-name).py [ defaults to 8088] Configure dialplan: Callflow: fetch 1: - sleep 1000 - play welcome - uuid_displace start looping playback - sleep 2000 (caller hears looping playback) fetch 2: - (pause on server side - **caller does not hear looping playback**) - sleep 2000 (caller hears looping playback) - uuid_displace stop playback - play prompt 2 fetch 3: - play goodbye - hangup """ import logging import time import xml.etree.ElementTree as ET from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.parse import unquote prompts = [ "/usr/share/freeswitch/sounds/en/us/callie/ivr/8000/ivr-welcome.wav", "/usr/share/freeswitch/sounds/en/us/callie/digits/8000/1.wav", "/usr/share/freeswitch/sounds/en/us/callie/base256/8000/hamlet.wav", "/usr/share/freeswitch/sounds/en/us/callie/voicemail/8000/vm-goodbye.wav", ] def extract_var(v, s): v_name_start = s.find(f"{v}=") v_value_start = v_name_start + len(v) + 1 v_value_end = s.find("&", v_value_start) if v_name_start < 0: return None if v_value_end < 0: return s[v_value_start:] return s[v_value_start:v_value_end] class S(BaseHTTPRequestHandler): def __init__(self, request, client_addr, server): super().__init__(request, client_addr, server) # disable default BaseHTTPRequestHandler logging def log_message(self, format, *args): pass def _set_response(self): self.send_response(200) self.send_header('Content-type', 'text/xml') self.end_headers() def do_GET(self): logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers)) self._set_response() def do_POST(self): content_length = int(self.headers['Content-Length']) post_data = self.rfile.read(content_length) post_data_str = post_data.decode('utf-8') session_id = extract_var("session_id", post_data_str) exiting = (post_data_str.find("&exiting=true") >= 0) self.server.turn_count[session_id] = self.server.turn_count.get(session_id, -1) + 1 turn_count = self.server.turn_count[session_id] logging.info("----------------------------------------------------------------------") logging.info(f"session_id={session_id}, turn={turn_count}") if turn_count < 1: logging.debug(f"turn_count={turn_count} POST request\nPath: {self.path}\nHeaders:\n{self.headers}\nBody:\n{post_data_str}\n") else: logging.debug(f"turn_count={turn_count} POST request\nBody:\n{post_data_str}\n") self._set_response() root = ET.Element('document', attrib={'type': 'text/freeswitch-httapi'}) work = ET.SubElement(root, 'work') if turn_count == 0: action = ET.SubElement(work, 'execute', attrib = { 'application': 'sleep', 'data': f'1000', }) action = ET.SubElement(work, 'playback', attrib={'file': prompts[0]}) action = ET.SubElement(work, 'execute', attrib = { 'application': 'set', 'data': f'api_result=${{uuid_displace {session_id} start {prompts[1]} 0 flmw}}', }) action = ET.SubElement(work, 'execute', attrib = { 'application': 'log', 'data': 'INFO uuid_displace start', }) action = ET.SubElement(work, 'execute', attrib = { 'application': 'sleep', 'data': f'1000', }) action = ET.SubElement(work, 'execute', attrib = { 'application': 'log', 'data': 'INFO long fetch start', }) elif turn_count == 1: # generate a pause during the fetch time.sleep(2.0) action = ET.SubElement(work, 'execute', attrib = { 'application': 'log', 'data': 'INFO long fetch complete', }) action = ET.SubElement(work, 'execute', attrib = { 'application': 'sleep', 'data': f'1000', }) action = ET.SubElement(work, 'execute', attrib = { 'application': 'set', 'data': f'api_result=${{uuid_displace {session_id} stop {prompts[1]}}}', }) action = ET.SubElement(work, 'playback', attrib={'file': prompts[2]}) elif turn_count > 1: action = ET.SubElement(work, 'playback', attrib={'file': prompts[3]}) action = ET.SubElement(work, 'hangup') action_tuples = [('hangup', ''),] else: pass # if exiting=true, the call is disconnected and we can return with an empty body and a 200 if exiting: body = b'OK' else: body = ET.tostring(root) self.wfile.write(body) logging.info(f'POST response:\n{body.decode()}\n') class MyHttpServer(HTTPServer): def __init__(self, server_address, handler_class=S): super().__init__(server_address, handler_class) self.headers = dict() self.turn_count = dict() def run(server_class=MyHttpServer, handler_class=S, port=8088): logging.basicConfig(format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', level=logging.DEBUG, datefmt='%Y-%m-%dT%H:%M:%SZ') server_address = ('', port) httpd = server_class(server_address, handler_class) logging.info(f'Starting httpd on {port}...\n') try: httpd.serve_forever() except KeyboardInterrupt: pass httpd.server_close() logging.info('Stopping httpd...\n') if __name__ == '__main__': from sys import argv if len(argv) == 2: run(port=int(argv[1])) else: run()