#!/usr/bin/env python # -*- coding: utf-8 -*- """Allows recording of all http traffic in a way that can (hopefully) be replayed later """ # Copyright 2002, 2003 St James Software # # This file is part of jToolkit. # # jToolkit is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # jToolkit is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with jToolkit; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from jToolkit import errors from jToolkit import timecache import urllib import random import os # the req.parsed_uri from the apache module URI_SCHEME = 0 URI_HOSTINFO = 1 URI_USER = 2 URI_PASSWORD = 3 URI_HOSTNAME = 4 URI_PORT = 5 URI_PATH = 6 URI_QUERY = 7 URI_FRAGMENT = 8 # TODO: add docs # TODO: add error message logging # TODO: add session logging # TODO: improve argument logging class HTTPRecorder(errors.ErrorHandler): def __init__(self, instance): errors.ErrorHandler.__init__(self, instance) self.pathcache = {} self.duppaths = {} self.reqtimes = {} self.basedir = instance.logrecord def getresponsefile(self, pathwords): return os.path.join(self.basedir, *pathwords) def getresponseurl(self, pathwords): pathwords = pathwords[:] pathwords[-1] = urllib.quote_plus(pathwords[-1]) return "." + '/'.join(pathwords) def getresponsepath(self, req): uri = req.parsed_uri if uri[URI_HOSTNAME]: pathwords = '/'.join((uri[URI_HOSTNAME], uri[URI_PATH])) else: pathwords = uri[URI_PATH] pathwords = pathwords.split('/') if pathwords[-1] == '': pathwords[-1] = 'index.htm' if uri[URI_QUERY]: querypart = uri[URI_QUERY] querypart = urllib.unquote_plus(querypart) querypart = urllib.quote_plus("?"+querypart) pathwords[-1] += querypart return pathwords def openrequest(self, req): req.logresponse = self.logresponse req.allowetag = False pathwords = self.getresponsepath(req) pathkey = tuple(pathwords) if hasattr(req, "record_timestamp"): self.logrequestmessage("request processed again: %s, %s
\n" % (req.uri, req.record_timestamp)) else: req.record_timestamp = "%x%04x" % (req.request_time, random.randint(0,65535)) if pathkey in self.pathcache or os.path.isfile(self.getresponsefile(pathwords)): pathwords[-1] += urllib.quote_plus("&record_time=%s" % (req.record_timestamp)) if pathkey not in self.duppaths: self.duppaths[pathkey] = {} if not pathkey in self.pathcache: self.pathcache[pathkey] = pathwords if pathkey in self.reqtimes: self.duppaths[pathkey][self.reqtimes[pathkey]] = self.pathcache[pathkey] self.duppaths[pathkey][req.record_timestamp] = pathwords return pathwords else: self.pathcache[pathkey] = pathwords self.reqtimes[pathkey] = req.record_timestamp return pathwords def closerequest(self, req): pathwords = self.getresponsepath(req) pathkey = tuple(pathwords) if pathkey not in self.pathcache or not hasattr(req, "record_timestamp"): pathwords[-1] += urllib.quote_plus("&record_error=notfound") return pathwords if pathkey in self.duppaths: return self.duppaths[pathkey][req.record_timestamp] else: return self.pathcache[pathkey] def logrequest(self, req): pathwords = self.openrequest(req) responseurl = self.getresponseurl(pathwords) self.logrequestmessage("request %s
\n" % (responseurl, req.the_request)) def logrequestmessage(self, logmessage): requestslog = os.path.join(self.basedir, "requests.htm") self.writelog(requestslog, logmessage) def logresponse(self, req, response): pathwords = self.closerequest(req) responseurl = self.getresponseurl(pathwords) self.logrequestmessage("response %s
\n" % (responseurl, req.unparsed_uri)) responsefilename = self.getresponsefile(pathwords) responsedir = os.path.abspath(os.path.dirname(responsefilename)) if not os.path.isdir(responsedir): baseparts = os.path.abspath(self.basedir).split(os.path.sep) subparts = responsedir.split(os.path.sep)[len(baseparts):] currentpath = self.basedir for part in subparts: currentpath = os.path.join(currentpath, part) if not os.path.isdir(currentpath): os.mkdir(currentpath) try: responsefile = open(responsefilename,'w') except IOError, e: # TODO: handle filenames that are too long... self.logrequestmessage("IOError opening response filename %s: %s\n" % (responsefilename, e)) return if isinstance(response, unicode): responsefile.write(response.encode('utf8')) else: responsefile.write(response) responsefile.close()