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