[Zope-CVS] CVS: Packages/tcpwatch - setup.py:1.2 tcpwatch.py:1.2

Shane Hathaway shane@zope.com
Tue, 27 May 2003 10:12:40 -0400


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

Modified Files:
	setup.py tcpwatch.py 
Log Message:
Merged tseaver-logging-branch with minor changes:

  - Indicate to the user that logging is enabled

  - Bumped version to 1.2

  - Updated TCPWatch homepage URL



=== Packages/tcpwatch/setup.py 1.1 => 1.2 ===
--- Packages/tcpwatch/setup.py:1.1	Sat May 24 13:14:38 2003
+++ Packages/tcpwatch/setup.py	Tue May 27 10:12:40 2003
@@ -4,11 +4,11 @@
 from distutils.sysconfig import get_python_lib
 
 setup( name="tcpwatch"
-     , version="1.1"
+     , version="1.2"
      , description="Connection forwarder / HTTP proxy"
      , author="Shane Hathaway"
      , author_email="shane@zope.com"
-     , url="http://cvs.zope.org/Packages/tcpwatch/"
+     , url="http://hathaway.freezope.org/Software/TCPWatch"
      , py_modules=[ 'tcpwatch' ]
      , scripts=[ 'launch' ]
      )


=== Packages/tcpwatch/tcpwatch.py 1.1 => 1.2 ===
--- Packages/tcpwatch/tcpwatch.py:1.1	Sat Mar  2 16:18:21 2002
+++ Packages/tcpwatch/tcpwatch.py	Tue May 27 10:12:40 2003
@@ -73,12 +73,13 @@
 #############################################################################
 from __future__ import nested_scopes
 
-VERSION = '1.1'
+VERSION = '1.2'
 COPYRIGHT = (
-    'TCPWatch %s Copyright 2001, 2002 Shane Hathaway, Zope Corporation'
+    'TCPWatch %s Copyright 2001 Shane Hathaway, Zope Corporation'
     % VERSION)
 
 import sys
+import os
 import socket
 import asyncore
 import getopt
@@ -385,6 +386,74 @@
         sys.stdout.flush()
 
 
+class RecordingObserver (BasicObserver):
+    """Log request to a file.
+
+    o Filenames mangle connection and transaction numbers from the
+      ForwardedConnectionInfo passed as 'fci'.
+
+    o Decorates an underlying observer, created via the passed 'sub_factory'.
+
+    o Files are created in the supplied 'record_directory'.
+
+    o Unless suppressed, log response and error to corresponding files.
+    """
+    _ERROR_SOURCES = ('Server', 'Client')
+
+    # __implements__ = IConnectionObserver
+
+    def __init__(self, fci, sub_factory, record_directory,
+                 record_prefix='watch', record_responses=1, record_errors=1):
+        self._connection_number = fci.connection_number
+        self._transaction = fci.transaction
+        self._decorated = sub_factory(fci)
+        self._directory = record_directory
+        self._prefix = record_prefix
+        self._response = record_responses
+        self._errors = record_errors
+
+    def connected(self, from_client):
+        """See IConnectionObserver.
+        """
+        self._decorated.connected(from_client)
+
+    def received(self, data, from_client):
+        """See IConnectionObserver.
+        """
+        if from_client or self._response:
+            extension = from_client and 'request' or 'response'
+            file = self._openForAppend(extension=extension)
+            file.write(data)
+            file.close()
+        self._decorated.received(data, from_client)
+
+    def closed(self, from_client):
+        """See IConnectionObserver.
+        """
+        self._decorated.closed(from_client)
+
+    def error(self, from_client, type, value):
+        """See IConnectionObserver.
+        """
+        if self._errors:
+            file = self._openForAppend(extension='errors')
+            file.write('(%s) %s: %s\n' % (self._ERROR_SOURCES[from_client],
+                                          type, value))
+        self._decorated.error(from_client, type, value)
+
+    def _openForAppend(self, extension):
+        """Open a file with the given extension for appending.
+
+        o File should be in the directory indicated by self._directory.
+
+        o File should have a filename '<prefix>_<conn #>.<extension>'.
+        """
+        filename = '%s_%07d%02d.%s' % (self._prefix, self._connection_number, 
+                                       self._transaction, extension)
+        fqpath = os.path.join(self._directory, filename)
+        return open(fqpath, 'a')
+
+
 #############################################################################
 #
 # Tkinter GUI
