# -*- Mode: Python; tab-width: 4 -*- # # http proxy extension # # ================ # WORK IN PROGRESS # ================ import socket import string import time import asyncore import asynchat import http_server import regex import resolver numeric_host = regex.compile ('[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+') class http_proxy_handler: url_regex = regex.compile ( # groups: 1=host 2=port 3=uri 'http://\([^/:]+\)\(:[0-9]+\)?\([^ ]*\).*', regex.casefold ) def match (self, request): uri = request.uri if self.url_regex.match (uri) == len(uri): return 1 else: return 0 def handle_request (self, request): # install the proxy version of the writable predicate request.channel.writable = request.channel.writable_for_proxy # our task: feed the request object a producer that will either # answer it correctly, or produce an error message. http_proxy_producer ( self.url_regex.group(1), self.url_regex.group(2), self.url_regex.group(3), request ) # for now, be sure and close the connection #request['Connection'] = 'close' #request.channel.producer_fifo.push (None) # this producer implements two interfaces: the async_chat interface, # and the 'stallable producer' interface. when this object is at the # front of an http channel's producer fifo, it will affect the channel's # response to the 'writable' predicate, depending on whether or not # any proxy data has accumulated. class http_proxy_producer (asynchat.async_chat): #resolver = resolver () resolver = resolver.caching_resolver ('127.0.0.1') def __init__ (self, host, port, uri, request): if numeric_host.match (host) == len(host): resolved = 1 self.ip = host else: resolved = 0 self.ip = None if not port: self.port = 80 else: self.port = string.atoi (port) self.uri = uri self.request = request if not resolved: if host in ['ad.doubleclick.net', 'ad.linkexchange.com', 'ads.imdb.com']: self.send_blank_gif() else: print 'sending DNS request' self.resolver.resolve (host, self.resolver_callback) else: self.resolver_callback (host, self.ip) self.outgoing_buffer = '' def resolver_callback (self, host, ttl, ip): asynchat.async_chat.__init__ (self) self.create_socket (socket.AF_INET, socket.SOCK_STREAM) # handle errors! self.ip = ip self.set_terminator (None) self.connect ((self.ip, self.port)) def collect_incoming_data (self, data): print 'pushing %d bytes' % len(data) self.request.channel.push (data) def found_terminator (self): print 'found_terminator should not have been called' def handle_connect (self): print 'connected' # for now, just send all the headers, and use HTTP/1.0 self.push ( string.join ( ['%s %s HTTP/1.0' % ( string.upper (self.request.command), self.uri )] + self.request.header, '\r\n' ) + '\r\n\r\n' ) done = 0 def handle_close (self): self.done = 1 self.close() self.request.channel.close_when_done() # this method is called by http_channel.writable_for_proxy(), # to let it know whether or not any data is actually available. def stalled (self): return len(self.outgoing_buffer) == 0 and not self.done def more (self): # just return the whole buffer. r = self.outgoing_buffer self.outgoing_buffer = '' return r def send_blank_gif (self): import blank_gif self.request['Content-Type'] = 'image/gif' self.request['Content-Length'] = len(blank_gif.data) self.request.push (blank_gif.data) self.request.done() if __name__ == '__main__': import sys if len(sys.argv) < 2: print 'usage: %s <root> <port>' % (sys.argv[0]) else: import monitor import filesys import default_handler fs = filesys.os_filesystem (sys.argv[1]) dh = default_handler.default_handler (fs) ph = http_proxy_handler() hs = http_server.http_server ('', string.atoi (sys.argv[2])) hs.install_handler (dh) hs.install_handler (ph) ms = monitor.monitor_server ('', 9998) if ('-p' in sys.argv): def profile_loop (): try: asyncore.loop() except KeyboardInterrupt: pass import profile profile.run ('profile_loop()', 'profile.out') else: asyncore.loop()