python: Added some actual documentation(!).
authorFredrik Tolf <fredrik@dolda2000.com>
Wed, 2 Mar 2011 09:33:57 +0000 (10:33 +0100)
committerFredrik Tolf <fredrik@dolda2000.com>
Wed, 2 Mar 2011 09:33:57 +0000 (10:33 +0100)
python/ashd/__init__.py
python/ashd/proto.py
python/ashd/util.py

index e69de29..c918ad6 100644 (file)
@@ -0,0 +1,5 @@
+"""Base module for ashd(7)-related fucntions.
+
+This module implements nothing. Please see the ashd.util or ashd.proto
+modules.
+"""
index 90ba012..92a21ca 100644 (file)
@@ -1,3 +1,12 @@
+"""Low-level protocol module for ashd(7)
+
+This module provides primitive functions that speak the raw ashd(7)
+protocol. Primarily, it implements the `req' class that is used to
+represent ashd requests. The functions it provides can also be used to
+create ashd handlers, but unless you require very precise control, the
+ashd.util module provides an easier-to-use interface.
+"""
+
 import os, socket
 import htlib
 
@@ -5,6 +14,30 @@ class protoerr(Exception):
     pass
 
 class req(object):
+    """Represents a single ashd request. Normally, you would not
+    create instances of this class manually, but receive them from the
+    recvreq function.
+
+    For the abstract structure of ashd requests, please see the
+    ashd(7) manual page. This class provides access to the HTTP
+    method, raw URL, HTTP version and rest string via the `method',
+    `url', `ver' and `rest' variables respectively. It also implements
+    a dict-like interface for case-independent access to the HTTP
+    headers. The raw headers are available as a list of (name, value)
+    tuples in the `headers' variable.
+
+    For responding, the response socket is available as a standard
+    Python stream object in the `sk' variable. Again, see the ashd(7)
+    manpage for what to receive and transmit on the response socket.
+
+    Note that instances of this class contain a reference to the live
+    socket used for responding to requests, which should be closed
+    when you are done with the request. The socket can be closed
+    manually by calling the close() method on this
+    object. Alternatively, this class implements the resource-manager
+    interface, so that it can be used in `with' statements.
+    """
+    
     def __init__(self, method, url, ver, rest, headers, fd):
         self.method = method
         self.url = url
@@ -15,9 +48,15 @@ class req(object):
         os.close(fd)
 
     def close(self):
+        "Close this request's response socket."
         self.sk.close()
 
     def __getitem__(self, header):
+        """Find a HTTP header case-insensitively. For example,
+        req["Content-Type"] returns the value of the content-type
+        header regardlessly of whether the client specified it as
+        "Content-Type", "content-type" or "Content-type".
+        """
         header = header.lower()
         for key, val in self.headers:
             if key.lower() == header:
@@ -25,6 +64,9 @@ class req(object):
         raise KeyError(header)
 
     def __contains__(self, header):
+        """Works analogously to the __getitem__ method for checking
+        header presence case-insensitively.
+        """
         header = header.lower()
         for key, val in self.headers:
             if key.lower() == header:
@@ -32,9 +74,25 @@ class req(object):
         return False
 
     def dup(self):
+        """Creates a duplicate of this request, referring to a
+        duplicate of the response socket.
+        """
         return req(self.method, self.url, self.ver, self.rest, self.headers, os.dup(self.sk.fileno()))
 
     def match(self, match):
+        """If the `match' argument matches exactly the leading part of
+        the rest string, this method strips that part of the rest
+        string off and returns True. Otherwise, it returns False
+        without doing anything.
+
+        This can be used for simple dispatching. For example:
+        if req.match("foo/"):
+            handle(req)
+        elif req.match("bar/"):
+            handle_otherwise(req)
+        else:
+            util.respond(req, "Not found", status = "404 Not Found", ctype = "text/plain")
+        """
         if self.rest[:len(match)] == match:
             self.rest = self.rest[len(match):]
             return True
@@ -51,6 +109,17 @@ class req(object):
         return False
 
 def recvreq(sock = 0):
+    """Receive a single ashd request on the specified socket file
+    descriptor (or standard input if unspecified).
+
+    The returned value is an instance of the `req' class. As per its
+    description, care should be taken to close() the request when
+    done, to avoid leaking response sockets.
+
+    This function may either raise on OSError if an error occurs on
+    the socket, or a ashd.proto.protoerr if the incoming request is
+    invalidly encoded.
+    """
     data, fd = htlib.recvfd(sock)
     if fd is None:
         return None
@@ -72,6 +141,13 @@ def recvreq(sock = 0):
         os.close(fd)
 
 def sendreq(sock, req):
