I offer the following implementations of keys() and items() for Z Publisher Request objects: def keys(self): keys = {} for key in self.environ.keys(): if isCGI_NAME(key) or key[:5] == 'HTTP_': keys[key] = 1 keys.update(self.other) lasturl = "" for n in "0123456789": key = "URL%s"%n try: if lasturl != self[key]: keys[key] = 1 lasturl = self[key] else: break except KeyError: pass for n in "0123456789": key = "BASE%s"%n try: if lasturl != self[key]: keys[key] = 1 lasturl = self[key] else: break except KeyError: pass return keys.keys() def items(self): result = [] for k in self.keys(): result.append((k, self[k])) return result They probably aren't useful in general settings, but are useful for people like myself who aren't familiar with what's in a Request and want to explore a bit. Also, when using this, I discovered what may be a bug. It's at least a feature. I am using ZopeHTTPServer. When I examine the REQUEST argument of a URL, among other things I find PATH_INFO: '/check_request' PATH_TRANSLATED: '/home/dolphin/skip/src/Zope-1.9.0b3-src/check_request' SCRIPT_NAME: '/' URL: 'http://127.0.0.2:8043/check_request' I suppose in a Z Publisher context the distinction between elements of PATH_INFO and elements of SCRIPT_NAME is fuzzy, but it seems somehow more intuitive to me that I should see PATH_INFO: '' PATH_TRANSLATED: '/home/dolphin/skip/src/Zope-1.9.0b3-src' SCRIPT_NAME: '/check_request' URL: 'http://127.0.0.2:8043/check_request' instead. In a CGI context you always have a non-empty SCRIPT_NAME. You might or might not have a non-empty PATH_INFO. Given that SCRIPT_NAME and PATH_INFO are holdovers from CGI, I think it makes sense to pile everything into SCRIPT_NAME and leave PATH_INFO empty. Skip Montanaro | Mojam: "Uniting the World of Music" http://www.mojam.com/ skip@calendar.com | Musi-Cal: http://concerts.calendar.com/ 518-372-5583