A fork of pappy proxy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
5.6 KiB

9 years ago
#!/usr/bin/env python2
import argparse
9 years ago
import crochet
import datetime
9 years ago
import os
import schema.update
import shutil
import sys
import tempfile
9 years ago
from . import comm
from . import config
from . import context
from . import http
from . import plugin
from . import proxy
from .console import ProxyCmd
9 years ago
from twisted.enterprise import adbapi
from twisted.internet import reactor, defer
from twisted.internet.error import CannotListenError
9 years ago
from twisted.internet.protocol import ServerFactory
from twisted.internet.threads import deferToThread
9 years ago
crochet.no_setup()
9 years ago
server_factory = None
main_context = context.Context()
all_contexts = [main_context]
plugin_loader = None
cons = None
9 years ago
9 years ago
@defer.inlineCallbacks
def wait_for_saves(ignored):
reset = True
printed = False
lastprint = 0
while reset:
reset = False
togo = 0
for c in all_contexts:
for r in c.all_reqs:
if r.reqid == '--':
reset = True
togo += 1
d = defer.Deferred()
d.callback(None)
yield d
if togo % 10 == 0 and lastprint != togo:
lastprint = togo
print '%d requests left to be saved (probably won\'t work)' % togo
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
9 years ago
def set_text_factory(conn):
conn.text_factory = str
def delete_datafile():
print 'Deleting temporary datafile'
os.remove(config.DATAFILE)
9 years ago
@defer.inlineCallbacks
def main():
9 years ago
global server_factory
global plugin_loader
global cons
settings = parse_args()
load_start = datetime.datetime.now()
if settings['lite']:
conf_settings = config.get_default_config()
conf_settings['debug_dir'] = None
conf_settings['debug_to_file'] = False
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')
delete_data_on_quit = False
9 years ago
# 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
9 years ago
dbpool = adbapi.ConnectionPool("sqlite3", config.DATAFILE,
check_same_thread=False,
cp_openfun=set_text_factory,
cp_max=1)
9 years ago
try:
yield schema.update.update_schema(dbpool, config.DATAFILE)
except Exception as e:
print 'Error updating schema: %s' % e
print 'Exiting...'
reactor.stop()
9 years ago
http.init(dbpool)
yield context.init()
# Run the proxy
if config.DEBUG_DIR and os.path.exists(config.DEBUG_DIR):
shutil.rmtree(config.DEBUG_DIR)
print 'Removing old debugging output'
9 years ago
server_factory = proxy.ProxyServerFactory(save_all=True)
9 years ago
listen_strs = []
9 years ago
ports = []
9 years ago
for listener in config.LISTENERS:
try:
9 years ago
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)
9 years ago
ports.append(port)
except CannotListenError as e:
print repr(e)
9 years ago
if listen_strs:
print 'Proxy is listening on %s' % (', '.join(listen_strs))
else:
print 'No listeners opened'
9 years ago
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)
9 years ago
context.reset_to_scope(main_context)
# Apologize for slow start times
load_end = datetime.datetime.now()
load_time = (load_end - load_start)
if load_time.total_seconds() > 20:
print 'Startup was slow (%s)! Sorry!' % load_time
9 years ago
print 'Database has {0} requests (~{1:.2f}ms per request)'.format(len(main_context.active_requests), ((load_time.total_seconds()/len(main_context.active_requests))*1000))
sys.argv = [sys.argv[0]] # cmd2 tries to parse args
9 years ago
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)
9 years ago
d.addCallback(close_listeners)
d.addCallback(wait_for_saves)
9 years ago
d.addCallback(lambda ignored: reactor.stop())
if delete_data_on_quit:
d.addCallback(lambda ignored: delete_datafile())
9 years ago
def start():
9 years ago
reactor.callWhenRunning(main)
reactor.run()
if __name__ == '__main__':
start()