[Zope-CVS] CVS: Packages/tcpwatch - tcpwatch:1.5

Shane Hathaway shane@cvs.zope.org
Thu, 21 Feb 2002 00:08:18 -0500


Update of /cvs-repository/Packages/tcpwatch
In directory cvs.zope.org:/tmp/cvs-serv16075

Modified Files:
	tcpwatch 
Log Message:
Functional proxy (needs further testing)

=== Packages/tcpwatch/tcpwatch 1.4 => 1.5 ===
 
     def received(self, data):
-        for d in self._dests:
-            d.write(data)
+        if data:
+            for d in self._dests:
+                d.write(data)
 
     def handle_read(self):
         data = self.recv(RECV_BUFFER_SIZE)
-        if data:
-            self.received(data)
+        self.received(data)
 
     def handle_write(self):
         if not self.connected:
@@ -382,7 +382,7 @@
 #############################################################################
 
 
-def setupTk(fwd_params, config_info, colorized=1):
+def setupTk(titlepart, config_info, colorized=1):
     """Starts the Tk application and returns an observer factory.
     """
 
@@ -533,19 +533,17 @@
 
 
 
-    def createApp(fwd_params):
+    def createApp(titlepart):
         app = TkTCPWatch()
         try:
             wm_title = app.master.wm_title
         except AttributeError:
             pass  # No wm_title method available.
         else:
-            titlepart = ', '.join(map(
-                lambda args: '%s:%d -> %s:%d' % args, fwd_params))
             wm_title('TCPWatch [%s]' % titlepart)
         return app
 
-    app = createApp(fwd_params)
+    app = createApp(titlepart)
 
     def tkObserverFactory(fci, app=app, colorized=colorized):
         return TkConnectionObserver(app, fci, colorized)
@@ -594,6 +592,8 @@
     def __init__(self, cl, buf=None):
         self.remain = cl
         self.buf = buf
+        if cl < 1:
+            self.completed = 1
 
     def received(self, data):
         rm = self.remain
@@ -711,7 +711,7 @@
     A structure that parses the HTTP stream.
     """
 
-    completed = 0  # Set once request is completed.
+    completed = 0    # Set once request is completed.
     empty = 0        # Set if no request was made.
     header_plus = ''
     chunked = 0
@@ -752,7 +752,7 @@
                     self.completed = 1
                 else:
                     self.parse_header(header_plus)
-                    if self.body_rcv is None:
+                    if self.body_rcv is None or self.body_rcv.completed:
                         self.completed = 1
                 return consumed
             else:
@@ -940,7 +940,7 @@
 
     def flush(self):
         if self.held and self._isMyTurn():
-            data = ''.join(self.held) + data
+            data = ''.join(self.held)
             del self.held[:]
             self.proxy_conn.write(data)
         if self.finished:
@@ -957,16 +957,21 @@
         self.finished = 1
         self.flush()
 
+    def error(self, t, v):
+        observer_stream = self.observer_stream
+        if observer_stream is not None:
+            observer_stream.error(t, v)
+
 
 
 class HTTPProxyConnection (ForwardingEndpoint):
 
     _req_parser = None
-    _obs = None
     _transaction = 0
+    _obs = None
 
     def __init__(self, conn, factory, counter, addr):
-        asyncore.dispatcher.__init__(self, conn)
+        ForwardingEndpoint.__init__(self, conn)
         self._obs_factory = factory
         self._counter = counter
         self._client_addr = addr
@@ -983,6 +988,7 @@
             self._transaction = self._transaction + 1
             obs.transaction = self._transaction
             self._obs = obs
+            self.set_dests((EndpointObserver(obs, 1),))
 
     def received(self, data):
         while data:
@@ -991,31 +997,43 @@
                 # Begin another request.
                 self._newRequest()
                 parser = self._req_parser
-            obs = self._obs
             if not parser.completed:
-                # Not yet connected to a server.
+                # Waiting for a complete request.
                 consumed = parser.received(data)
-                if obs is not None:
-                    obs.received(data[:consumed], 1)
-                data = data[:consumed]
+                ForwardingEndpoint.received(self, data[:consumed])
+                data = data[consumed:]
             if parser.completed:
-                # Connected to a server.
+                # Connect to a server.
                 self.openProxyConnection(parser)
                 # Expect a new request or a closed connection.
-                self._obs = None
                 self._req_parser = None
 
     def openProxyConnection(self, request):
-        url = request.first_line.strip().split(' ', 1)[-1]
+        first_line = request.first_line.strip()
+        if not ' ' in first_line:
+            raise ValueError, 'Malformed request'
+        command, url = first_line.split(' ', 1)
         pos = url.rfind(' HTTP/')
         if pos >= 0:
+            protocol = url[pos + 1:]
             url = url[:pos].rstrip()
+        else:
+            protocol = 'HTTP/1.0'
         if url.startswith('http://'):
-            host = url[7:].split('/', 1)[0]
+            # Standard proxy
+            urlpart = url[7:]
+            if '/' in urlpart:
+                host, path = url[7:].split('/', 1)
+                path = '/' + path
+            else:
+                host = urlpart
+                path = '/'
         else:
+            # Transparent proxy
             host = request.headers.get('HOST')
+            path = url
         if not host:
-            raise NotImplementedError, 'Not a full HTTP proxy'
+            raise ValueError, 'Request not supported'
 
         if ':' in host:
             host, port = host.split(':')
@@ -1024,7 +1042,6 @@
             port = 80
 
         obs = self._obs
-
         if obs is not None:
             eo = EndpointObserver(obs, 0)
             buf = HTTPProxyResponseBuffer(self, eo)
@@ -1033,8 +1050,13 @@
         self._response_buffers.append(buf)
 
         ep = ForwardingEndpoint()  # connects server to buf (to self)
+        ep.write('%s %s %s\r\n' % (command, path, protocol))
+        ep.write(request.header)
+        ep.write('\r\n')
         ep.set_dests((buf,))
         ep.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+        print 'connecting to', repr((host, port))
+        print repr(request.header_plus)
         ep.connect((host, port))
 
 
@@ -1075,8 +1097,10 @@
 
 def usage():
     print COPYRIGHT
+    print 'Utility for monitoring TCP and HTTP connections'
     print 'Simple usage: tcpwatch -L listen_port:dest_hostname:dest_port'
     print """
