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

Shane Hathaway shane@cvs.zope.org
Thu, 21 Feb 2002 12:23:59 -0500


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

Modified Files:
	tcpwatch 
Log Message:
Added docstrings, comments, and changed a variable name for clarity.

Also provided a "medium color" option which is now the default.


=== Packages/tcpwatch/tcpwatch 1.6 => 1.7 ===
 
-"""TCPWatch, a connection forwarder for monitoring connections.
+"""TCPWatch, a connection forwarder and HTTP proxy for monitoring connections.
 
 Requires Python 2.1 or above.
 """
@@ -74,7 +74,6 @@
 COPYRIGHT = ('TCPWatch %s Copyright 2002 Shane Hathaway, Zope Corporation'
              % VERSION)
 
-
 import sys
 import socket
 import asyncore
@@ -297,7 +296,7 @@
             who = 'server'
 
         t = time() - self._start
-        min, sec = divmod(int(t), 60)
+        min, sec = divmod(t, 60)
         self.write('[%02d:%06.3f - %s %s]\n' % (min, sec, who, m))
         self.flush()
 
@@ -498,14 +497,30 @@
                 BasicObserver.received(self, data, from_client)
                 return
             output = []
-            def append(ss, escaped, output=output,
-                       from_client=from_client, escape=escape):
-                if escaped:
-                    output.append((escape(ss), from_client
-                                   and 'clientesc' or 'serveresc'))
-                else:
-                    output.append((ss, from_client
-                                   and 'client' or 'server'))
+
+            extra_color = (self._colorized == 2)
+
+            if extra_color:
+                # 4 colors: Change the color client/server and escaped chars
+                def append(ss, escaped, output=output,
+                           from_client=from_client, escape=escape):
+                    if escaped:
+                        output.append((escape(ss), from_client
+                                       and 'clientesc' or 'serveresc'))
+                    else:
+                        output.append((ss, from_client
+                                       and 'client' or 'server'))
+            else:
+                # 2 colors: Only change color for client/server
+                segments = []
+                def append(ss, escaped, segments=segments,
+                           escape=escape):
+                    if escaped:
+                        segments.append(escape(ss))
+                    else:
+                        segments.append(ss)
+
+            # Escape the input data.
             was_escaped = 0
             start_idx = 0
             for idx in xrange(len(data)):
@@ -521,6 +536,11 @@
             if ss:
                 append(ss, was_escaped)
 
+            if not extra_color:
+                output.append((''.join(segments),
+                               from_client and 'client' or 'server'))
+
+            # Send output to the frame.
             self._output.extend(output)
             self._frame.updateConnection(self._id, output)
             if data.endswith('\n'):
@@ -586,6 +606,7 @@
 
 
 class StreamedReceiver:
+    """Accepts data up to a specific limit."""
 
     completed = 0
 
@@ -617,6 +638,7 @@
 
 
 class UnlimitedReceiver:
+    """Accepts data without limits."""
 
     completed = 0
 
@@ -627,6 +649,7 @@
 
 
 class ChunkedReceiver:
+    """Accepts all chunks."""
 
     chunk_remainder = 0
     control_line = ''
@@ -707,8 +730,7 @@
 
 
 class HTTPStreamParser:
-    """
-    A structure that parses the HTTP stream.
+    """A structure that parses the HTTP stream.
     """
 
     completed = 0    # Set once request is completed.
@@ -726,8 +748,8 @@
         self.is_a_request = is_a_request
 
     def received(self, data):
-        """
-        Receives the HTTP stream for one request.
+        """Receives the HTTP stream for one request.
+
         Returns the number of bytes consumed.
         Sets the completed flag once both the header and the
         body have been received.
@@ -768,9 +790,9 @@
 
 
     def parse_header(self, header_plus):
-        """
-        Parses the header_plus block of text (the headers plus the
-        first line of the request).
+        """Parses the header_plus block of text.
+
+        (header_plus is the headers plus the first line of the request).
         """
         index = header_plus.find('\n')
         if index >= 0:
@@ -819,8 +841,7 @@
 
 
     def get_header_lines(self):
-        """
-        Splits the header into lines, putting multi-line headers together.
+        """Splits the header into lines, putting multi-line headers together.
         """
         r = []
         lines = self.header.split('\n')
@@ -836,6 +857,8 @@
 
 
 class HTTPConnectionSplitter:
+    """Makes a new observer for each HTTP subconnection and forwards events.
+    """
 
     # __implements__ = IConnectionObserver
     req_index = 0
@@ -902,25 +925,27 @@
 
 
 class HTTPProxyResponseBuffer:
