python: Added some actual documentation(!).
[ashd.git] / python / ashd / util.py
CommitLineData
2baf419b
FT
1"""High-level utility module for ashd(7)
2
3This module implements a rather convenient interface for writing ashd
4handlers, wrapping the low-level ashd.proto module.
5"""
6
4e7888f7
FT
7import os, socket
8import proto
9
10def stdfork(argv, chinit = None):
2baf419b
FT
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 """
4e7888f7
FT
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
2baf419b
FT
43class 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
4e7888f7 116def respond(req, body, status = ("200 OK"), ctype = "text/html"):
2baf419b
FT
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 """
4e7888f7
FT
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
147def serveloop(handler, sock = 0):
2baf419b
FT
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 """
4e7888f7
FT
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()