X-Git-Url: http://www.dolda2000.com/gitweb/?p=ashd.git;a=blobdiff_plain;f=python%2Fashd%2Fproto.py;h=8dc5ecd133c2ff63cd54978e7879fa809d843010;hp=90ba0124248452f6a7c6ee84f8522e41b70abb0f;hb=f56b079086cb35e50697a4752b38679ae5e44679;hpb=4e7888f79c780b6d79420374f5ab3843d7784fad diff --git a/python/ashd/proto.py b/python/ashd/proto.py index 90ba012..8dc5ecd 100644 --- a/python/ashd/proto.py +++ b/python/ashd/proto.py @@ -1,10 +1,45 @@ +"""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 +__all__ = ["req", "recvreq", "sendreq"] + 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 +50,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 +66,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 +76,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 +111,18 @@ 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. If end-of-file is + received on the socket, None is returned. + + This function may either raise an OSError if an error occurs on + the socket, or an ashd.proto.protoerr if the incoming request is + invalidly encoded. + """ data, fd = htlib.recvfd(sock) if fd is None: return None @@ -72,6 +144,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'