From 1a0b54bbc6ab364a3350eb798b4a9e42ebdaa4ca Mon Sep 17 00:00:00 2001 From: Ben Davis Date: Wed, 18 Nov 2015 09:48:52 -0800 Subject: [PATCH 1/3] 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() From 4c9045fbbf663d276a75cbc685bbf28a3b288480 Mon Sep 17 00:00:00 2001 From: Ben Davis Date: Wed, 18 Nov 2015 12:06:43 -0800 Subject: [PATCH 2/3] Updated README to include FastAGI documentation --- README.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.rst b/README.rst index b073de1..2563f6d 100644 --- a/README.rst +++ b/README.rst @@ -89,6 +89,17 @@ directly on the host where Asterisk is running. Since Asterisk doesn't run on windows platforms (and probably never will) the agi part of the package can only be run on Asterisk platforms. +FastAGI +------- + +FastAGI support is a python based raw SocketServer, To start the server +python fastagi.py should start it listening on localhost and the default +asterisk FastAGI port. This does require the newest version of pyst2. +The FastAGI server runs in as a Forked operation for each request, in +an attempt to prevent blocking by a single bad service. As a result the +FastAGI server may consume more memory then a single process. If you need +to use a single process simply uncomment the approperate line. Future versions +of this will use a config file to set options. Credits ------- From 24e7cf12586c304248607b1e8383fe3d7ccc5ed6 Mon Sep 17 00:00:00 2001 From: Ben Davis Date: Wed, 18 Nov 2015 12:09:49 -0800 Subject: [PATCH 3/3] Corrected typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 2563f6d..090c146 100644 --- a/README.rst +++ b/README.rst @@ -98,7 +98,7 @@ asterisk FastAGI port. This does require the newest version of pyst2. The FastAGI server runs in as a Forked operation for each request, in an attempt to prevent blocking by a single bad service. As a result the FastAGI server may consume more memory then a single process. If you need -to use a single process simply uncomment the approperate line. Future versions +to use a single process simply uncomment the appropriate line. Future versions of this will use a config file to set options. Credits