python: Documented the ashd.wsgidir module.
[ashd.git] / python / ashd / proto.py
CommitLineData
2baf419b
FT
1"""Low-level protocol module for ashd(7)
2
3This module provides primitive functions that speak the raw ashd(7)
4protocol. Primarily, it implements the `req' class that is used to
5represent ashd requests. The functions it provides can also be used to
6create ashd handlers, but unless you require very precise control, the
7ashd.util module provides an easier-to-use interface.
8"""
9
c270f222
FT
10import os, socket
11import htlib
12
13class protoerr(Exception):
14 pass
15
16class req(object):
2baf419b
FT
17 """Represents a single ashd request. Normally, you would not
18 create instances of this class manually, but receive them from the
19 recvreq function.
20
21 For the abstract structure of ashd requests, please see the
22 ashd(7) manual page. This class provides access to the HTTP
23 method, raw URL, HTTP version and rest string via the `method',
24 `url', `ver' and `rest' variables respectively. It also implements
25 a dict-like interface for case-independent access to the HTTP
26 headers. The raw headers are available as a list of (name, value)
27 tuples in the `headers' variable.
28
29 For responding, the response socket is available as a standard
30 Python stream object in the `sk' variable. Again, see the ashd(7)
31 manpage for what to receive and transmit on the response socket.
32
33 Note that instances of this class contain a reference to the live
34 socket used for responding to requests, which should be closed
35 when you are done with the request. The socket can be closed
36 manually by calling the close() method on this
37 object. Alternatively, this class implements the resource-manager
38 interface, so that it can be used in `with' statements.
39 """
40
c270f222
FT
41 def __init__(self, method, url, ver, rest, headers, fd):
42 self.method = method
43 self.url = url
44 self.ver = ver
45 self.rest = rest
46 self.headers = headers
47 self.sk = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM).makefile('r+')
48 os.close(fd)
49
50 def close(self):
2baf419b 51 "Close this request's response socket."
c270f222
FT
52 self.sk.close()
53
54 def __getitem__(self, header):
2baf419b
FT
55 """Find a HTTP header case-insensitively. For example,
56 req["Content-Type"] returns the value of the content-type
57 header regardlessly of whether the client specified it as
58 "Content-Type", "content-type" or "Content-type".
59 """
c270f222
FT
60 header = header.lower()
61 for key, val in self.headers:
62 if key.lower() == header:
63 return val
64 raise KeyError(header)
65
66 def __contains__(self, header):
2baf419b
FT
67 """Works analogously to the __getitem__ method for checking
68 header presence case-insensitively.
69 """
c270f222
FT
70 header = header.lower()
71 for key, val in self.headers:
72 if key.lower() == header:
73 return True
74 return False
75
76 def dup(self):
2baf419b
FT
77 """Creates a duplicate of this request, referring to a
78 duplicate of the response socket.
79 """
c270f222
FT
80 return req(self.method, self.url, self.ver, self.rest, self.headers, os.dup(self.sk.fileno()))
81
82 def match(self, match):
2baf419b
FT
83 """If the `match' argument matches exactly the leading part of
84 the rest string, this method strips that part of the rest
85 string off and returns True. Otherwise, it returns False
86 without doing anything.
87
88 This can be used for simple dispatching. For example:
89 if req.match("foo/"):
90 handle(req)
91 elif req.match("bar/"):
92 handle_otherwise(req)
93 else:
94 util.respond(req, "Not found", status = "404 Not Found", ctype = "text/plain")
95 """
c270f222
FT
96 if self.rest[:len(match)] == match:
97 self.rest = self.rest[len(match):]
98 return True
99 return False
100
101 def __str__(self):
102 return "\"%s %s %s\"" % (self.method, self.url, self.ver)
103
104 def __enter__(self):
105 return self
106
107 def __exit__(self, *excinfo):
108 self.sk.close()
109 return False
110
111def recvreq(sock = 0):
2baf419b
FT
112 """Receive a single ashd request on the specified socket file
113 descriptor (or standard input if unspecified).
114
115 The returned value is an instance of the `req' class. As per its
116 description, care should be taken to close() the request when
b0ac3ba1
FT
117 done, to avoid leaking response sockets. If end-of-file is
118 received on the socket, None is returned.
2baf419b
FT
119
120 This function may either raise on OSError if an error occurs on
121 the socket, or a ashd.proto.protoerr if the incoming request is
122 invalidly encoded.
123 """
c270f222
FT
124 data, fd = htlib.recvfd(sock)
125 if fd is None:
126 return None
127 try:
c270f222
FT
128 parts = data.split('\0')[:-1]
129 if len(parts) < 5:
130 raise protoerr("Truncated request")
131 method, url, ver, rest = parts[:4]
132 headers = []
133 i = 4
134 while True:
135 if parts[i] == "": break
136 if len(parts) - i < 3:
137 raise protoerr("Truncated request")
138 headers.append((parts[i], parts[i + 1]))
139 i += 2
140 return req(method, url, ver, rest, headers, os.dup(fd))
141 finally:
142 os.close(fd)
143
144def sendreq(sock, req):
2baf419b
FT
145 """Encode and send a single request to the specified socket file
146 descriptor using the ashd protocol. The request should be an
147 instance of the `req' class.
148
149 This function may raise an OSError if an error occurs on the
150 socket.
151 """
c270f222
FT
152 data = ""
153 data += req.method + '\0'
154 data += req.url + '\0'
155 data += req.ver + '\0'
156 data += req.rest + '\0'
157 for key, val in req.headers:
158 data += key + '\0'
159 data += val + '\0'
160 data += '\0'
161 htlib.sendfd(sock, req.sk.fileno(), data)