+""" HTTP Proxy Server
+
+This module acts as an HTTP Proxy Server. However it adds the
+multitaskhttpd auto-generated session cookie to the headers.
+
+It also changes the connection type to use keep-alive, so that
+the connection stays open to the server, resulting in an
+"apparent" persistent connection.
+
+Thus, a service on the receiving end of this proxy server may reliably
+keep persistent state, even though the browsers connecting to it
+may be doing HTTP 1.0 or have an unreliable internet connection.
+
+"""
+
+
+__version__ = "0.6"
+
+__all__ = ["SimpleHTTPRequestHandler"]
+
+import os
+import posixpath
+import BaseHTTPServer
+import urllib
+import urlparse
+import traceback
+import cgi
+import shutil
+import mimetools
+import multitask
+import socket
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+import httpd
+
+class NotConnected(Exception):
+ pass
+
+class ProxyConnection:
+
+ auto_open = 1
+ debuglevel = 0
+ strict = 0
+
+ def __init__(self):
+ self.sock = None
+ self.host = "127.0.0.1"
+ self.port = 60001
+
+
+ def connect(self):
+ """Connect to the host and port specified in __init__."""
+ msg = "getaddrinfo returns an empty list"
+ for res in socket.getaddrinfo(self.host, self.port, 0,
+ socket.SOCK_STREAM):
+ af, socktype, proto, canonname, sa = res
+ try:
+ self.sock = socket.socket(af, socktype, proto)
+ if self.debuglevel > 0:
+ print "connect: (%s, %s)" % (self.host, self.port)
+ self.sock.connect(sa)
+ except socket.error, msg:
+ if self.debuglevel > 0:
+ print 'connect fail:', (self.host, self.port)
+ if self.sock:
+ self.sock.close()
+ self.sock = None
+ continue
+ break
+ if not self.sock:
+ raise socket.error, msg
+
+ def close(self):
+ """Close the connection to the HTTP server."""
+ if self.sock:
+ self.sock.close() # close it manually... there may be other refs
+ self.sock = None
+
+ def send(self, str):
+ """Send `str' to the server."""
+ if self.sock is None:
+ if self.auto_open:
+ self.connect()
+ else:
+ raise NotConnected()
+
+ # send the data to the server. if we get a broken pipe, then close
+ # the socket. we want to reconnect when somebody tries to send again.
+ #
+ # NOTE: we DO propagate the error, though, because we cannot simply
+ # ignore the error... the caller will know if they can retry.
+ if self.debuglevel > 0:
+ print "send:", repr(str)
+ try:
+ yield multitask.send(self.sock, str)
+ except socket.error, v:
+ if v[0] == 32: # Broken pipe
+ self.close()
+ raise
+
+ def read(self, num_bytes=1024):
+ data = (yield multitask.recv(self.sock, num_bytes))
+
+
+class ProxyServerRequestHandler(object):
+
+ """Simple HTTP request handler with GET and HEAD commands.
+
+ This serves files from the current directory and any of its
+ subdirectories. The MIME type for files is determined by
+ calling the .guess_type() method.
+
+ The GET and HEAD requests are identical except that the HEAD
+ request omits the actual contents of the file.
+
+ """
+
+ server_version = "SimpleHTTP/" + __version__
+
+ def on_query(self, client, reqtype, *args):
+ """Serve a request."""
+ self.client = client
+ self.hr = args[0]
+ if not hasattr(self.client, "proxy"):
+ self.client.proxy = ProxyConnection()
+ self.client.proxy.connect()
+
+ multitask.add(self.proxy_relay(reqtype))
+
+ return True
+
+ def onPOST(self, client, *args):
+ """Serve a POST request."""
+ return self.on_query(client, "POST", *args)
+
+ def onGET(self, client, *args):
+ """Serve a GET request."""
+ return self.on_query(client, "GET", *args)
+
+ def proxy_relay(self, reqtype):
+
+ p = self.client.proxy
+
+ # send command
+ req = "%s %s %s\n" % (reqtype, self.hr.path, self.hr.request_version)
+ print "req", req
+ yield multitask.send(p.sock, req)
+
+ # send headers
+ hdrs = str(self.hr.headers)
+ print "hdrs", hdrs
+ yield multitask.send(p.sock, hdrs)
+ yield multitask.send(p.sock, "\n")
+
+ # now content
+ if self.hr.headers.has_key('content-length'):
+ max_chunk_size = 10*1024*1024
+ size_remaining = int(self.hr.headers["content-length"])
+ L = []
+ print "size_remaining", size_remaining
+ while size_remaining:
+ chunk_size = min(size_remaining, max_chunk_size)
+ data = self.hr.rfile.read(chunk_size)
+ yield multitask.send(p.sock, data)
+ size_remaining -= len(L[-1])
+
+ # now read response and write back
+ res = ''
+ while True:
+ #data = p.read()
+ data = (yield multitask.recv(p.sock, 1024))
+ print "reading from proxy", repr(data)
+ if data == '':
+ break
+ res += data
+
+ f = StringIO(res)
+ requestline = f.readline()
+ yield self.client.writeMessage(requestline)
+
+ # Examine the headers and look for a Connection directive
+ respheaders = mimetools.Message(f, 0)
+ print "response headers", str(respheaders)
+ remote = self.client.remote
+ rcooks = httpd.process_cookies(respheaders, remote, "Set-Cookie", False)
+ rcooks['session'] = self.hr.response_cookies['session'].value # nooo
+ rcooks['session']['expires'] = \
+ self.hr.response_cookies['session']['expires']
+ self.hr.response_cookies = rcooks
+ print "rcooks", str(rcooks)
+
+ # send all but Set-Cookie headers
+ del respheaders['Set-Cookie'] # being replaced
+ yield self.client.writeMessage(str(respheaders))
+
+ # now replacement cookies
+ for k, v in rcooks.items():
+ val = v.output()
+ yield self.client.writeMessage(val+"\r\n")
+
+ # check connection for "closed" header
+ conntype = respheaders.get('Connection', "")
+ if conntype.lower() == 'close':
+ self.hr.close_connection = 1
+ elif (conntype.lower() == 'keep-alive' and
+ self.hr.protocol_version >= "HTTP/1.1"):
+ self.hr.close_connection = 0
+
+ # write rest of data
+ print "writing to client body"
+ yield self.client.writeMessage("\r\n")
+ yield self.client.writeMessage(f.read())
+ if self.hr.close_connection:
+ try:
+ yield self.client.connectionClosed()
+ except httpd.ConnectionClosed:
+ print 'close_connection done'
+ pass
+
+ raise StopIteration
+