examples: setsid is now built into htparser.
[ashd.git] / python / ashd / scgi.py
1 import sys
2 import threading
3
4 class protoerr(Exception):
5     pass
6
7 class closed(IOError):
8     def __init__(self):
9         super(closed, self).__init__("The client has closed the connection.")
10
11 def readns(sk):
12     hln = 0
13     while True:
14         c = sk.read(1)
15         if c == ':':
16             break
17         elif c >= '0' or c <= '9':
18             hln = (hln * 10) + (ord(c) - ord('0'))
19         else:
20             raise protoerr, "Invalid netstring length byte: " + c
21     ret = sk.read(hln)
22     if sk.read(1) != ',':
23         raise protoerr, "Non-terminated netstring"
24     return ret
25
26 def readhead(sk):
27     parts = readns(sk).split('\0')[:-1]
28     if len(parts) % 2 != 0:
29         raise protoerr, "Malformed headers"
30     ret = {}
31     i = 0
32     while i < len(parts):
33         ret[parts[i]] = parts[i + 1]
34         i += 2
35     return ret
36
37 class reqthread(threading.Thread):
38     def __init__(self, sk, handler):
39         super(reqthread, self).__init__(name = "SCGI request handler")
40         self.bsk = sk.dup()
41         self.sk = self.bsk.makefile("r+")
42         self.handler = handler
43
44     def run(self):
45         try:
46             head = readhead(self.sk)
47             self.handler(head, self.sk)
48         finally:
49             self.sk.close()
50             self.bsk.close()
51
52 def handlescgi(sk, handler):
53     t = reqthread(sk, handler)
54     t.start()
55
56 def servescgi(socket, handler):
57     while True:
58         nsk, addr = socket.accept()
59         try:
60             handlescgi(nsk, handler)
61         finally:
62             nsk.close()
63
64 def wrapwsgi(handler):
65     def handle(head, sk):
66         env = dict(head)
67         env["wsgi.version"] = 1, 0
68         if "HTTP_X_ASH_PROTOCOL" in env:
69             env["wsgi.url_scheme"] = env["HTTP_X_ASH_PROTOCOL"]
70         elif "HTTPS" in env:
71             env["wsgi.url_scheme"] = "https"
72         else:
73             env["wsgi.url_scheme"] = "http"
74         env["wsgi.input"] = sk
75         env["wsgi.errors"] = sys.stderr
76         env["wsgi.multithread"] = True
77         env["wsgi.multiprocess"] = False
78         env["wsgi.run_once"] = False
79
80         resp = []
81         respsent = []
82
83         def flushreq():
84             if not respsent:
85                 if not resp:
86                     raise Exception, "Trying to write data before starting response."
87                 status, headers = resp
88                 respsent[:] = [True]
89                 try:
90                     sk.write("Status: %s\n" % status)
91                     for nm, val in headers:
92                         sk.write("%s: %s\n" % (nm, val))
93                     sk.write("\n")
94                 except IOError:
95                     raise closed()
96
97         def write(data):
98             if not data:
99                 return
100             flushreq()
101             try:
102                 sk.write(data)
103                 sk.flush()
104             except IOError:
105                 raise closed()
106
107         def startreq(status, headers, exc_info = None):
108             if resp:
109                 if exc_info:                # Interesting, this...
110                     try:
111                         if respsent:
112                             raise exc_info[0], exc_info[1], exc_info[2]
113                     finally:
114                         exc_info = None     # CPython GC bug?
115                 else:
116                     raise Exception, "Can only start responding once."
117             resp[:] = status, headers
118             return write
119
120         respiter = handler(env, startreq)
121         try:
122             try:
123                 for data in respiter:
124                     write(data)
125                 if resp:
126                     flushreq()
127             except closed:
128                 pass
129         finally:
130             if hasattr(respiter, "close"):
131                 respiter.close()
132     return handle