diff --git a/README.rst b/README.rst index b073de1..090c146 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 appropriate line. Future versions +of this will use a config file to set options. Credits ------- diff --git a/asterisk/agi.py b/asterisk/agi.py old mode 100644 new mode 100755 index aceb562..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,9 +111,9 @@ 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 """ @@ -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()