-    """Ensures that responses on a persistent HTTP connection occur
+    """Ensures that responses to a persistent HTTP connection occur
     in the correct order."""
 
     finished = 0
 
-    def __init__(self, proxy_conn, observers=()):
+    def __init__(self, proxy_conn, watching_streams=()):
         self.response_parser = HTTPStreamParser(0)
         self.proxy_conn = proxy_conn
-        self.observers = observers
-        self.held = []
+        self.watching_streams = watching_streams
+        self.held = []  # Data held in the buffer
 
     def _isMyTurn(self):
+        """Returns a true value if it's time for this response
+        to respond to the client."""
         bufs = self.proxy_conn._response_buffers
         if bufs:
             return (bufs[0] is self)
         return 1
 
     def write(self, data):
-        # Received data from the server.
+        """Receives data from the HTTP server to be sent back to the client."""
         while data:
             parser = self.response_parser
             if parser.completed:
@@ -930,16 +955,14 @@
             consumed = parser.received(data)
             fragment = data[:consumed]
             data = data[consumed:]
-            for o in self.observers:
-                o.write(fragment)
-            if not self._isMyTurn():
-                # Wait for earlier responses to finish.
-                self.held.append(fragment)
-                return
+            for s in self.watching_streams:
+                s.write(fragment)
+            self.held.append(fragment)
             self.flush()
-            self.proxy_conn.write(fragment)
 
     def flush(self):
+        """Flushes buffers and, if done, allows the next response to take over.
+        """
         if self.held and self._isMyTurn():
             data = ''.join(self.held)
             del self.held[:]
@@ -949,21 +972,25 @@
             if bufs and bufs[0] is self:
                 del bufs[0]
             if bufs:
-                bufs[0].flush()  # kick.
+                bufs[0].flush()  # kick!
 
     def close(self):
-        for o in self.observers:
-            o.close()
+        """The HTTP server closed the connection.
+        """
+        for s in self.watching_streams:
+            s.close()
         self.finished = 1
         self.flush()
 
     def error(self, t, v):
-        for o in self.observers:
-            o.error(t, v)
+        for s in self.watching_streams:
+            if hasattr(s, 'error'):
+                s.error(t, v)
 
 
 
 class HTTPProxyConnection (ForwardingEndpoint):
+    """A connection from a client to the proxy server"""
 
     _req_parser = None
     _transaction = 0
@@ -978,18 +1005,20 @@
         self._newRequest()
 
     def _newRequest(self):
+        """Starts a new request on a persistent connection."""
         if self._req_parser is None:
             self._req_parser = HTTPStreamParser(1)
         factory = self._obs_factory
         if factory is not None:
             fci = ForwardedConnectionInfo(self._counter, self._client_addr)
-            obs = factory(fci)
             self._transaction = self._transaction + 1
-            obs.transaction = self._transaction
+            fci.transaction = self._transaction
+            obs = factory(fci)
             self._obs = obs
             self.set_dests((EndpointObserver(obs, 1),))
 
     def received(self, data):
+        """Accepts data received from the client."""
         while data:
             parser = self._req_parser
             if parser is None:
@@ -1008,9 +1037,12 @@
                 self._req_parser = None
 
     def openProxyConnection(self, request):
+        """Parses the client connection and opens a connection to an
+        HTTP server.
+        """
         first_line = request.first_line.strip()
         if not ' ' in first_line:
-            raise ValueError, 'Malformed request'
+            raise ValueError, ('Malformed request: %s' % first_line)
         command, url = first_line.split(' ', 1)
         pos = url.rfind(' HTTP/')
         if pos >= 0:
@@ -1032,7 +1064,7 @@
             host = request.headers.get('HOST')
             path = url
         if not host:
-            raise ValueError, 'Request not supported'
+            raise ValueError, ('Request type not supported: %s' % url)
 
         if ':' in host:
             host, port = host.split(':')
@@ -1050,6 +1082,7 @@
 
         ep = ForwardingEndpoint()  # connects server to buf (to self)
         ep.write('%s %s %s\r\n' % (command, path, protocol))
+        # Duplicate the headers sent by the client.
         ep.write(request.header)
         ep.write('\r\n')
         ep.set_dests((buf,))
@@ -1059,6 +1092,7 @@
 
 
 class HTTPProxyService (asyncore.dispatcher):
+    """A minimal HTTP proxy server"""
 
     _counter = 0
 
@@ -1111,6 +1145,7 @@
 
 Output options:
   -n No color in GUI (faster and consumes less RAM)
+  -c Extra color (colorizes escaped characters)
   -s Output to stdout instead of a Tkinter window
 """
     sys.exit()
@@ -1123,7 +1158,7 @@
 
 def main(args):
 
-    optlist, extra = getopt.getopt(args, 'hL:np:s', ['help', 'http'])
+    optlist, extra = getopt.getopt(args, 'chL:np:s', ['help', 'http'])
 
     fwd_params = []
     proxy_params = []
@@ -1139,6 +1174,8 @@
             split_http = 1
         elif option == '-n':
             colorized = 0
+        elif option == '-c':
+            colorized = 2
         elif option == '-s':
             show_config = 1
             obs_factory = StdoutObserver
@@ -1155,6 +1192,7 @@
                 usageError('-p requires a port or a host:port parameter')
             proxy_params.append((listen_host, listen_port))
         elif option == '-L':
+            # TCP forwarder
             info = value.split(':')
             listen_host = ''
             dest_host = ''
@@ -1178,6 +1216,7 @@
     if not fwd_params and not proxy_params:
         usage()
 
+    # Prepare the configuration display.
     config_info_lines = []
     title_lst = []
     if fwd_params:
@@ -1196,22 +1235,25 @@
     titlepart = ', '.join(title_lst)
 
     if obs_factory is None:
-        # Use Tk by default.
+        # If no observer factory has been specified, use Tkinter.
         obs_factory = setupTk(titlepart, config_info, colorized)
 
     chosen_factory = obs_factory
     if split_http:
+        # Put an HTTPConnectionSplitter between the events and the output.
         def _factory(fci, sub_factory=obs_factory):
             return HTTPConnectionSplitter(sub_factory, fci)
         chosen_factory = _factory
     
     services = []
     try:
+        # Start forwarding services.
         for params in fwd_params:
             args = params + (chosen_factory,)
             s = ForwardingService(*args)
             services.append(s)
 
+        # Start proxy services.
         for params in proxy_params:
             args = params + (obs_factory,)  # Don't parse HTTP twice.
             s = HTTPProxyService(*args)
@@ -1220,6 +1262,7 @@
         if show_config:
             print config_info
 
+        # Run the main loop.
         try:
             asyncore.loop(timeout=1.0)
         except KeyboardInterrupt: