From 1a0b54bbc6ab364a3350eb798b4a9e42ebdaa4ca Mon Sep 17 00:00:00 2001 From: Ben Davis Date: Wed, 18 Nov 2015 09:48:52 -0800 Subject: [PATCH] Modified agi.py to accept stdin/out/error to allow support for FastAGI access. Included new fastagi.py to be used as basic FastAGI server. --- asterisk/agi.py | 49 ++++++++++++++++++++++++--------------------- asterisk/fastagi.py | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 23 deletions(-) mode change 100644 => 100755 asterisk/agi.py create mode 100755 asterisk/fastagi.py diff --git a/asterisk/agi.py b/asterisk/agi.py old mode 100644 new mode 100755 index aa0ba35..01e3ba9 --- a/asterisk/agi.py +++ b/asterisk/agi.py @@ -8,9 +8,9 @@ pyvr {'agi_callerid' : 'mars.putland.int', 'agi_channel' : 'IAX[kputland@kputland]/119', 'agi_context' : 'default', - 'agi_dnid' : '666', + 'agi_dnid' : '1000', 'agi_enhanced' : '0.0', - 'agi_extension': '666', + 'agi_extension': '1000', 'agi_language' : 'en', 'agi_priority' : '1', 'agi_rdnis' : '', @@ -77,7 +77,7 @@ class AGIUsageError(AGIError): class AGIInvalidCommand(AGIError): pass - + class AGI: """ This class encapsulates communication between Asterisk an a python script. @@ -85,21 +85,24 @@ class AGI: Asterisk. """ - def __init__(self): + def __init__(self, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr): self._got_sighup = False signal.signal(signal.SIGHUP, self._handle_sighup) # handle SIGHUP - sys.stderr.write('ARGS: ') - sys.stderr.write(str(sys.argv)) - sys.stderr.write('\n') + self.stderr.write('ARGS: ') + self.stderr.write(str(sys.argv)) + self.stderr.write('\n') + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr self.env = {} self._get_agi_env() def _get_agi_env(self): while 1: - line = sys.stdin.readline().strip() - sys.stderr.write('ENV LINE: ') - sys.stderr.write(line) - sys.stderr.write('\n') + line = self.stdin.readline().strip() + self.stderr.write('ENV LINE: ') + self.stderr.write(line) + self.stderr.write('\n') if line == '': #blank line signals end break @@ -108,12 +111,12 @@ class AGI: data = data.strip() if key != '': self.env[key] = data - sys.stderr.write('class AGI: self.env = ') - sys.stderr.write(pprint.pformat(self.env)) - sys.stderr.write('\n') + self.stderr.write('class AGI: self.env = ') + self.stderr.write(pprint.pformat(self.env)) + self.stderr.write('\n') def _quote(self, string): - """ provides double quotes to string, converts int/bool to string """ + """ provides double quotes to string, converts int/bool to string """ if isinstance(string, int): string = str(string) if isinstance(string, float): @@ -149,16 +152,16 @@ class AGI: command = command.strip() if command[-1] != '\n': command += '\n' - sys.stderr.write(' COMMAND: %s' % command) - sys.stdout.write(command) - sys.stdout.flush() + self.stderr.write(' COMMAND: %s' % command) + self.stdout.write(command) + self.stdout.flush() def get_result(self, stdin=sys.stdin): """Read the result of a command from Asterisk""" code = 0 result = {'result': ('', '')} - line = stdin.readline().strip() - sys.stderr.write(' RESULT_LINE: %s\n' % line) + line = self.stdin.readline().strip() + self.stderr.write(' RESULT_LINE: %s\n' % line) m = re_code.search(line) if m: code, response = m.groups() @@ -175,16 +178,16 @@ class AGI: if key == 'result' and value == '-1': raise AGIAppError("Error executing application, or hangup") - sys.stderr.write(' RESULT_DICT: %s\n' % pprint.pformat(result)) + self.stderr.write(' RESULT_DICT: %s\n' % pprint.pformat(result)) return result elif code == 510: raise AGIInvalidCommand(response) elif code == 520: usage = [line] - line = stdin.readline().strip() + line = self.stdin.readline().strip() while line[:3] != '520': usage.append(line) - line = stdin.readline().strip() + line = self.stdin.readline().strip() usage.append(line) usage = '%s\n' % '\n'.join(usage) raise AGIUsageError(usage) diff --git a/asterisk/fastagi.py b/asterisk/fastagi.py new file mode 100755 index 0000000..db76608 --- /dev/null +++ b/asterisk/fastagi.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# FastAGI service for Asterisk +# Requires modified pyst2 to support reading stdin/out/err +# +# Copyright 2011 VOICE1, LLC +# By: Ben Davis + +import sys +import SocketServer +import asterisk.agi +# import pkg_resources +# PYST_VERSION = pkg_resources.get_distribution("pyst2").version + +__verison__ = 0.1 + +#TODO: Read options from config file. +HOST, PORT = "127.0.0.1", 4573 + +class FastAGI(SocketServer.StreamRequestHandler): + # Close connections not finished in 5seconds. + timeout = 5 + def handle(self): + try: + agi=asterisk.agi.AGI(stdin=self.rfile, stdout=self.wfile, stderr=sys.stderr) + agi.verbose("pyst2: FastAGI on: {}:{}".format(HOST, PORT)) + except TypeError as e: + sys.stderr.write('Unable to connect to agi://{} {}\n'.format(self.client_address[0], str(e))) + except SocketServer.socket.timeout as e: + sys.stderr.write('Timeout receiving data from {}\n'.format(self.client_address)) + except SocketServer.socket.error as e: + sys.stderr.write('Could not open the socket. Is someting else listening on this port?\n') + except Exception as e: + sys.stderr.write('An unknown error: {}\n'.format(str(e))) + +if __name__ == "__main__": + # server = SocketServer.TCPServer((HOST, PORT), FastAGI) + server = SocketServer.ForkingTCPServer((HOST, PORT), FastAGI) + + # Keep server running until CTRL-C is pressed. + server.serve_forever()