+Forwarded connection setup:
   -L <listen_port>:<dest_port>
      Set up a local forwarded connection
   -L <listen_port>:<dest_host>:<dest_port>
@@ -1084,10 +1108,13 @@
   -L <listen_host>:<listen_port>:<dest_host>:<dest_port>
      Set up a forwarded connection to a specified host, bound to an interface
 
+HTTP setup:
   -h (or --http) Parse as HTTP, splitting up multi-transaction connections
   -p [<listen_host>:]<listen_port> Run an HTTP proxy (implies -h)
+
+Output options:
+  -n No color in GUI (faster and consumes less RAM)
   -s Output to stdout instead of a Tkinter window
-  -n No color in GUI (consumes less memory)
 """
     sys.exit()
 
@@ -1102,6 +1129,7 @@
     optlist, extra = getopt.getopt(args, 'hL:np:s', ['help', 'http'])
 
     fwd_params = []
+    proxy_params = []
     obs_factory = None
     show_config = 0
     split_http = 0
@@ -1117,20 +1145,18 @@
         elif option == '-s':
             show_config = 1
             obs_factory = StdoutObserver
-##        elif option == '-p':
-##            # HTTP proxy (also turns on -h)
-##            info = value.split(':')
-##            listen_host = ''
-##            if len(info) == 1:
-##                listen_port = int(info[0])
-##            elif len(info) == 2:
-##                listen_host = info[0]
-##                listen_port = int(info[1])
-##            else:
-##                usageError('-p requires a port or a host:port parameter')
-##            proxy_params.append(
-##                (listen_host, listen_port, '(HTTP)', 0))
-##            split_http = 1
+        elif option == '-p':
+            # HTTP proxy
+            info = value.split(':')
+            listen_host = ''
+            if len(info) == 1:
+                listen_port = int(info[0])
+            elif len(info) == 2:
+                listen_host = info[0]
+                listen_port = int(info[1])
+            else:
+                usageError('-p requires a port or a host:port parameter')
+            proxy_params.append((listen_host, listen_port))
         elif option == '-L':
             info = value.split(':')
             listen_host = ''
@@ -1152,37 +1178,55 @@
             fwd_params.append(
                 (listen_host, listen_port, dest_host, dest_port))
 
-    if not fwd_params:
+    if not fwd_params and not proxy_params:
         usage()
 
-    config_info = '\n'.join(map(
-        lambda args: 'Forwarding %s:%d -> %s:%d' % args, fwd_params))
+    config_info_lines = []
+    title_lst = []
+    if fwd_params:
+        config_info_lines.extend(map(
+            lambda args: 'Forwarding %s:%d -> %s:%d' % args, fwd_params))
+        title_lst.extend(map(
+            lambda args: '%s:%d -> %s:%d' % args, fwd_params))
+    if proxy_params:
+        config_info_lines.extend(map(
+            lambda args: 'HTTP proxy listening on %s:%d' % args, proxy_params))
+        title_lst.extend(map(
+            lambda args: '%s:%d -> proxy' % args, proxy_params))
     if split_http:
-        config_info += '\n\n*** HTTP connection splitting enabled. ***'
+        config_info_lines.append('HTTP connection splitting enabled.')
+    config_info = '\n'.join(config_info_lines)
+    titlepart = ', '.join(title_lst)
 
     if obs_factory is None:
         # Use Tk by default.
-        obs_factory = setupTk(fwd_params, config_info, colorized)
+        obs_factory = setupTk(titlepart, config_info, colorized)
 
+    chosen_factory = obs_factory
     if split_http:
         def _factory(fci, sub_factory=obs_factory):
             return HTTPConnectionSplitter(sub_factory, fci)
-        obs_factory = _factory
+        chosen_factory = _factory
     
     services = []
     try:
         for params in fwd_params:
-            args = params + (obs_factory,)
+            args = params + (chosen_factory,)
             s = ForwardingService(*args)
             services.append(s)
 
+        for params in proxy_params:
+            args = params + (obs_factory,)  # Don't parse HTTP twice.
+            s = HTTPProxyService(*args)
+            services.append(s)
+
         if show_config:
             print config_info
 
         try:
             asyncore.loop(timeout=1.0)
         except KeyboardInterrupt:
-            print >> sys.stderr, 'tcpwatch finished.'
+            print >> sys.stderr, 'TCPWatch finished.'
     finally:
         for s in services:
             s.close()