--- /dev/null
+""" 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
+
val = v.OutputString()
self.send_header("Set-Cookie", val)
+def process_cookies(headers, remote, cookie_key="Cookie", add_sess=True):
+ ch = headers.getheaders(cookie_key)
+ print "messageReceived cookieheaders=", '; '.join(ch)
+ res = []
+ for c in ch:
+ c = c.split(";")
+ if len(c) == 0:
+ continue
+ c = map(strip, c)
+ c = filter(lambda x: x, c)
+ res += c
+ has_sess = False
+ response_cookies = SimpleCookie()
+ for c in res:
+ print "found cookie", repr(c)
+ name, value = c.split("=")
+ response_cookies[name] = value
+ #response_cookies[name]['path'] = "/"
+ #response_cookies[name]['domain'] = remote[0]
+ #response_cookies[name]['version'] = 0
+ if name == "session":
+ response_cookies[name]['expires'] = 50000
+ has_sess = True
+ if not add_sess:
+ return response_cookies
+ if not has_sess:
+ response_cookies['session'] = uuid.uuid4().hex
+ response_cookies['session']['expires'] = 50000
+ #response_cookies['session']['path'] = '/'
+ #response_cookies['session']['domain'] = remote[0]
+ #response_cookies['session']['version'] = 0
+ return response_cookies
+
class ConnectionClosed:
'raised when the client closed the connection'
def messageReceived(self, msg):
if _debug: print 'messageReceived cmd=', msg.command, msg.path
- ch = msg.headers.getheaders("Cookie")
- ch += msg.headers.getheaders("cookie")
- print "messageReceived cookieheaders=", '; '.join(ch)
- res = []
- for c in ch:
- c = c.split(";")
- if len(c) == 0:
- continue
- c = map(strip, c)
- c = filter(lambda x: x, c)
- res += c
- has_sess = False
- msg.response_cookies = SimpleCookie()
- for c in res:
- print "found cookie", repr(c)
- name, value = c.split("=")
- msg.response_cookies[name] = value
- #msg.response_cookies[name]['path'] = "/"
- #msg.response_cookies[name]['domain'] = self.remote[0]
- #msg.response_cookies[name]['expires'] = 'None'
- #msg.response_cookies[name]['version'] = 0
- if name == "session":
- has_sess = True
- if not has_sess:
- msg.response_cookies['session'] = uuid.uuid4().hex
- #msg.response_cookies['session']['expires'] = 'None'
- #msg.response_cookies['session']['path'] = '/'
- #msg.response_cookies['session']['domain'] = self.remote[0]
- #msg.response_cookies['session']['version'] = 0
+ msg.response_cookies = process_cookies(msg.headers, self.remote)
if msg.headers.has_key('content-length'):
max_chunk_size = 10*1024*1024
if result is True or result is None:
if session not in self.clients:
self.clients[session] = [inst]; inst._clients=self.clients[session]
- self.clients[session].append(client)
- msg.wfile.seek(0)
- data = msg.wfile.read()
- msg.wfile.seek(0)
- msg.wfile.truncate()
- yield client.writeMessage(data)
- if close_connection:
- if _debug:
- print 'close_connection requested'
- try:
- yield client.connectionClosed()
- except ClientClosed:
+ if result is None:
+ msg.wfile.seek(0)
+ data = msg.wfile.read()
+ msg.wfile.seek(0)
+ msg.wfile.truncate()
+ yield client.writeMessage(data)
+ if close_connection:
if _debug:
- print 'close_connection done'
- pass
+ print 'close_connection requested'
+ try:
+ yield client.connectionClosed()
+ except ConnectionClosed:
+ if _debug:
+ print 'close_connection done'
+ pass
else:
+ print "result", result
yield client.rejectConnection(reason='Rejected in onConnect')
except StopIteration: raise
except: