<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card id="index" title="Text File" newcontext="true">
<p>
Received: with LISTAR (v1.0.0; list gopher);
 Thu, 19 Jul 2001 00:16:38 -0500 (EST)
Return-Path: &lt;jdnelson@chorus.net&gt;
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 &lt;gopher@complete.org&gt;; 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 &lt;gopher@complete.org&gt;; 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 &lt;gopher@complete.org&gt;; 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 &quot;Mon, 09 Jul 2001 08:05:07 CDT.&quot;
             &lt;20010709080507.A8009@volkerding.charter.net&gt;
References: &lt;20010709080507.A8009@volkerding.charter.net&gt;
Mime-Version: 1.0
Content-type: text/plain
Date: Thu, 19 Jul 2001 00:16:18 -0500
From: Jon Nelson &lt;jdnelson@chorus.net&gt;
Message-Id: &lt;E15N6Ac-0005Ox-00@honker.jon.net&gt;
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: &lt;mailto:listar@complete.org?Subject=help&gt;
List-unsubscribe: &lt;mailto:gopher-request@complete.org?Subject=unsubscribe&gt;
List-software: Listar version 1.0.0
X-List-ID: Gopher &lt;gopher.complete.org&gt;
List-subscribe: &lt;mailto:gopher-request@complete.org?Subject=subscribe&gt;
List-owner: &lt;mailto:jgoerzen@complete.org&gt;
List-post: &lt;mailto:gopher@complete.org&gt;
List-archive: &lt;http://www.complete.org/mailinglists/archives/&gt;
X-list: gopher
</p>
<p>&gt;
&gt; Hi all,
&gt; 	I&#x27;ve finally gotten PyGS, the Python Gopher Server that I&#x27;ve been
&gt; tinkering with for some time to a point where it actually dishes stuff out
&gt; semi-consistently.
</p>
<p>Hey.
</p>
<p>I also wrote a python gopher server (quite a while ago).
I haven&#x27;t had time to mess with it, but it&#x27;s yours to take a look at if you
like.  If you think any of it is worth keeping (I hope so), then let&#x27;s
talk about collaboration on a merged project?
</p>
<p></p>
<p></p>
<p></p>
<p>-- Attached file included as plaintext by Listar --
-- File: Log.py
-- Desc: Log.py
</p>
<p>import sys
</p>
<p>LOG_FATAL = 0
LOG_ERROR = 1
LOG_WARN = 2
LOG_ALERT = 3
LOG_INFO = 4
LOG_DEBUG = 5
</p>
<p>class Log:
  def __init__(self, logfile=sys.stderr, loglevel=LOG_INFO):
    self.logfile = logfile
    self.loglevel = loglevel
</p>
<p>  def log (self, msg, level=LOG_INFO):
    &quot;just prints its parameter&quot;
</p>
<p>    if level == LOG_FATAL:
      self.logfile.write(&quot;F:&quot; + msg + &quot;\n&quot;)
      sys.exit(1)
    elif level == LOG_ERROR:
      self.logfile.write(&quot;E:&quot; + msg + &quot;\n&quot;)
    elif level == LOG_WARN:
      if level &lt;= self.loglevel:
        self.logfile.write(&quot;W:&quot; + msg + &quot;\n&quot;)
    elif level == LOG_ALERT:
      if level &lt;= self.loglevel:
        self.logfile.write(&quot;A:&quot; + msg + &quot;\n&quot;)
    elif level == LOG_INFO:
      if level &lt;= self.loglevel:
        self.logfile.write(&quot;I:&quot; + msg + &quot;\n&quot;)
    elif level == LOG_DEBUG:
      if level &lt;= self.loglevel:
        self.logfile.write(&quot;D:&quot; + msg + &quot;\n&quot;)
    else:
      self.logfile.write(&quot;?:&quot; + msg + &quot;\n&quot;)