+    """Encode and send a single request to the specified socket file
+    descriptor using the ashd protocol. The request should be an
+    instance of the `req' class.
+
+    This function may raise an OSError if an error occurs on the
+    socket.
+    """
     data = ""
     data += req.method + '\0'
     data += req.url + '\0'
index 15d61b0..2ac7dc0 100644 (file)
@@ -1,7 +1,25 @@
+"""High-level utility module for ashd(7)
+
+This module implements a rather convenient interface for writing ashd
+handlers, wrapping the low-level ashd.proto module.
+"""
+
 import os, socket
 import proto
 
 def stdfork(argv, chinit = None):
+    """Fork a persistent handler process using the `argv' argument
+    list, as per the standard ashd(7) calling convention. For an
+    easier-to-use interface, see the `pchild' class.
+
+    If a callable object of no arguments is provided in the `chinit'
+    argument, it will be called in the child process before exec()'ing
+    the handler program, and can be used to set parameters for the new
+    process, such as working directory, nice level or ulimits.
+
+    Returns the file descriptor of the socket for sending requests to
+    the new child.
+    """
     csk, psk = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET)
     pid = os.fork()
     if pid == 0:
@@ -22,7 +40,95 @@ def stdfork(argv, chinit = None):
     psk.close()
     return fd
 
+class pchild(object):
+    """class pchild(argv, autorespawn=False, chinit=None)
+
+    Represents a persistent child handler process, started as per the
+    standard ashd(7) calling convention. It will be called with the
+    `argv' argument lest, which should be a list (or other iterable)
+    of strings.
+
+    If `autorespawn' is specified as True, the child process will be
+    automatically restarted if a request cannot be successfully sent
+    to it.
+
+    For a description of the `chinit' argument, see `stdfork'.
+
+    When this child handler should be disposed of, care should be
+    taken to call the close() method to release its socket and let it
+    exit. This class also implements the resource-manager interface,
+    so that it can be used in `with' statements.
+    """
+    
+    def __init__(self, argv, autorespawn = False, chinit = None):
+        self.argv = argv
+        self.chinit = chinit
+        self.fd = -1
+        self.respawn = autorespawn
+        self.spawn()
+
+    def spawn(self):
+        """Start the child handler, or restart it if it is already
+        running. You should not have to call this method manually
+        unless you explicitly want to manage the process' lifecycle.
+        """
+        self.close()
+        self.fd = stdfork(self.argv, self.chinit)
+
+    def close(self):
+        """Close this child handler's socket. For normal child
+        handlers, this will make the program terminate normally.
+        """
+        if self.fd >= 0:
+            os.close(self.fd)
+            self.fd = -1
+
+    def __del__(self):
+        self.close()
+
+    def passreq(self, req):
+        """Pass the specified request (which should be an instance of
+        the ashd.proto.req class) to this child handler. If the child
+        handler fails for some reason, and `autorespawn' was specified
+        as True when creating this handler, one attempt will be made
+        to restart it.
+
+        Note: You still need to close the request normally.
+
+        This method may raise an OSError if the request fails and
+        autorespawning was either not requested, or if the
+        autorespawning fails.
+        """
+        try:
+            proto.sendreq(self.fd, req)
+        except OSError:
+            if self.respawn:
+                self.spawn()
+                proto.sendreq(self.fd, req)
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *excinfo):
+        self.close()
+        return False
+
 def respond(req, body, status = ("200 OK"), ctype = "text/html"):
+    """Simple function for conveniently responding to a request.
+
+    Sends the specified body text to the request's response socket,
+    prepending an HTTP header with the appropriate Content-Type and
+    Content-Length headers, and then closes the response socket.
+
+    The `status' argument can be used to specify a non-200 response,
+    and the `ctype' argument can be used to specify a non-HTML
+    MIME-type.
+
+    If `body' is a Unicode object, it will be encoded as UTF-8.
+
+    For example:
+        respond(req, "Not found", status = "404 Not Found", ctype = "text/plain")
+    """
     if type(body) == unicode:
         body = body.decode("utf-8")
         if ctype[:5] == "text/" and ctype.find(';') < 0:
@@ -39,6 +145,15 @@ def respond(req, body, status = ("200 OK"), ctype = "text/html"):
         req.close()
 
 def serveloop(handler, sock = 0):
+    """Implements a simple loop for serving requests sequentially, by
+    receiving requests from standard input (or the specified socket),
+    passing them to the specified handler function, and finally making
+    sure to close them. Returns when end-of-file is received on the
+    incoming socket.
+
+    The handler function should be a callable object of one argument,
+    and is called once for each received request.
+    """
     while True:
         req = proto.recvreq(sock)
         if req is None: