from webob import Request, Response import threading from paste.util import threadedprint from Queue import Queue from itertools import count import tempita from paste.urlparser import StaticURLParser from paste.util.filemixin import FileMixin import os import sys import simplejson here = os.path.dirname(os.path.abspath(__file__)) #def debug(msg, *args): # args = '%s %s' % (msg, ' '.join(map(repr, args))) # print >> sys.stderr, args class PdbCapture(object): def __init__(self, app): self.app = app threadedprint.install(leave_stdout=True) threadedprint.install_stdin() self.counter = count() self.static_app = StaticURLParser(os.path.join(here, 'pdbcapture/static')) self.media_app = StaticURLParser(os.path.join(here, 'eval-media')) self.states = {} def get_template(self, template_name): filename = os.path.join(os.path.dirname(__file__), template_name) return tempita.HTMLTemplate.from_filename(filename) def __call__(self, environ, start_response): req = Request(environ) if req.GET.get('__pdbid__'): id = int(req.GET['__pdbid__']) response = self.states[id]['response'] return response(environ, start_response) if req.path_info_peek() == '.pdbcapture': req.path_info_pop() if req.path_info_peek() == 'static': req.path_info_pop() return self.static_app(environ, start_response) if req.path_info_peek() == 'media': req.path_info_pop() return self.media_app(environ, start_response) resp = self.internal_request(req) return resp(environ, start_response) id = self.counter.next() state = dict(id=id, event=threading.Event(), base_url=req.application_url, stdout=[], stdin=[], stdin_event=threading.Event()) t = threading.Thread(target=self.call_app, args=(req, state)) t.setDaemon(True) t.start() state['event'].wait() if 'response' in state: # Normal request, nothing happened resp = state['response'] return resp(environ, start_response) if 'exc_info' in state: raise state['exc_info'][0], state['exc_info'][1], state['exc_info'][2] self.states[id] = state tmpl = self.get_template('pdbcapture_response.html') body = tmpl.substitute(req=req, state=state, id=id) resp = Response(body) return resp(environ, start_response) def internal_request(self, req): id = int(req.params['id']) state = self.states[id] if 'response' in state: body = {'response': 1} else: if req.params.get('stdin'): state['stdin'].append(req.params['stdin']) state['stdin_event'].set() stdout = ''.join(state['stdout']) state['stdout'][:] = [] body = {'stdout': stdout} if not state['stdin_event'].isSet(): body['stdinPending'] = 1 resp = Response(content_type='application/json', body=simplejson.dumps(body)) return resp def call_app(self, req, state): event = state['event'] stream_handler = StreamHandler(stdin=state['stdin'], stdin_event=state['stdin_event'], stdout=state['stdout'], signal_event=state['event']) threadedprint.register(stream_handler) threadedprint.register_stdin(stream_handler) try: resp = req.get_response(self.app) state['response'] = resp except: state['exc_info'] = sys.exc_info() event.set() class StreamHandler(FileMixin): def __init__(self, stdin, stdout, stdin_event, signal_event): self.stdin = stdin self.stdout = stdout self.stdin_event = stdin_event self.signal_event = signal_event def write(self, text): self.stdout.append(text) def read(self, size=None): self.signal_event.set() text = ''.join(self.stdin) if size is None or size == -1: self.stdin[:] = [] sys.stdout.write(text) return text while len(text) < size: self.stdin_event.clear() self.stdin_event.wait() text = ''.join(self.stdin) pending = text[:size] self.stdin[:] = [text[size:]] sys.stdout.write(pending) return pending def test_app(environ, start_response): import pdb message = "Hey, what's up?" pdb.set_trace() start_response('200 OK', [('Content-type', 'text/plain')]) return [message] if __name__ == '__main__': from paste import httpserver httpserver.serve(PdbCapture(test_app))