</p>
<p></p>
<p></p>
<p>-- Attached file included as plaintext by Listar --
-- File: gopherd.py
-- Desc: gopherd.py
</p>
<p>#! /usr/bin/python
import SocketServer,os,re,string,signal,socket,sys,threading,os.path,time
from stat import *
from Log import *
</p>
<p>log=None
VERSION=&quot;$Id$&quot;
sighup_flag=0
sigterm_flag=0
myLock = threading.Lock()
DOCROOT=&quot;&quot;
</p>
<p>request_counter = 0
</p>
<p>def dirsplit(base):
  try:
    files = os.listdir(base)
  except OSError, details:
    log(&quot;Unable to list directory (%s)&quot; % base, LOG_ERROR)
    return ([],[])
</p>
<p>  dirs = []
</p>
<p>  for f in files[:]:
    if f[0] == &#x27;.&#x27;:
      continue
    newfile = base + &quot;/&quot; + f
    try:
      if S_ISDIR(os.stat(newfile)[ST_MODE]):
        dirs.append(f)
        files.remove(f)
    except:
      files.remove(f)
  return(files, dirs)
</p>
<p>def alarm_handler(a,b):
  log(&quot;client timed out&quot;, LOG_ALERT)
</p>
<p>def hup_handler(a,b):
  global sighup_flag
  log(&#x27;caught sighup...&#x27;, LOG_ALERT)
  sighup_flag = 1
</p>
<p>def term_handler(a,b):
  global sigterm_flag
  log(&#x27;caught sigterm...&#x27;, LOG_ALERT)
  sigterm_flag = 1
</p>
<p>class myHandler(SocketServer.StreamRequestHandler):
  protocol = &quot;&quot;&quot;
  {File/Directory=0/1}{User Display String}\t{selector string}\t{host}\t{port}crlf
</p>
<p>   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!
</p>
<p>       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.
  &quot;&quot;&quot;
</p>
<p>  notes = &quot;&quot;&quot;
  for files:
</p>
<p>  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.
</p>
<p>  &quot;&quot;&quot;
</p>
<p>  def getFile(self, path):
#    print &quot;File: %s&quot; % path
    try:
      f = open(self.docroot + path, &#x27;rb&#x27;)
      lines = f.readlines()
      f.close()
      return string.join(lines)
    except IOError:
      return &quot;There was an error!&quot;
</p>
<p>  def fileType(self, path):
    root, ext = os.path.splitext(path)
    type = &#x27;9&#x27;
    if ext == &#x27;.jpg&#x27; or ext == &#x27;.jpeg&#x27;:
      type = &#x27;I&#x27;
    elif ext == &#x27;.gif&#x27;:
      type = &#x27;g&#x27;
    elif ext == &#x27;.html&#x27; or ext == &#x27;.htm&#x27;:
      type = &#x27;h&#x27;
    elif ext == &#x27;.txt&#x27; or ext == &#x27;.text&#x27;:
      type = &#x27;0&#x27;
    return type
</p>
<p>  def getDirectory(self, path):
#    print &quot;Dir: %s&quot; % path
</p>
<p>    files, dirs = dirsplit(self.docroot + path)
    response = &quot;&quot;
    for i in files:
      response = response + &quot;%c%s\t%s/%s\t%s\t%d\r\n&quot; % (self.fileType(i), i, path, i, self.host, self.port)
    for i in dirs:
      response = response + &quot;1%s\t%s/%s/\t%s\t%d\r\n&quot; % (i, path, i, self.host, self.port)
    return response
</p>
<p>  def start(self, blah):
    return self.getDirectory(&quot;&quot;)
    pass
</p>
<p>  def respond(self, response):
    # make response pretty (ie, lines that start w/periods, etc....)
    response = response + &quot;.\r\n&quot;
    try:
      self.wfile.write(response)
      log(&quot;[%d] Wrote %d bytes.&quot; % (self.counter, len(response)), LOG_INFO)
    except socket.error, details:
      log(&quot;[%d] Socket error %s writing to client .&quot; % (self.counter, str(details)), LOG_ALERT)
#    print response
</p>
<p>    return
</p>
<p>  def handle_error(request, client_address):
    request.close()
    SocketServer.BaseRequestHandler.handle_error(request, client_address)
</p>
<p>  def handle(self):
    global request_counter, myLock, DOCROOT
</p>
<p>    myLock.acquire()
    request_counter = request_counter + 1
    self.counter = request_counter
    myLock.release()
</p>
<p>    self.docroot = DOCROOT
</p>
<p>    self.host, self.port = self.connection.getsockname()
    peerhost, peerport = self.client_address
    log(&quot;[%d] Handling request from %s:%d&quot; % (self.counter, peerhost, peerport), LOG_INFO)
</p>
<p>    signal.alarm(15)
    try:
      data = self.rfile.readline()
      signal.alarm(0)
    except socket.error:
      log(&quot;[%d] Socket error receiving from client.&quot; % self.counter, LOG_ALERT)
      return
</p>
<p>    if not data:
      log(&quot;[%d] No response from client.&quot; % self.counter, LOG_ALERT)
      self.respond(&#x27;400 ERR no data recieved.\n&#x27;)
      return
</p>
<p>    if string.find(data, &#x27;..&#x27;) != -1:
      log(&quot;[%d] Illegal Request.&quot; % (self.counter), LOG_ALERT)
      self.respond(&#x27;400 ERR Illegal request.\n&#x27;)
      return
</p>
<p>#    log(&quot;[%d] Attempting to match request: &#x27;%s&#x27;&quot; % (self.counter, data), LOG_INFO)
</p>
<p>    START_RE = re.compile(r&#x27;^(\B|/)\r\n$&#x27;)
    FILE_RE = re.compile(r&#x27;^(.+)\r\n$&#x27;)
    DIR_RE = re.compile(r&#x27;^(.+)/\r\n$&#x27;)
    mycommands = [ (START_RE, self.start, {}), \
                   (DIR_RE, self.getDirectory, {}),\
                   (FILE_RE, self.getFile, {})\
                 ]
</p>
<p>    for c in mycommands:
      myre, myfunc, myargs = c
      m = myre.match(data)
      if m:
        self.file = m.group(1)
        log(&#x27;[%d] Matched request for &quot;%s&quot;&#x27; % (self.counter, self.file), LOG_INFO)
        response = apply(myfunc, m.groups(), myargs)
        if response:
          self.respond(response)
        return
</p>
<p>    log(&quot;[%d] Unable to match request. (\&quot;%s\&quot;)&quot; % (self.counter, data), LOG_ALERT)
    self.respond(&#x27;400 ERR Unable to match request.\n&#x27;)
    return
</p>
<p>class MyServer(SocketServer.TCPServer):
  pass
</p>
<p>def main():
  global log
  global sighup_flag
  global sigterm_flag
  global VERSION
  global DOCROOT
</p>
<p>#  LOGFILE=&#x27;mylog&#x27;
#  myLog = Log(open(LOGFILE, &quot;a+&quot;, 0), LOG_INFO)
  myLog = Log(sys.stderr, LOG_INFO)
  log = myLog.log
  if len(sys.argv) != 2:
    log(&quot;Insufficient parameters.&quot;, LOG_FATAL)
  host = &quot;&quot; # bind to all addresses
  s = MyServer( (host, 1170 ), myHandler)
</p>
<p>  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:] != &quot;/&quot;:
    DOCROOT = DOCROOT + &quot;/&quot;
  log(&quot;Setting DOCROOT to %s&quot; % DOCROOT, LOG_ALERT)
  log(&quot;Starting pygopherd server version %s.&quot; % 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(&quot;Shutting down.&quot;, LOG_ALERT)
        except:
          pass
        sys.exit(0)
      except socket.error, details:
        if details[0] == 4: # interrupted system call
          continue
        else:
          del s
          log(&quot;socket received an error: %s&quot; % str(details), LOG_ERROR)
          sys.exit(1)
      except:
        del s
        log(&quot;aiieee!  unknown error!  closing socket first.&quot;, LOG_ERROR)
        raise
  sys.exit(0)  # never gets here
</p>
<p>if __name__ == &quot;__main__&quot;:
  main()
</p>
<p></p>
<p>-- Attached file included as plaintext by Listar --
</p>
<p>Pound for pound, the amoeba is the most vicious animal on earth.
</p>
<p>Jon Nelson
jnelson@boa.org
</p>
<p></p>
<p></p>
</card>
</wml>
