add HTTP Proxy Server (to 127.0.0.1 port 60001)
authorlkcl <lkcl@teenymac.(none)>
Wed, 14 Jul 2010 00:03:26 +0000 (01:03 +0100)
committerlkcl <lkcl@teenymac.(none)>
Wed, 14 Jul 2010 00:03:26 +0000 (01:03 +0100)
ProxyServer.py [new file with mode: 0644]
httpd.py

diff --git a/ProxyServer.py b/ProxyServer.py
new file mode 100644 (file)
index 0000000..4d3e20f
--- /dev/null
@@ -0,0 +1,224 @@
+""" 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
+
index 7b5e2a61c98a7fd60c8eeb386154bbd6a63a15a7..55c10900a79298815fc15bc3854c453680210a8a 100644 (file)
--- a/httpd.py
+++ b/httpd.py
@@ -101,6 +101,39 @@ class MultitaskHTTPRequestHandler(BaseHTTPRequestHandler):
             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'
 
@@ -467,35 +500,7 @@ class Client(Protocol):
             
     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
@@ -672,22 +677,23 @@ class HTTPServer(object):
                         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: