+"""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
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
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:
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:
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
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
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'
+"""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:
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:
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: