Received: with LISTAR (v1.0.0; list gopher); Thu, 19 Jul 2001 00:16:38 -0500 (EST) Return-Path: Delivered-To: gopher@complete.org Received: from smtp1.chorus.net (smtp1.chorus.net [216.165.129.132]) by pi.glockenspiel.complete.org (Postfix) with ESMTP id 36F963B80D for ; Thu, 19 Jul 2001 00:16:37 -0500 (EST) Received: from honker.jon.net (mail@a21-43.madison.chorus.net [216.165.149.43]) by smtp1.chorus.net (8.9.3+Sun/8.9.3/Chorus Networks) with ESMTP id AAA28503 for ; Thu, 19 Jul 2001 00:16:20 -0500 (CDT) Received: from localhost ([127.0.0.1] helo=honker.jon.net ident=jnelson) by honker.jon.net with esmtp (Exim 3.22 #1 (Debian)) id 15N6Ac-0005Ox-00 for ; Thu, 19 Jul 2001 00:16:18 -0500 X-Mailer: exmh version 2.3.1 01/18/2001 (debian 2.3.1-1) with nmh-1.0.4+dev To: gopher@complete.org Subject: [gopher] Re: PyGS - a Python Gopher Server... In-Reply-To: Your message of "Mon, 09 Jul 2001 08:05:07 CDT." <20010709080507.A8009@volkerding.charter.net> References: <20010709080507.A8009@volkerding.charter.net> Mime-Version: 1.0 Content-type: text/plain Date: Thu, 19 Jul 2001 00:16:18 -0500 From: Jon Nelson Message-Id: Content-Transfer-Encoding: 8bit X-archive-position: 194 X-listar-version: Listar v1.0.0 Sender: gopher-bounce@complete.org Errors-to: gopher-bounce@complete.org X-original-sender: jdnelson@chorus.net Precedence: bulk Reply-to: gopher@complete.org List-help: List-unsubscribe: List-software: Listar version 1.0.0 X-List-ID: Gopher List-subscribe: List-owner: List-post: List-archive: X-list: gopher > > Hi all, > I've finally gotten PyGS, the Python Gopher Server that I've been > tinkering with for some time to a point where it actually dishes stuff out > semi-consistently. Hey. I also wrote a python gopher server (quite a while ago). I haven't had time to mess with it, but it's yours to take a look at if you like. If you think any of it is worth keeping (I hope so), then let's talk about collaboration on a merged project? -- Attached file included as plaintext by Listar -- -- File: Log.py -- Desc: Log.py import sys LOG_FATAL = 0 LOG_ERROR = 1 LOG_WARN = 2 LOG_ALERT = 3 LOG_INFO = 4 LOG_DEBUG = 5 class Log: def __init__(self, logfile=sys.stderr, loglevel=LOG_INFO): self.logfile = logfile self.loglevel = loglevel def log (self, msg, level=LOG_INFO): "just prints its parameter" if level == LOG_FATAL: self.logfile.write("F:" + msg + "\n") sys.exit(1) elif level == LOG_ERROR: self.logfile.write("E:" + msg + "\n") elif level == LOG_WARN: if level <= self.loglevel: self.logfile.write("W:" + msg + "\n") elif level == LOG_ALERT: if level <= self.loglevel: self.logfile.write("A:" + msg + "\n") elif level == LOG_INFO: if level <= self.loglevel: self.logfile.write("I:" + msg + "\n") elif level == LOG_DEBUG: if level <= self.loglevel: self.logfile.write("D:" + msg + "\n") else: self.logfile.write("?:" + msg + "\n") -- Attached file included as plaintext by Listar -- -- File: gopherd.py -- Desc: gopherd.py #! /usr/bin/python import SocketServer,os,re,string,signal,socket,sys,threading,os.path,time from stat import * from Log import * log=None VERSION="$Id$" sighup_flag=0 sigterm_flag=0 myLock = threading.Lock() DOCROOT="" request_counter = 0 def dirsplit(base): try: files = os.listdir(base) except OSError, details: log("Unable to list directory (%s)" % base, LOG_ERROR) return ([],[]) dirs = [] for f in files[:]: if f[0] == '.': continue newfile = base + "/" + f try: if S_ISDIR(os.stat(newfile)[ST_MODE]): dirs.append(f) files.remove(f) except: files.remove(f) return(files, dirs) def alarm_handler(a,b): log("client timed out", LOG_ALERT) def hup_handler(a,b): global sighup_flag log('caught sighup...', LOG_ALERT) sighup_flag = 1 def term_handler(a,b): global sigterm_flag log('caught sigterm...', LOG_ALERT) sigterm_flag = 1 class myHandler(SocketServer.StreamRequestHandler): protocol = """ {File/Directory=0/1}{User Display String}\t{selector string}\t{host}\t{port}crlf 0 Item is a file 1 Item is a directory 2 Item is a CSO phone-book server 3 Error 4 Item is a BinHexed Macintosh file. 5 Item is DOS binary archive of some sort. Client must read until the TCP connection closes. Beware. 6 Item is a UNIX uuencoded file. 7 Item is an Index-Search server. 8 Item points to a text-based telnet session. 9 Item is a binary file! Client must read until the TCP connection closes. Beware. + Item is a redundant server T Item points to a text-based tn3270 session. g Item is a GIF format graphics file. h HTML, HyperText Markup Language I Item is some kind of image file. Client decides how to display. """ notes = """ for files: Note: Lines beginning with periods must be prepended with an extra period to ensure that the transmission is not terminated early. The client should strip extra periods at the beginning of the line. """ def getFile(self, path): # print "File: %s" % path try: f = open(self.docroot + path, 'rb') lines = f.readlines() f.close() return string.join(lines) except IOError: return "There was an error!" def fileType(self, path): root, ext = os.path.splitext(path) type = '9' if ext == '.jpg' or ext == '.jpeg': type = 'I' elif ext == '.gif': type = 'g' elif ext == '.html' or ext == '.htm': type = 'h' elif ext == '.txt' or ext == '.text': type = '0' return type def getDirectory(self, path): # print "Dir: %s" % path files, dirs = dirsplit(self.docroot + path) response = "" for i in files: response = response + "%c%s\t%s/%s\t%s\t%d\r\n" % (self.fileType(i), i, path, i, self.host, self.port) for i in dirs: response = response + "1%s\t%s/%s/\t%s\t%d\r\n" % (i, path, i, self.host, self.port) return response def start(self, blah): return self.getDirectory("") pass def respond(self, response): # make response pretty (ie, lines that start w/periods, etc....) response = response + ".\r\n" try: self.wfile.write(response) log("[%d] Wrote %d bytes." % (self.counter, len(response)), LOG_INFO) except socket.error, details: log("[%d] Socket error %s writing to client ." % (self.counter, str(details)), LOG_ALERT) # print response return def handle_error(request, client_address): request.close() SocketServer.BaseRequestHandler.handle_error(request, client_address) def handle(self): global request_counter, myLock, DOCROOT myLock.acquire() request_counter = request_counter + 1 self.counter = request_counter myLock.release() self.docroot = DOCROOT self.host, self.port = self.connection.getsockname() peerhost, peerport = self.client_address log("[%d] Handling request from %s:%d" % (self.counter, peerhost, peerport), LOG_INFO) signal.alarm(15) try: data = self.rfile.readline() signal.alarm(0) except socket.error: log("[%d] Socket error receiving from client." % self.counter, LOG_ALERT) return if not data: log("[%d] No response from client." % self.counter, LOG_ALERT) self.respond('400 ERR no data recieved.\n') return if string.find(data, '..') != -1: log("[%d] Illegal Request." % (self.counter), LOG_ALERT) self.respond('400 ERR Illegal request.\n') return # log("[%d] Attempting to match request: '%s'" % (self.counter, data), LOG_INFO) START_RE = re.compile(r'^(\B|/)\r\n$') FILE_RE = re.compile(r'^(.+)\r\n$') DIR_RE = re.compile(r'^(.+)/\r\n$') mycommands = [ (START_RE, self.start, {}), \ (DIR_RE, self.getDirectory, {}),\ (FILE_RE, self.getFile, {})\ ] for c in mycommands: myre, myfunc, myargs = c m = myre.match(data) if m: self.file = m.group(1) log('[%d] Matched request for "%s"' % (self.counter, self.file), LOG_INFO) response = apply(myfunc, m.groups(), myargs) if response: self.respond(response) return log("[%d] Unable to match request. (\"%s\")" % (self.counter, data), LOG_ALERT) self.respond('400 ERR Unable to match request.\n') return class MyServer(SocketServer.TCPServer): pass def main(): global log global sighup_flag global sigterm_flag global VERSION global DOCROOT # LOGFILE='mylog' # myLog = Log(open(LOGFILE, "a+", 0), LOG_INFO) myLog = Log(sys.stderr, LOG_INFO) log = myLog.log if len(sys.argv) != 2: log("Insufficient parameters.", LOG_FATAL) host = "" # bind to all addresses s = MyServer( (host, 1170 ), myHandler) signal.signal(signal.SIGALRM,alarm_handler) signal.signal(signal.SIGHUP,hup_handler) signal.signal(signal.SIGTERM,term_handler) sighup_flag = 1 DOCROOT=sys.argv[1] if DOCROOT[-1:] != "/": DOCROOT = DOCROOT + "/" log("Setting DOCROOT to %s" % DOCROOT, LOG_ALERT) log("Starting pygopherd server version %s." % VERSION, LOG_ALERT) # child = not os.fork() child = 1 if child: while 1: try: if sighup_flag: sighup_flag=0 # do something here if sigterm_flag: raise KeyboardInterrupt s.handle_request() except KeyboardInterrupt: s.socket.close() del s try: log("Shutting down.", LOG_ALERT) except: pass sys.exit(0) except socket.error, details: if details[0] == 4: # interrupted system call continue else: del s log("socket received an error: %s" % str(details), LOG_ERROR) sys.exit(1) except: del s log("aiieee! unknown error! closing socket first.", LOG_ERROR) raise sys.exit(0) # never gets here if __name__ == "__main__": main() -- Attached file included as plaintext by Listar -- Pound for pound, the amoeba is the most vicious animal on earth. Jon Nelson jnelson@boa.org