#!/usr/bin/env python2 import argparse import crochet import datetime import os import schema.update import shutil import sys import tempfile from . import comm from . import config from . import context from . import http from . import plugin from . import proxy from . import requestcache from .console import ProxyCmd from twisted.enterprise import adbapi from twisted.internet import reactor, defer from twisted.internet.error import CannotListenError from twisted.internet.protocol import ServerFactory from twisted.internet.threads import deferToThread crochet.no_setup() server_factory = None main_context = context.Context() all_contexts = [main_context] plugin_loader = None cons = None try: from guppy import hpy heapstats = hpy() heapstats.setref() except ImportError: heapstats = None def parse_args(): # parses sys.argv and returns a settings dictionary parser = argparse.ArgumentParser(description='An intercepting proxy for testing web applications.') parser.add_argument('-l', '--lite', help='Run the proxy in "lite" mode', action='store_true') args = parser.parse_args(sys.argv[1:]) settings = {} if args.lite: settings['lite'] = True else: settings['lite'] = False return settings def set_text_factory(conn): conn.text_factory = str def delete_datafile(): print 'Deleting temporary datafile' os.remove(config.DATAFILE) @defer.inlineCallbacks def main(): global server_factory global plugin_loader global cons settings = parse_args() if settings['lite']: conf_settings = config.get_default_config() conf_settings['debug_dir'] = None conf_settings['debug_to_file'] = False conf_settings['history_size'] = 0 with tempfile.NamedTemporaryFile(delete=False) as tf: conf_settings['data_file'] = tf.name print 'Temporary datafile is %s' % tf.name delete_data_on_quit = True config.load_settings(conf_settings) else: # Initialize config config.load_from_file('./config.json') config.global_load_from_file() delete_data_on_quit = False # If the data file doesn't exist, create it with restricted permissions if not os.path.isfile(config.DATAFILE): with os.fdopen(os.open(config.DATAFILE, os.O_CREAT, 0o0600), 'r') as f: pass dbpool = adbapi.ConnectionPool("sqlite3", config.DATAFILE, check_same_thread=False, cp_openfun=set_text_factory, cp_max=1) try: yield schema.update.update_schema(dbpool, config.DATAFILE) except Exception as e: print 'Error updating schema: %s' % e print 'Exiting...' reactor.stop() http.init(dbpool) yield http.Request.cache.load_ids() context.reset_context_caches() # Run the proxy if config.DEBUG_DIR and os.path.exists(config.DEBUG_DIR): shutil.rmtree(config.DEBUG_DIR) print 'Removing old debugging output' server_factory = proxy.ProxyServerFactory(save_all=True) listen_strs = [] ports = [] for listener in config.LISTENERS: try: port = reactor.listenTCP(listener[0], server_factory, interface=listener[1]) listener_str = 'port %d' % listener[0] if listener[1] not in ('127.0.0.1', 'localhost'): listener_str += ' (bound to %s)' % listener[1] listen_strs.append(listener_str) ports.append(port) except CannotListenError as e: print repr(e) if listen_strs: print 'Proxy is listening on %s' % (', '.join(listen_strs)) else: print 'No listeners opened' com_factory = ServerFactory() com_factory.protocol = comm.CommServer # Make the port different for every instance of pappy, then pass it to # anything we run. Otherwise we can only have it running once on a machine comm_port = reactor.listenTCP(0, com_factory, interface='127.0.0.1') comm.set_comm_port(comm_port.getHost().port) # Load the scope yield context.load_scope(http.dbpool) context.reset_to_scope(main_context) sys.argv = [sys.argv[0]] # cmd2 tries to parse args cons = ProxyCmd() plugin_loader = plugin.PluginLoader(cons) for d in config.PLUGIN_DIRS: if not os.path.exists(d): os.makedirs(d) plugin_loader.load_directory(d) @defer.inlineCallbacks def close_listeners(ignored): for port in ports: yield port.stopListening() d = deferToThread(cons.cmdloop) d.addCallback(close_listeners) d.addCallback(lambda ignored: reactor.stop()) if delete_data_on_quit: d.addCallback(lambda ignored: delete_datafile()) def start(): reactor.callWhenRunning(main) reactor.run() if __name__ == '__main__': start()