@@ -1198,6 +1267,20 @@
   -c Extra color (colorizes escaped characters)
   -r Show carriage returns (ASCII 13)
   -s Output to stdout instead of a Tkinter window
+
+Recording options:
+  -R (or --record-directory) <path>
+    Write recorded data to <path>.  By default, creates request and
+    response files for each request, and writes a corresponding error file
+    for any error detected by tcpwatch.  Requires either running as an
+    HTTP proxy ('-p'), or with splitting turned on ('-h').
+  --record-prefix=<prefix>
+    Use <prefix> as the file prefix for logged request / response / error
+    files (defaults to 'watch').
+  --no-record-responses
+    Suppress writing '.response' files.
+  --no-record-errors
+    Suppress writing '.error' files.
 """
     sys.exit()
 
@@ -1210,7 +1293,15 @@
 def main(args):
     global show_cr
 
-    optlist, extra = getopt.getopt(args, 'chL:np:rs', ['help', 'http'])
+    try:
+        optlist, extra = getopt.getopt(args, 'chL:np:rsR:',
+                                       ['help', 'http',
+                                        'record-directory=', 'record-prefix='
+                                        'no-record-responses',
+                                        'no-record-errors',
+                                       ])
+    except getopt.GetoptError, msg:
+        usageError(msg)
 
     fwd_params = []
     proxy_params = []
@@ -1218,6 +1309,11 @@
     show_config = 0
     split_http = 0
     colorized = 1
+    record_directory = None
+    record_prefix = 'watch'
+    record_responses = 1
+    record_errors = 1
+    recording = {}
 
     for option, value in optlist:
         if option == '--help':
@@ -1266,10 +1362,21 @@
                 usageError('-L requires 2, 3, or 4 colon-separated parameters')
             fwd_params.append(
                 (listen_host, listen_port, dest_host, dest_port))
+        elif option == '-R' or option == '--record-directory':
+            record_directory = value
+        elif option == '--record-prefix':
+            record_prefix = value
+        elif option == '--no-record-responses':
+            record_responses = 0
+        elif option == '--no-record-errors':
+            record_errors = 0
 
     if not fwd_params and not proxy_params:
         usage()
 
+    if record_directory and not split_http and not proxy_params:
+        usageError( 'Recording requires enabling either proxy or splitting.' )
+
     # Prepare the configuration display.
     config_info_lines = []
     title_lst = []
@@ -1285,12 +1392,26 @@
             lambda args: '%s:%d -> proxy' % args, proxy_params))
     if split_http:
         config_info_lines.append('HTTP connection splitting enabled.')
+    if record_directory:
+        config_info_lines.append(
+            'Recording to directory %s.' % record_directory)
     config_info = '\n'.join(config_info_lines)
     titlepart = ', '.join(title_lst)
 
     if obs_factory is None:
         # If no observer factory has been specified, use Tkinter.
         obs_factory = setupTk(titlepart, config_info, colorized)
+
+    if record_directory:
+        def _decorateRecorder(fci, sub_factory=obs_factory,
+                              record_directory=record_directory,
+                              record_prefix=record_prefix,
+                              record_responses=record_responses,
+                              record_errors=record_errors):
+            return RecordingObserver(fci, sub_factory, record_directory,
+                                     record_prefix, record_responses,
+                                     record_errors)
+        obs_factory = _decorateRecorder
 
     chosen_factory = obs_factory
     if split_http: