python: Added some actual documentation(!).
[ashd.git] / python / ashd / util.py
1 """High-level utility module for ashd(7)
2
3 This module implements a rather convenient interface for writing ashd
4 handlers, wrapping the low-level ashd.proto module.
5 """
6
7 import os, socket
8 import proto
9
10 def stdfork(argv, chinit = None):
11     """Fork a persistent handler process using the `argv' argument
12     list, as per the standard ashd(7) calling convention. For an
13     easier-to-use interface, see the `pchild' class.
14
15     If a callable object of no arguments is provided in the `chinit'
16     argument, it will be called in the child process before exec()'ing
17     the handler program, and can be used to set parameters for the new
18     process, such as working directory, nice level or ulimits.
19
20     Returns the file descriptor of the socket for sending requests to
21     the new child.
22     """
23     csk, psk = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET)
24     pid = os.fork()
25     if pid == 0:
26         try:
27             os.dup2(csk.fileno(), 0)
28             for fd in xrange(3, 1024):
29                 try:
30                     os.close(fd)
31                 except:
32                     pass
33             if chinit is not None:
34                 chinit()
35             os.execvp(argv[0], argv)
36         finally:
37             os._exit(127)
38     csk.close()
39     fd = os.dup(psk.fileno())
40     psk.close()
41     return fd
42
43 class pchild(object):
44     """class pchild(argv, autorespawn=False, chinit=None)
45
46     Represents a persistent child handler process, started as per the
47     standard ashd(7) calling convention. It will be called with the
48     `argv' argument lest, which should be a list (or other iterable)
49     of strings.
50
51     If `autorespawn' is specified as True, the child process will be
52     automatically restarted if a request cannot be successfully sent
53     to it.
54
55     For a description of the `chinit' argument, see `stdfork'.
56
57     When this child handler should be disposed of, care should be
58     taken to call the close() method to release its socket and let it
59     exit. This class also implements the resource-manager interface,
60     so that it can be used in `with' statements.
61     """
62     
63     def __init__(self, argv, autorespawn = False, chinit = None):
64         self.argv = argv
65         self.chinit = chinit
66         self.fd = -1
67         self.respawn = autorespawn
68         self.spawn()
69
70     def spawn(self):
71         """Start the child handler, or restart it if it is already
72         running. You should not have to call this method manually
73         unless you explicitly want to manage the process' lifecycle.
74         """
75         self.close()
76         self.fd = stdfork(self.argv, self.chinit)
77
78     def close(self):
79         """Close this child handler's socket. For normal child
80         handlers, this will make the program terminate normally.
81         """
82         if self.fd >= 0:
83             os.close(self.fd)
84             self.fd = -1
85
86     def __del__(self):
87         self.close()
88
89     def passreq(self, req):
90         """Pass the specified request (which should be an instance of
91         the ashd.proto.req class) to this child handler. If the child
92         handler fails for some reason, and `autorespawn' was specified
93         as True when creating this handler, one attempt will be made
94         to restart it.
95
96         Note: You still need to close the request normally.
97
98         This method may raise an OSError if the request fails and
99         autorespawning was either not requested, or if the
100         autorespawning fails.
101         """
102         try:
103             proto.sendreq(self.fd, req)
104         except OSError:
105             if self.respawn:
106                 self.spawn()
107                 proto.sendreq(self.fd, req)
108
109     def __enter__(self):
110         return self
111
112     def __exit__(self, *excinfo):
113         self.close()
114         return False
115
116 def respond(req, body, status = ("200 OK"), ctype = "text/html"):
117     """Simple function for conveniently responding to a request.
118
119     Sends the specified body text to the request's response socket,
120     prepending an HTTP header with the appropriate Content-Type and
121     Content-Length headers, and then closes the response socket.
122
123     The `status' argument can be used to specify a non-200 response,
124     and the `ctype' argument can be used to specify a non-HTML
125     MIME-type.
126
127     If `body' is a Unicode object, it will be encoded as UTF-8.
128
129     For example:
130         respond(req, "Not found", status = "404 Not Found", ctype = "text/plain")
131     """
132     if type(body) == unicode:
133         body = body.decode("utf-8")
134         if ctype[:5] == "text/" and ctype.find(';') < 0:
135             ctype = ctype + "; charset=utf-8"
136     else:
137         body = str(body)
138     try:
139         req.sk.write("HTTP/1.1 %s\n" % status)
140         req.sk.write("Content-Type: %s\n" % ctype)
141         req.sk.write("Content-Length: %i\n" % len(body))
142         req.sk.write("\n")
143         req.sk.write(body)
144     finally:
145         req.close()
146
147 def serveloop(handler, sock = 0):
148     """Implements a simple loop for serving requests sequentially, by
149     receiving requests from standard input (or the specified socket),
150     passing them to the specified handler function, and finally making
151     sure to close them. Returns when end-of-file is received on the
152     incoming socket.
153
154     The handler function should be a callable object of one argument,
155     and is called once for each received request.
156     """
157     while True:
158         req = proto.recvreq(sock)
159         if req is None:
160             break
161         try:
162             handler(req)
163         finally:
164             req.close()